Instancing with signals

信号提供了一种解耦游戏对象的方式,让你避免强行强制固定节点排列。当使用 get_parent() 时,你会发现可能需要调用信号的一个标志,直接引用一个节点的父节点意味着你不能轻易地将该节点移动到场景树的另一个位置。当你在运行时实例化对象,并且可能想把它们放在运行中的场景树的任意位置时,这可能是个问题。

下面我们将考虑这种情况的一个例子:发射子弹。

射击示例

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.

我们用 Area2D 来表示子弹,它以给定的速度直线运动:

extends Area2D

var velocity = Vector2.ZERO

func _physics_process(delta):
    position += velocity * delta
public class Bullet : Area2D
{
    Vector2 Velocity = new Vector2();

    public override void _PhysicsProcess(float delta)
    {
        Position += Velocity * delta;
    }
}

然而,如果子弹作为游戏角色的子节点添加,那么当它旋转时,子弹将仍然保持“附着”在游戏角色身上:

../../_images/signals_shoot1.gif

相反,我们需要子弹独立于游戏角色的移动——一旦发射,子弹就会继续沿着直线运动,游戏角色就不能再影响它们了。与其作为游戏角色的子节点被添加到场景树中,不如将子弹作为“主”游戏场景的子节点添加上去更有意义,后者可能是游戏角色的父节点,甚至可能是更高层级的树。

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

var bullet_instance = Bullet.instance()
get_parent().add_child(bullet_instance)
Node bulletInstance = Bullet.Instance();
GetParent().AddChild(bulletInstance);

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):
    look_at(get_global_mouse_position())
public class Player : Sprite
{
    [Signal]
    delegate void Shoot(PackedScene bullet, Vector2 direction, Vector2 location);

    private PackedScene _bullet = GD.Load<PackedScene>("res://Bullet.tscn");

    public override void _Input(InputEvent event)
    {
        if (input is InputEventMouseButton mouseButton)
        {
            if (mouseButton.ButtonIndex == (int)ButtonList.Left && mouseButton.Pressed)
            {
                EmitSignal(nameof(Shoot), _bullet, Rotation, Position);
            }
        }
    }

    public override _Process(float delta)
    {
        LookAt(GetGlobalMousePosition());
    }
}

在主场景中,我们连接游戏角色的信号(它将出现在“Node”选项卡中)。

func _on_Player_shoot(Bullet, direction, location):
    var b = Bullet.instance()
    add_child(b)
    b.rotation = direction
    b.position = location
    b.velocity = b.velocity.rotated(direction)
public void _on_Player_Shoot(PackedScene bullet, Vector2 direction, Vector2 location)
{
    var bulletInstance = (Bullet)bullet.Instance();
    AddChild(bulletInstance);
    bulletInstance.Rotation = direction;
    bulletInstance.Position = location;
    bulletInstance.Velocity = bulletInstance.Velocity.Rotated(direction);
}

现在子弹将保持自己的运动独立于游戏角色的旋转:

../../_images/signals_shoot2.gif