Instancing with signals

Signals provide a way to decouple game objects, allowing you to avoid forcing a fixed arrangement of nodes. One sign that a signal might be called for is when you find yourself using get_parent(). Referring directly to a node's parent means that you can't easily move that node to another location in the scene tree. This can be especially problematic when you are instancing objects at runtime and may want to place them in an arbitrary location in the running scene tree.

Below we'll consider an example of such a situation: firing bullets.

Пример стрельбы

Consider a player character that can rotate and shoot towards the mouse. Every time the mouse button is clicked, we create an instance of the bullet at the player's location. See Создание экземпляров for details.

We'll use an Area2D for the bullet, which moves in a straight line at a given velocity:

extends Area2D

var velocity = Vector2.ZERO

func _physics_process(delta):
    position += velocity * delta

Однако, если пули добавятся как дети игрока, то они останутся "привязанными" к тому как игрок вращается (нагляднее ниже):


Вместо этого, нам нужно чтобы пули были независимыми от движения игрока после выстрела, они должны продолжать движение по прямой линии, и игрок больше не должен влиять на них. Вместо того, чтобы быть добавлены к дереву сцены в качестве ребенка игрока, имеет больше смысла добавить пулю в качестве дочернего элемента "основной" игровой сцены, которая может быть родителем игрока или даже дальше выше по дереву сцен.

You could do this by adding the bullet to the main scene directly:

var bullet_instance = Bullet.instance()

However, this will lead to a different problem. Now if you try to test your "Player" scene independently, it will crash on shooting, because there is no parent node to access. This makes it a lot harder to test your player code independently and also means that if you decide to change your main scene's node structure, the player's parent may no longer be the appropriate node to receive the bullets.

Решение заключается в использовании сигнала, сообщающего об "испускании" пули от игрока. Игроку не нужно "знать", что происходит с пулями. Узел подключенный сигналом может "получить" пули и предпринять соответствующие действия для их порождения.

Вот код для игрока с использованием сигналов для испускания пуль:

extends Sprite

signal shoot(bullet, direction, location)

var Bullet = preload("res://Bullet.tscn")

func _input(event):
    if event is InputEventMouseButton:
        if event.button_index == BUTTON_LEFT and event.pressed:
            emit_signal("shoot", Bullet, rotation, position)

func _process(delta):

В главной сцене, мы подключим сигнал игрока (он появится на вкладке "Узел").

func _on_Player_shoot(Bullet, direction, location):
    var b = Bullet.instance()
    b.rotation = direction
    b.position = location
    b.velocity = b.velocity.rotated(direction)

Теперь пули будут продолжать свое собственное движение независимо от вращения игрока: