327 lines
9.0 KiB
GDScript
327 lines
9.0 KiB
GDScript
class_name AbstractEnemy
|
|
extends CharacterBody2D
|
|
|
|
|
|
enum Type {
|
|
Standing,
|
|
Walking,
|
|
Random,
|
|
}
|
|
|
|
enum Facing {
|
|
Front,
|
|
Rear,
|
|
}
|
|
|
|
enum State {
|
|
Idle,
|
|
WalkLeft,
|
|
WalkRight,
|
|
LookAround,
|
|
ChasingLeft,
|
|
ChasingRight,
|
|
}
|
|
|
|
|
|
const ANIMATION_FALL_DOWN = "fall_down"
|
|
const ANIMATION_FALL_DOWN_LEFT = "fall_down_left"
|
|
const ANIMATION_FALL_DOWN_RIGHT = "fall_down_right"
|
|
const ANIMATION_FALL_UP = "fall_up"
|
|
const ANIMATION_FALL_UP_LEFT = "fall_up_left"
|
|
const ANIMATION_FALL_UP_RIGHT = "fall_up_right"
|
|
const ANIMATION_IDLE_FRONT = "idle_front"
|
|
const ANIMATION_IDLE_REAR = "idle_rear"
|
|
const ANIMATION_LOOK_AROUND_FRONT_1 = "look_around_front_1"
|
|
const ANIMATION_LOOK_AROUND_FRONT_2 = "look_around_front_2"
|
|
const ANIMATION_LOOK_AROUND_REAR_1 = "look_around_rear_1"
|
|
const ANIMATION_LOOK_AROUND_REAR_2 = "look_around_rear_2"
|
|
const ANIMATION_WALK_LEFT = "walk_left"
|
|
const ANIMATION_WALK_RIGHT = "walk_right"
|
|
const ANIMATION_CHASE_LEFT = "chase_left"
|
|
const ANIMATION_CHASE_RIGHT = "chase_right"
|
|
|
|
const LOOK_AROUND_FRONT_ANIMATIONS : Array[String] = [
|
|
ANIMATION_LOOK_AROUND_FRONT_1,
|
|
ANIMATION_LOOK_AROUND_FRONT_2,
|
|
]
|
|
const LOOK_AROUND_REAR_ANIMATIONS : Array[String] = [
|
|
ANIMATION_LOOK_AROUND_REAR_1,
|
|
ANIMATION_LOOK_AROUND_REAR_2,
|
|
]
|
|
|
|
const MAX_WALK_SPEED = 85
|
|
const MAX_CHASE_SPEED = 170
|
|
const ACCELERATION = 600.0
|
|
const LOOK_AROUND_CHANCE = 25
|
|
const WALK_CHANCE = 25
|
|
|
|
const DIRECTION_LEFT = -1
|
|
const DIRECTION_RIGHT = 1
|
|
|
|
|
|
@export var type : Type = Type.Standing
|
|
@export var facing : Facing = Facing.Front
|
|
@export var initial_state : State = State.Idle
|
|
|
|
|
|
var _target_x := 0.0
|
|
var _target_found := false
|
|
|
|
|
|
var _state : State:
|
|
set = _set_state
|
|
|
|
@onready var sprite : AnimatedSprite2D = $AnimatedSprite2D
|
|
|
|
@onready var left_wall_ray : RayCast2D = $%LeftWallRay
|
|
@onready var right_wall_ray : RayCast2D = $%RightWallRay
|
|
|
|
@onready var left_player_close_ray : RayCast2D = $%LeftPlayerCloseRay
|
|
@onready var right_player_close_ray : RayCast2D = $%RightPlayerCloseRay
|
|
|
|
@onready var left_player_distant_ray : RayCast2D = $%LeftPlayerDistantRay
|
|
@onready var right_player_distant_ray : RayCast2D = $%RightPlayerDistantRay
|
|
|
|
|
|
func _ready() -> void:
|
|
_state = initial_state
|
|
|
|
|
|
func _physics_process(delta: float) -> void:
|
|
if not is_on_floor():
|
|
velocity += get_gravity() * delta
|
|
|
|
match _state:
|
|
State.ChasingLeft:
|
|
_process_player_ray(left_player_distant_ray)
|
|
if position.x < _target_x:
|
|
SoundManager.play_sfx_stream(SoundManager.sfx_stream_player_lost, global_position)
|
|
_state = State.LookAround
|
|
else:
|
|
_update_x_velocity(DIRECTION_LEFT, MAX_CHASE_SPEED, delta)
|
|
_check_wall_collision_and_switch_state(DIRECTION_LEFT)
|
|
State.ChasingRight:
|
|
_process_player_ray(right_player_distant_ray)
|
|
if position.x > _target_x:
|
|
SoundManager.play_sfx_stream(SoundManager.sfx_stream_player_lost, global_position)
|
|
_state = State.LookAround
|
|
else:
|
|
_update_x_velocity(DIRECTION_RIGHT, MAX_CHASE_SPEED, delta)
|
|
_check_wall_collision_and_switch_state(DIRECTION_RIGHT)
|
|
State.WalkLeft:
|
|
if _process_player_ray(left_player_distant_ray):
|
|
_set_chase_state()
|
|
_update_x_velocity(DIRECTION_LEFT, MAX_WALK_SPEED, delta)
|
|
_check_wall_collision_and_switch_state(DIRECTION_LEFT)
|
|
State.WalkRight:
|
|
if _process_player_ray(right_player_distant_ray):
|
|
_set_chase_state()
|
|
_update_x_velocity(DIRECTION_RIGHT, MAX_WALK_SPEED, delta)
|
|
_check_wall_collision_and_switch_state(DIRECTION_RIGHT)
|
|
State.LookAround:
|
|
_update_x_velocity(0, MAX_WALK_SPEED * 2, delta)
|
|
if not _target_found:
|
|
var close_rays : Array[RayCast2D] = [left_player_close_ray, right_player_close_ray]
|
|
_target_found = _process_player_rays(close_rays)
|
|
|
|
_update_animation()
|
|
|
|
move_and_slide()
|
|
|
|
|
|
func _set_state(value: State) -> void:
|
|
_state = value
|
|
|
|
left_player_close_ray.process_mode = Node.PROCESS_MODE_DISABLED
|
|
right_player_close_ray.process_mode = Node.PROCESS_MODE_DISABLED
|
|
left_player_distant_ray.process_mode = Node.PROCESS_MODE_DISABLED
|
|
right_player_distant_ray.process_mode = Node.PROCESS_MODE_DISABLED
|
|
left_player_close_ray.hide()
|
|
right_player_close_ray.hide()
|
|
left_player_distant_ray.hide()
|
|
right_player_distant_ray.hide()
|
|
|
|
match _state:
|
|
State.ChasingLeft, State.WalkLeft:
|
|
left_player_distant_ray.process_mode = Node.PROCESS_MODE_INHERIT
|
|
left_player_distant_ray.show()
|
|
State.ChasingRight, State.WalkRight:
|
|
right_player_distant_ray.process_mode = Node.PROCESS_MODE_INHERIT
|
|
right_player_distant_ray.show()
|
|
State.LookAround:
|
|
left_player_close_ray.process_mode = Node.PROCESS_MODE_INHERIT
|
|
right_player_close_ray.process_mode = Node.PROCESS_MODE_INHERIT
|
|
left_player_close_ray.show()
|
|
right_player_close_ray.show()
|
|
|
|
|
|
func _process_player_ray(ray: RayCast2D) -> bool:
|
|
if ray.is_colliding():
|
|
ray.force_raycast_update()
|
|
var collider := ray.get_collider()
|
|
if collider is Player:
|
|
_target_x = collider.position.x
|
|
return true
|
|
return false
|
|
|
|
|
|
func _process_player_rays(rays: Array[RayCast2D]) -> bool:
|
|
for ray in rays:
|
|
if _process_player_ray(ray):
|
|
return true
|
|
return false
|
|
|
|
|
|
func _update_x_velocity(direction: int, max_speed: float, delta: float) -> void:
|
|
velocity.x = move_toward(velocity.x, direction * max_speed, ACCELERATION * delta)
|
|
|
|
|
|
func _check_wall_collision_and_switch_state(direction: int) -> void:
|
|
var this_wall_ray := _get_wall_ray(direction)
|
|
var other_wall_ray := _get_wall_ray(-direction)
|
|
|
|
if this_wall_ray.is_colliding():
|
|
if other_wall_ray.is_colliding():
|
|
_state = State.LookAround
|
|
else:
|
|
match _state:
|
|
State.WalkLeft:
|
|
_state = State.WalkRight
|
|
State.WalkRight:
|
|
_state = State.WalkLeft
|
|
State.ChasingLeft, State.ChasingRight:
|
|
var stream := SoundManager.sfx_stream_player_lost
|
|
SoundManager.play_sfx_stream(stream, global_position)
|
|
_state = State.LookAround
|
|
|
|
|
|
func _get_wall_ray(direction: int) -> RayCast2D:
|
|
if direction < 0:
|
|
return left_wall_ray
|
|
else:
|
|
return right_wall_ray
|
|
|
|
|
|
func _update_animation() -> void:
|
|
if is_zero_approx(velocity.y):
|
|
match _state:
|
|
State.WalkLeft:
|
|
_play_animation(ANIMATION_WALK_LEFT)
|
|
State.WalkRight:
|
|
_play_animation(ANIMATION_WALK_RIGHT)
|
|
State.ChasingLeft:
|
|
_play_animation(ANIMATION_CHASE_LEFT)
|
|
State.ChasingRight:
|
|
_play_animation(ANIMATION_CHASE_RIGHT)
|
|
State.Idle:
|
|
_play_idle_animation()
|
|
State.LookAround:
|
|
_play_look_around_animation()
|
|
else:
|
|
_play_fall_animation()
|
|
|
|
|
|
func _play_idle_animation() -> void:
|
|
match facing:
|
|
Facing.Front:
|
|
_play_animation(ANIMATION_IDLE_FRONT)
|
|
Facing.Rear:
|
|
_play_animation(ANIMATION_IDLE_REAR)
|
|
|
|
|
|
func _play_look_around_animation() -> void:
|
|
if _is_current_animation_look_around(): return
|
|
|
|
match facing:
|
|
Facing.Front:
|
|
var animation := _get_random_animation(LOOK_AROUND_FRONT_ANIMATIONS)
|
|
_play_animation(animation)
|
|
Facing.Rear:
|
|
var animation := _get_random_animation(LOOK_AROUND_REAR_ANIMATIONS)
|
|
_play_animation(animation)
|
|
|
|
|
|
func _play_fall_animation() -> void:
|
|
if is_zero_approx(velocity.x):
|
|
var animation := ANIMATION_FALL_UP if velocity.y < 0 else ANIMATION_FALL_DOWN
|
|
_play_animation(animation)
|
|
elif velocity.x < 0:
|
|
var animation := ANIMATION_FALL_UP_LEFT if velocity.y < 0 else ANIMATION_FALL_DOWN_LEFT
|
|
_play_animation(animation)
|
|
else:
|
|
var animation := ANIMATION_FALL_UP_RIGHT if velocity.y < 0 else ANIMATION_FALL_DOWN_RIGHT
|
|
_play_animation(animation)
|
|
|
|
|
|
func _is_current_animation_look_around() -> bool:
|
|
if not sprite.is_playing(): return false
|
|
if sprite.animation in LOOK_AROUND_FRONT_ANIMATIONS: return true
|
|
if sprite.animation in LOOK_AROUND_REAR_ANIMATIONS: return true
|
|
return false
|
|
|
|
|
|
func _get_random_animation(animations: Array[String]) -> String:
|
|
var index := randi_range(0, animations.size() - 1)
|
|
return animations[index]
|
|
|
|
|
|
func _play_animation(animation: String) -> void:
|
|
if not sprite.is_playing() or sprite.animation != animation:
|
|
sprite.play(animation)
|
|
|
|
|
|
func _set_walking_state() -> void:
|
|
var is_left_colliding := left_wall_ray.is_colliding()
|
|
var is_right_colliding := right_wall_ray.is_colliding()
|
|
|
|
if is_left_colliding and is_right_colliding:
|
|
_state = State.Idle
|
|
elif not is_left_colliding and not is_right_colliding:
|
|
_state = State.WalkLeft if randi_range(1, 2) == 1 else State.WalkRight
|
|
elif not is_left_colliding:
|
|
_state = State.WalkLeft
|
|
else:
|
|
_state = State.WalkRight
|
|
|
|
|
|
func _set_chase_state() -> void:
|
|
SoundManager.play_sfx_stream(SoundManager.sfx_stream_player_spoted, global_position)
|
|
if _target_x < position.x:
|
|
_state = State.ChasingLeft
|
|
else:
|
|
_state = State.ChasingRight
|
|
|
|
|
|
func _is_walking_state() -> bool:
|
|
return _state == State.WalkLeft or _state == State.WalkRight
|
|
|
|
|
|
func _on_animation_finished() -> void:
|
|
if _target_found:
|
|
_set_chase_state()
|
|
_target_found = false
|
|
return
|
|
|
|
match type:
|
|
Type.Standing:
|
|
_state = State.Idle
|
|
Type.Walking:
|
|
_set_walking_state()
|
|
Type.Random:
|
|
if randi_range(1, 100) <= WALK_CHANCE:
|
|
_set_walking_state()
|
|
else:
|
|
_state = State.Idle
|
|
|
|
|
|
func _on_animation_looped() -> void:
|
|
if _state == State.Idle or (type == Type.Random and _is_walking_state()):
|
|
if randi_range(1, 100) <= LOOK_AROUND_CHANCE:
|
|
_state = State.LookAround
|
|
|
|
|
|
func _on_player_touch_area_entered(body: Node2D) -> void:
|
|
if body is Player:
|
|
_target_x = body.position.x
|
|
_set_chase_state()
|