How can I improve my racing opponent's code with collision and better driving.

:information_source: Attention Topic was automatically imported from the old Question2Answer platform.
:bust_in_silhouette: Asked By Wulfimus375

My screen as I work on it

So as in the picture, I’m working on what is effectively a micro machines clone as part of teaching myself to code in Godot. My problem is, I have worked out how to get my opponent to move using the below code:

extends RigidBody2D
@onready var _follow :PathFollow2D = get_parent()
var _speed :float = 300.0

func _physics_process(delta):
	_follow.set_progress(_follow.get_progress() + _speed * delta)

I also worked out general tilesets (I still have more to learn on all of it) but, I have no idea how to improve my opponent. They follow the path incredibly well until the corners at which point they go all kinds of weird over-shooting the corner, acting like they’re colliding with the walls, and yet their actual collision body stays right where they started out, meaning my car cannot collide with them.

Any ideas what could help with these? Would it be better to use a different kind of node for the opponent than RigidBody2D? Am I using completely inappropriate scripting for them? If so, how would I need to code them to allow the collision to follow them and if possible make them act better on the corners?

Any help would be greatly appreciated.

The path following and physics are essential fighting each other here. Perhaps revisit how you want the AI to move, something like context-based steering?

Context-based steering :: Godot 3 Recipes

spaceyjase | 2023-04-17 10:46

Ah, I hadn’t considered this at the time because I’d only just an hour or so ago considered rays as a potential solution myself. Thank you so much for the help, I’ll give it a crack and report back.

Wulfimus375 | 2023-04-17 12:35

Hello! Me again! So I’ve tried this and all I’m getting is a null interest value. I’m thinking it’s because of this little bit of code here:

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)

because again, interest is returning as “null” Any further help would be greatly appreciated

Wulfimus375 | 2023-04-18 23:52

Hard to say without the rest of the scene and code. Do you mean the interest array is null, or the index/ray slot is null (which should be zero or a value)? If it’s the latter, could be the loop isn’t running so worth verifying num_rays. It’s debug time!

spaceyjase | 2023-04-19 07:30

What I’m using is a CharacterBody2D as a child of a PathFollow2D

extends CharacterBody2D

@export var max_speed = 350
@export var steer_force = 0.1
@export var look_ahead = 100
@export var num_rays = 8

# context array
var ray_directions = []
var interest = []
var danger = []

var chosen_dir = Vector2.ZERO
var acceleration = Vector2.ZERO

func _ready():
	for i in num_rays:
		var angle = i * 2 * PI / num_rays
		ray_directions[i] = Vector2.RIGHT.rotated(angle)

func _physics_process(delta):
	var desired_velocity = chosen_dir.rotated(rotation) * max_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_progress()"):
		var path_direction = owner.get_progress()
		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

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 params =
	var space_state = get_world_2d().direct_space_state
	for i in num_rays:
		var result = space_state.intersect_ray(params)
		danger[i] = 1.0 if result else 0.0 = position + ray_directions[i].rotated(rotation) * look_ahead
	params.exclude = [self]
func choose_direction():
	# Eliminate interest in slots with danger
	for i in num_rays:
		if danger[i] > 0.0:
			interest[i] = 0.0
	# 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()

This is what I’m seeing:


I’ve tried various versions with differing effects but still nothing is working.

Wulfimus375 | 2023-04-19 23:04

I’m not near a godot install today but can take a look for you tomorrow if you’re still stuck; code looks fine, odd the values are set to null which could imply the num_rays loops aren’t executing (because even if d where null, max(0, d) would return 0).

Has num_rays been changed in the editor to something other than 8 (might help to print and/or use breakpoints to verify)?

If you’ve got a project to share, I can take a look (as that’s easier than recreating it!).

spaceyjase | 2023-04-20 08:14

Here you go: GitHub - Xenimme/MinMicMay

There are a few different versions of the script in there, none of them seem to work as wanted but heyo! Also, in the version I showed up above in this thread, I did manually change num_rays in the resize to 8 but I see no reason why that would be causing such an issue. There are also some missing collisions, from various tests, but those too aren’t anywhere that should be affecting this.

I think it’s a translation issue, since I had to translate stuff from 3.x to 4.x as a complete beginner.

EDIT: Been working on it with a friend to try get to the bottom of it too. Discovered it had stopped returning Null and I have no idea why, but that I had missed a section of code for get_path_direction. Still not working, but getting closer

Wulfimus375 | 2023-04-20 21:01

OK, I see what the problem is:

if owner and owner.has_method("get_progress()"):

Firstly, this isn’t being called because ‘owner’ in the scene is the Main node. I think the original recipe had the car as a sub-scene, so its owner would have been the PathFollow2D and the follow script called. It’s a bit confusing so don’t worry too much about who owns what…

So owner needs to change to something else and in set_interest it can be changed to reference its parent using $“…”:

func set_interest():
	# Set interest in each slot based on world direction
	if $".." and $"..".has_method("get_path_direction"):
		var path_direction = $"..".get_path_direction(global_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

(ideally this would be its own scene so owner is the correct node; going up the tree isn’t generally a good thing)

Next is the PathFollow2D script, which has some errors. For the existing scene, it can be changed to this:

extends PathFollow2D

func get_path_direction(pos):
    progress = $"..".curve.get_closest_offset(pos)
    return transform.x

(offset in 3.5 is now progress)

I think that will get you working, the NPC car will follow the path… sort of; there are some other bugs and code smells but I’ll leave that as extra homework :wink:

Oh, and good job getting this far - no mean feat if you claim to be a complete beginner; good work!

spaceyjase | 2023-04-21 11:30

Thank you so much for your help!

It’s going berserk with speed, but other than that it seems to slowly be coming together. It has detected my car when close and acted in response, even if it has been all over the shop.

I think my next step is to translate all this code back in to plain text to try and diagnose the issue with a “What does this actually do” approach.

Wulfimus375 | 2023-04-22 00:54