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/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 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 diff --git a/project.godot b/project.godot index f3c2921..c07991c 100644 --- a/project.godot +++ b/project.godot @@ -28,6 +28,69 @@ window/size/window_width_override=960 window/size/window_height_override=540 window/stretch/mode="canvas_items" +[global_group] + +players="" + +[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" 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,