Solarus-Games English Forum

Solarus => Development => Topic started by: yankscally on May 02, 2017, 02:03:26 AM

Title: 2 Player
Post by: yankscally on May 02, 2017, 02:03:26 AM
http://www.solarus-games.org/doc/1.5/lua_api_hero.html (http://www.solarus-games.org/doc/1.5/lua_api_hero.html)

mentioned in this link is

QuoteThe hero is the character controlled by the player. There is always exactly one hero on the current map. The hero is automatically created by the engine: you cannot create or remove him.

How would I add an extra player??

it seems like the code I want to mess with is hidden somewhere and I can't read/write it. Can anyone point me in the right direction?
Title: Re: 2 Player
Post by: Diarandor on May 02, 2017, 02:43:28 AM
To make this feature, there are two cases:
-Multiplayer online: you would need to modify the C++ code of the engine. Very hard, I guess.
-Multiplayer offline: it can already be done with Lua scripts, using custom entities. Of course, it's a lot of work.
Title: Re: 2 Player
Post by: Zefk on May 02, 2017, 06:20:05 AM
@yankscally

I am making an ally Ai project, so the functions I make might help you with making a second player. I will be start making functions (probably a custom entity metatable library (http://www.solarus-games.org/doc/latest/lua_api_main.html#lua_api_main_get_metatable)) after the first release of the Solarus book project (https://github.com/Zefk/Solarus-ARPG-Game-Development-Book_2) I am working on.

This would be unrelated to controlling your player 2 character. It is common to have "wasd" for keyboard related second players. Key 'd' is default for pausing the game...you will need to probably use "wqes".

w = up
s = down
a = left
d = right

w = up
s = down
q = left
e = right

Straight Movement:

Straight trajectory in any direction.
http://www.solarus-games.org/doc/latest/lua_api_movement.html

You can set the angle to the following values to get basic directions, but you can use negatives and other values. I used 3 in straight:set_angle(3) in my book.

Angle:

East (right) = 0
North (up) = math.pi / 2
West (left) = math.pi
South (down)= 3 * math.pi / 2

                          "N" ever (north)
"W"atermelon(West)------|----------"E"at (East)
                          "S"oggy (south)
                   

Code ( lua) Select
--Straight up

function player_up()
local straight = sol.movement.create("straight")
straight:set_angle(math.pi / 2)
straight:start(entity)
end


Key press:
A tip is remember to have a true/false Boolean for a menu.
http://www.solarus-games.org/doc/latest/lua_api_main.html#lua_api_main_on_key_pressed

Code ( lua) Select

menu = false

function sol.main:on_key_pressed(key)
  if key == "w" and menu == false then
    player_up()
    entity:get_sprite():set_animation("walking")
  else
    straight:stop()
    entity:get_sprite():set_animation("stopped")
  end
end


For "menu = false" probably better to use set_value/get_value (http://www.solarus-games.org/doc/latest/lua_api_game.html#lua_api_game_set_value)

You will have to decide on the action button. It is 'C' for the hero. It probably would be convenient to use key '1' or 'a'. You probably could provide a option to change it.

Code ( lua) Select

local menu = false
local action_button = "a"

function sol.main:on_key_pressed(key)
  if key == action_button and menu == false then
    entity:get_sprite():set_animation("sword")
  else
    entity:get_sprite():set_animation("stopped")
  end
end


You might want to use a timer for movements because maybe you want to look in that direction, but not move.
http://www.solarus-games.org/doc/latest/lua_api_custom_entity.html#lua_api_custom_entity_set_direction
http://www.solarus-games.org/doc/latest/lua_api_timer.html

You might want to think about joypads too.
http://www.solarus-games.org/doc/latest/lua_api_main.html#lua_api_main_on_joypad_button_pressed
Title: Re: 2 Player
Post by: Diarandor on May 02, 2017, 10:08:54 AM
Note that french keyboards have some differences in the position of the letters. Since there are many french people in the Solarus community, I recommend to use as default keys some of the keys that have the same position for both english and french keyboards.
Title: Re: 2 Player
Post by: Christopho on May 02, 2017, 10:32:01 AM
For networking multiplayer, you don't need to change the C++ code of the engine. You can use a Lua networking library like LuaSocket. It is true that there is no direct networking support in the Solarus API yet: you will have to do a lot of things manually, but no C++.
Title: Re: 2 Player
Post by: Zefk on May 02, 2017, 11:17:27 AM
I guess this is what a french keyboard looks like: https://www.branah.com/french

Luasocket installation: http://w3.impa.br/~diego/software/luasocket/installation.html
Title: Re: 2 Player
Post by: yankscally on May 02, 2017, 12:32:26 PM
Quote from: Diarandor on May 02, 2017, 10:08:54 AM
Note that french keyboards have some differences in the position of the letters. Since there are many french people in the Solarus community, I recommend to use as default keys some of the keys that have the same position for both english and french keyboards.
Quote from: Christopho on May 02, 2017, 10:32:01 AM
For networking multiplayer, you don't need to change the C++ code of the engine. You can use a Lua networking library like LuaSocket. It is true that there is no direct networking support in the Solarus API yet: you will have to do a lot of things manually, but no C++.
Quote from: Zefk on May 02, 2017, 06:20:05 AM
@yankscally

I am making an ally Ai project, so the functions I make might help you with making a second player. I will be start making functions (probably a custom entity metatable library (http://www.solarus-games.org/doc/latest/lua_api_main.html#lua_api_main_get_metatable)) after the first release of the Solarus book project (https://github.com/Zefk/Solarus-ARPG-Game-Development-Book_2) I am working on.

This would be unrelated to controlling your player 2 character. It is common to have "wasd" for keyboard related second players. Key 'd' is default for pausing the game...you will need to probably use "wqes".

w = up
s = down
a = left
d = right

w = up
s = down
q = left
e = right

Straight Movement:


Wow, this is really helpful info guys, thanks for the support, and so quickly too. I guess I can do a lot today with this. I will code a 1P/2P system for now and think about luasocket multiplayer when I understand it all a bit better. I'm using an enemy script for now but changing the code to controlling it with set keys seems like about x20 multiplied the lines of code I expected. I will probably use the custom entity.
Title: Re: 2 Player
Post by: MetalZelda on May 02, 2017, 12:36:34 PM
You might be able to do it through game:on_key_pressed(key)

Remember that you might need to configure the player input, in this case, key should be a savegame value
Title: Re: 2 Player
Post by: Diarandor on May 02, 2017, 01:01:07 PM
The only bad thing about controlling the second hero with keyboard may be a possible limitation of maximum number of keys that can be pressed. I recommend to control the second hero with a gamepad.
Title: Re: 2 Player
Post by: yankscally on May 02, 2017, 01:13:09 PM
Code ( lua) Select
local entity = ...
local game = entity:get_game()
local map = entity:get_map()
local hero = map:get_hero()
local sprite
local movement


function entity:on_created()
  sprite = entity:create_sprite("hero/tunic3"):set_animation("stopped")
end

function player_up()
local straight = sol.movement.create("straight")
straight:set_angle(math.pi / 2)
straight:start(entity)

end

function player_down()
local straight = sol.movement.create("straight")
straight:set_angle(3 * math.pi / 2)
straight:start(entity)
end

function player_left()
local straight = sol.movement.create("straight")
straight:set_angle(math.pi)
straight:start(entity)
end

function player_right()
local straight = sol.movement.create("straight")
straight:set_angle(0)
straight:start(entity)
end

menu = false

function sol.main:on_key_pressed(key)
  if key == "i" and menu == false then
    player_up()
    entity:get_sprite():set_animation("walking")
  else
    straight:stop()
    entity:get_sprite():set_animation("stopped")
  end
end

function sol.main:on_key_pressed(key)
  if key == "j" and menu == false then
    player_left()
    entity:get_sprite():set_animation("walking")
  else
    straight:stop()
    entity:get_sprite():set_animation("stopped")
  end
end

function sol.main:on_key_pressed(key)
  if key == "l" and menu == false then
    player_right()
    entity:get_sprite():set_animation("walking")
  else
    straight:stop()
    entity:get_sprite():set_animation("stopped")
  end
end

function sol.main:on_key_pressed(key)
  if key == "k" and menu == false then
    player_down()
    entity:get_sprite():set_animation("walking")
  else
    straight:stop()
    entity:get_sprite():set_animation("stopped")
  end
end

function entity:on_restarted()


end



I got this so far thanks to Zefk, and it loads in a sprite, it does not move but pressing the left key makes the sprite "walking"
I get this error

Error: In on_key_pressed: [string "entities/player_2.lua"]:85: attempt to index global 'straight' (a nil value)


I thought it would be easier to use IJKL for the movement at the minute so I could test quickly with both hands
Title: Re: 2 Player
Post by: yankscally on May 02, 2017, 01:28:16 PM
http://www.solarus-games.org/doc/latest/lua_api_movement.html (http://www.solarus-games.org/doc/latest/lua_api_movement.html)

what about the code described here??
Quotemovement:get_direction4()

From the four main directions, returns the closest one to the current trajectory.

East is 0, North is 1, West is 2, South is 3. As the real trajectory does not necessarily follows one of the four main directions, it will be converted to the closest one.

If you use this movement to control a sprite (or a map entity that has a sprite), you can use this function to make the sprite face the direction of the movement.

Return value (number): The closest direction corresponding to the angle of this movement.
Example of use:

-- Example of code from an enemy script.

-- This function is called when the enemy should start or restart its movement.
function enemy:on_restarted()

  -- Create a movement that makes random straight trajectories.
  local movement = sol.movement.create("random")

  -- This function is called when the trajectory has changed.
  function movement:on_movement_changed()
    -- The angle of the movement has changed: update the sprite accordingly.
    local direction = movement:get_direction4()
    enemy:get_sprite():set_direction(direction)
  end

  movement:start(enemy)
end
Title: Re: 2 Player
Post by: yankscally on May 02, 2017, 04:17:02 PM
after fine tuning the code, trying to edit out mistakes I made and such, I now get these error messages, similar.
Error: In on_key_pressed: [string "entities/player_2.lua"]:57: attempt to index upvalue 'math' (a nil value)
Error: In on_key_pressed: [string "entities/player_2.lua"]:66: attempt to index global 'straight' (a nil value)

player 2 code so far
Code ( lua) Select
local entity = ...
local game = entity:get_game()
local map = entity:get_map()
local hero = map:get_hero()
local sprite
local math



---CREATE
function entity:on_created()
  sprite = entity:create_sprite("hero/tunic3"):set_animation("stopped")
  entity:set_can_traverse("hero", false)
  entity:get_direction(2)
end

--------------CONTROLS GO HERE
--- UP

function player_up()
local straight = sol.movement.create("straight")
straight:set_angle(math.pi / 2)
straight:start(entity)
end
menu = false
function sol.main:on_key_pressed(key)
  if key == "i" and menu == false then
    player_up()
    entity:get_sprite():set_animation("walking")
  else
    straight:stop()
    entity:get_sprite():set_animation("stopped")
  end
end

-- DOWN

function player_down()
local straight = sol.movement.create("straight")
straight:set_angle(3 * math.pi / 2)
straight:start(entity)
end
menu = false
function entity:on_key_pressed(key)
  if key == "k" and menu == false then
    player_down()
    entity:get_sprite():set_animation("walking")
  else
    straight:stop()
    entity:get_sprite():set_animation("stopped")
  end
end
--- Left

function player_left()
local straight = sol.movement.create("straight")
straight:set_angle(math.pi)
straight:start(entity)
end
menu = false
function sol.main:on_key_pressed(key)
  if key == "j" and menu == false then
    player_left()
    entity:get_sprite():set_animation("walking")
  else
    straight:stop()
    entity:get_sprite():set_animation("stopped")
  end
end

-- right

function player_right()
local straight = sol.movement.create("straight")
straight:set_angle(0)
straight:start(entity)
end
menu = false
function entity:on_key_pressed(key)
  if key == "l" and menu == false then
    player_right()
    entity:get_sprite():set_animation("walking")
  else
    straight:stop()
    entity:get_sprite():set_animation("stopped")
  end
end


--- on restart

function entity:on_restarted()
  movement:set_speed(48)
  movement:start(entity)
end

Title: Re: 2 Player
Post by: Zefk on May 02, 2017, 10:35:47 PM
You might need to have the straight movement variable outside the function. You probably only need to declare it once. The same with the menu variable.

You only need one key press function.

I am not sure why math.pi is not working. Just use 3.14 (That is pi). You got to remove the math variable for math.pi to work. You will have to set the direction. The following script should just work. I added on_released for stopping. Sorry that I did not mention that the code above was pseudo code.
http://www.solarus-games.org/doc/latest/lua_api_custom_entity.html#lua_api_custom_entity_set_direction
http://www.solarus-games.org/doc/latest/lua_api_main.html#lua_api_main_on_key_released

You can use the camera entity to track your player 2, but you might not want that unless player 1 dies.
Code ( lua) Select
local camera = map:get_camera()
camera:start_tracking(entity)


You do not need on_restarted() because I think that is an enemy only function.

Do this for the player speed:

---Create sprite
Code ( lua) Select
function entity:on_created()
  sprite = entity:create_sprite("hero/tunic1"):set_animation("stopped")
  entity:set_can_traverse("hero", false)
  entity:get_direction(2)
  straight:set_speed(80)
end


Script:
Code ( lua) Select
local entity = ...
local game = entity:get_game()
local map = entity:get_map()
local hero = map:get_hero()
local sprite
local straight = sol.movement.create("straight")
menu = false

---Create sprite
function entity:on_created()
  sprite = entity:create_sprite("hero/tunic3"):set_animation("stopped")
  entity:set_can_traverse("hero", false)
  entity:get_direction(2)
  straight:set_speed(80)
end

--CONTROLS
--UP
function player_up()
straight:set_angle(math.pi / 2)
straight:start(entity)
end

--DOWN
function player_down()
straight:set_angle(3 * math.pi / 2)
straight:start(entity)
end

--Left
function player_left()
straight:set_angle(math.pi)
straight:start(entity)
end

--right
function player_right()
straight:set_angle(0)
straight:start(entity)
end


--Key press function
function sol.main:on_key_pressed(key)

--right
  if key == "l" and menu == false then
    player_right()
    entity:get_sprite():set_animation("walking")
  end

--left
  if key == "j" and menu == false then
    player_left()
    entity:get_sprite():set_animation("walking")
  end

--down
  if key == "k" and menu == false then
    player_down()
    entity:get_sprite():set_animation("walking")

  end

--up
  if key == "i" and menu == false then
    player_up()
    entity:get_sprite():set_animation("walking")
  end
end


function sol.main:on_key_released(key)

   if key == "l" then
       straight:stop()
       entity:get_sprite():set_animation("stopped")
   end
   
   if key == "j" then
       straight:stop()
       entity:get_sprite():set_animation("stopped")
   end
   
   if key == "k" then
       straight:stop()
       entity:get_sprite():set_animation("stopped")
   end
   
   if key == "i" then
       straight:stop()
       entity:get_sprite():set_animation("stopped")
   end

end
Title: Re: 2 Player
Post by: Diarandor on May 02, 2017, 10:59:17 PM
Why using 3.14? He should use math.pi, which is the standard value in the math library. It is clear that he is hiding the math library with a useless local variable "math", that is the problem.
Title: Re: 2 Player
Post by: Zefk on May 02, 2017, 11:03:05 PM
@Diarandor
Ah! I did not notice that tricky math variable.

@yankscally
No need for the math variable.

Title: Re: 2 Player
Post by: yankscally on May 02, 2017, 11:28:26 PM
Quote from: Zefk on May 02, 2017, 11:03:05 PM
@Diarandor
Ah! I did not notice that tricky math variable.

@yankscally
No need for the math variable.

Your code worked, straight up. Thank you Zefk! A little bit of time writing that code for you would have taken me days to figure out, but I would have got there eventually. I'm from an music/art background so im no wizard but I do know the basics of code, I really appreciate the help! If you need some sounds for your game, I will happily supply them I'm working on some sound packs that are pretty neat, including a very nice sounding dialog menu, I could show you if you are interested!
Title: Re: 2 Player
Post by: Zefk on May 02, 2017, 11:32:06 PM
You are welcome. I updated that post about camera movements and other info. You will still have to set the direction as I mentioned.
http://forum.solarus-games.org/index.php/topic,962.msg5558.html#msg5558

Code ( lua) Select
local entity = ...
local game = entity:get_game()
local map = entity:get_map()
local hero = map:get_hero()
local sprite
local straight = sol.movement.create("straight")
menu = false
local camera = map:get_camera()
camera:start_tracking(entity)


---Create sprite
function entity:on_created()
  sprite = entity:create_sprite("hero/tunic3"):set_animation("stopped")
  entity:set_can_traverse("hero", false)
  entity:get_direction(2)
  straight:set_speed(80)
end

--CONTROLS
--UP
function player_up()
straight:set_angle(math.pi / 2)
straight:start(entity)
end

--DOWN
function player_down()
straight:set_angle(3 * math.pi / 2)
straight:start(entity)
end

--Left
function player_left()
straight:set_angle(math.pi)
straight:start(entity)
end

--right
function player_right()
straight:set_angle(0)
straight:start(entity)
end


--Key press function
function sol.main:on_key_pressed(key)

--right
  if key == "l" and menu == false then
    player_right()
    entity:get_sprite():set_animation("walking")
  end

--left
  if key == "j" and menu == false then
    player_left()
    entity:get_sprite():set_animation("walking")
  end

--down
  if key == "k" and menu == false then
    player_down()
    entity:get_sprite():set_animation("walking")

  end

--up
  if key == "i" and menu == false then
    player_up()
    entity:get_sprite():set_animation("walking")
  end
end


function sol.main:on_key_released(key)

   if key == "l" then
       straight:stop()
       entity:get_sprite():set_animation("stopped")
   end
   
   if key == "j" then
       straight:stop()
       entity:get_sprite():set_animation("stopped")
   end
   
   if key == "k" then
       straight:stop()
       entity:get_sprite():set_animation("stopped")
   end
   
   if key == "i" then
       straight:stop()
       entity:get_sprite():set_animation("stopped")
   end

end

Title: Re: 2 Player
Post by: Zefk on May 03, 2017, 08:20:47 AM
I made a quick library example for the custom entity and attached it to this post. I made it mostly for the book project, but it can be used to shorten the script above. I included documentation and you will need a markdown reader like haroopad (http://pad.haroopress.com/user.html#download).

If you want to use it, then the script would look like this:

(https://media.giphy.com/media/xUA7basC8kQNPPOjtK/giphy.gif)

Code ( lua) Select
local entity = ...
local game = entity:get_game()
local map = entity:get_map()
local hero = map:get_hero()
local sprite
menu = false


local camera = map:get_camera()
camera:start_tracking(entity)

---Create sprite
function entity:on_created()
  sprite = entity:create_sprite("hero/tunic1"):set_animation("stopped")
  entity:set_can_traverse("hero", false)
  entity:speed(80)
end

--Key press function
function sol.main:on_key_pressed(key)

--right
  if key == "l" and menu == false then
    entity:right()
  end

--up
  if key == "i" and menu == false then
    entity:up()
  end

--left
  if key == "j" and menu == false then
    entity:left()
  end

--down
  if key == "k" and menu == false then
    entity:down()
  end
end

function sol.main:on_key_released(key)
   --right
   if key == "l" then
       entity:stop()
   end

   --up
   if key == "i" then
       entity:stop()
   end

   --left
   if key == "j" then
       entity:stop()
   end

   --down
   if key == "k" then
       entity:stop()
   end
end
Title: Re: 2 Player
Post by: Diarandor on May 03, 2017, 08:51:34 AM
It is really cool to see that working.  :)
The feature of switching heroes would be very useful too for your project.