实例化与信号

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

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

射击示例

考虑一个可以旋转并向鼠标射击的玩家角色. 每次点击鼠标按钮, 我们就在玩家的位置创建一个子弹的实例. 详见 实例化 .

我们用 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

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

你可以通过将子弹直接添加到主场景中来做到这一点:

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

然而, 这将导致一个不同的问题. 现在, 如果你试图独立测试你的 "Player" 场景, 它将在拍摄时崩溃, 因为没有父节点可以访问. 这使得独立测试你的播放器代码变得更加困难, 也意味着如果你决定改变你的主场景的节点结构, 播放器的父节点可能不再是接收子弹的适当节点.

解决这个问题的方法是使用一个信号来 "发射" 游戏角色的子弹. 游戏角色不需要 "知道" 子弹在那之后发生了什么——任何连接到信号的节点都可以 "接收" 子弹并采取适当的行动来产生它们.

下面是游戏角色使用信号发射子弹的代码:

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