519 lines
12 KiB
GDScript
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
|
|
)
|