mirror of
https://github.com/kodzukye/omens-ascent.git
synced 2026-08-10 05:13:10 +00:00
181 lines
5.5 KiB
GDScript
181 lines
5.5 KiB
GDScript
class_name Creeper
|
|
extends CharacterBody2D
|
|
|
|
@onready var _hurtbox: HurtBox = $HurtBox
|
|
@onready var _health: HealthComponent = $HealthComponent
|
|
@onready var _sprite: AnimatedSprite2D = $AnimatedSprite2D
|
|
@onready var _detection_area: Area2D = $DetectionArea
|
|
@onready var _hitbox: HitBox = $HitBox
|
|
@onready var _hitbox_collision: CollisionShape2D = $HitBox/CollisionShape2D
|
|
@onready var _attack_range_area: Area2D = $AttackRangeArea
|
|
@onready var _ledge_detector: RayCast2D = $RayCast2D
|
|
|
|
# Creeper Exported Variables
|
|
@export_group("Movement")
|
|
@export var move_speed: float = 60.0
|
|
@export var patrol_speed: float = 20.0
|
|
@export var ledge_check_offset: float = 10.0
|
|
|
|
@export_group("Combat")
|
|
@export var attack_cooldown: float = 1.0
|
|
@export var backoff_min_distance: float = 30.0
|
|
@export var attack_hit_frame_start: int = 4
|
|
@export var attack_hit_frame_end: int = 5
|
|
@export var heal_amount_on_death: int = 4
|
|
|
|
# Creeper Runtime Variables
|
|
var _player: Node2D = null
|
|
var _player_in_attack_range: bool = false
|
|
var _is_attacking: bool = false
|
|
var _is_hurt: bool = false
|
|
var _is_dead: bool = false
|
|
var _attack_cooldown_timer: float = 0.0
|
|
var _facing_direction: float = 1.0
|
|
var _patrol_direction: float = 1.0
|
|
|
|
func _ready() -> void:
|
|
add_to_group("enemies")
|
|
_hurtbox.hit_received.connect(_on_hurtbox_hit_received)
|
|
_health.health_changed.connect(_on_health_changed)
|
|
_health.died.connect(_on_died)
|
|
_sprite.animation_finished.connect(_on_animation_finished)
|
|
_detection_area.body_entered.connect(_on_detection_area_body_entered)
|
|
_detection_area.body_exited.connect(_on_detection_area_body_exited)
|
|
_attack_range_area.area_entered.connect(_on_hitbox_area_entered)
|
|
_attack_range_area.area_exited.connect(_on_hitbox_area_exited)
|
|
|
|
func _physics_process(delta: float) -> void:
|
|
_apply_gravity(delta)
|
|
_update_attack_cooldown(delta)
|
|
|
|
if not _is_dead:
|
|
_process_ai()
|
|
_update_hitbox()
|
|
|
|
move_and_slide()
|
|
|
|
func _update_hitbox() -> void:
|
|
var is_active_hit_frame: bool = _sprite.animation == &"attack" \
|
|
and _sprite.frame >= attack_hit_frame_start \
|
|
and _sprite.frame <= attack_hit_frame_end
|
|
_hitbox_collision.disabled = not (_is_attacking and is_active_hit_frame)
|
|
|
|
func _apply_gravity(delta: float) -> void:
|
|
if not is_on_floor():
|
|
velocity.y += ProjectSettings.get_setting("physics/2d/default_gravity") * delta
|
|
|
|
func _update_attack_cooldown(delta: float) -> void:
|
|
if _attack_cooldown_timer > 0.0:
|
|
_attack_cooldown_timer -= delta
|
|
|
|
func _process_ai() -> void:
|
|
if _is_hurt:
|
|
velocity.x = 0.0
|
|
return
|
|
|
|
if _is_attacking:
|
|
velocity.x = 0.0
|
|
return
|
|
|
|
if _player_in_attack_range and _attack_cooldown_timer <= 0.0:
|
|
_start_attack()
|
|
return
|
|
|
|
if _player_in_attack_range:
|
|
_facing_direction = signf(_player.global_position.x - global_position.x)
|
|
var distance_to_player: float = absf(_player.global_position.x - global_position.x)
|
|
if distance_to_player < backoff_min_distance:
|
|
velocity.x = -_facing_direction * move_speed
|
|
if _sprite.animation != &"walk_1":
|
|
_sprite.play(&"walk_1")
|
|
else:
|
|
velocity.x = 0.0
|
|
if _sprite.animation != &"idle":
|
|
_sprite.play(&"idle")
|
|
_sprite.flip_h = _facing_direction > 0.0
|
|
_hitbox.scale.x = -_facing_direction
|
|
_attack_range_area.scale.x = -_facing_direction
|
|
return
|
|
|
|
if _player:
|
|
_facing_direction = signf(_player.global_position.x - global_position.x)
|
|
if _has_ground_ahead(_facing_direction):
|
|
velocity.x = _facing_direction * move_speed
|
|
if _sprite.animation != &"walk_1":
|
|
_sprite.play(&"walk_1")
|
|
else:
|
|
velocity.x = 0.0
|
|
if _sprite.animation != &"idle":
|
|
_sprite.play(&"idle")
|
|
else:
|
|
_facing_direction = _patrol_direction
|
|
velocity.x = _patrol_direction * patrol_speed
|
|
if _sprite.animation != &"walk_2":
|
|
_sprite.play(&"walk_2")
|
|
|
|
_sprite.flip_h = _facing_direction > 0.0
|
|
_hitbox.scale.x = -_facing_direction
|
|
_attack_range_area.scale.x = -_facing_direction
|
|
|
|
if not _player:
|
|
_update_patrol_direction()
|
|
|
|
func _has_ground_ahead(direction: float) -> bool:
|
|
_ledge_detector.position.x = ledge_check_offset * direction
|
|
_ledge_detector.force_raycast_update()
|
|
return _ledge_detector.is_colliding()
|
|
|
|
func _update_patrol_direction() -> void:
|
|
if is_on_wall() or not _has_ground_ahead(_patrol_direction):
|
|
_patrol_direction = -_patrol_direction
|
|
|
|
func _start_attack() -> void:
|
|
_is_attacking = true
|
|
_attack_cooldown_timer = attack_cooldown
|
|
velocity.x = 0.0
|
|
_sprite.play(&"attack")
|
|
|
|
func _on_detection_area_body_entered(body: Node2D) -> void:
|
|
_player = body
|
|
|
|
func _on_detection_area_body_exited(body: Node2D) -> void:
|
|
if body == _player:
|
|
_player = null
|
|
|
|
func _on_hitbox_area_entered(area: Area2D) -> void:
|
|
if area is HurtBox and area.get_parent() == _player:
|
|
_player_in_attack_range = true
|
|
|
|
func _on_hitbox_area_exited(area: Area2D) -> void:
|
|
if area is HurtBox and area.get_parent() == _player:
|
|
_player_in_attack_range = false
|
|
|
|
func _on_hurtbox_hit_received(hitbox: HitBox) -> void:
|
|
_health.take_damage(hitbox.damage)
|
|
|
|
func _on_health_changed(_new_value: int, _max_value: int) -> void:
|
|
_is_attacking = false
|
|
_is_hurt = true
|
|
_sprite.play(&"hurt")
|
|
|
|
func _on_died() -> void:
|
|
_is_dead = true
|
|
velocity = Vector2.ZERO
|
|
_hurtbox.set_deferred("monitoring", false)
|
|
_hitbox_collision.set_deferred("disabled", true)
|
|
remove_from_group("enemies")
|
|
EventBus.enemy_defeated.emit()
|
|
EventBus.player_heal_requested.emit(heal_amount_on_death)
|
|
|
|
func _on_animation_finished() -> void:
|
|
if _sprite.animation == &"hurt":
|
|
_is_hurt = false
|
|
if _is_dead:
|
|
_sprite.play(&"death_1")
|
|
else:
|
|
_sprite.play(&"idle")
|
|
elif _sprite.animation == &"attack":
|
|
_is_attacking = false
|
|
elif _sprite.animation == &"death_1":
|
|
queue_free()
|