95 lines
2.8 KiB
GDScript
95 lines
2.8 KiB
GDScript
extends CharacterBody2D
|
|
|
|
@export var patrol_points: PackedVector2Array = PackedVector2Array()
|
|
@export var move_speed: float = 60.0
|
|
@export var wait_time: float = 1.0
|
|
@export var detection_fill_rate: float = 1.7
|
|
@export var detection_decay_rate: float = 0.5
|
|
|
|
@onready var animation_player: AnimationPlayer = $Cat/AnimationPlayer
|
|
@onready var sprite: Sprite2D = $Cat/Sprite2D
|
|
@onready var vision_cone: Node2D = $VisionCone2D
|
|
@onready var vision_area: Area2D = $VisionCone2D/VisionConeArea
|
|
@onready var vision_renderer: Polygon2D = $VisionCone2D/VisionConeRenderer
|
|
|
|
const NORMAL_COLOR := Color(0.95, 0.78, 0.22, 0.45)
|
|
const ALERT_COLOR := Color(0.9, 0.12, 0.12, 0.55)
|
|
|
|
var _points: Array[Vector2] = []
|
|
var _target_index: int = 0
|
|
var _wait_timer: float = 0.0
|
|
var _facing := Vector2.DOWN
|
|
var _detection: float = 0.0
|
|
var _sees_ghost: bool = false
|
|
var _ghost_in_cone: Ghost = null
|
|
|
|
func _ready() -> void:
|
|
animation_player.play("idle_sit")
|
|
if patrol_points.is_empty():
|
|
_points = [global_position]
|
|
else:
|
|
for p in patrol_points:
|
|
_points.append(global_position + p)
|
|
vision_area.body_entered.connect(_on_vision_body_entered)
|
|
vision_area.body_exited.connect(_on_vision_body_exited)
|
|
_update_cone_rotation()
|
|
|
|
func _physics_process(delta: float) -> void:
|
|
if GameManager.game_over:
|
|
velocity = Vector2.ZERO
|
|
return
|
|
_patrol(delta)
|
|
_update_detection(delta)
|
|
|
|
func _patrol(delta: float) -> void:
|
|
if _points.size() <= 1:
|
|
velocity = Vector2.ZERO
|
|
return
|
|
var target: Vector2 = _points[_target_index]
|
|
var to_target := target - global_position
|
|
if to_target.length() < 4.0:
|
|
velocity = Vector2.ZERO
|
|
if animation_player.current_animation != "idle_sit":
|
|
animation_player.play("idle_sit")
|
|
_wait_timer += delta
|
|
if _wait_timer >= wait_time:
|
|
_wait_timer = 0.0
|
|
_target_index = (_target_index + 1) % _points.size()
|
|
else:
|
|
var dir := to_target.normalized()
|
|
velocity = dir * move_speed
|
|
if animation_player.current_animation != "walk":
|
|
animation_player.play("walk")
|
|
_set_facing(dir)
|
|
move_and_slide()
|
|
|
|
func _set_facing(dir: Vector2) -> void:
|
|
if dir.length() < 0.01:
|
|
return
|
|
_facing = dir
|
|
if absf(dir.x) > 0.05:
|
|
sprite.flip_h = dir.x > 0
|
|
_update_cone_rotation()
|
|
|
|
func _update_cone_rotation() -> void:
|
|
vision_cone.global_rotation = _facing.angle() - PI / 2.0
|
|
|
|
func _update_detection(delta: float) -> void:
|
|
if _ghost_in_cone != null and _ghost_in_cone.held_item != null:
|
|
_detection += detection_fill_rate * delta
|
|
else:
|
|
_detection -= detection_decay_rate * delta
|
|
_detection = clampf(_detection, 0.0, 1.0)
|
|
vision_renderer.color = NORMAL_COLOR.lerp(ALERT_COLOR, _detection)
|
|
GameManager.report_npc_alert(get_instance_id(), _detection)
|
|
if _detection >= 1.0:
|
|
GameManager.report_caught()
|
|
|
|
func _on_vision_body_entered(body: Node2D) -> void:
|
|
if body is Ghost:
|
|
_ghost_in_cone = body
|
|
|
|
func _on_vision_body_exited(body: Node2D) -> void:
|
|
if body is Ghost:
|
|
_ghost_in_cone = null
|