godot radom spawn location

117 Views Asked by At

So i have been learning godot and now i want to implement random spawn locations in the game, right now i can only spawn on one side and i want 4 at least.


extends Path3D

var timer = 0
var spawntime = 2
var enemy = preload("res://whitecube.tscn")

func _process(delta):
    timer += delta
    if (timer > spawntime)
        var newenemy = enemy.instantiate()
        add_child(newenemy)
        timer = 0

this is the code i have in my path3D to spawn them in each 2 seconds extends PathFollow3D


@export var movespeed = 6

func _process(delta):
    set_progress(get_progress() + movespeed * delta)

and this is the path they folow and speed


i tried to create 1 markers3d and drag the path/pathfollow3d scene inside,then copy and paste it 4 times but the paths are all connected so if i move the path1, the path 2 moves too and i dont know why.Anyways After creating that i thought about creating a node3d which would be used as a spawn location and a random number generator from 1 to 4, inside the "if (timer > spawntime)" i would do one if for each location and set the spawnlocation = marker1 ,2 ,3 or 4 depending on the random number.

i have been learning this language for about 2 months only. So if what i am saying is stupid i deeply apologize and ask you for your help. Thank you.

1

There are 1 best solutions below

1
Theraot On

In the following code path is a reference to the Path3D. Since you are writing in an script attached to the Path3D, you do not need path. So instead of - for example - path.curve.get_baked_length() you can write self.curve.get_baked_length() or simply curve.get_baked_length(). However I'll keep writing path for generality.

Furthermore, if you need to pick between multiple Path3D you could have references to them in an Array, pick one at random from the array (for example with pick_random), and that will be your path.


Picking a position at random on the Path3D

Now, the task is getting a position at random from a Path3D, correct?

I'll start by getting the length of the Path3D:

var path_length := path.curve.get_baked_length()

Now we can pick a distance along the Path3D like this:

var path_length := path.curve.get_baked_length()
var offset := randf_range(0.0, path_length)

Then we sample the Path3D on that distance:

var path_length := path.curve.get_baked_length()
var offset := randf_range(0.0, path_length)
var result_path_position := path.curve.sample_baked(offset)

Assuming we are going to add a new instance as child of a different node, let us convert that to global coordinates:

var path_length := path.curve.get_baked_length()
var offset := randf_range(0.0, path_length)
var result_global_position := path.to_global(path.curve.sample_baked(offset))

In the following code node_parent is the reference to the Node3D under which you will add the new instance as child. Again, if you are writing the code on an script attached to that Node3D you do not need to write node_parent.

The next task is to instantiate a new child at that position, right?

So let us go from global coordinates to local coordinates of the node_parent:

var path_length := path.curve.get_baked_length()
var offset := randf_range(0.0, path_length)
var result_global_position := path.to_global(path.curve.sample_baked(offset))
var result_position := node_parent.to_local(result_global_position)

Note: If node_parent and path are the same node, you don't need these global an local conversions at all. You can work with the result_path_position we had above.

New let us instantiate, set position, and add the instance as child:

var path_length := path.curve.get_baked_length()
var offset := randf_range(0.0, path_length)
var result_global_position := path.to_global(path.curve.sample_baked(offset))
var result_position := node_parent.to_local(result_global_position)

var instance := scene.instantiate()
instance.position = result_position
node_parent.add_child(instance)

Following a Path3D

If you want to do this with PathFollow3D then each instance needs to be a child of a different PathFollow3D. The PathFollow3D must be added as children of the Path3D. And you can control the position along the path with either progress or progress_ratio.

For example, if you want the instances to advance along the path at a given speed, you would - of course - start by setting the progress to 0.0, and then each frame you increase progress by speed * delta.


Of course, you don't need PathFollow3D. You could have a reference to the Path3D

@export var path:Path3D

You also need the speed you want to advance at, and keep track of your position on the track:

@export var path:Path3D
@export var speed:float


var progress:float

And advance yourself:

@export var path:Path3D
@export var speed:float


var progress:float


func _physics_process(delta:float) -> void:
    progress = progress + speed * delta
    var dest := path.to_global(path.curve.sample_baked(progress))
    global_position = dest

You could, instead of keeping track of progress, find the closest position to the path:

@export var path:Path3D
@export var speed:float


func _ready() -> void:
    if path.curve.point_count == 0:
        global_position = path.global_position
    else:
        global_position = path.to_global(path.curve.get_point_position(0))


func _physics_process(delta:float) -> void:
    var offset = path.curve.get_closest_offset(path.to_local(global_position))
    offset = offset + speed * delta
    var dest := path.to_global(path.curve.sample_baked(offset))
    global_position = dest

Here I'm placing the instance at the start of the path in _ready(). It is also an example of handling an empty Path3D (if the Path3D has no points, I place the instance at the position of the Path3D itself).

You can also imagine that you could find out where is the closest offset, and compare it with the current position, so we can write code to return to the path (for example if something can push the instance off track):

var offset = path.curve.get_closest_offset(path.to_local(global_position))
var ideal := path.to_global(path.curve.sample_baked(offset))
var dest := global_position
if ideal.distance_to(global_position) > 0.001:
    # Return to the path
    dest = global_position.move_toward(ideal, speed * delta)
else:
    # Follow the path
    offset = offset + speed * delta
    dest = path.to_global(path.curve.sample_baked(offset))

global_position = dest

This would not have been possible with PathFollow3D.


Handling if the instance reached the end of the Path3D is possible by taking the length of the Path3D (as shown in the case of picking a position at random) and comparing the offset with that. I'll leave that to you.


It is also possible to use sample_baked_with_rotation which gives you a Transform3D, so the instance can follow the rotation of the Path3D, the code would need to be adjusted to work with Transform3D instead of just Vector3. For that you will need to get the position of the Transform3D by reading its origin property. And, of course, you would set globa_transform instead of global_position.

However, I would suggest to rotate according to the direction the instance is moving (i.e. the direction between global_position and dest) which would also work when the instance is returning to the path.

If you don't need to interpolate the rotation, using look_at to look at dest would suffice, which reminds me, you can query the up vector from the path with sample_baked_up_vector.


Note that in these codes I'm writing global_position. If you are moving a CharacterBody3D you might want to use move_and_slide instead...

velocity = (dest - global_position) / delta
move_and_slide()

Again, I hope you can adapt this code to your needs.