Spawning Enemy, but not in Collision Shape

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

I add and spawn a enemy with this, 10 away from the player:

var Enemy = load("res://Enemy/Enemy.tscn").instance()
add_child(Enemy)
Enemy.global_transform.origin.x = Player.global_transform.origin.x + 10
Enemy.global_transform.origin.z = Player.global_transform.origin.z + 10

But I don’t want to spawn the Enemy in another object (they all have CollisionShapes). Is there a way to check if there is a collision at a specific point in the “world”?
So what I would like to do:

var Enemy = load("res://Enemy/Enemy.tscn").instance()
x = Player.global_transform.origin.x
z = Player.global_transform.origin.z
while is_collision(x, 0, z):
    x += 1
    z += 1
add_child(Enemy)
Enemy.global_transform.origin.x = x
Enemy.global_transform.origin.z = z

My whole game world is flat, this is the reason why I don’t have to care about the y-coordinate. Maybe I have to change the y-coordinate from 0 to 1 or 2, if 0 is in the ground.
[is_collision(x-coordinate, y-coordinate, z-coordinate) is just a fictional function that replaces the function I search.]

:bust_in_silhouette: Reply From: UnRealCloud 1

I found a quick solution, if it suits you.

You can create the enemy, give the player’s position, test if it collides. If it collides you move him away a little until it dosn’t collide.

There is a function in kinematicbody, test_move(), that can test a mouvement.
https://docs.godotengine.org/en/stable/classes/class_kinematicbody2d.html#methods

Here the code I used

var Enemy = load("res://Enemy/Enemy.tscn").instance()
# Add the Enemy before test anything
add_child(Enemy)
Enemy.translation = Players.translation
while Enemy.test_move(transform, Vector3(0,0,0) == true:
    Enemy.translation.x += 1
    Enemy.translation.z += 1
 

Thanks, I tried this, but the while loop doesn’t seem to work. (it never ends)

Liemaeu | 2020-07-21 09:11

It’s weird because our code do litteraly the same thing.
Anyway

UnRealCloud 1 | 2020-07-21 13:16

:bust_in_silhouette: Reply From: klaas

Hi,
would be easier to keep track of your game objects.

Put your enemy in a group like “enemies”

For performance reasons i would use a simple radius for keeping the distance. (used a myRadius variable)

The when spawning test for other enemies:

func spawn():
   var Enemy = load("res://Enemy/Enemy.tscn").instance()
   var spawnPosition = Player.global_transform.origin
   while isTooNearToOtherEnemy( spawnPosition  ):
      spawnPosition.x += rand_range(-1,1)
      spawnPosition.z += rand_range(-1,1)
   add_child(Enemy)
   Enemy.global_transform.origin = spawnPosition

func isTooNearToOtherEnemy( spawnPosition:Vector3 ):
   var enemies = get_tree().get_nodes_in_group ( "enemies" )
   for enemy in enemies:
      if spawnPosition.distance_to(enemy.global_tranform.origin) < enemy.myRadius:
         return true
   return false

Thanks, but I only have one enemy.

But with this he is still spawning in other object, isn’t he?

Liemaeu | 2020-07-21 09:10

:bust_in_silhouette: Reply From: avencherus

Many ways of doing it. One I can think of quickly is that shapes have collision methods that you can work with if you get the shapes involved.

An example would look like this:

extends Node2D

var actors = []

func make_actor_at(p_position):

	var s = Sprite.new()
	var k = KinematicBody2D.new()
	var r = RectangleShape2D.new()

	var tex = preload("res://icon.png")
	r.extents = tex.get_size() / 2.0
	s.texture = tex

	k.add_child(s)
	k.create_shape_owner(k)
	k.shape_owner_add_shape(0, r)

	var a1_xform = Transform2D(0.0, p_position) * k.shape_owner_get_transform(0)


	var collisions = 0

	for actor in actors:

		var shape = actor.shape_owner_get_shape(0, 0)
		var s_xform = actor.shape_owner_get_transform(0)
		
		print(s_xform)

		var a2_xform = actor.global_transform * s_xform
		
		print(shape)

		if(r.collide(a1_xform, shape, a2_xform)):
			print("Collides with: ", actor)
			collisions += 1

	if(collisions == 0):
		
		actors.append(k)
		
		add_child(k)
		k.global_position = p_position

	else:
		k.free()


func _input(e):

	if(e is InputEventMouseButton and e.pressed and e.button_index == BUTTON_LEFT):
		make_actor_at(get_global_mouse_position())

Another alternative is to get the direct space state, and construct queries. This can be accessed on any Node2D.

get_world_2d().direct_space_state

The methods and their requirements can be found here:

And a more advanced option is to work directly with the Physics2DServer. Creating the bodies and shapes, and handling things using their RIDs. (Though it’s more for situations when you want performance.)

https://docs.godotengine.org/en/stable/classes/class_physics2dserver.html

There are also other creative things you could do if you bring the actor in invisibly, and use other typical techniques to check collisions, resolve them, and then show the actor.

:bust_in_silhouette: Reply From: Liemaeu

Thanks for your help, I found a simple “workaround”:

Enemy.move_and_slide(Vector3(0,0,0))
while Enemy.get_slide_count() > 0:
    Enemy.global_transform.origin.x += 1
    Enemy.global_transform.origin.z += 1
    Enemy.move_and_slide(Vector3(0,0,0))