extends RigidBody2D @export_group("Movement Properties") @export var speed := 100.0 @export var turn_speed := 25.0 @export_group("Vision Properties") @export var ray : RayCast2D @export_flags_2d_physics var player_collision_layer := 2 @export var max_view_distance := 120.0 @export_range(0.0, 360.0) var angle_cone_of_vision := 120.0 @export_range(0, 72) var sweeping_steps := 34.0 @export var debug_vision := false @export_group("Behaviour Properties") @export var follow_keep_distance := 30.0 enum STATES { IDLE, TARGETING } var state : STATES var target_positons : Array var target_distance : float func _ready(): # Set Raycast into view Direction ray.target_position = Vector2(max_view_distance, 0) pass func swipe_view() -> Array: # Set Raycast to starting position ray.rotation_degrees = -angle_cone_of_vision / 2 # Create Array to Store Angles of Hits var hit_angles : Array # Clear Player Positions Array target_positons.clear() # Reset Targets distance to Infinity target_distance = INF for step in sweeping_steps + 1: # Check Ray ray.force_raycast_update() if ray.is_colliding(): # Get Collider var collider = ray.get_collider() var collider_class = collider.get_class() var collision_layer : int # Get Collision Layer if collider_class == "TileMap": collider = collider as TileMap collision_layer = collider.tile_set.get_physics_layer_collision_layer(0) else: collision_layer = collider.get_collision_layer() # Break if No relevant Collision if collision_layer & player_collision_layer > 0: # Checking Collision Layer hit_angles.append(ray.rotation_degrees) target_positons.append(to_local(ray.get_collision_point())) # Check distance target_distance = min(target_distance, ray.get_collision_point().distance_to(self.global_position)) # Rotate Ray ray.rotation_degrees += angle_cone_of_vision / sweeping_steps return hit_angles func look_at_player(delta : float) -> float: # Get All Angles at which the Player can be seen var hit_angles = swipe_view() # If The Player cant be seen, return and do nothing if hit_angles.size() <= 0: state = STATES.IDLE return 0 state = STATES.TARGETING # Calculate Average Angle Of Player var average_angle : float for angle in hit_angles: average_angle += angle average_angle /= hit_angles.size() # Rotate towards Player, but limit it by rotation speed self.rotation_degrees += clampf(average_angle, -2 * PI * delta * turn_speed, 2 * PI * delta * turn_speed) return average_angle func follow_player(angle : float): if(target_distance > follow_keep_distance): self.apply_central_force(Vector2.RIGHT.rotated(self.rotation).rotated(deg_to_rad(angle)) * speed) pass func _physics_process(delta: float): var angle := look_at_player(delta) match state: STATES.TARGETING: follow_player(angle) STATES.IDLE: # Do Idle Stuff print("Dam, Nice a pause, time for a Coffee") pass func _process(delta: float): if debug_vision: queue_redraw() pass func _draw(): # Draw a Debug Arc in case that Debug Vision is enabled if debug_vision: # Draw All Ray Positions var angle = -angle_cone_of_vision / 2 for step in sweeping_steps + 1: self.draw_line(Vector2(), Vector2(max_view_distance,0).rotated(deg_to_rad(angle)), Color.DARK_RED, 0.5, false) angle += angle_cone_of_vision / sweeping_steps # Draw Ray Arc self.draw_arc(Vector2(), max_view_distance, deg_to_rad(-angle_cone_of_vision / 2), deg_to_rad(angle_cone_of_vision / 2), sweeping_steps + 1, Color.RED, 0.5, false) # Draw A Circle Around the Player if hes seen for pos in target_positons: self.draw_circle(pos, 1, Color.RED) pass