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 @export var debug_destroy: bool: set(value): queue_free() #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 length := 0.0 var last_pos := global_position nav_probe.target_position = target # Fail early if the target is unreachable. # NOTE: this call also forces a navigation path refresh! Do not remove! if not nav_probe.is_target_reachable(): return false var path := nav_probe.get_current_navigation_path().slice( nav_probe.get_current_navigation_path_index() ) if not path: # Shouldn't be possible, but if it is it would cause problems if we didn't fail here print_debug("Target is reachable but has no path (tell the developer!)") return false # Integrate along path for waypoint: Vector3 in path: length += last_pos.distance_to(waypoint) if length > limit: return false last_pos = waypoint return true func _physics_process(delta: float) -> void: var motion := Vector3.ZERO if pathfinding and not nav_agent.is_navigation_finished(): 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