How do I create a fork / split effect for projectiles?

Godot Version



Hi forum! I’m creating an 2d vampire surviors like game, and I have problem creating fork / split effect.

This is a projectile (bullet) scene, which accepts a stats object(damage, speed, duration…) and target nearest enemy on ready. The components read stats from root and handle related behaviors. The ProjectileStatsManager handles behaviors such as pierce, fork, chain and returning.

The behaviors are connected to the hitbox signal area_entered(whenever hits an enemy). For fork effect i wrote:

func _on_area_entered(area):
	if owner_node.stats.fork > 0:
		owner_node.stats.fork -= 1

func fork():
	var forked_node = owner_node.duplicate()
	owner_node.target_direction = owner_node.target_direction.rotated(PI/6)
	forked_node.target_direction = forked_node.target_direction.rotated(-PI/6)

However, the forked_node seems to be linked to the owner_node, thus distrupting hit counting. For example, both forked and original should chain 1 time after forking, but as they are linked and sharing the same stats object, only one of them can chain after forking.

To make things worse, as stats.fork > 1, the forked projectile would collide with the hurtbox instantly on born, triggering everything again. To prevent this, i added a var to ProjectileStatsManager:

func _on_area_entered(area):
	if owner_node.stats.fork > 0:
		if area == forked_from:
			print("same area forked from!")
		forked_from = area
		owner_node.stats.fork -= 1

But it seems that the inner value is lost when duplicating. The forked node has a null value of forked_from.

I can’t think of a valid method of creating a fork effect :joy:. can you guys help me? I would be much obliged!

You probably don’t wanna duplicate using an instance, then. See Node — Godot Engine (stable) documentation in English duplicate flags.
And you probably want to set forked_from AFTER you duplicate the object. XD

while using duplicate(7), another bug occurs

the VelocityComponent is not duplicated :face_with_monocle:

also, shouldn’t i set forked_from before duplicating so the value is copied to the forked node?

If you set it before the copy, both the original and the copy are marked as forked_from the same, idk.
If you are seeing “previously freed” it means you are deleting a component from memory with free() while it’s still needed. The VelocityComponent is a scene instance, so you need to duplicate that too. Duplicating a node tree is not the same as duplicating a node. It might be easier if you kept data in Resource instances instead of Scene instances, but that’s a whole refactor. You can always write a recursive node duplicator. That’s not too hard. I am not sure how flexible your system is for that, tho.

thanks for repling! I check VelocityComponent script and found this

func _ready():
	if owner is CharacterBody2D:
		body = owner
		print(str(owner) + "is not CharacterBody2D")

while forking it printed

<Object#null>is not CharacterBody2D

I replaced owner with get_parent() and it got fixed!

and by @export var forked_from the value is preserved during duplicating.

There’re still many bugs, but I believe the fork is functioning as expected. Thank you again :partying_face: