From 6b7b6351b03928933225d3c8b03a57f1f2d73f5b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Manuel=20Qui=C3=B1ones?= Date: Fri, 8 Nov 2024 13:48:45 -0300 Subject: [PATCH 1/5] Project settings: add input actions for 2 players Use custom actions for player 1 instead of the builtin actions. Copy the current builtins that has not only keyboard, but also joystick support. Add actions for player 2. Use WASD keys for keyboard, and the same for joystick as player 1, but for device number 1. https://github.com/endlessm/moddable-platformer/issues/8 --- project.godot | 59 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 59 insertions(+) diff --git a/project.godot b/project.godot index f3c2921..37622eb 100644 --- a/project.godot +++ b/project.godot @@ -28,6 +28,65 @@ window/size/window_width_override=960 window/size/window_height_override=540 window/stretch/mode="canvas_items" +[input] + +player_2_up={ +"deadzone": 0.5, +"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":87,"physical_keycode":0,"key_label":0,"unicode":119,"location":0,"echo":false,"script":null) +, Object(InputEventJoypadButton,"resource_local_to_scene":false,"resource_name":"","device":1,"button_index":11,"pressure":0.0,"pressed":false,"script":null) +, Object(InputEventJoypadMotion,"resource_local_to_scene":false,"resource_name":"","device":1,"axis":1,"axis_value":-1.0,"script":null) +] +} +player_2_down={ +"deadzone": 0.5, +"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":83,"physical_keycode":0,"key_label":0,"unicode":115,"location":0,"echo":false,"script":null) +, Object(InputEventJoypadButton,"resource_local_to_scene":false,"resource_name":"","device":1,"button_index":12,"pressure":0.0,"pressed":false,"script":null) +, Object(InputEventJoypadMotion,"resource_local_to_scene":false,"resource_name":"","device":1,"axis":1,"axis_value":1.0,"script":null) +] +} +player_2_left={ +"deadzone": 0.5, +"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":65,"physical_keycode":0,"key_label":0,"unicode":97,"location":0,"echo":false,"script":null) +, Object(InputEventJoypadButton,"resource_local_to_scene":false,"resource_name":"","device":1,"button_index":13,"pressure":0.0,"pressed":false,"script":null) +, Object(InputEventJoypadMotion,"resource_local_to_scene":false,"resource_name":"","device":1,"axis":0,"axis_value":-1.0,"script":null) +] +} +player_2_right={ +"deadzone": 0.5, +"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":68,"physical_keycode":0,"key_label":0,"unicode":100,"location":0,"echo":false,"script":null) +, Object(InputEventJoypadButton,"resource_local_to_scene":false,"resource_name":"","device":1,"button_index":14,"pressure":0.0,"pressed":false,"script":null) +, Object(InputEventJoypadMotion,"resource_local_to_scene":false,"resource_name":"","device":1,"axis":2,"axis_value":1.0,"script":null) +] +} +player_1_up={ +"deadzone": 0.5, +"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":4194320,"physical_keycode":0,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null) +, Object(InputEventJoypadButton,"resource_local_to_scene":false,"resource_name":"","device":0,"button_index":11,"pressure":0.0,"pressed":false,"script":null) +, Object(InputEventJoypadMotion,"resource_local_to_scene":false,"resource_name":"","device":0,"axis":1,"axis_value":-1.0,"script":null) +] +} +player_1_down={ +"deadzone": 0.5, +"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":4194322,"physical_keycode":0,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null) +, Object(InputEventJoypadButton,"resource_local_to_scene":false,"resource_name":"","device":0,"button_index":12,"pressure":0.0,"pressed":false,"script":null) +, Object(InputEventJoypadMotion,"resource_local_to_scene":false,"resource_name":"","device":0,"axis":1,"axis_value":1.0,"script":null) +] +} +player_1_left={ +"deadzone": 0.5, +"events": [null, Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":4194319,"physical_keycode":0,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null) +, Object(InputEventJoypadButton,"resource_local_to_scene":false,"resource_name":"","device":0,"button_index":13,"pressure":0.0,"pressed":false,"script":null) +, Object(InputEventJoypadMotion,"resource_local_to_scene":false,"resource_name":"","device":0,"axis":0,"axis_value":-1.0,"script":null) +] +} +player_1_right={ +"deadzone": 0.5, +"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":4194321,"physical_keycode":0,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null) +, Object(InputEventJoypadButton,"resource_local_to_scene":false,"resource_name":"","device":0,"button_index":14,"pressure":0.0,"pressed":false,"script":null) +, Object(InputEventJoypadMotion,"resource_local_to_scene":false,"resource_name":"","device":0,"axis":0,"axis_value":1.0,"script":null) +] +} + [layer_names] 2d_physics/layer_1="Player" From 4bbcc302b4b46081f6cb9ad07af826234c2998d0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Manuel=20Qui=C3=B1ones?= Date: Fri, 8 Nov 2024 14:25:18 -0300 Subject: [PATCH 2/5] Player: Add player property This property tells which player controls the character. It can be player one, two or both. Add a constant dict holding the mapping from player to input actions. Add helper functions to Action.just_pressed(), Action.just_released(), Action.get_axis() that consider when both players control this character. https://github.com/endlessm/moddable-platformer/issues/8 --- scripts/global.gd | 1 + scripts/player.gd | 61 ++++++++++++++++++++++++++++++++++++++++++++--- 2 files changed, 59 insertions(+), 3 deletions(-) diff --git a/scripts/global.gd b/scripts/global.gd index 325ca59..45832b2 100644 --- a/scripts/global.gd +++ b/scripts/global.gd @@ -9,6 +9,7 @@ signal gravity_changed(gravity: float) signal timer_added enum Endings { WIN, LOSE } +enum Player { ONE, TWO, BOTH } ## Timer for finishing the level. var timer: Timer diff --git a/scripts/player.gd b/scripts/player.gd index fa1b5f2..0aa8624 100644 --- a/scripts/player.gd +++ b/scripts/player.gd @@ -3,6 +3,24 @@ class_name Player extends CharacterBody2D ## A player's character, which can walk, jump, and stomp on enemies. +const _PLAYER_ACTIONS = { + Global.Player.ONE: + { + "jump": "player_1_up", + "left": "player_1_left", + "right": "player_1_right", + }, + Global.Player.TWO: + { + "jump": "player_2_up", + "left": "player_2_left", + "right": "player_2_right", + }, +} + +## Which player controls this character? +@export var player: Global.Player = Global.Player.ONE + ## Use this to change the sprite frames of your character. @export var sprite_frames: SpriteFrames = _initial_sprite_frames: set = _set_sprite_frames @@ -104,6 +122,43 @@ func stomp(): _jump() +func _player_just_pressed(action): + if player == Global.Player.BOTH: + return ( + Input.is_action_just_pressed(_PLAYER_ACTIONS[Global.Player.ONE][action]) + or Input.is_action_just_pressed(_PLAYER_ACTIONS[Global.Player.TWO][action]) + ) + return Input.is_action_just_pressed(_PLAYER_ACTIONS[player][action]) + + +func _player_just_released(action): + if player == Global.Player.BOTH: + return ( + Input.is_action_just_released(_PLAYER_ACTIONS[Global.Player.ONE][action]) + or Input.is_action_just_released(_PLAYER_ACTIONS[Global.Player.TWO][action]) + ) + return Input.is_action_just_released(_PLAYER_ACTIONS[player][action]) + + +func _get_player_axis(action_a, action_b): + if player == Global.Player.BOTH: + return clamp( + ( + Input.get_axis( + _PLAYER_ACTIONS[Global.Player.ONE][action_a], + _PLAYER_ACTIONS[Global.Player.ONE][action_b] + ) + + Input.get_axis( + _PLAYER_ACTIONS[Global.Player.TWO][action_a], + _PLAYER_ACTIONS[Global.Player.TWO][action_b] + ) + ), + -1, + 1 + ) + return Input.get_axis(_PLAYER_ACTIONS[player][action_a], _PLAYER_ACTIONS[player][action_b]) + + func _physics_process(delta): # Don't move if there are no lives left. if Global.lives <= 0: @@ -114,7 +169,7 @@ func _physics_process(delta): coyote_timer = (coyote_time + delta) double_jump_armed = false - if Input.is_action_just_pressed("ui_accept"): + if _player_just_pressed("jump"): jump_buffer_timer = (jump_buffer + delta) if jump_buffer_timer > 0 and (double_jump_armed or coyote_timer > 0): @@ -122,7 +177,7 @@ func _physics_process(delta): # Reduce velocity if the player lets go of the jump key before the apex. # This allows controlling the height of the jump. - if Input.is_action_just_released("ui_accept") and velocity.y < 0: + if _player_just_released("jump") and velocity.y < 0: velocity.y *= (1 - (jump_cut_factor / 100.00)) # Add the gravity. @@ -131,7 +186,7 @@ func _physics_process(delta): # Get the input direction and handle the movement/deceleration. # As good practice, you should replace UI actions with custom gameplay actions. - var direction = Input.get_axis("ui_left", "ui_right") + var direction = _get_player_axis("left", "right") if direction: velocity.x = move_toward( velocity.x, From 894d3205f5803e53d09d91318d7f8dcd8bda7a24 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Manuel=20Qui=C3=B1ones?= Date: Fri, 8 Nov 2024 14:31:52 -0300 Subject: [PATCH 3/5] HUD: Explain player controls Using the current label in the center. Nothing fancy. --- hud.tscn | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/hud.tscn b/hud.tscn index 0c022c5..802f9ca 100644 --- a/hud.tscn +++ b/hud.tscn @@ -14,15 +14,19 @@ anchor_left = 0.5 anchor_top = 0.5 anchor_right = 0.5 anchor_bottom = 0.5 -offset_left = -194.5 -offset_top = -396.0 -offset_right = 194.5 -offset_bottom = -217.0 +offset_left = -367.5 +offset_top = -487.0 +offset_right = 367.5 +offset_bottom = 56.0 grow_horizontal = 2 grow_vertical = 2 size_flags_horizontal = 4 theme_override_font_sizes/font_size = 64 -text = "Press any key +text = "Controls +Player One: Arrow Keys +Player Two: WASD + +Press any key to Start" horizontal_alignment = 1 From 0ab51de4916a41bd42964a20d84986cb34017f42 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Manuel=20Qui=C3=B1ones?= Date: Fri, 8 Nov 2024 14:48:01 -0300 Subject: [PATCH 4/5] Check collisions with player using groups Add a global group "players". Add the player scene to it. Change the enemy collision and falling platform collision to use that player instead of the node name. https://github.com/endlessm/moddable-platformer/issues/8 --- components/enemy/enemy.gd | 2 +- components/platform/platform.gd | 2 +- components/player/player.tscn | 2 +- project.godot | 4 ++++ 4 files changed, 7 insertions(+), 3 deletions(-) diff --git a/components/enemy/enemy.gd b/components/enemy/enemy.gd index 6358732..807459d 100644 --- a/components/enemy/enemy.gd +++ b/components/enemy/enemy.gd @@ -69,7 +69,7 @@ func _on_gravity_changed(new_gravity): func _on_hitbox_body_entered(body): - if body.name == "Player": + if body.is_in_group("players"): if squashable and body.velocity.y > 0 and body.position.y < position.y: body.stomp() queue_free() diff --git a/components/platform/platform.gd b/components/platform/platform.gd index ccf7dfe..09e2e7f 100644 --- a/components/platform/platform.gd +++ b/components/platform/platform.gd @@ -83,7 +83,7 @@ func _ready(): func _on_area_2d_body_entered(body): - if not body.name == "Player": + if not body.is_in_group("players"): return if fall_time > 0: fall_timer.start(fall_time) diff --git a/components/player/player.tscn b/components/player/player.tscn index 3228bdb..89ef363 100644 --- a/components/player/player.tscn +++ b/components/player/player.tscn @@ -7,7 +7,7 @@ radius = 31.0 height = 92.0 -[node name="Player" type="CharacterBody2D"] +[node name="Player" type="CharacterBody2D" groups=["players"]] collision_layer = 3 collision_mask = 5 floor_constant_speed = true diff --git a/project.godot b/project.godot index 37622eb..c07991c 100644 --- a/project.godot +++ b/project.godot @@ -28,6 +28,10 @@ window/size/window_width_override=960 window/size/window_height_override=540 window/stretch/mode="canvas_items" +[global_group] + +players="" + [input] player_2_up={ From 093f8bf15ce33839be7056184b577aaddfb5a5a2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Manuel=20Qui=C3=B1ones?= Date: Fri, 8 Nov 2024 15:12:42 -0300 Subject: [PATCH 5/5] Docs: Document multiplayer and player controls --- doc/MODS.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/doc/MODS.md b/doc/MODS.md index d467043..2230695 100644 --- a/doc/MODS.md +++ b/doc/MODS.md @@ -135,6 +135,15 @@ on one of these nodes. In the Inspector, you can adjust their behaviour: These platforms can be made into moving platforms by using Godot's Animation functionality. +### Multiplayer and player controls + +Click on the `Player` node. In the inspector, try changing the Player +dropdown to "Two" or "Both". + +Duplicate the `Player` node. Move it next to the existing player. Then +change the Player to "Two" in the duplicated one. Now the game is +multiplayer. + ### Player and flag appearance Click on the `Player` node. In the inspector, observe where it says `Sprite