➕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,176 @@
|
||||
extends CharacterBody2D
|
||||
|
||||
|
||||
signal died
|
||||
|
||||
|
||||
# First real enemy on purpose: no generic Enemy base class yet.
|
||||
# Once a second/third enemy exists, we can extract only the behaviour they
|
||||
# actually share instead of guessing an abstraction in advance.
|
||||
|
||||
const PLAYER_HURTBOX_MASK: int = 1 << 3
|
||||
|
||||
|
||||
@export_category("Movement")
|
||||
@export var move_speed: float = 85.0
|
||||
@export var max_fall_speed: float = 1200.0
|
||||
|
||||
@export_category("Combat")
|
||||
@export_range(1, 100, 1) var max_health: int = 3
|
||||
@export_range(0, 100, 1) var contact_damage: int = 1
|
||||
@export var contact_damage_interval: float = 0.45
|
||||
|
||||
@export_category("Debug")
|
||||
## Enabled while the enemy has no final sprite/animation. The grey rectangle is
|
||||
## the temporary body; red overlays are the gameplay hurtbox/contact hitbox.
|
||||
@export var show_debug_hitboxes: bool = true
|
||||
|
||||
|
||||
@onready var body_collision: CollisionShape2D = $CollisionShape2D
|
||||
@onready var hurtbox_collision: CollisionShape2D = $Hurtbox/CollisionShape2D
|
||||
@onready var contact_hitbox: Area2D = $ContactHitbox
|
||||
@onready var contact_collision: CollisionShape2D = $ContactHitbox/CollisionShape2D
|
||||
@onready var floor_probe: RayCast2D = $FloorProbe
|
||||
|
||||
|
||||
var current_health: int
|
||||
var move_direction: float = -1.0
|
||||
var contact_damage_timer: float = 0.0
|
||||
var is_dead: bool = false
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
current_health = max_health
|
||||
contact_hitbox.collision_layer = 0
|
||||
contact_hitbox.collision_mask = PLAYER_HURTBOX_MASK
|
||||
contact_hitbox.monitoring = true
|
||||
_update_floor_probe()
|
||||
queue_redraw()
|
||||
|
||||
|
||||
func _physics_process(delta: float) -> void:
|
||||
if is_dead:
|
||||
return
|
||||
|
||||
contact_damage_timer = max(contact_damage_timer - delta, 0.0)
|
||||
_apply_gravity(delta)
|
||||
_turn_before_ledge()
|
||||
|
||||
velocity.x = move_direction * move_speed
|
||||
move_and_slide()
|
||||
|
||||
if is_on_wall():
|
||||
_turn_around()
|
||||
|
||||
_damage_player_on_contact()
|
||||
queue_redraw()
|
||||
|
||||
|
||||
func _apply_gravity(delta: float) -> void:
|
||||
if is_on_floor():
|
||||
return
|
||||
|
||||
velocity += get_gravity() * delta
|
||||
velocity.y = min(velocity.y, max_fall_speed)
|
||||
|
||||
|
||||
func _turn_before_ledge() -> void:
|
||||
# The ray sits just in front of the feet. If it stops finding floor while the
|
||||
# enemy is grounded, turning here prevents the basic walker from suiciding
|
||||
# off every platform.
|
||||
if is_on_floor() and not floor_probe.is_colliding():
|
||||
_turn_around()
|
||||
|
||||
|
||||
func _turn_around() -> void:
|
||||
move_direction *= -1.0
|
||||
_update_floor_probe()
|
||||
|
||||
|
||||
func _update_floor_probe() -> void:
|
||||
floor_probe.position.x = abs(floor_probe.position.x) * move_direction
|
||||
|
||||
|
||||
func _damage_player_on_contact() -> void:
|
||||
if contact_damage_timer > 0.0:
|
||||
return
|
||||
|
||||
for area in contact_hitbox.get_overlapping_areas():
|
||||
var target: Node = area.get_parent()
|
||||
if target != null and target.has_method("take_damage"):
|
||||
target.take_damage(contact_damage, global_position)
|
||||
contact_damage_timer = contact_damage_interval
|
||||
return
|
||||
|
||||
|
||||
func take_damage(amount: int, source_position: Vector2) -> void:
|
||||
if amount <= 0 or is_dead:
|
||||
return
|
||||
|
||||
current_health = max(current_health - amount, 0)
|
||||
|
||||
if current_health == 0:
|
||||
_die()
|
||||
return
|
||||
|
||||
# Turn away from the attacker. This gives a tiny readable reaction without
|
||||
# inventing a hit-stun/knockback system before it is actually needed.
|
||||
var reaction_direction: float = signf(global_position.x - source_position.x)
|
||||
if reaction_direction != 0.0:
|
||||
move_direction = reaction_direction
|
||||
_update_floor_probe()
|
||||
|
||||
|
||||
func _die() -> void:
|
||||
is_dead = true
|
||||
died.emit()
|
||||
queue_free()
|
||||
|
||||
|
||||
# ============================================================================
|
||||
# TEMPORARY VISUAL + DEBUG HITBOXES
|
||||
# ============================================================================
|
||||
|
||||
func _draw() -> void:
|
||||
# The enemy has no sprite yet. This neutral block makes the actual entity
|
||||
# visible independently from the red debug overlays.
|
||||
_draw_rectangle_collision(
|
||||
body_collision,
|
||||
Color(0.18, 0.18, 0.20, 0.90),
|
||||
Color(0.85, 0.85, 0.90, 1.0)
|
||||
)
|
||||
|
||||
if not show_debug_hitboxes:
|
||||
return
|
||||
|
||||
# Hurtbox: where punches / ground pound are allowed to damage the enemy.
|
||||
_draw_rectangle_collision(
|
||||
hurtbox_collision,
|
||||
Color(1.0, 0.05, 0.05, 0.12),
|
||||
Color(1.0, 0.05, 0.05, 0.55)
|
||||
)
|
||||
|
||||
# Contact hitbox: slightly larger, so it is obvious exactly when touching
|
||||
# the enemy can damage the player.
|
||||
_draw_rectangle_collision(
|
||||
contact_collision,
|
||||
Color(1.0, 0.05, 0.05, 0.18),
|
||||
Color(1.0, 0.05, 0.05, 0.85),
|
||||
contact_hitbox.position
|
||||
)
|
||||
|
||||
|
||||
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://dbt03i3eiutv5
|
||||
@@ -0,0 +1,42 @@
|
||||
[gd_scene load_steps=4 format=3]
|
||||
|
||||
[ext_resource type="Script" path="res://enemies/walker/walker.gd" id="1_walker"]
|
||||
|
||||
[sub_resource type="RectangleShape2D" id="BodyShape"]
|
||||
size = Vector2(42, 78)
|
||||
|
||||
[sub_resource type="RectangleShape2D" id="ContactShape"]
|
||||
size = Vector2(58, 88)
|
||||
|
||||
[node name="Walker" type="CharacterBody2D"]
|
||||
collision_layer = 4
|
||||
collision_mask = 1
|
||||
script = ExtResource("1_walker")
|
||||
|
||||
[node name="CollisionShape2D" type="CollisionShape2D" parent="."]
|
||||
position = Vector2(0, 0)
|
||||
shape = SubResource("BodyShape")
|
||||
|
||||
[node name="Hurtbox" type="Area2D" parent="."]
|
||||
collision_layer = 16
|
||||
collision_mask = 0
|
||||
monitoring = false
|
||||
monitorable = true
|
||||
|
||||
[node name="CollisionShape2D" type="CollisionShape2D" parent="Hurtbox"]
|
||||
shape = SubResource("BodyShape")
|
||||
|
||||
[node name="ContactHitbox" type="Area2D" parent="."]
|
||||
collision_layer = 0
|
||||
collision_mask = 8
|
||||
monitoring = true
|
||||
monitorable = false
|
||||
|
||||
[node name="CollisionShape2D" type="CollisionShape2D" parent="ContactHitbox"]
|
||||
shape = SubResource("ContactShape")
|
||||
|
||||
[node name="FloorProbe" type="RayCast2D" parent="."]
|
||||
position = Vector2(24, 34)
|
||||
target_position = Vector2(0, 34)
|
||||
collision_mask = 1
|
||||
enabled = true
|
||||
Reference in new issue
Block a user