class_name GrunkBeast extends CharacterBody3D ## Grunk beast controller #region Constants const STALKING_SOUND_LIMIT := 20.0 #endregion #region Exported Properties @export var base_speed := 60.0 @export var pursuit_speed := 180.0 #endregion #region Member Variables var gravity: Vector3 = ( ProjectSettings.get_setting("physics/3d/default_gravity") * ProjectSettings.get_setting("physics/3d/default_gravity_vector") ) var pathfinding := true @onready var nav_agent: NavigationAgent3D = %NavAgent @onready var nav_probe: NavigationAgent3D = %NavProbe @onready var stalking_timer: Timer = %StalkingTimer @onready var blackboard: Blackboard = %Blackboard #endregion #region Character Controller func is_pursuing() -> bool: return blackboard.has_value("pursuit_target") func is_stalking() -> bool: return false # TODO func get_speed() -> float: if is_pursuing(): return pursuit_speed return base_speed func path_shorter_than(target: Vector3, limit: float) -> bool: var limit_sq := limit * limit var length_sq := 0.0 var last_pos := global_position nav_probe.target_position = target for waypoint: Vector3 in nav_probe.get_current_navigation_path().slice( nav_probe.get_current_navigation_path_index() ): # Using distance squared to save a sqrt instruction length_sq += last_pos.distance_squared_to(waypoint) if length_sq > limit_sq: return false last_pos = waypoint return true func _physics_process(delta: float) -> void: var motion := Vector3.ZERO if pathfinding: var path_pos := nav_agent.get_next_path_position() var relative_pos := path_pos - global_position motion = relative_pos.normalized() * get_speed() * delta velocity.x = motion.x velocity.z = motion.z if not is_on_floor(): velocity += gravity * delta move_and_slide() func on_sound_detected(source: Vector3) -> void: # Check that the source isn't too far away, e.g. a sound from another room if path_shorter_than(source, STALKING_SOUND_LIMIT): blackboard.set_value("stalking_target", source) stalking_timer.start() #endregion