class_name Player extends CharacterBody2D ## Character controller. ## ## Controls the player character in this 2D side-scrolling platformer signal player_died enum FaceDirection { LEFT, RIGHT } enum State { IDLE, RUN, JUMP, FALL, SHOOT_STILL, SHOOT_RUN, PUNCH, HURT, DIE } const JUMP_VELOCITY := -500.0 ## Power applied for jumping const SPEED := 200.0 ## (Max) movement speed @export var BUMP_POWER := 50 ## Power from bumping into objects @export var PUNCH_POWER := 300 ## Power from shoving objects @export var ACCELERATION := 10 ## Amount of "slide-y-ness" in side-to-side-movement @export var HARD_GRAVITY := 2 ## Factor applied during FALL state var current_state: State = State.IDLE ## Player state for FSM var facing : FaceDirection = FaceDirection.RIGHT ## Facing direction for animation orientation var direction : float = 0.0 var punch_target : RigidBody2D var punch_enabled : bool = false var jump_buffer_timer : Timer var shoot_cooldown_timer : Timer @onready var right_cast: RayCast2D = $RightCast @onready var left_cast: RayCast2D = $LeftCast @onready var right_spawn: Marker2D = $RightSpawn @onready var left_spawn: Marker2D = $LeftSpawn @onready var player_sprite: AnimatedSprite2D = $PlayerSprite func _ready() -> void: jump_buffer_timer = Timer.new() jump_buffer_timer.one_shot = true add_child(jump_buffer_timer) shoot_cooldown_timer = Timer.new() shoot_cooldown_timer.one_shot = true add_child(shoot_cooldown_timer) func _physics_process(delta: float) -> void: if current_state != State.HURT and current_state != State.DIE: handle_input() move_and_slide() handle_movement(delta) handle_collisions() update_state() update_animation() update_debug() func handle_damage(_health, _max_health) -> void: current_state = State.HURT func handle_death() -> void: current_state = State.DIE func handle_input() -> void: # Handle jumping. if Input.is_action_just_pressed("jump"): jump_buffer_timer.start(0.1) if is_on_floor() and jump_buffer_timer.time_left > 0: velocity.y = JUMP_VELOCITY current_state = State.JUMP jump_buffer_timer.stop() # Set direction. direction = Input.get_axis("move_left", "move_right") if direction < 0: facing = FaceDirection.LEFT if direction > 0: facing = FaceDirection.RIGHT # Handle shoving. if Input.is_action_just_pressed("punch"): print_debug("Trying to punch") if punch_enabled == true: print_debug("Punching enabled") var punch_direction : int match facing: FaceDirection.RIGHT: punch_direction = 1 FaceDirection.LEFT: punch_direction = -1 print_debug("Punching %s" % punch_target.name) current_state = State.PUNCH punch_target.apply_central_impulse(Vector2(punch_direction, 0) * PUNCH_POWER) # Handle shooting if Input.is_action_just_pressed("shoot"): if shoot_cooldown_timer.time_left == 0: if velocity.x: current_state = State.SHOOT_RUN else: current_state = State.SHOOT_STILL match facing: FaceDirection.RIGHT: %SceneManager.make_bullet(right_spawn.global_transform, 700) FaceDirection.LEFT: %SceneManager.make_bullet(left_spawn.global_transform, -700) shoot_cooldown_timer.start(0.2) else: print("can't shoot right now because there are %s seconds left in the cooldown timer" % shoot_cooldown_timer.time_left) # Handle throwing grenades if Input.is_action_just_pressed("throw"): match facing: FaceDirection.RIGHT: %SceneManager.make_grenade(right_spawn.global_transform, 1.0) FaceDirection.LEFT: %SceneManager.make_grenade(left_spawn.global_transform, -1.0) func handle_movement(delta) -> void: # Left-right movement. Use acceleration for smoothing if direction: velocity.x = move_toward(velocity.x, SPEED * direction, ACCELERATION) else: velocity.x = move_toward(velocity.x, 0, ACCELERATION) if not is_on_floor(): # Add gravity. if current_state == State.JUMP: # Character is jumping; apply normal gravity velocity += get_gravity() * delta if velocity.y > 0: current_state = State.FALL else: # Character falling; apply hard gravity current_state = State.FALL velocity += get_gravity() * HARD_GRAVITY * delta func handle_collisions() -> void: for i in get_slide_collision_count(): var c = get_slide_collision(i) if c.get_collider() is RigidBody2D and c.get_collider() is not Grenade: c.get_collider().apply_central_impulse(-c.get_normal() * BUMP_POWER) if right_cast.is_colliding() and facing == FaceDirection.RIGHT: #print_debug("Colliding with something to the right") var collider = right_cast.get_collider() #print_debug("Colliding with %s " % collider.name) if collider is Node and collider is RigidBody2D and collider.is_in_group("punchable"): #print_debug("We have a punch target") punch_target = collider punch_enabled = true if left_cast.is_colliding() and facing == FaceDirection.LEFT: #print_debug("Colliding with something to the left") var collider = left_cast.get_collider() #print_debug("Colliding with %s " % collider.name) if collider is Node and collider is RigidBody2D and collider.is_in_group("punchable"): print_debug("We have a punch target") punch_target = collider punch_enabled = true if not right_cast.is_colliding() and not left_cast.is_colliding(): punch_enabled = false func update_state() -> void: match current_state: # If player is moving left or right State.IDLE when velocity.x !=0: current_state = State.RUN # If player stops walking, or starts falling State.RUN: # If not moving left or right if velocity.x == 0: current_state = State.IDLE # If falling if not is_on_floor() and velocity.y > 0: current_state = State.FALL # When jump peaks, we start to fall State.JUMP when velocity.y > 0: current_state = State.FALL # Player lands, either still or moving State.FALL when is_on_floor(): if velocity.x == 0: current_state = State.IDLE else: current_state = State.RUN # Player shooting State.SHOOT_STILL: await player_sprite.animation_finished current_state = State.IDLE # Player shooting while moving State.SHOOT_RUN: await player_sprite.animation_finished current_state = State.RUN # Player shoving State.PUNCH: await player_sprite.animation_finished current_state = State.IDLE State.HURT: await player_sprite.animation_finished current_state = State.IDLE State.DIE: await player_sprite.animation_finished player_died.emit() func update_animation() -> void: match facing: FaceDirection.LEFT: player_sprite.flip_h = true FaceDirection.RIGHT: player_sprite.flip_h = false match current_state: State.IDLE: player_sprite.play("idle") State.RUN: player_sprite.play("run") State.JUMP: player_sprite.play("jump") State.FALL: player_sprite.play("fall") State.SHOOT_STILL: player_sprite.play("shoot_still") State.SHOOT_RUN: player_sprite.play("shoot_run") State.PUNCH: player_sprite.play("punch") State.HURT: player_sprite.play("hurt") State.DIE: player_sprite.play("die") func update_debug(): %StateLabel.text = "Current state: %s" % State.keys()[current_state]