UPDATE: I fixed several issues with the original code through much trial and error, and at this point, my enemy is walking along the path, but facing the wrong direction. As I understand it, with the way I'm using the look_at() function, it shouldn't be possible that he's moving in one direction and looking in another. Additionally, he's still running into things despite the danger being calculated. I've replaced the color-changing sticks I was using to debug the raycasts with sphere meshes that are supposed to be placed at the collision point of each raycast and change color based on the danger value, but they're only doing that at certain angles, whereas the ones that are supposed to be positioned at the collision point of other raycasts are just clipping through walls instead. Any ideas?
Updated code:
extends CharacterBody3D
var default_speed = 95
var first_nav_point = true
var current_nav_index = 0
var next_path_point = Vector3.ZERO
var physics_frame_counter: int = 0
var raycast_visualization = true
var number_of_directions = 16
var movement_directions = []
var interest = []
var danger = []
var raycast_visualizers = []
var chosen_direction = Vector2.ZERO
@onready var last_global_position = global_position + Vector3(1,1,1)
@onready var player = get_node(GameVariables.player_path)
@onready var animation_tree = $Model.get_node("AnimationTree")
@onready var animation_state = $Model.get_node("AnimationTree").get("parameters/playback")
@onready var animation = $Model.get_node("AnimationPlayer")
@onready var vision_cone = $ShapeCast3D
@onready var model = $Model
@onready var mesh = $Model.get_node("Armature/Skeleton3D/Cube")
@onready var navigation_agent = $NavigationAgent3D
@onready var nav_points = [get_node("../NavPoint1"), get_node("../NavPoint2"), get_node("../NavPoint3"), get_node("../NavPoint4")]
@onready var nearest_nav_point = nav_points[0]
@onready var next_nav_point = nearest_nav_point
@onready var raycast_visualizer_scene = load("res://assets/enemies/raycast_visualizer.tscn")
func _ready():
movement_directions.resize(number_of_directions)
interest.resize(number_of_directions)
danger.resize(number_of_directions)
raycast_visualizers.resize(number_of_directions)
for direction in number_of_directions:
var angle = direction * 2 * PI / number_of_directions
movement_directions[direction] = Vector2.DOWN.rotated(angle)
if raycast_visualization:
raycast_visualizers[direction] = raycast_visualizer_scene.instantiate()
add_child(raycast_visualizers[direction])
raycast_visualizers[direction].global_position = global_position
raycast_visualizers[direction].visible = true
var raycast_visualizer_mesh = raycast_visualizers[direction].get_node("MeshInstance3D")
var unique_raycast_visualizer_mesh = raycast_visualizer_mesh.mesh.duplicate()
var unique_raycast_visualizer_mat = raycast_visualizer_mesh.mesh.get("material").duplicate()
raycast_visualizer_mesh.set("mesh", unique_raycast_visualizer_mesh)
raycast_visualizer_mesh.mesh.set("material", unique_raycast_visualizer_mat)
set_physics_process(false)
call_deferred("await_physics")
func _physics_process(delta):
if navigation_agent.is_navigation_finished():
return
set_interest()
set_danger()
var direction = choose_direction()
velocity = direction * default_speed * delta
move_and_slide()
look_at(direction)
func await_physics():
if physics_frame_counter < 2:
await get_tree().physics_frame
physics_frame_counter += 1
await_physics()
else:
set_physics_process(true)
func set_interest():
var next_path_position = to_local(navigation_agent.get_next_path_position())
var path_direction = Vector2(next_path_position.x, next_path_position.z).normalized()
for direction in number_of_directions:
var dot = movement_directions[direction].rotated(rotation.y).dot(path_direction)
interest[direction] = max(dot, 0)
func set_danger():
var space_state = get_world_3d().direct_space_state
var danger_raycast_length = 2.5
var danger_raycast_offset = Vector3(0, 0.5, 0)
for direction in number_of_directions:
#direction_3d is LOCAL
var direction_3d: Vector3
direction_3d.x = movement_directions[direction].rotated(rotation.y).x
direction_3d.y = 0
direction_3d.z = movement_directions[direction].rotated(rotation.y).y
var cast_from = danger_raycast_offset
var cast_to = direction_3d * danger_raycast_length
cast_to.y = danger_raycast_offset.y
var danger_raycast = PhysicsRayQueryParameters3D.create(to_global(cast_from), to_global(cast_to), collision_mask, [self])
danger_raycast.hit_from_inside = true
var danger_raycast_result = space_state.intersect_ray(danger_raycast)
if not danger_raycast_result.has("position"):
danger[direction] = 0.0
else:
var collision_distance = cast_from.distance_to(to_local(danger_raycast_result.position))
var formatted_distance = remap(-collision_distance, -danger_raycast_length, 0.0, 0.0, 1.0)
danger[direction] = float((int(formatted_distance * 1000) ^ 2)) / 1000
danger[direction] = clamp(danger[direction], 0.0, 1.0)
if raycast_visualization:
if danger_raycast_result.has("position"):
raycast_visualizers[direction].position = to_local(danger_raycast_result.position - Vector3(0.1, 0, 0.1))
else:
raycast_visualizers[direction].position = cast_to
raycast_visualizers[direction].get_node("MeshInstance3D").mesh.get("material").set("shader_parameter/blend", danger[direction])
func choose_direction():
chosen_direction = Vector2.ZERO
var chosen_direction_3d := Vector3.ZERO
for direction in number_of_directions:
interest[direction] -= danger[direction]
interest[direction] = max(interest[direction], 0)
chosen_direction += movement_directions[direction] * interest[direction]
#print(interest[direction])
chosen_direction = chosen_direction.normalized()
chosen_direction_3d.x = -chosen_direction.x
chosen_direction_3d.z = -chosen_direction.y
#print(str("Interest: ", interest, ", Danger: ", danger))
return chosen_direction_3d
func _on_NavigationAgent3D_target_reached():
if current_nav_index + 1 <= nav_points.size() - 1:
current_nav_index += 1
else:
current_nav_index = 0
next_nav_point = nav_points[current_nav_index]