2 Player

Started by yankscally, May 02, 2017, 02:03:26 AM

Previous topic - Next topic
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?

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.
"If you make people think they're thinking, they'll love you. But if you really make them think, they'll hate you."

May 02, 2017, 06:20:05 AM #2 Last Edit: May 02, 2017, 07:12:07 AM by Zefk
@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) after the first release of the Solarus book project 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

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

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.
"If you make people think they're thinking, they'll love you. But if you really make them think, they'll hate you."

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++.


May 02, 2017, 12:32:26 PM #6 Last Edit: May 02, 2017, 12:34:48 PM by yankscally
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) after the first release of the Solarus book project 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.

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

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.
"If you make people think they're thinking, they'll love you. But if you really make them think, they'll hate you."

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

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

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


May 02, 2017, 10:35:47 PM #12 Last Edit: May 02, 2017, 11:27:22 PM by Zefk
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

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.
"If you make people think they're thinking, they'll love you. But if you really make them think, they'll hate you."

@Diarandor
Ah! I did not notice that tricky math variable.

@yankscally
No need for the math variable.