Files
Omens-Ascent/scenes/actors/creeper.gd

179 lines
5.4 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
# 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()
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()