I know that KidsCanCode.com has a tutorial on context-based steering but, it's focuses on 2d and I don't know how to alter the code so it can work in 3d. I've watched the YouTube video on the subject , so I know that it can work in 3d( even though the video didn't show examples of the 3d code). Does anyone know how to make the code work in 3d? GDScript had a video on a simple and equally important topic entitled The Easy Way to Make Enemies See in Godot; video also had similar looking code. However, I don't know how to make the code in that video work in 3d. Does anyone know how to make the code in these videos work in 3d? Every time I try to follow the tutorial in 3d, I change the 2d raycast to a 3d raycast and the Vector 2 to Vector 3. However, the code doesn't seem to work at all. Does anyone know how I can adapt the code in these tutorials in 3d? It would be great if I could use this technique for an enemy AI I'm working on.

I was interested in it also, so I'm kind of looking at it. What I did so far is to add 8 raycasts and cast them in that half circle pattern using his code. There's a lot to understand in that program and I'm not that good at trig and stuff. I checked collisions in debug so I could see the raycasts. I'll be a while at it. Someone else could probably do it in a few minutes. This is what I have so far:

@fire7side said: I was interested in it also, so I'm kind of looking at it. What I did so far is to add 8 raycasts and cast them in that half circle pattern using his code. There's a lot to understand in that program and I'm not that good at trig and stuff. I checked collisions in debug so I could see the raycasts. I'll be a while at it. Someone else could probably do it in a few minutes. This is what I have so far:

Thanks. I'm starting to get some ideas. A line of code that was giving me problems was this:

ray.cast_to. = Vector3.UP.rotated(angle)* max_view_distance

In the tutorials, this could would have had a Vector2 instead of a Vector3. So when I turned the Vector2 into a Vector3, the rotate function gave me an error demanding an additional argument and I got another error when I add a float as another argument. After some experimenting, I know that the arrays are being added, it's just that I have to find a way to rotate them probably. So here's my plan, I'm just going to make a variable with a number which I want to rotate a raycast and then use it in the loop. However, I'll add more to that number, every time the loop repeats itself. I'm too tired to try it now but, I'll do it tomorrow and then share if it was successful.

This is what I did on it.

func _ready():
	interest.resize(num_rays)
	danger.resize(num_rays)
	ray_directions.resize(num_rays)
	var myvec = Vector3.RIGHT
	for i in num_rays:
		var angle = i * PI /(num_rays -1)
		ray_directions[i] = myvec.rotated(Vector3.UP,angle)

I didn't test it on anything but 8. His code was made for a full circle, but that doesn't really make sense to me. That's not what it showed on his diagram.

I don't know if you got it working or not. I think this the key to it. It's the set default interest which is when the object is pointing right at the target.

func set_default_interest():
	for i in num_rays:
		var d = ray_directions[i].dot(transform.basis.z)
		interest[i] = min(0, d)
		print(d)

It prints out:

0
-0.433884
-0.781832
-0.974928
-0.974928
-0.781832
-0.433884
0

since it goes around from right to left, the two center ones have the most interest, although absolute value might be needed because it travels in the -z direction.

@fire7side

Okay, so I'm up and I'm finally focused. So I tried my suggested solution( for adding and rotating the rays) and it worked. However, I didn't get what you have with admittedly has a cooler looking end result. Check it out:

func raycast_generator():
	var ray_count = angle_cone_of_vision / angle_between_raycast 
	for index in ray_count:
		var ray = RayCast.new() 
		var angle = 0
		ray.cast_to.z = -100
		if angle < angle + angle_index:
			angle = angle + angle_index
			angle_index += angle_index # angle_index is orignally 10
		if ray.rotation.y != angle:
			ray.rotation.y = angle
		ray.enabled = true
		add_child(ray)

Admittedly, what I have here does have some problems but the rays have been created and they've be rotated around the character and away from each other( though I suspect that some are still on top of each other). I'm glad that I came up with something I understand for myself. Though I haven't completely explored your solution yet. You can tell me what you think of what I've got going on here and how it can be improved.

Edit: Here is a screenshot of what it looks like in action:

Edit again:

Okay, so after looking back on your code. I remember I forgot about the danger and interest arrays.

Okay, so after looking at your code. I made a little alteration to my code to make it looker cleaner, while get the same result I had previously.

func raycast_generator():
	var ray_count = angle_cone_of_vision / angle_between_raycast 
	for index in ray_count:
		var ray = RayCast.new() 
		var angle = 0
		ray.cast_to.z = -100
		ray.rotation.y = 10 * index
		ray.enabled = true
		add_child(ray)

I do suspect my code this has the other problem I've mentioned before.

