信号によるインスタンス化

シグナルは、ゲームオブジェクトを分離する方法を提供し、ノードの固定配置を強制することを回避できます。シグナルが必要となる兆候の1つは、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);

ただし、これは別の問題につながります。 「プレイヤー」シーンを個別にテストしようとすると、(弾丸の追加先として)アクセスする親ノードがないため、確認時にクラッシュします。これにより、プレイヤーコードを個別にテストすることが非常に難しくなります。また、メインシーンのノード構造を変更する場合、プレイヤーの親が弾丸を受け取る適切なノードではなくなる可能性があります。

これに対する解決策は、プレイヤーから弾丸を「放出」するシグナルを使用することです。プレイヤーはその後、弾丸に何が起こるかを「知る」必要はありません - 信号に接続されているノードは、弾丸を「受信」し、弾丸を発生させるために適切なアクションを実行できます。

シグナルを使用して弾丸を発射するプレイヤーのコードは次のとおりです:

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());
    }
}

メインシーンで、プレイヤーのシグナルを接続します([ノード]タブに表示されます)。

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