Raycast3Ds for AI Movement stops detecting collision

Godot Version

4.2.2.stable.mono - C#

Question

I implemented a rudimentary AI Movement system for a turn-based game using raycasts to determine the potential paths the AI could take. There are three Raycasts, ForwardRay, LeftRay and RightRay. If the AI is standing in a hallway, it will detect a collision for LeftRay and RightRay, but not for ForwardRay. This would indicate to the AI to move forward. The AI’s movement to the next square is instant.

The game is a dungeon crawler and right now only has hallways which fork into different directions. I noticed that under a specific circumstance an issue arises. If I have a short hallway with a staticBody3D on one end and a staticBody3D at the other, I get a strange situation.

Turn1: No viable paths, turn around 180 degrees
Turn2: Only can go forward
Turn3: Only can go forward
Turn4: No viable paths, turn around 180 degrees
Turn5: Only can go forward
Turn6: Left and Forward are viable, Right has a collision, Turn Left

Strangely it’s only on the second pass through the hallway that this occurs on. And it occurs at the same turn every time. Turn 6.

I can’t for the life of me figure this out. This enemy essentially ends up with wall hacks and just pops up behind you. Cool for a ghost, not for a slime lol.

I’ve tried adding a time delay and ForceUpdateRaycasts (Thought maybe the teleportation movement was not allowing the raycasts to update) But no cigar. I thought maybe the hallway was cursed so I moved it to a different hallway of similar length and the issue still occured.

My walls are in a Gridmap but have StaticBody3D colliders attached to them.

I have noticed sometimes when creating a new monster, the raycasts don’t detect any collisions at all, but this occurs seemingly at random for the created objects and only at the start.

I can’t seem to find anyone else having similar issues. My assumption must be that there is a better way to do the movement than with raycasts that this method isn’t often taken. So any assistance in either fixing the Raycast or recommending a different turn-based Wander function would save me from this headache.

Thank you!

Is there a reason you didn’t want to use a NavigationRegion3D?

Not any specific reason. I just assumed I could do all I needed with just the raycasts and colliders. Does the NavigationRegion3D have a way to determine if an x,y,z coordinate is within it’s region? And are they useful for grid-based movement? I’m familiar with using a NavMesh in Unreal4 for real-time movement but that’s about it.

I’ve never used it for grid-based movement, but that’s more a part of your movement controller than pathfinding. Basically, you setup a NavigationRegion3D in your level, with everything you want checked underneath it, and then add a NavigationAgent3D to anything you want to use it.

Setup for an enemy would go something like:

class_name Enemy extends CharacterBody3D

@onready var navigation_agent_3d: NavigationAgent3D = $NavigationAgent3D
@onready var player: Player = get_tree().get_first_node_in_group("Player")
@onready var rig: Node3D = $Rig

func _physics_process(delta: float) -> void:
	var velocity_target := Vector3.ZERO
	navigation_agent_3d.target_position = player.global_position

	if rig.is_idle():
		check_for_attacks()
		if not navigation_agent_3d.is_target_reached():
			velocity_target = get_local_navigation_direction() * speed
			orient_rig(navigation_agent_3d.get_next_path_position())
	
	navigation_agent_3d.velocity = velocity_target
	
	# Add the gravity.
	if not is_on_floor():
		velocity += get_gravity() * delta

func orient_rig(target_position: Vector3) -> void:
	target_position.y = rig.global_position.y
	if rig.global_position.is_equal_approx(target_position):
		return
	rig.look_at(target_position, Vector3.UP, true)


func get_local_navigation_direction() -> Vector3:
	var destination = navigation_agent_3d.get_next_path_position()
	var local_destination = destination - global_position
	return local_destination.normalized()


func _on_navigation_agent_3d_velocity_computed(safe_velocity: Vector3) -> void:
	if safe_velocity.length() > RUN_VELOCITY_THRESHOLD:
		rig.run_weight_target = 1.0
	else:
		rig.run_weight_target = 0.0
	velocity = safe_velocity
	move_and_slide()

I recommend you find a tutorial online to learn how to set it up.