instancing_with_signals.rst 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159
  1. .. meta::
  2. :keywords: Signal
  3. .. _doc_instancing_with_signals:
  4. Instancing with signals
  5. =======================
  6. Signals provide a way to decouple game objects, allowing you to avoid forcing a
  7. fixed arrangement of nodes. One sign that a signal might be called for is when
  8. you find yourself using ``get_parent()``. Referring directly to a node's parent
  9. means that you can't easily move that node to another location in the scene tree.
  10. This can be especially problematic when you are instancing objects at runtime
  11. and may want to place them in an arbitrary location in the running scene tree.
  12. Below we'll consider an example of such a situation: firing bullets.
  13. Shooting example
  14. ----------------
  15. Consider a player character that can rotate and shoot towards the mouse. Every
  16. time the mouse button is clicked, we create an instance of the bullet at the
  17. player's location. See :ref:`doc_instancing` for details.
  18. We'll use an ``Area2D`` for the bullet, which moves in a straight line at a
  19. given velocity:
  20. .. tabs::
  21. .. code-tab:: gdscript GDScript
  22. extends Area2D
  23. var velocity = Vector2.ZERO
  24. func _physics_process(delta):
  25. position += velocity * delta
  26. .. code-tab:: csharp
  27. public class Bullet : Area2D
  28. {
  29. Vector2 Velocity = new Vector2();
  30. public override void _PhysicsProcess(float delta)
  31. {
  32. Position += Velocity * delta;
  33. }
  34. }
  35. However, if the bullets are added as children of the player, then they will
  36. remain "attached" to the player as it rotates:
  37. .. image:: img/signals_shoot1.gif
  38. Instead, we need the bullets to be independent of the player's movement - once
  39. fired, they should continue traveling in a straight line and the player can no
  40. longer affect them. Instead of being added to the scene tree as a child of the
  41. player, it makes more sense to add the bullet as a child of the "main" game
  42. scene, which may be the player's parent or even further up the tree.
  43. You could do this by adding the bullet to the main scene directly:
  44. .. tabs::
  45. .. code-tab:: gdscript GDScript
  46. var bullet_instance = Bullet.instance()
  47. get_parent().add_child(bullet_instance)
  48. .. code-tab:: csharp
  49. Node bulletInstance = Bullet.Instance();
  50. GetParent().AddChild(bulletInstance);
  51. However, this will lead to a different problem. Now if you try to test your
  52. "Player" scene independently, it will crash on shooting, because there is no
  53. parent node to access. This makes it a lot harder to test your player code
  54. independently and also means that if you decide to change your main scene's
  55. node structure, the player's parent may no longer be the appropriate node to
  56. receive the bullets.
  57. The solution to this is to use a signal to "emit" the bullets from the player.
  58. The player then has no need to "know" what happens to the bullets after that -
  59. whatever node is connected to the signal can "receive" the bullets and take the
  60. appropriate action to spawn them.
  61. Here is the code for the player using signals to emit the bullet:
  62. .. tabs::
  63. .. code-tab:: gdscript GDScript
  64. extends Sprite
  65. signal shoot(bullet, direction, location)
  66. var Bullet = preload("res://Bullet.tscn")
  67. func _input(event):
  68. if event is InputEventMouseButton:
  69. if event.button_index == BUTTON_LEFT and event.pressed:
  70. emit_signal("shoot", Bullet, rotation, position)
  71. func _process(delta):
  72. look_at(get_global_mouse_position())
  73. .. code-tab:: csharp
  74. public class Player : Sprite
  75. {
  76. [Signal]
  77. delegate void Shoot(PackedScene bullet, Vector2 direction, Vector2 location);
  78. private PackedScene _bullet = GD.Load<PackedScene>("res://Bullet.tscn");
  79. public override void _Input(InputEvent event)
  80. {
  81. if (input is InputEventMouseButton mouseButton)
  82. {
  83. if (mouseButton.ButtonIndex == (int)ButtonList.Left && mouseButton.Pressed)
  84. {
  85. EmitSignal(nameof(Shoot), _bullet, Rotation, Position);
  86. }
  87. }
  88. }
  89. public override _Process(float delta)
  90. {
  91. LookAt(GetGlobalMousePosition());
  92. }
  93. }
  94. In the main scene, we then connect the player's signal (it will appear in the
  95. "Node" tab).
  96. .. tabs::
  97. .. code-tab:: gdscript GDScript
  98. func _on_Player_shoot(Bullet, direction, location):
  99. var b = Bullet.instance()
  100. add_child(b)
  101. b.rotation = direction
  102. b.position = location
  103. b.velocity = b.velocity.rotated(direction)
  104. .. code-tab:: csharp
  105. public void _on_Player_Shoot(PackedScene bullet, Vector2 direction, Vector2 location)
  106. {
  107. var bulletInstance = (Bullet)bullet.Instance();
  108. AddChild(bulletInstance);
  109. bulletInstance.Rotation = direction;
  110. bulletInstance.Position = location;
  111. bulletInstance.Velocity = bulletInstance.Velocity.Rotated(direction);
  112. }
  113. Now the bullets will maintain their own movement independent of the player's
  114. rotation:
  115. .. image:: img/signals_shoot2.gif