➕ADD: Personajes y enemigo de testeo
This commit is contained in:
1 parent
3d356166ca
commit
1b797a3e90
21 files changed
+696
-340
No files matched your search
@@ -0,0 +1,10 @@
|
||||
extends Camera2D
|
||||
|
||||
|
||||
## Límites provisionales de cámara para la sala de pruebas actual.
|
||||
## Cuando existan niveles reales, estos límites podrán configurarse por nivel.
|
||||
func _ready() -> void:
|
||||
limit_left = 0
|
||||
limit_top = 0
|
||||
limit_right = 3000
|
||||
limit_bottom = 2000
|
||||
@@ -0,0 +1 @@
|
||||
uid://kg3i7b0dj2p8
|
||||
@@ -0,0 +1,24 @@
|
||||
extends Resource
|
||||
class_name CharacterData
|
||||
|
||||
|
||||
## Datos que pueden variar entre los cuatro integrantes de la banda.
|
||||
##
|
||||
## La lógica vive en Player. Este recurso solo contiene configuración para que
|
||||
## cambiar de personaje no obligue a duplicar ni modificar player.gd.
|
||||
|
||||
@export_category("Identity")
|
||||
@export var id: StringName = &"character"
|
||||
@export var display_name: String = "Character"
|
||||
|
||||
@export_category("Visuals")
|
||||
## Los cuatro recursos apuntan por ahora a las mismas animaciones de Diego.
|
||||
## Más adelante cada integrante podrá tener su propio SpriteFrames sin tocar Player.
|
||||
@export var sprite_frames: SpriteFrames
|
||||
|
||||
@export_category("Stats")
|
||||
@export_range(1, 100, 1) var max_health: int = 5
|
||||
@export var move_speed: float = 300.0
|
||||
@export var jump_velocity: float = -600.0
|
||||
@export_range(0, 100, 1) var basic_attack_damage: int = 1
|
||||
@export_range(0, 100, 1) var air_attack_damage: int = 3
|
||||
@@ -0,0 +1 @@
|
||||
uid://bi04q2fe5goqn
|
||||
@@ -0,0 +1,15 @@
|
||||
[gd_resource type="Resource" script_class="CharacterData" load_steps=3 format=3]
|
||||
|
||||
[ext_resource type="Script" path="res://player/character_data.gd" id="1_data"]
|
||||
[ext_resource type="SpriteFrames" path="res://player/characters/shared/prototype_sprite_frames.tres" id="2_frames"]
|
||||
|
||||
[resource]
|
||||
script = ExtResource("1_data")
|
||||
id = &"bassist"
|
||||
display_name = "Bassist"
|
||||
sprite_frames = ExtResource("2_frames")
|
||||
max_health = 5
|
||||
move_speed = 300.0
|
||||
jump_velocity = -600.0
|
||||
basic_attack_damage = 1
|
||||
air_attack_damage = 3
|
||||
@@ -0,0 +1,15 @@
|
||||
[gd_resource type="Resource" script_class="CharacterData" load_steps=3 format=3]
|
||||
|
||||
[ext_resource type="Script" path="res://player/character_data.gd" id="1_data"]
|
||||
[ext_resource type="SpriteFrames" path="res://player/characters/shared/prototype_sprite_frames.tres" id="2_frames"]
|
||||
|
||||
[resource]
|
||||
script = ExtResource("1_data")
|
||||
id = &"drummer"
|
||||
display_name = "Drummer"
|
||||
sprite_frames = ExtResource("2_frames")
|
||||
max_health = 5
|
||||
move_speed = 300.0
|
||||
jump_velocity = -600.0
|
||||
basic_attack_damage = 1
|
||||
air_attack_damage = 3
|
||||
@@ -0,0 +1,15 @@
|
||||
[gd_resource type="Resource" script_class="CharacterData" load_steps=3 format=3]
|
||||
|
||||
[ext_resource type="Script" path="res://player/character_data.gd" id="1_data"]
|
||||
[ext_resource type="SpriteFrames" path="res://player/characters/shared/prototype_sprite_frames.tres" id="2_frames"]
|
||||
|
||||
[resource]
|
||||
script = ExtResource("1_data")
|
||||
id = &"guitarist"
|
||||
display_name = "Guitarist"
|
||||
sprite_frames = ExtResource("2_frames")
|
||||
max_health = 5
|
||||
move_speed = 300.0
|
||||
jump_velocity = -600.0
|
||||
basic_attack_damage = 1
|
||||
air_attack_damage = 3
|
||||
@@ -0,0 +1,248 @@
|
||||
[gd_resource type="SpriteFrames" load_steps=36 format=3]
|
||||
|
||||
[ext_resource type="Texture2D" uid="uid://cjxrw1p7eh82i" path="res://assets/characters/ankarde/Diego-idle-trimmed.png" id="1_idle"]
|
||||
[ext_resource type="Texture2D" uid="uid://dg1x17ci14p4f" path="res://assets/characters/ankarde/Diego-walk-trimmed.png" id="2_walk"]
|
||||
|
||||
[sub_resource type="AtlasTexture" id="AtlasTexture_lquwl"]
|
||||
atlas = ExtResource("1_idle")
|
||||
region = Rect2(0, 0, 256, 256)
|
||||
|
||||
[sub_resource type="AtlasTexture" id="AtlasTexture_7mycd"]
|
||||
atlas = ExtResource("1_idle")
|
||||
region = Rect2(256, 0, 256, 256)
|
||||
|
||||
[sub_resource type="AtlasTexture" id="AtlasTexture_272bh"]
|
||||
atlas = ExtResource("1_idle")
|
||||
region = Rect2(512, 0, 256, 256)
|
||||
|
||||
[sub_resource type="AtlasTexture" id="AtlasTexture_5vw27"]
|
||||
atlas = ExtResource("1_idle")
|
||||
region = Rect2(768, 0, 256, 256)
|
||||
|
||||
[sub_resource type="AtlasTexture" id="AtlasTexture_kek77"]
|
||||
atlas = ExtResource("1_idle")
|
||||
region = Rect2(1024, 0, 256, 256)
|
||||
|
||||
[sub_resource type="AtlasTexture" id="AtlasTexture_4c57u"]
|
||||
atlas = ExtResource("1_idle")
|
||||
region = Rect2(0, 256, 256, 256)
|
||||
|
||||
[sub_resource type="AtlasTexture" id="AtlasTexture_efxa6"]
|
||||
atlas = ExtResource("1_idle")
|
||||
region = Rect2(256, 256, 256, 256)
|
||||
|
||||
[sub_resource type="AtlasTexture" id="AtlasTexture_dg77c"]
|
||||
atlas = ExtResource("1_idle")
|
||||
region = Rect2(512, 256, 256, 256)
|
||||
|
||||
[sub_resource type="AtlasTexture" id="AtlasTexture_ycdy4"]
|
||||
atlas = ExtResource("1_idle")
|
||||
region = Rect2(768, 256, 256, 256)
|
||||
|
||||
[sub_resource type="AtlasTexture" id="AtlasTexture_w48qg"]
|
||||
atlas = ExtResource("1_idle")
|
||||
region = Rect2(1024, 256, 256, 256)
|
||||
|
||||
[sub_resource type="AtlasTexture" id="AtlasTexture_vivmo"]
|
||||
atlas = ExtResource("1_idle")
|
||||
region = Rect2(0, 512, 256, 256)
|
||||
|
||||
[sub_resource type="AtlasTexture" id="AtlasTexture_2cqfq"]
|
||||
atlas = ExtResource("1_idle")
|
||||
region = Rect2(256, 512, 256, 256)
|
||||
|
||||
[sub_resource type="AtlasTexture" id="AtlasTexture_yaehf"]
|
||||
atlas = ExtResource("2_walk")
|
||||
region = Rect2(0, 0, 256, 256)
|
||||
|
||||
[sub_resource type="AtlasTexture" id="AtlasTexture_074og"]
|
||||
atlas = ExtResource("2_walk")
|
||||
region = Rect2(256, 0, 256, 256)
|
||||
|
||||
[sub_resource type="AtlasTexture" id="AtlasTexture_cegan"]
|
||||
atlas = ExtResource("2_walk")
|
||||
region = Rect2(512, 0, 256, 256)
|
||||
|
||||
[sub_resource type="AtlasTexture" id="AtlasTexture_82xsv"]
|
||||
atlas = ExtResource("2_walk")
|
||||
region = Rect2(768, 0, 256, 256)
|
||||
|
||||
[sub_resource type="AtlasTexture" id="AtlasTexture_getpj"]
|
||||
atlas = ExtResource("2_walk")
|
||||
region = Rect2(1024, 0, 256, 256)
|
||||
|
||||
[sub_resource type="AtlasTexture" id="AtlasTexture_ryguw"]
|
||||
atlas = ExtResource("2_walk")
|
||||
region = Rect2(0, 256, 256, 256)
|
||||
|
||||
[sub_resource type="AtlasTexture" id="AtlasTexture_d13ii"]
|
||||
atlas = ExtResource("2_walk")
|
||||
region = Rect2(256, 256, 256, 256)
|
||||
|
||||
[sub_resource type="AtlasTexture" id="AtlasTexture_1u8w0"]
|
||||
atlas = ExtResource("2_walk")
|
||||
region = Rect2(512, 256, 256, 256)
|
||||
|
||||
[sub_resource type="AtlasTexture" id="AtlasTexture_0odxb"]
|
||||
atlas = ExtResource("2_walk")
|
||||
region = Rect2(768, 256, 256, 256)
|
||||
|
||||
[sub_resource type="AtlasTexture" id="AtlasTexture_lswn8"]
|
||||
atlas = ExtResource("2_walk")
|
||||
region = Rect2(1024, 256, 256, 256)
|
||||
|
||||
[sub_resource type="AtlasTexture" id="AtlasTexture_a6jrf"]
|
||||
atlas = ExtResource("2_walk")
|
||||
region = Rect2(0, 512, 256, 256)
|
||||
|
||||
[sub_resource type="AtlasTexture" id="AtlasTexture_xuqvo"]
|
||||
atlas = ExtResource("2_walk")
|
||||
region = Rect2(256, 512, 256, 256)
|
||||
|
||||
[sub_resource type="AtlasTexture" id="AtlasTexture_qsp4k"]
|
||||
atlas = ExtResource("2_walk")
|
||||
region = Rect2(512, 512, 256, 256)
|
||||
|
||||
[sub_resource type="AtlasTexture" id="AtlasTexture_kq58d"]
|
||||
atlas = ExtResource("2_walk")
|
||||
region = Rect2(768, 512, 256, 256)
|
||||
|
||||
[sub_resource type="AtlasTexture" id="AtlasTexture_seu75"]
|
||||
atlas = ExtResource("2_walk")
|
||||
region = Rect2(1024, 512, 256, 256)
|
||||
|
||||
[sub_resource type="AtlasTexture" id="AtlasTexture_htxhm"]
|
||||
atlas = ExtResource("2_walk")
|
||||
region = Rect2(0, 768, 256, 256)
|
||||
|
||||
[sub_resource type="AtlasTexture" id="AtlasTexture_jq2sk"]
|
||||
atlas = ExtResource("2_walk")
|
||||
region = Rect2(256, 768, 256, 256)
|
||||
|
||||
[sub_resource type="AtlasTexture" id="AtlasTexture_4k2k6"]
|
||||
atlas = ExtResource("2_walk")
|
||||
region = Rect2(512, 768, 256, 256)
|
||||
|
||||
[sub_resource type="AtlasTexture" id="AtlasTexture_0rl1p"]
|
||||
atlas = ExtResource("2_walk")
|
||||
region = Rect2(768, 768, 256, 256)
|
||||
|
||||
[sub_resource type="AtlasTexture" id="AtlasTexture_ok6jj"]
|
||||
atlas = ExtResource("2_walk")
|
||||
region = Rect2(1024, 768, 256, 256)
|
||||
|
||||
[sub_resource type="AtlasTexture" id="AtlasTexture_facbu"]
|
||||
atlas = ExtResource("2_walk")
|
||||
region = Rect2(0, 1024, 256, 256)
|
||||
|
||||
[resource]
|
||||
animations = [{
|
||||
"frames": [{
|
||||
"duration": 1.0,
|
||||
"texture": SubResource("AtlasTexture_lquwl")
|
||||
}, {
|
||||
"duration": 1.0,
|
||||
"texture": SubResource("AtlasTexture_7mycd")
|
||||
}, {
|
||||
"duration": 1.0,
|
||||
"texture": SubResource("AtlasTexture_272bh")
|
||||
}, {
|
||||
"duration": 1.0,
|
||||
"texture": SubResource("AtlasTexture_5vw27")
|
||||
}, {
|
||||
"duration": 1.0,
|
||||
"texture": SubResource("AtlasTexture_kek77")
|
||||
}, {
|
||||
"duration": 1.0,
|
||||
"texture": SubResource("AtlasTexture_4c57u")
|
||||
}, {
|
||||
"duration": 1.0,
|
||||
"texture": SubResource("AtlasTexture_efxa6")
|
||||
}, {
|
||||
"duration": 1.0,
|
||||
"texture": SubResource("AtlasTexture_dg77c")
|
||||
}, {
|
||||
"duration": 1.0,
|
||||
"texture": SubResource("AtlasTexture_ycdy4")
|
||||
}, {
|
||||
"duration": 1.0,
|
||||
"texture": SubResource("AtlasTexture_w48qg")
|
||||
}, {
|
||||
"duration": 1.0,
|
||||
"texture": SubResource("AtlasTexture_vivmo")
|
||||
}, {
|
||||
"duration": 1.0,
|
||||
"texture": SubResource("AtlasTexture_2cqfq")
|
||||
}],
|
||||
"loop": 1,
|
||||
"name": &"idle",
|
||||
"speed": 5.0
|
||||
}, {
|
||||
"frames": [{
|
||||
"duration": 1.0,
|
||||
"texture": SubResource("AtlasTexture_yaehf")
|
||||
}, {
|
||||
"duration": 1.0,
|
||||
"texture": SubResource("AtlasTexture_074og")
|
||||
}, {
|
||||
"duration": 1.0,
|
||||
"texture": SubResource("AtlasTexture_cegan")
|
||||
}, {
|
||||
"duration": 1.0,
|
||||
"texture": SubResource("AtlasTexture_82xsv")
|
||||
}, {
|
||||
"duration": 1.0,
|
||||
"texture": SubResource("AtlasTexture_getpj")
|
||||
}, {
|
||||
"duration": 1.0,
|
||||
"texture": SubResource("AtlasTexture_ryguw")
|
||||
}, {
|
||||
"duration": 1.0,
|
||||
"texture": SubResource("AtlasTexture_d13ii")
|
||||
}, {
|
||||
"duration": 1.0,
|
||||
"texture": SubResource("AtlasTexture_1u8w0")
|
||||
}, {
|
||||
"duration": 1.0,
|
||||
"texture": SubResource("AtlasTexture_0odxb")
|
||||
}, {
|
||||
"duration": 1.0,
|
||||
"texture": SubResource("AtlasTexture_lswn8")
|
||||
}, {
|
||||
"duration": 1.0,
|
||||
"texture": SubResource("AtlasTexture_a6jrf")
|
||||
}, {
|
||||
"duration": 1.0,
|
||||
"texture": SubResource("AtlasTexture_xuqvo")
|
||||
}, {
|
||||
"duration": 1.0,
|
||||
"texture": SubResource("AtlasTexture_qsp4k")
|
||||
}, {
|
||||
"duration": 1.0,
|
||||
"texture": SubResource("AtlasTexture_kq58d")
|
||||
}, {
|
||||
"duration": 1.0,
|
||||
"texture": SubResource("AtlasTexture_seu75")
|
||||
}, {
|
||||
"duration": 1.0,
|
||||
"texture": SubResource("AtlasTexture_htxhm")
|
||||
}, {
|
||||
"duration": 1.0,
|
||||
"texture": SubResource("AtlasTexture_jq2sk")
|
||||
}, {
|
||||
"duration": 1.0,
|
||||
"texture": SubResource("AtlasTexture_4k2k6")
|
||||
}, {
|
||||
"duration": 1.0,
|
||||
"texture": SubResource("AtlasTexture_0rl1p")
|
||||
}, {
|
||||
"duration": 1.0,
|
||||
"texture": SubResource("AtlasTexture_ok6jj")
|
||||
}, {
|
||||
"duration": 1.0,
|
||||
"texture": SubResource("AtlasTexture_facbu")
|
||||
}],
|
||||
"loop": 1,
|
||||
"name": &"walk",
|
||||
"speed": 5.0
|
||||
}]
|
||||
@@ -0,0 +1,15 @@
|
||||
[gd_resource type="Resource" script_class="CharacterData" load_steps=3 format=3]
|
||||
|
||||
[ext_resource type="Script" path="res://player/character_data.gd" id="1_data"]
|
||||
[ext_resource type="SpriteFrames" path="res://player/characters/shared/prototype_sprite_frames.tres" id="2_frames"]
|
||||
|
||||
[resource]
|
||||
script = ExtResource("1_data")
|
||||
id = &"singer"
|
||||
display_name = "Singer"
|
||||
sprite_frames = ExtResource("2_frames")
|
||||
max_health = 5
|
||||
move_speed = 300.0
|
||||
jump_velocity = -600.0
|
||||
basic_attack_damage = 1
|
||||
air_attack_damage = 3
|
||||
@@ -0,0 +1,497 @@
|
||||
extends CharacterBody2D
|
||||
|
||||
|
||||
signal health_changed(current_health: int, max_health: int)
|
||||
signal died
|
||||
|
||||
|
||||
# Collision layers used by this first playable slice.
|
||||
# Keeping the numbers here makes the scene setup easier to understand:
|
||||
# 1 -> world
|
||||
# 2 -> player body
|
||||
# 3 -> enemy body
|
||||
# 4 -> player hurtbox
|
||||
# 5 -> enemy hurtbox
|
||||
const ENEMY_HURTBOX_MASK: int = 1 << 4
|
||||
|
||||
|
||||
# ============================================================================
|
||||
# CHARACTER
|
||||
# ============================================================================
|
||||
|
||||
@export_category("Character")
|
||||
|
||||
## Resource that identifies the selected band member and contains the stats
|
||||
## that are allowed to differ between characters.
|
||||
@export var character_data: CharacterData
|
||||
|
||||
|
||||
# ============================================================================
|
||||
# MOVEMENT
|
||||
# ============================================================================
|
||||
|
||||
@export_category("Movement")
|
||||
|
||||
@export var ground_acceleration: float = 2200.0
|
||||
@export var ground_friction: float = 2600.0
|
||||
@export var air_acceleration: float = 1000.0
|
||||
|
||||
|
||||
# ============================================================================
|
||||
# JUMP
|
||||
# ============================================================================
|
||||
|
||||
@export_category("Jump")
|
||||
|
||||
## Allows a jump a few milliseconds after walking off a ledge.
|
||||
@export var coyote_time: float = 0.12
|
||||
|
||||
## Remembers an early jump press just before landing.
|
||||
@export var jump_buffer_time: float = 0.12
|
||||
|
||||
## Releasing jump while rising cuts the jump short.
|
||||
@export_range(0.1, 1.0) var jump_cut_multiplier: float = 0.45
|
||||
|
||||
@export var gravity_multiplier: float = 1.0
|
||||
@export var fall_gravity_multiplier: float = 1.7
|
||||
@export var max_fall_speed: float = 1200.0
|
||||
|
||||
|
||||
# ============================================================================
|
||||
# DAMAGE RECEIVED
|
||||
# ============================================================================
|
||||
|
||||
@export_category("Damage received")
|
||||
|
||||
## Short grace period so a contact hitbox cannot drain all health in one frame.
|
||||
@export var invulnerability_duration: float = 0.65
|
||||
|
||||
## Horizontal and vertical impulse applied when the player is hit.
|
||||
@export var damage_knockback: Vector2 = Vector2(300.0, 260.0)
|
||||
|
||||
|
||||
# ============================================================================
|
||||
# PUNCH
|
||||
# ============================================================================
|
||||
|
||||
@export_category("Punch")
|
||||
|
||||
@export var punch_duration: float = 0.13
|
||||
@export var punch_cooldown: float = 0.28
|
||||
@export var punch_hitbox_size: Vector2 = Vector2(55.0, 38.0)
|
||||
@export var punch_offset: Vector2 = Vector2(48.0, 0.0)
|
||||
|
||||
|
||||
# ============================================================================
|
||||
# GROUND POUND
|
||||
# ============================================================================
|
||||
|
||||
@export_category("Ground Pound")
|
||||
|
||||
## Minimum downward speed when the air attack starts.
|
||||
@export var ground_pound_start_speed: float = 450.0
|
||||
|
||||
## Extra gravity while performing the air attack.
|
||||
@export var ground_pound_gravity_multiplier: float = 4.5
|
||||
|
||||
## Small amount of steering kept during the fall.
|
||||
@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 = Vector2(0.0, 28.0)
|
||||
|
||||
|
||||
# ============================================================================
|
||||
# DEBUG
|
||||
# ============================================================================
|
||||
|
||||
@export_category("Debug")
|
||||
|
||||
## Draws the actual hurtbox and attack areas used by gameplay.
|
||||
## It is deliberately enabled while the final animations do not exist.
|
||||
@export var show_debug_hitboxes: bool = true
|
||||
|
||||
## Keeps the ground-pound area visible briefly after impact.
|
||||
@export var debug_impact_duration: float = 0.18
|
||||
|
||||
|
||||
# ============================================================================
|
||||
# NODES
|
||||
# ============================================================================
|
||||
|
||||
@onready var animated_sprite: AnimatedSprite2D = $AnimatedSprite2D
|
||||
@onready var hurtbox_collision: CollisionShape2D = $Hurtbox/CollisionShape2D
|
||||
@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
|
||||
|
||||
|
||||
# ============================================================================
|
||||
# RUNTIME STATE
|
||||
# ============================================================================
|
||||
|
||||
var current_health: int = 1
|
||||
var facing_direction: float = 1.0
|
||||
|
||||
var coyote_timer: float = 0.0
|
||||
var jump_buffer_timer: float = 0.0
|
||||
var invulnerability_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
|
||||
var is_dead: bool = false
|
||||
|
||||
## Prevents one target from receiving damage every physics frame during the
|
||||
## same punch. A new punch starts with an empty set again.
|
||||
var punch_hit_targets: Dictionary = {}
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
if character_data == null:
|
||||
push_error("Player needs a CharacterData resource.")
|
||||
return
|
||||
|
||||
current_health = character_data.max_health
|
||||
if character_data.sprite_frames != null:
|
||||
animated_sprite.sprite_frames = character_data.sprite_frames
|
||||
|
||||
_setup_attack_hitboxes()
|
||||
_update_facing()
|
||||
_update_animation()
|
||||
health_changed.emit(current_health, character_data.max_health)
|
||||
|
||||
|
||||
func _physics_process(delta: float) -> void:
|
||||
if character_data == null or is_dead:
|
||||
return
|
||||
|
||||
var was_on_floor: bool = 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()
|
||||
|
||||
move_and_slide()
|
||||
|
||||
# Landing is only reliable after move_and_slide() has resolved collisions.
|
||||
if ground_pound_active and not was_on_floor and is_on_floor():
|
||||
_ground_pound_impact()
|
||||
|
||||
_update_animation()
|
||||
queue_redraw()
|
||||
|
||||
|
||||
# ============================================================================
|
||||
# MOVEMENT
|
||||
# ============================================================================
|
||||
|
||||
func _handle_horizontal_movement(delta: float) -> void:
|
||||
var direction: float = Input.get_axis("move_left", "move_right")
|
||||
|
||||
if direction != 0.0:
|
||||
facing_direction = signf(direction)
|
||||
_update_facing()
|
||||
|
||||
var target_speed: float = direction * character_data.move_speed
|
||||
|
||||
if ground_pound_active:
|
||||
target_speed *= ground_pound_horizontal_control
|
||||
velocity.x = move_toward(
|
||||
velocity.x,
|
||||
target_speed,
|
||||
air_acceleration * delta
|
||||
)
|
||||
return
|
||||
|
||||
if is_on_floor():
|
||||
var acceleration: float = ground_acceleration if direction != 0.0 else ground_friction
|
||||
velocity.x = move_toward(velocity.x, target_speed, acceleration * delta)
|
||||
else:
|
||||
velocity.x = move_toward(velocity.x, target_speed, air_acceleration * delta)
|
||||
|
||||
|
||||
func _update_facing() -> void:
|
||||
animated_sprite.flip_h = facing_direction < 0.0
|
||||
|
||||
# Only the horizontal offset changes when turning around. The hitbox size
|
||||
# stays identical for both directions.
|
||||
punch_hitbox.position = Vector2(
|
||||
abs(punch_offset.x) * facing_direction,
|
||||
punch_offset.y
|
||||
)
|
||||
|
||||
|
||||
# ============================================================================
|
||||
# GRAVITY AND JUMP
|
||||
# ============================================================================
|
||||
|
||||
func _apply_gravity(delta: float) -> void:
|
||||
if is_on_floor():
|
||||
return
|
||||
|
||||
var gravity: Vector2 = get_gravity()
|
||||
var multiplier: float = gravity_multiplier
|
||||
|
||||
if ground_pound_active:
|
||||
multiplier = ground_pound_gravity_multiplier
|
||||
elif velocity.y > 0.0:
|
||||
multiplier = fall_gravity_multiplier
|
||||
|
||||
velocity += gravity * multiplier * delta
|
||||
velocity.y = min(velocity.y, max_fall_speed)
|
||||
|
||||
|
||||
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 = character_data.jump_velocity
|
||||
jump_buffer_timer = 0.0
|
||||
coyote_timer = 0.0
|
||||
|
||||
if Input.is_action_just_released("jump") and velocity.y < 0.0:
|
||||
velocity.y *= jump_cut_multiplier
|
||||
|
||||
|
||||
# ============================================================================
|
||||
# ATTACK INPUT
|
||||
# ============================================================================
|
||||
|
||||
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()
|
||||
|
||||
|
||||
# ============================================================================
|
||||
# PUNCH
|
||||
# ============================================================================
|
||||
|
||||
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 area in punch_hitbox.get_overlapping_areas():
|
||||
_damage_target(area, character_data.basic_attack_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
|
||||
|
||||
# If the player is still rising, the attack immediately turns the movement
|
||||
# into a fall instead of waiting for the jump apex.
|
||||
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 area in ground_pound_hitbox.get_overlapping_areas():
|
||||
_damage_target(area, character_data.air_attack_damage, hit_targets)
|
||||
|
||||
|
||||
# ============================================================================
|
||||
# DEALING AND RECEIVING DAMAGE
|
||||
# ============================================================================
|
||||
|
||||
func _damage_target(collider: Node, damage: int, hit_targets: Dictionary) -> void:
|
||||
var target: Node = collider
|
||||
|
||||
# Hurtboxes are Area2D children. Damage belongs to the entity that owns the
|
||||
# hurtbox, so we ask the parent for take_damage().
|
||||
if not target.has_method("take_damage"):
|
||||
target = target.get_parent()
|
||||
|
||||
if target == null or not target.has_method("take_damage"):
|
||||
return
|
||||
|
||||
var target_id: int = target.get_instance_id()
|
||||
if hit_targets.has(target_id):
|
||||
return
|
||||
|
||||
hit_targets[target_id] = true
|
||||
target.take_damage(damage, global_position)
|
||||
|
||||
|
||||
func take_damage(amount: int, source_position: Vector2) -> void:
|
||||
if amount <= 0 or is_dead or invulnerability_timer > 0.0:
|
||||
return
|
||||
|
||||
current_health = max(current_health - amount, 0)
|
||||
invulnerability_timer = invulnerability_duration
|
||||
|
||||
# Knock the player away from the source. If both centers are aligned, use
|
||||
# the opposite of the current facing direction as a deterministic fallback.
|
||||
var knockback_direction: float = signf(global_position.x - source_position.x)
|
||||
if knockback_direction == 0.0:
|
||||
knockback_direction = -facing_direction
|
||||
|
||||
velocity.x = knockback_direction * damage_knockback.x
|
||||
velocity.y = -damage_knockback.y
|
||||
|
||||
health_changed.emit(current_health, character_data.max_health)
|
||||
|
||||
if current_health == 0:
|
||||
_die()
|
||||
|
||||
|
||||
func _die() -> void:
|
||||
is_dead = true
|
||||
died.emit()
|
||||
|
||||
# Reloading from a deferred call avoids changing the scene tree while a
|
||||
# physics overlap is still being processed.
|
||||
call_deferred("_reload_level")
|
||||
|
||||
|
||||
func _reload_level() -> void:
|
||||
get_tree().reload_current_scene()
|
||||
|
||||
|
||||
# ============================================================================
|
||||
# TIMERS AND ANIMATION
|
||||
# ============================================================================
|
||||
|
||||
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)
|
||||
invulnerability_timer = max(invulnerability_timer - delta, 0.0)
|
||||
|
||||
|
||||
func _update_animation() -> void:
|
||||
# There are only idle and walk animations at the moment. Until proper jump
|
||||
# and attack animations arrive, walk is used only for horizontal walking and
|
||||
# idle is the safe fallback for every other state.
|
||||
if is_on_floor() and abs(velocity.x) > 10.0:
|
||||
animated_sprite.play("walk")
|
||||
else:
|
||||
animated_sprite.play("idle")
|
||||
|
||||
|
||||
# ============================================================================
|
||||
# HITBOX SETUP
|
||||
# ============================================================================
|
||||
|
||||
func _setup_attack_hitboxes() -> void:
|
||||
var punch_shape: RectangleShape2D = RectangleShape2D.new()
|
||||
punch_shape.size = punch_hitbox_size
|
||||
punch_collision.shape = punch_shape
|
||||
punch_hitbox.collision_layer = 0
|
||||
punch_hitbox.collision_mask = ENEMY_HURTBOX_MASK
|
||||
punch_hitbox.monitoring = true
|
||||
|
||||
var pound_shape: CircleShape2D = 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 = ENEMY_HURTBOX_MASK
|
||||
ground_pound_hitbox.monitoring = true
|
||||
|
||||
|
||||
# ============================================================================
|
||||
# DEBUG HITBOX DRAWING
|
||||
# ============================================================================
|
||||
|
||||
func _draw() -> void:
|
||||
if not show_debug_hitboxes:
|
||||
return
|
||||
|
||||
# Player hurtbox: always visible and deliberately subtle.
|
||||
_draw_rectangle_collision(
|
||||
hurtbox_collision,
|
||||
Color(1.0, 0.08, 0.08, 0.10),
|
||||
Color(1.0, 0.08, 0.08, 0.45)
|
||||
)
|
||||
|
||||
# Punch: only visible while it can actually deal damage.
|
||||
if punch_timer > 0.0:
|
||||
_draw_rectangle_collision(
|
||||
punch_collision,
|
||||
Color(1.0, 0.05, 0.05, 0.25),
|
||||
Color(1.0, 0.05, 0.05, 0.95),
|
||||
punch_hitbox.position
|
||||
)
|
||||
|
||||
# Ground pound: show the real damage radius throughout the fall, then make
|
||||
# it stronger for a few frames at the exact impact moment.
|
||||
if ground_pound_active or ground_pound_debug_timer > 0.0:
|
||||
var fill_alpha: float = 0.14 if ground_pound_active else 0.28
|
||||
var outline_alpha: float = 0.60 if ground_pound_active else 0.95
|
||||
draw_circle(
|
||||
ground_pound_hitbox.position,
|
||||
ground_pound_radius,
|
||||
Color(1.0, 0.18, 0.05, fill_alpha)
|
||||
)
|
||||
draw_arc(
|
||||
ground_pound_hitbox.position,
|
||||
ground_pound_radius,
|
||||
0.0,
|
||||
TAU,
|
||||
48,
|
||||
Color(1.0, 0.18, 0.05, outline_alpha),
|
||||
2.0
|
||||
)
|
||||
|
||||
|
||||
func _draw_rectangle_collision(
|
||||
collision: CollisionShape2D,
|
||||
fill_color: Color,
|
||||
outline_color: Color,
|
||||
extra_offset: Vector2 = Vector2.ZERO
|
||||
) -> void:
|
||||
var rectangle: RectangleShape2D = collision.shape as RectangleShape2D
|
||||
if rectangle == null:
|
||||
return
|
||||
|
||||
var center: Vector2 = collision.position + extra_offset
|
||||
var rect: Rect2 = Rect2(center - rectangle.size / 2.0, rectangle.size)
|
||||
draw_rect(rect, fill_color, true)
|
||||
draw_rect(rect, outline_color, false, 2.0)
|
||||
@@ -0,0 +1 @@
|
||||
uid://dccfebno1gvh7
|
||||
@@ -0,0 +1,58 @@
|
||||
[gd_scene load_steps=6 format=3 uid="uid://nwbjxttgghpq"]
|
||||
|
||||
[ext_resource type="Script" uid="uid://dccfebno1gvh7" path="res://player/player.gd" id="1_player"]
|
||||
[ext_resource type="SpriteFrames" path="res://player/characters/shared/prototype_sprite_frames.tres" id="2_frames"]
|
||||
[ext_resource type="Script" uid="uid://kg3i7b0dj2p8" path="res://player/camera_2d.gd" id="3_camera"]
|
||||
[ext_resource type="Resource" path="res://player/characters/singer.tres" id="4_character"]
|
||||
|
||||
[sub_resource type="RectangleShape2D" id="PlayerShape"]
|
||||
size = Vector2(65, 115)
|
||||
|
||||
[node name="Player" type="CharacterBody2D"]
|
||||
z_index = 1
|
||||
collision_layer = 2
|
||||
collision_mask = 1
|
||||
script = ExtResource("1_player")
|
||||
character_data = ExtResource("4_character")
|
||||
|
||||
[node name="CollisionShape2D" type="CollisionShape2D" parent="."]
|
||||
position = Vector2(0.5, 5.5)
|
||||
shape = SubResource("PlayerShape")
|
||||
|
||||
[node name="Hurtbox" type="Area2D" parent="."]
|
||||
collision_layer = 8
|
||||
collision_mask = 0
|
||||
monitoring = false
|
||||
monitorable = true
|
||||
|
||||
[node name="CollisionShape2D" type="CollisionShape2D" parent="Hurtbox"]
|
||||
position = Vector2(0.5, 5.5)
|
||||
shape = SubResource("PlayerShape")
|
||||
|
||||
[node name="PunchHitbox" type="Area2D" parent="."]
|
||||
collision_layer = 0
|
||||
collision_mask = 16
|
||||
monitoring = true
|
||||
monitorable = false
|
||||
|
||||
[node name="CollisionShape2D" type="CollisionShape2D" parent="PunchHitbox"]
|
||||
|
||||
[node name="GroundPoundHitbox" type="Area2D" parent="."]
|
||||
collision_layer = 0
|
||||
collision_mask = 16
|
||||
monitoring = true
|
||||
monitorable = false
|
||||
|
||||
[node name="CollisionShape2D" type="CollisionShape2D" parent="GroundPoundHitbox"]
|
||||
|
||||
[node name="AnimatedSprite2D" type="AnimatedSprite2D" parent="."]
|
||||
z_index = -1
|
||||
scale = Vector2(0.80251265, 0.8025128)
|
||||
sprite_frames = ExtResource("2_frames")
|
||||
animation = &"idle"
|
||||
speed_scale = 2.0
|
||||
|
||||
[node name="Camera2D" type="Camera2D" parent="."]
|
||||
scale = Vector2(0.5, 0.5)
|
||||
zoom = Vector2(2, 2)
|
||||
script = ExtResource("3_camera")
|
||||
Reference in new issue
Block a user