I have been trying to make a battle-royale like game where the enemies all target other enemies or players close to them and if there are none they go to random positions. However they are all going to relatively the same spot and i dont know why. Here is my code:
extends KinematicBody2D
onready var player = get_node("../Square_Player")
var Bullet = preload("res://Bullet.tscn")
var time : int = 0
var targets : Array = []
var target : Vector2
var posible_targets : Array = []
var num : int = 0
func _ready():
for node in get_tree().get_nodes_in_group("targets"):
if node != self:
targets.append(node)
func spawn_bullet(target):
var bullet = Bullet.instance()
bullet.position = position
bullet.rotation = rotation
bullet.target = target
bullet.p_name = self.name
get_parent().add_child(bullet)
func _process(delta):
# using if statement check for error
if target == null:
targets.shuffle()
for t in targets:
if (t.position - position).length() < 1000:
posible_targets.append(t)
num = rand_range(0, posible_targets.size())
target = posible_targets[num].position
#target = t.position
else:
target = Vector2(rand_range(0, 1000), rand_range(0, 1000))
else:
pass
if target == Vector2(0, 0):
target = Vector2(rand_range(0, 1000), rand_range(0, 1000))
else:
pass
print(target)
var direction = target - position
var distance = direction.length()
direction = direction.normalized()
position += direction * 200 * delta
# rotate the enemy to face the player
var angle = direction.angle_to(Vector2(1, 0))
rotation = -angle + 89.525
time += 1
# if the enemy is close enough to the player, kill the player
if distance < 1000 and time > 75:
spawn_bullet(target)
time = 0
func die():
# make it invisible
set_process(false)
set_visible(false)
# remove it from the targets group
if "targets" in get_groups():
remove_from_group("targets")
queue_free()
I have tried to change the random numbers that are generated and i have played with different ammounts of enemies to see if the physics has been broken.
First and foremost, remember to call randomize
to seed the default random number generator. You can do so in _ready
:
func _ready():
randomize()
for node in get_tree().get_nodes_in_group("targets"):
if node != self:
targets.append(node)
The random numbers are reproducible, as long as you have the same seed. By calling randomize
you make Godot pick a seed based on the current time. Which is what makes it different every time.
Using randomize
will affect shuffle
, rand_range
and any other function that uses random without a RandomNumberGenerator
instance (and if you had a RandomNumberGenerator
you would call randomize
on it).
Second, I find this odd:
targets.shuffle()
for t in targets:
if (t.position - position).length() < 1000:
posible_targets.append(t)
num = rand_range(0, posible_targets.size())
target = posible_targets[num].position
#target = t.position
else:
target = Vector2(rand_range(0, 1000), rand_range(0, 1000))
So, for each target that satisfies (t.position - position).length() < 1000
, you add it to posible_targets
and then pick a target from posible_targets
at random. So you picking a target at random each time you add a target to posible_targets
.
You could pick once at the end of the loop. Or better yet, you could simply pick the first target that satisfies (t.position - position).length() < 1000
, because the targets are already shuffled.
Like this:
# if we don't find another target, this will be the value
target = Vector2(rand_range(0, 1000), rand_range(0, 1000))
targets.shuffle()
for t in targets:
if (t.position - position).length() < 1000:
target = t.position
break
By the way, I don't see you clean posible_targets
, you were always adding, never removing. But, as you can see, you don't need posible_targets
anyway.
I also want to comment on this:
if "targets" in get_groups():
remove_from_group("targets")
queue_free()
You can check if the node is in a group using is_in_group
. However, there is little point in removing it since you are releasing the node with queue_free
anyway.
Which brings me to this:
for node in get_tree().get_nodes_in_group("targets"):
if node != self:
targets.append(node)
First of all, know that each time you call get_nodes_in_group
, you get a new array. So there is no problem if you modify it. Thus, you can remove self
from it:
targets = get_tree().get_nodes_in_group("targets")
targets.erase(self)
But since the targets might being released (queue_free
). That array would be obsolete.
Consider:
# if we don't find another target, this will be the value
target = Vector2(rand_range(0, 1000), rand_range(0, 1000))
var targets := get_tree().get_nodes_in_group("targets")
targets.shuffle()
for t in targets:
if t != self and (t.position - position).length() < 1000:
target = t.position
break
Alternatively, add a check to see if you are not trying to access a dead target:
# if we don't find another target, this will be the value
target = Vector2(rand_range(0, 1000), rand_range(0, 1000))
targets.shuffle()
for t in targets:
if is_instance_valid(t) and (t.position - position).length() < 1000:
target = t.position
break
You might also be interested in removing said dead targets, but I'll leave that up to you.
Last but far from least, you have declared target
like this:
var target : Vector2
Which means, this will never be true
: target == null
.
This will, however I won't recommend considering Vector(0.0, 0.0)
an invalid target (how do you know there isn't a target there?):
if target == Vector2(0, 0):
target = Vector2(rand_range(0, 1000), rand_range(0, 1000))
Instead, you might want to pick a target in _ready
. Which also suggest to extract the code to pick a target to another function which you can call where you need it.
This also puts into question when do you want to pick a new target. Perhaps you want to keep a reference to the node you got from get_tree().get_nodes_in_group("targets")
and pick a new target when that dies? That, I don't know, it is your game design.