Files
mortal-maze-the-game/player.gd
T

519 lines
12 KiB
GDScript

extends CharacterBody2D
# ============================================================================
# MOVIMIENTO
# ============================================================================
@export_category("Movement")
@export var speed: float = 300.0
@export var ground_acceleration: float = 2200.0
@export var ground_friction: float = 2600.0
@export var air_acceleration: float = 1000.0
# ============================================================================
# SALTO
# ============================================================================
@export_category("Jump")
@export var jump_velocity: float = -600.0
# Permite saltar ligeramente después de abandonar el suelo.
@export var coyote_time: float = 0.12
# Permite pulsar salto ligeramente antes de tocar el suelo.
@export var jump_buffer_time: float = 0.12
# Al soltar el botón durante la subida reducimos la velocidad vertical.
# Cuanto más pequeño, más brusco será el salto corto.
@export_range(0.1, 1.0) var jump_cut_multiplier: float = 0.45
# La gravedad normal durante la subida.
@export var gravity_multiplier: float = 1.0
# La caída es más rápida que la subida.
@export var fall_gravity_multiplier: float = 1.7
@export var max_fall_speed: float = 1200.0
# ============================================================================
# PUÑETAZO
# ============================================================================
@export_category("Punch")
@export var punch_damage: int = 1
@export var punch_duration: float = 0.13
@export var punch_cooldown: float = 0.28
@export var punch_hitbox_size := Vector2(55.0, 38.0)
@export var punch_offset := Vector2(38.0, 0.0)
# ============================================================================
# GROUND POUND
# ============================================================================
@export_category("Ground Pound")
@export var ground_pound_damage: int = 3
# Velocidad mínima que adquiere al comenzar el golpe hacia abajo.
@export var ground_pound_start_speed: float = 450.0
# Gravedad adicional mientras cae haciendo ground pound.
@export var ground_pound_gravity_multiplier: float = 4.5
# Control horizontal mientras cae.
@export_range(0.0, 1.0) var ground_pound_horizontal_control: float = 0.25
@export var ground_pound_radius: float = 85.0
@export var ground_pound_offset := Vector2(0.0, 20.0)
# ============================================================================
# DEBUG
# ============================================================================
@export_category("Debug")
@export var show_attack_hitboxes: bool = true
@export var debug_impact_duration: float = 0.18
# ============================================================================
# NODOS
# ============================================================================
@onready var animated_sprite: AnimatedSprite2D = $AnimatedSprite2D
@onready var punch_hitbox: Area2D = $PunchHitbox
@onready var punch_collision: CollisionShape2D = $PunchHitbox/CollisionShape2D
@onready var ground_pound_hitbox: Area2D = $GroundPoundHitbox
@onready var ground_pound_collision: CollisionShape2D = \
$GroundPoundHitbox/CollisionShape2D
# ============================================================================
# ESTADO INTERNO
# ============================================================================
var facing_direction: float = 1.0
var coyote_timer: float = 0.0
var jump_buffer_timer: float = 0.0
var punch_timer: float = 0.0
var attack_cooldown_timer: float = 0.0
var ground_pound_active: bool = false
var ground_pound_debug_timer: float = 0.0
# Evita que un enemigo reciba daño 60 veces durante el mismo puñetazo.
var punch_hit_targets: Dictionary = {}
func _ready() -> void:
_setup_hitboxes()
func _physics_process(delta: float) -> void:
var was_on_floor := is_on_floor()
_update_timers(delta)
_update_jump_windows(delta)
_handle_attack_input()
_handle_jump()
_apply_gravity(delta)
_handle_horizontal_movement(delta)
_update_punch_damage()
_update_animation()
move_and_slide()
# El impacto debe comprobarse DESPUÉS de move_and_slide(),
# porque es entonces cuando sabemos si hemos aterrizado.
if ground_pound_active and not was_on_floor and is_on_floor():
_ground_pound_impact()
queue_redraw()
# ============================================================================
# MOVIMIENTO
# ============================================================================
func _handle_horizontal_movement(delta: float) -> void:
var direction := Input.get_axis("move_left", "move_right")
if direction != 0.0:
facing_direction = sign(direction)
_update_facing()
if ground_pound_active:
var target_speed := (
direction
* speed
* ground_pound_horizontal_control
)
velocity.x = move_toward(
velocity.x,
target_speed,
air_acceleration * delta
)
return
if is_on_floor():
if direction != 0.0:
velocity.x = move_toward(
velocity.x,
direction * speed,
ground_acceleration * delta
)
else:
velocity.x = move_toward(
velocity.x,
0.0,
ground_friction * delta
)
else:
velocity.x = move_toward(
velocity.x,
direction * speed,
air_acceleration * delta
)
func _update_facing() -> void:
animated_sprite.flip_h = facing_direction < 0.0
punch_hitbox.position = Vector2(
abs(punch_offset.x) * facing_direction,
punch_offset.y
)
# ============================================================================
# GRAVEDAD
# ============================================================================
func _apply_gravity(delta: float) -> void:
if is_on_floor():
return
var gravity := get_gravity()
if ground_pound_active:
velocity += (
gravity
* ground_pound_gravity_multiplier
* delta
)
elif velocity.y > 0.0:
# Más gravedad al caer.
velocity += gravity * fall_gravity_multiplier * delta
else:
# Gravedad normal durante la subida.
velocity += gravity * gravity_multiplier * delta
velocity.y = min(velocity.y, max_fall_speed)
# ============================================================================
# SALTO
# ============================================================================
func _update_jump_windows(delta: float) -> void:
if is_on_floor():
coyote_timer = coyote_time
else:
coyote_timer = max(coyote_timer - delta, 0.0)
if Input.is_action_just_pressed("jump"):
jump_buffer_timer = jump_buffer_time
else:
jump_buffer_timer = max(
jump_buffer_timer - delta,
0.0
)
func _handle_jump() -> void:
if (
jump_buffer_timer > 0.0
and coyote_timer > 0.0
and not ground_pound_active
):
velocity.y = jump_velocity
jump_buffer_timer = 0.0
coyote_timer = 0.0
# Salto variable.
#
# Mantienes pulsado:
# salto alto
#
# Sueltas pronto:
# salto corto
if (
Input.is_action_just_released("jump")
and velocity.y < 0.0
):
velocity.y *= jump_cut_multiplier
# ============================================================================
# ATAQUE
# ============================================================================
func _handle_attack_input() -> void:
if not Input.is_action_just_pressed("attack"):
return
if attack_cooldown_timer > 0.0:
return
if is_on_floor():
_start_punch()
else:
_start_ground_pound()
# ============================================================================
# PUÑETAZO
# ============================================================================
func _start_punch() -> void:
punch_timer = punch_duration
attack_cooldown_timer = punch_cooldown
punch_hit_targets.clear()
func _update_punch_damage() -> void:
if punch_timer <= 0.0:
return
for body in punch_hitbox.get_overlapping_bodies():
_damage_target(
body,
punch_damage,
punch_hit_targets
)
for area in punch_hitbox.get_overlapping_areas():
_damage_target(
area,
punch_damage,
punch_hit_targets
)
# ============================================================================
# GROUND POUND
# ============================================================================
func _start_ground_pound() -> void:
if ground_pound_active:
return
ground_pound_active = true
attack_cooldown_timer = punch_cooldown
# Si todavía estamos subiendo, cortamos inmediatamente la subida.
velocity.y = max(
velocity.y,
ground_pound_start_speed
)
func _ground_pound_impact() -> void:
ground_pound_active = false
ground_pound_debug_timer = debug_impact_duration
var hit_targets: Dictionary = {}
for body in ground_pound_hitbox.get_overlapping_bodies():
_damage_target(
body,
ground_pound_damage,
hit_targets
)
for area in ground_pound_hitbox.get_overlapping_areas():
_damage_target(
area,
ground_pound_damage,
hit_targets
)
# ============================================================================
# DAÑO
# ============================================================================
func _damage_target(
collider: Node,
damage: int,
hit_targets: Dictionary
) -> void:
if collider == self:
return
var target := collider
# Esto permite que el enemigo tenga una Hurtbox Area2D.
if not target.has_method("take_damage"):
var parent := target.get_parent()
if parent != null and parent.has_method("take_damage"):
target = parent
if not target.has_method("take_damage"):
return
var target_id := target.get_instance_id()
if hit_targets.has(target_id):
return
hit_targets[target_id] = true
target.take_damage(
damage,
global_position
)
# ============================================================================
# TIMERS
# ============================================================================
func _update_timers(delta: float) -> void:
punch_timer = max(
punch_timer - delta,
0.0
)
attack_cooldown_timer = max(
attack_cooldown_timer - delta,
0.0
)
ground_pound_debug_timer = max(
ground_pound_debug_timer - delta,
0.0
)
# ============================================================================
# ANIMACIONES
# ============================================================================
func _update_animation() -> void:
if ground_pound_active:
animated_sprite.play("walk")
elif not is_on_floor():
animated_sprite.play("walk")
elif abs(velocity.x) > 10.0:
animated_sprite.play("walk")
else:
animated_sprite.play("idle")
# ============================================================================
# CONFIGURACIÓN DE HITBOX
# ============================================================================
func _setup_hitboxes() -> void:
# Puñetazo
var punch_shape := RectangleShape2D.new()
punch_shape.size = punch_hitbox_size
punch_collision.shape = punch_shape
punch_hitbox.collision_layer = 0
punch_hitbox.collision_mask = 2
punch_hitbox.monitoring = true
# Ground pound
var pound_shape := CircleShape2D.new()
pound_shape.radius = ground_pound_radius
ground_pound_collision.shape = pound_shape
ground_pound_hitbox.position = ground_pound_offset
ground_pound_hitbox.collision_layer = 0
ground_pound_hitbox.collision_mask = 2
ground_pound_hitbox.monitoring = true
_update_facing()
# ============================================================================
# DEBUG HITBOXES
# ============================================================================
func _draw() -> void:
if not show_attack_hitboxes:
return
# Puñetazo
if punch_timer > 0.0:
var center := punch_hitbox.position
var rect := Rect2(
center - punch_hitbox_size / 2.0,
punch_hitbox_size
)
draw_rect(
rect,
Color(1.0, 0.1, 0.1, 0.25),
true
)
draw_rect(
rect,
Color(1.0, 0.1, 0.1, 0.9),
false,
2.0
)
# Ground pound:
# solamente mostramos el área cuando realmente produce daño.
if ground_pound_debug_timer > 0.0:
var center := ground_pound_hitbox.position
draw_circle(
center,
ground_pound_radius,
Color(1.0, 0.5, 0.0, 0.20)
)
draw_arc(
center,
ground_pound_radius,
0.0,
TAU,
48,
Color(1.0, 0.5, 0.0, 0.9),
3.0
)