It's pretty important that the rays are evenly spaced for the steering, I think. Not positive on that. Your world looks definitely better than mine. Pretty cool. I noticed in his code he didn't factor in the distance when something was hit. At least I didn't see it. I guess I'll try the same thing at first. I'm not sure if rotation is in angles or radians but I think it's in radians which means that thing is really winding around. You could try deg_to_rad(360/8) and see what it does.

@fire7side said: You could try deg_to_rad(360/8) and see what it does.

When I tried that number in Deg2Rad, all the arrays stacked on each other. When I did it without Ded2Rad, I got a crazy amount of rays. If my maths is correct, I think I got 45 rays:

I feel that we're on to sometime. I don't think we should get distracted by trying to come up with the simplest solutions but, it would be funny if we came up with a solution that is similar that what KidsCanCode intended.

Edit: I wonder what would happen if I were to just turn rotation_degrees into just rotation. Hmm. I'll be right back.

Edit again: Oh way, I already had rotation instead of rotation degrees.

Another edit: Okay, so after turning the ray_count back to angle_cone_of_vision / angle_between_raycast and turning the rotation to rotation_degrees. I got sometime that looked like this:

Edit: Hmm...I probably wasn't following your instructions properly.

Okay, so I followed your suggestion properly( I think). Here is what I got:

and here is what my code looks like:

func raycast_generator():
	var ray_count = 8
	for index in ray_count:
		var ray = RayCast.new() 
		var angle = 0
		ray.cast_to.z = -20
		ray.rotation.y = deg2rad(360/8) * index
		ray.enabled = true
		add_child(ray)

Edit: All of the rays seems equally spaced out.

That's good, but you need to save the direction vector for later. I don't know if you declared ray_directions in your code like his. Then I think you would do ray_directions[index] = ray.normalized. That's if ray is a vector 3. Somewhere after it's all done with the rotation. If it's not a vector3, which it might not be, then I don't know what you do. Maybe there's some way of getting it from raycast.

@fire7side

So my attempt to adding the directions to the ray_directions array is based on what you've got and what he's got. Here is what it looks like:

func raycast_generator():
	ray_direction.resize(ray_num)
	var vec = Vector3.RIGHT
	for index in ray_num:
		var ray = RayCast.new() 
		ray.cast_to.z = -20
		ray.rotation.y = deg2rad(360/ray_num) * index
		ray.enabled = true
		add_child(ray)
		ray_direction[index] = vec.rotated(Vector3.UP,deg2rad(360/ray_num) * index)
		print(ray_direction[index])

I've managed to get rid of the ray_count variable as it was taking up unnecessary space. However, some sort of ray_num variable has returned for convinence. Here is what was printed in the the output:


(1, 0, 0)
(0.707107, 0, -0.707107)
(-0, 0, -1)
(-0.707107, 0, -0.707107)
(-1, 0, 0)
(-0.707107, 0, 0.707107)
(0, 0, 1)
(0.707107, 0, 0.707107)

It looks like I did get vectors into the array but, I'm not completely sure if it's the correct location. What do you think?

I think it's right. You could maybe draw lines or raycasts from 0,0,0 to those points to see. Direction vectors don't show location, they only show direction. Every reading has a zero in the y location, so it's a flat disc.

@ I'll admit that I don't know how to check this with code. I thought it would be easy as finding some sort of direction property off the raycast but, I was wrong. I'm a bit tired now. If I still have access to the internet tomorrow, I'll be back then.

I'm pretty sure it's right. I ended up having to make a circular pattern also. I have it working in default mode where it wanders around and turns when it gets near something. This is my code so far:

extends KinematicBody

export var max_speed = 350
export var steer_force = 0.1
export var look_ahead = 4
export var num_rays = 8

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

var chosen_dir = Vector3.ZERO
var velocity = Vector3.ZERO
var acceleration = Vector3.ZERO
var raycasts = []
var last_dir = Vector3.ZERO

# Called when the node enters the scene tree for the first time.
func _ready():
	ray_directions.resize(num_rays)
	interest.resize(num_rays)
	danger.resize(num_rays)
	var myvec = Vector3.RIGHT
	for i in num_rays:
		var angle = i * 2*PI /(num_rays)
		ray_directions[i] = myvec.rotated(Vector3.UP,angle)
	raycasts = $rayFront.get_children()
	var i = 0
	for ray in raycasts:
		ray.set_cast_to(ray_directions[i]*look_ahead)
		ray.add_exception(get_node("player"))
		i+=1


