extends CharacterBody2D
class_name RayAi1
@export_group("Rand Speed")
@export var rand_speed: bool
@export var min_speed = 150
@export var max_speed = 200
@export var desiered_speed = 150
@export_group("Rand Look Ahead")
@export var rand_look_ahead: bool
@export var min_look_ahead = 100
@export var max_look_ahead = 200
@export var look_ahead = 120
@export_group("other")
@export var steer_force = 0.1
@export var num_rays = 15
var traction: float
var is_drifting = false
var steer_direction
# context array
var ray_directions = []
var interest = []
var danger = []
var chosen_dir = Vector2.ZERO
var vel = Vector2.ZERO
var acceleration = Vector2.ZERO
func call_on_ready():
interest.resize(num_rays)
danger.resize(num_rays)
ray_directions.resize(num_rays)
for i in num_rays:
var angle = i * 2 * PI / num_rays
ray_directions[i] = Vector2.RIGHT.rotated(angle)
func call_in_physics_process(delta):
if rand_speed:
desiered_speed = randi_range(min_speed, max_speed)
if rand_look_ahead:
look_ahead = randi_range(min_look_ahead, max_look_ahead)
var yip = 0
set_interest()
set_danger()
choose_direction()
var desired_velocity = chosen_dir.rotated(rotation) * desiered_speed
velocity = velocity.lerp(desired_velocity, steer_force)
rotation = velocity.angle()
move_and_collide(velocity * delta)
func set_interest():
# Set interest in each slot based on world direction
if owner and owner.has_method("get_path_direction"):
var path_direction = owner.get_path_direction(position)
for i in num_rays:
var d = ray_directions[i].rotated(rotation).dot(path_direction)
interest[i] = max(0, d)
# If no world path, use default interest
else:
set_default_interest()
func set_default_interest():
# Default to moving forward
for i in num_rays:
var d = ray_directions[i].rotated(rotation).dot(transform.x)
interest[i] = max(0, d)
func set_danger():
# Cast rays to find danger directions
var _space_state = get_world_2d().direct_space_state
for i in num_rays:
var params = PhysicsRayQueryParameters2D.create(position,
position + ray_directions[i].rotated(rotation) * look_ahead)
var result = get_world_2d().direct_space_state.intersect_ray(params)
danger[i] = 1.0 if result else 0.0
func choose_direction():
# Eliminate interest in slots with danger
for i in num_rays:
interest[i] -= danger[i]
# Choose direction based on remaining interest
chosen_dir = Vector2.ZERO
for i in num_rays:
chosen_dir += ray_directions[i] * interest[i]
chosen_dir = chosen_dir.normalized()
From what I can tell about the ai is that it moves and slides with sensed directions. lerping it’s rotation based on velocity angle and direction with some random gas pedal.
Now the drifting code uses speed and player input axis turning or player input back to start drifting. Only stopping the drift once speed falls or steering stops.
You can get speed by calculating the length of velocity after this point. And you can determine if the AI is turning if you find the difference of rotation and velocity angle before this point.
var stearing = rotation - velocity.angle()
rotation = velocity.angle()
Then with speed and stearing variables you can use this logic from the player. Replacing input get axis with stearing
I don’t know how to replicate the backwards. Since the AI doesn’t break?
Also this may require more tuning of the AI since it may not handle the drift physics very well. But let’s see what happens with activating drift traction on the ai.
A better way to approach ai, is to use the same vehicle code. But in order to do that you need to encapsulate and cache the player input on a separate node, and set variables on the vehicle that will later be used in the physics process.
In this way, you write all the animation and driving logic once and then either place a ai node to drive it, or a player input node to drive it.
Understanding the AI code where it casts rays and chooses a direction to move and slide. Is a tuning in itself.
If you generally like how the ai navigates as is, then I wouldn’t make a singular vehicle class with an input interface (for ai or player). You would use the variables I mentioned above to just play the animations and not change the AI physics. The movements might not match between player and AI, but that’s a compromise you would have to decide…
In the end It’s just double the work, and if you change something on one you may have to do the same thing on the other.
I don’t know what the upfront cost to make a singular vehicle with the AI re-tuning is. It’s just my opinion, but I would approach it like that.