How to connect 2 NPC's with a line

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

I’ve got a 2d top-down space game, visually similar to Space Rock, which y’all know very well

I have a randomly spawned enemies and a want to spawn them by two and have drawn a line between them. This line should move and change it’s length relatively to positions of spawned NPC’s and move with them together.

But i have no idea how to realise that! The only clue i’ve got is that I can use Line2D node. But what’s next? I need your help! I can provide further information if it’s needed.
scrnsht from game to explain what I mean

:bust_in_silhouette: Reply From: aXu_AP

You’re on the right track! You just need to set line coordinates (they are in array called points) every frame to match target coordinates:

extends Line2D

export var first_target : NodePath
export var second_target : NodePath

var target1 : Node2D
var target2 : Node2D


func _ready() -> void:
	# Load targets from path only if they were set
	if first_target:
		target1 = get_node(first_target)
	if second_target:
		target2 = get_node(second_target)
	
	points = [ Vector2.ZERO, Vector2.ZERO ]


func _process(delta: float) -> void:
	if is_instance_valid(target1) and is_instance_valid(target2):
	    points[0] = to_local(target1.global_position)
	    points[1] = to_local(target2.global_position)

Usage: set target1 and target2 in code, or First Target and Second Target in editor to some nodes.

Edit: added instance validity check to avoid errors on target deletion. You might also want to add queue_free() in else case for automatic deletion of the line, but that’s up to you.

Ok, thanks for your comprehensive answer, BUT…
I have no existing targets, they’re being spawned only when the game has started. So how should I call these nodes if they’re spawning during the whole game one by one? Can i get spawned target id’s for example and to write in your code above some links to new generated targets?

I mean, a have a single NPC scene, and it’s copies are called to spawn every 3 sec, by 2 pieces. They have unique sprites, maybe that could help :confused:

Yozhik | 2021-10-17 19:07

That’s no problem. Whenever you spawn instances of the targets, also add new instance of this code. Set target1 and target2 to the newly spawned instances and you’re good to go.
Reply if you need more throrough example :slight_smile:

aXu_AP | 2021-10-17 19:28

func repeat_me():
if G.player_alive == 1:
	var rand = RandomNumberGenerator.new()
	var enemyscene = load("res://EnemyShip.tscn")
	var screen_size = get_viewport().get_visible_rect().size
	for i in range(1, 3):
		var enemy = enemyscene.instance()
		rand.randomize()
		var x = rand.randf_range(0, screen_size.x)
		rand.randomize()
		var y = rand.randf_range(0, screen_size.y)
		enemy.position.y = y
		enemy.position.x = x
		add_child(enemy)

this code make enemies spawn randomly. Probably it sounds stupid, but I have no idea how to access these “childs”

Yozhik | 2021-10-17 19:44

The easiest way to access newly spawned childs is to save them in a variable after creating them. Practically this is what you are doing (the enemy is in enemy variable), but you lose access to that variable after the for loop.

Here’s how’d I do it. I have moved the code for spawning single enemy to a separate function, as it makes the thing a bit easier. Also randomize() initializes the random number generator, but you can consequently call for random numbers after that. No need to initialize it multiple times (as it might create needless overhead).
Code from answer is considered to be at TargetLine.gd. You might need to modify bits of this to work in your project, but I hope that the principles are clear.

# Use one random number generator repeatedly
var rand = RandomNumberGenerator.new()
# Preload resources on startup
var enemyscene = preload("res://EnemyShip.tscn")
var target_line_scene = preload("res://TargetLine.gd")


func _ready() -> void:
	rand.randomize() # randomize needs to be called only once, as it initializes the genetaror


func repeat_me():
	if G.player_alive == 1:
		# Spawn enemy pair
		var enemy1 = spawn_enemy()
		var enemy2 = spawn_enemy()
		# Add line between them
		var line = target_line_scene.new()
		line.target1 = enemy1
		line.target2 = enemy2
		add_child(line)


# Spawns a single enemy and returns it
func spawn_enemy():
	var screen_size = get_viewport().get_visible_rect().size
	var enemy = enemyscene.instance()
	var x = rand.randf_range(0, screen_size.x)
	var y = rand.randf_range(0, screen_size.y)
	enemy.position.y = y
	enemy.position.x = x
	add_child(enemy)
	return enemy

aXu_AP | 2021-10-17 20:26

Worked perfectly fine for me, thank you man.

Yozhik | 2021-10-18 13:04

I’ve got only one question left. How can I check collision of other objects with this line? Regular Area2D with Collisionshape2d won’t work, will it?

Yozhik | 2021-10-18 13:44

No problem, nice that I was of assistance.
Look into raycasting. Use either RayCast2D node (with similiar code to align it every frame) or raycast query. If you run into new problems, post a new question to keep thread cleanly in single topic :slight_smile:

aXu_AP | 2021-10-18 14:26