func _physics_process(delta):
	set_interest()
	set_danger()
	choose_direction()
	var rotAngle = .1

	chosen_dir.x = lerp(last_dir.x,chosen_dir.x,.03)
	chosen_dir.z = lerp(last_dir.z,chosen_dir.z,.03)
	look_at(Vector3(chosen_dir.x+translation.x,translation.y,chosen_dir.z+translation.z),Vector3.UP)
	move_and_collide(chosen_dir * delta * 2)
	last_dir = chosen_dir
	
func set_interest():
	set_default_interest()
	
func set_default_interest():
	for i in num_rays:
		var d = ray_directions[i].dot(transform.basis.z)
		interest[i] = min(0, d)
		#print(d)
	
func set_danger():
	var i = 0
	for ray in raycasts:
		if ray.is_colliding():
			danger[i] = 1
			if i < 4:
				interest[i+4]+= -1
			else: interest[i-4]+= -1

		else: danger[i] = 0
		i+= 1
	
func choose_direction():
	# Eliminate interest in slots with danger
	for i in num_rays:
		if danger[i] > 0.5:
			interest[i] = 0.0
	# Choose direction based on remaining interest
	chosen_dir = Vector3.ZERO
	for i in num_rays:
		chosen_dir += ray_directions[i] * abs(interest[i])
	chosen_dir = chosen_dir.normalized()

@fire7side

So I was wanted to try understanding your code, before attempting something on my own. However, it just seems that your code works the best. So I just deceived to keep my code for the raycast_generator function but, borrow everything else you've got and I edited to avoid any errors that would arise. When I ran the game, I noticed the ai started moving. However, despite it's attempt and trying to avoid obstacles, it kept hitting into things and getting such. Still, I'm glad that it's at least trying to avoid obstacles.

@fire7side

Okay, so I think my AI is actually moving towards anything the raycast touches as oppose to away from it.

It was working for me. I did some more improvements that might help. It moves toward a target now.

It played for me with the youtube link but doesn't in this window for some reason.

Here's the code. Not sure if I'll do more on it or not. Hope you get it working. A look_ahead of 5 worked the best for me.

xtends KinematicBody

export var max_speed = 3
export var steer_force = 0.5
export var look_ahead = 4
export var num_rays = 8
onready var target_position = get_parent().get_target_position()
var stop = true

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

var chosen_dir = Vector3.ZERO
var velocity = Vector3.ZERO
var acceleration = Vector3.ZERO
var raycasts = []
var last_dir = Vector3.ZERO

# Called when the node enters the scene tree for the first time.
func _ready():
	target_position = get_parent().get_target_position()
	ray_directions.resize(num_rays)
	interest.resize(num_rays)
	danger.resize(num_rays)
	var myvec = Vector3.RIGHT
	for i in num_rays:
		var angle = i * 2*PI /(num_rays)
		ray_directions[i] = myvec.rotated(Vector3.UP,angle)
	raycasts = $rayFront.get_children()
	var i = 0
	for ray in raycasts:
		ray.set_cast_to(ray_directions[i]*look_ahead)
		ray.add_exception(get_parent())
		i+=1
func _unhandled_input(event):
	if event is InputEventKey:
		if event.pressed and event.scancode == KEY_0:
			stop = false

func _physics_process(delta):
	set_interest()
	set_danger()
	choose_direction()

	chosen_dir.x = lerp(last_dir.x,chosen_dir.x,.01)
	chosen_dir.z = lerp(last_dir.z,chosen_dir.z,.01)
	look_at(Vector3(chosen_dir.x+translation.x,translation.y,chosen_dir.z+translation.z),Vector3.UP)
	if !stop:
		move_and_collide(chosen_dir * delta * max_speed)
		last_dir = chosen_dir
	
	
func set_interest():
	if target_position == null:
		set_default_interest()
	else:
			var direction = (translation - target_position).normalized()
			for i in num_rays:
				var d = ray_directions[i].dot(direction)* steer_force
				interest[i] = min(0, d)
				if(translation - target_position).length() < 1:
					stop = true

		
	
func set_default_interest():
	for i in num_rays:
		var d = ray_directions[i].dot(transform.basis.z)
		interest[i] = min(0, d)
		#print(d)
	
func set_danger():
	var i = 0
	for ray in raycasts:
		if ray.is_colliding():
			danger[i] = 1
			if i < 4:
				interest[i+4]+= -1
			elif i > 3: interest[i-4]+= -1

		else: danger[i] = 0
		i+= 1
	
func choose_direction():
	# Eliminate interest in slots with danger
	for i in num_rays:
		if danger[i] > 0.5:
			interest[i] = 0.0
	# Choose direction based on remaining interest
	chosen_dir = Vector3.ZERO
	for i in num_rays:
		chosen_dir += ray_directions[i] * abs(interest[i])
	chosen_dir = chosen_dir.normalized()