platformer-game-test/src/Actors/Player.gd

309 lines
8.8 KiB
GDScript

extends KinematicBody2D
class_name Player
export var run_speed := 100.0
export var jump_power := 180.0
export var gravity: = 500.0
export var max_gravity:= 450.0
export var respawn_position:=Vector2.ZERO
export var wall_slide_friction:=.2
export var wall_jump_speed_factor := Vector2(1 ,.8)
export var dash_thrust = 550.0
export var max_wall_slide_gravity := 100.0
var LandingDust = load("res://src/Actors/LandingDust.tscn")
var JumpDust = load("res://src/Actors/JumpDust.tscn")
var LightBeam = load("res://src/Actors/LightBeam.tscn")
signal landed
signal jumping
signal died
var _velocity: Vector2 = Vector2.ZERO
var _landing_position:Vector2 = Vector2.ZERO
var _is_wall_jumping := false
var _alive := true
var _can_dash := true
var _is_jump_canceled := false
var _direction := Vector2.ZERO
var _falling_start_position := .0
enum States {
IDLE,
IN_AIR,
DASHING,
WALL_SLIDING,
ATTACKING
}
var _state = States.IDLE
enum Direction{
RIGHT = 1,
LEFT = -1
}
export var abilities:= {
"dash": false,
"wall_jump": false,
"attack": false,
"light_beam": false
}
func _ready() -> void:
respawn_position = position
_falling_start_position = respawn_position.y
$AnimationPlayer.play("idle")
func _physics_process(delta: float) -> void:
if _alive:
_update_state()
_input_check()
_velocity = calculate_move_velocity(_direction,_is_jump_canceled, delta)
_velocity = move_and_slide(_velocity, Vector2.UP)
_check_collisions()
update_sprite(_direction)
func _input_check() -> void:
if GameState.get_state() == GameState.States.GAME or GameState.get_state() == GameState.States.NEW_GAME:
_is_jump_canceled = Input.is_action_just_released("jump") and !_is_wall_jumping and _velocity.y < 0.0;
_get_direction()
_check_dash()
if Input.is_action_just_pressed("attack") and abilities.attack:
_attack()
else:
_direction = Vector2.ZERO
func _check_collisions():
for i in get_slide_count():
var collision = get_slide_collision(i)
if collision.collider.name == 'TrapTiles':
die()
return
func _update_state(new_state:int = -1):
var auto_update = new_state == -1;
if new_state == -1:
new_state = _state
if _state == States.DASHING: return
if _state == States.ATTACKING: return
if _state == States.IDLE:
if _velocity.y > 0 and not is_on_floor():
# Falling from ground
new_state = States.IN_AIR
elif _state == States.IN_AIR:
if is_on_floor():
_on_landed()
new_state = States.IDLE
elif _velocity.y >=0 and is_on_wall():
if _state != States.WALL_SLIDING:
_velocity.y=0
_falling_start_position = position.y
new_state = States.WALL_SLIDING
elif _state == States.WALL_SLIDING:
if is_on_floor() or not is_on_wall():
new_state = States.IDLE
else: new_state = States.IDLE
_transition_state(_state, new_state, auto_update)
func _transition_state(old_state, new_state, auto_update):
if new_state != old_state:
# print("[Player]: State Update: [%s] -> [%s] | Automatic: %s" % [old_state, new_state, auto_update])
_state = new_state
if old_state == States.IDLE:
# idle -> attak = ground attack - stop _velocity.x
if new_state == States.ATTACKING:
_direction.x = 0;
_velocity.x = 0;
if old_state == States.DASHING:
if new_state == States.IDLE:
_can_dash = true
if old_state == States.WALL_SLIDING:
if new_state == States.IDLE:
_on_landed()
func _on_landed():
_can_dash = true
var dust = LandingDust.instance()
dust.position = position
get_parent().add_child(dust)
var fall_distance = position.y - _falling_start_position
if fall_distance > 150: $Camera.start_shake()
_falling_start_position = position.y
emit_signal("landed", position)
func _on_jump(wall_jump:bool = false):
AudioManager.play_sfx(AudioManager.Sfx.PLAYER_JUMP)
var dust:Node = JumpDust.instance()
dust.position = position;
get_parent().add_child(dust)
if wall_jump:
dust.position.x += 2 * -$Sprite.scale.x
dust.get_node("Sprite").rotate(deg2rad(90) * -$Sprite.scale.x)
emit_signal("jumping", position)
func _get_direction() -> void:
var right_strength = round(Input.get_action_strength("direction_right"))
var left_strength = round(Input.get_action_strength("direction_left"))
var vertical_movement = right_strength - left_strength if not _state == States.DASHING else 0
var is_in_jumpable_state = _state == States.IDLE or _state == States.WALL_SLIDING
_direction = Vector2(
vertical_movement,
-1.0 if Input.is_action_just_pressed("jump") and is_in_jumpable_state else 1.0
)
func _check_dash():
if Input.is_action_just_pressed("dash") and _can_dash and abilities.dash:
var dash_velocity := Vector2($Sprite.scale.x * dash_thrust,0)
$Camera.start_shake(.1, 15, 2)
_can_dash = false
# Wall dash first
if _state == States.WALL_SLIDING:
dash_velocity.x = $Sprite.scale.x * dash_thrust * -1
if dash_velocity.x < 0 :
$DashParticlesLeft.emitting = true;
else:
$DashParticlesRight.emitting = true
_velocity = dash_velocity
$DashTimeout.start()
_update_state(States.DASHING) # turn off gravity while dashing
func calculate_move_velocity(direction:Vector2, is_jump_canceled:bool, delta:float)->Vector2:
var output: = _velocity
var current_gravity = gravity * wall_slide_friction if _state == States.WALL_SLIDING else gravity
output.x = lerp(_velocity.x, run_speed * direction.x, .1 if _state == States.IN_AIR else .5)
output.y += current_gravity * delta
if _state == States.DASHING:
return _velocity
if direction.y == -1.0: # we are jumping
var wall_jump := false
if _state == States.WALL_SLIDING and abilities.wall_jump:
# wall jump
wall_jump = true
var walljump__x_direction = -1 * direction.x
var desired = (run_speed * wall_jump_speed_factor.x * walljump__x_direction)
output.x = desired
output.y = jump_power * wall_jump_speed_factor.y * direction.y
else:
#jump
output.y = jump_power * direction.y
_update_state(States.IN_AIR)
_on_jump(wall_jump)
else:
if is_jump_canceled:
output.y = 0
if output.y < max_gravity * -1:
output.y = max_gravity
if _state == States.WALL_SLIDING and output.y > max_wall_slide_gravity:
output.y = max_wall_slide_gravity
return output;
func _respawn():
position = respawn_position
_velocity = Vector2.ZERO
func _revive():
_alive = true
func update_sprite(_direction:Vector2) -> void:
if not _alive: return;
var air_animation = "jump" if _velocity.y <= 0 else "fall"
if _state == States.ATTACKING: return
var attack = Input.is_action_just_pressed("attack") and abilities.attack
if _velocity.x > .5 and not _state == States.WALL_SLIDING:
face_player(true)
$AnimationPlayer.play("run" if is_on_floor() else air_animation)
elif _velocity.x < -.5 and not _state == States.WALL_SLIDING:
face_player(false)
$AnimationPlayer.play("run" if is_on_floor() else air_animation)
else:
if not _state == States.IN_AIR: $AnimationPlayer.play("idle")
if _state == States.WALL_SLIDING:
$AnimationPlayer.play("wall_slide")
return
if _state == States.IN_AIR:
$AnimationPlayer.play(air_animation)
if air_animation == "fall" and position.y < _falling_start_position:
_falling_start_position = position.y
return
if _state == States.DASHING:
$AnimationPlayer.play("jump")
return
func _attack():
if _state == States.DASHING:
var beam = LightBeam.instance()
beam.direction = Vector2($Sprite.scale.x, 0)
beam.position = $Sprite/LightBeamInitPoint.get_global_transform().get_origin()
get_parent().add_child(beam)
$DashTimeout.stop()
_on_DashTimeout_timeout(false)
_update_state(States.ATTACKING)
AudioManager.play_sfx(AudioManager.Sfx.PLAYER_ATTACK)
var attack_animation = "attack_right" if $Sprite.scale.x > 0 else "attack_left"
$AnimationPlayer.play(attack_animation)
$AnimationPlayer.playback_speed = 2.5
yield($AnimationPlayer, "animation_finished")
$AnimationPlayer.seek(0)
$AnimationPlayer.playback_speed = 1
_update_state(States.IDLE)
func _on_die_animation_done():
_respawn()
_revive()
func die():
emit_signal("died")
_alive = false
$AnimationPlayer.play("die")
$Camera.start_shake()
GameState.player_died()
func set_ability(ability:String, enabled:bool=false):
if ability == 'dash':
abilities.dash = enabled
elif ability == 'wall_jump':
abilities.wall_jump = enabled
elif ability == 'attack':
abilities.attack = enabled
else:
pass
func _on_DashTimeout_timeout(timed_out=true) -> void:
$AnimationPlayer.playback_speed = 1
$DashParticlesLeft.emitting = false
$DashParticlesRight.emitting = false
_velocity.x = run_speed * _velocity.normalized().x;
if timed_out: _update_state(States.IN_AIR)
pass # Replace with function body.
func _on_SordRange_body_entered(body: Node) -> void:
if body.is_in_group("Enemies"):
body.take_damage($Sprite.scale.x)
pass # Replace with function body.
func get_camera():
return $Camera
func face_player(right:bool = true):
$Sprite.scale = Vector2(
1 if right else -1,
1
)