diff --git a/getting_started/scripting/gdscript/gdscript_styleguide.rst b/getting_started/scripting/gdscript/gdscript_styleguide.rst index 253d4875d..0b57e1baf 100644 --- a/getting_started/scripting/gdscript/gdscript_styleguide.rst +++ b/getting_started/scripting/gdscript/gdscript_styleguide.rst @@ -40,10 +40,10 @@ Each indent level should be one greater than the block containing it. **Bad**: :: - + for i in range(10): print("hello") - + for i in range(10): print("hello") @@ -167,7 +167,7 @@ Use PascalCase: ``extends KinematicBody`` Also when loading a class into a constant or variable: :: - + const MyCoolNode = preload('res://my_cool_node.gd') Functions and Variables @@ -185,7 +185,7 @@ Signals Use past tense: :: - + signal door_opened signal score_changed diff --git a/getting_started/step_by_step/img/signals_connect_dialog_timer.png b/getting_started/step_by_step/img/signals_connect_dialog_timer.png new file mode 100644 index 000000000..21ff6a6d2 Binary files /dev/null and b/getting_started/step_by_step/img/signals_connect_dialog_timer.png differ diff --git a/getting_started/step_by_step/img/signals_disconnect_timer.png b/getting_started/step_by_step/img/signals_disconnect_timer.png new file mode 100644 index 000000000..68a43d95c Binary files /dev/null and b/getting_started/step_by_step/img/signals_disconnect_timer.png differ diff --git a/getting_started/step_by_step/img/signals_node_setup.png b/getting_started/step_by_step/img/signals_node_setup.png new file mode 100644 index 000000000..d32690f3f Binary files /dev/null and b/getting_started/step_by_step/img/signals_node_setup.png differ diff --git a/getting_started/step_by_step/img/signals_node_tab_timer.png b/getting_started/step_by_step/img/signals_node_tab_timer.png new file mode 100644 index 000000000..e44ffca9d Binary files /dev/null and b/getting_started/step_by_step/img/signals_node_tab_timer.png differ diff --git a/getting_started/step_by_step/img/signals_shoot1.gif b/getting_started/step_by_step/img/signals_shoot1.gif new file mode 100644 index 000000000..dc40d27a8 Binary files /dev/null and b/getting_started/step_by_step/img/signals_shoot1.gif differ diff --git a/getting_started/step_by_step/img/signals_shoot2.gif b/getting_started/step_by_step/img/signals_shoot2.gif new file mode 100644 index 000000000..525282b6e Binary files /dev/null and b/getting_started/step_by_step/img/signals_shoot2.gif differ diff --git a/getting_started/step_by_step/index.rst b/getting_started/step_by_step/index.rst index f4cec7841..c49c3b179 100644 --- a/getting_started/step_by_step/index.rst +++ b/getting_started/step_by_step/index.rst @@ -11,6 +11,7 @@ Step by step instancing_continued scripting scripting_continued + signals your_first_game exporting godot_design_philosophy diff --git a/getting_started/step_by_step/signals.rst b/getting_started/step_by_step/signals.rst new file mode 100644 index 000000000..46df46624 --- /dev/null +++ b/getting_started/step_by_step/signals.rst @@ -0,0 +1,329 @@ +.. _doc_signals: + +Signals +======= + +Introduction +------------ + +Signals are Godot's version of the *observer* pattern. They allow a node to +send out a message that other nodes can listen for and respond to. For example, +rather than continuously checking a button to see if it's being pressed, the +button can emit a signal when it's pressed. + +.. note:: You can read more about the observer pattern here: http://gameprogrammingpatterns.com/observer.html + +Signals are a way to *decouple* your game objects, which leads to better organized +and more manageable code. Instead of forcing game objects to expect other objects +to always be present, they can instead emit signals that any interested objects can +subscribe to and respond. + +Below you can see some examples of how you can use signals in your own projects. + +Timer example +------------- + +To see how signals work, let's try using a :ref:`Timer ` node. Create +a new scene with a Node and two children: a Timer and a :ref:`Sprite `. +You can use the Godot icon for the Sprite's texture, or any other image you +like. Attach a script to the root node, but don't add any code to it yet. + +Your scene tree should look like this: + +.. image:: img/signals_node_setup.png + +In the Timer node's properties, check the "On" box next to *Autostart*. This will +cause the timer to start automatically when you run the scene. You can leave the +*Wait Time* at 1 second. + +Next to the "Inspector" tab is a tab labeled "Node". Click on this tab and you'll +see all of the signals that the selected node can emit. In the case of the Timer +node, the one we're concerned with is "timeout". This signal is emitted whenever +the Timer reaches ``0``. + +.. image:: img/signals_node_tab_timer.png + +Click on the "timeout()" signal and click "Connect...". You'll see the following +window, where you can define how you want to connect the signal: + +.. image:: img/signals_connect_dialog_timer.png + +On the left side, you'll see the nodes in your scene and can select the node that +you want to "listen" for the signal. Note that the Timer node is red - this is +*not* an error, but is a visual indication that it's the node that is emitting +the signal. Select the root node. + +.. warning:: The target node *must* have a script attached or you'll receive + an error message. + +On the bottom of the window is a field labeled "Method In Node". This is the name +of the function in the target node's script that you want to use. By default, +Godot will create this function using the naming convention ``_on__`` +but you can change it if you wish. + +Click "Connect" and you'll see that the function has been created in the script: + +.. tabs:: + .. code-tab:: gdscript GDScript + + extends Node + + func _on_Timer_timeout(): + pass # replace with function body + + .. code-tab:: csharp + + public class TimerExample : Node + { + private void _on_Timer_timeout() + { + // Replace with function body. + } + } + +Now we can replace the placeholder code with whatever code we want to run when +the signal is received. Let's make the Sprite blink: + +.. tabs:: + .. code-tab:: gdscript GDScript + + extends Node + + func _on_Timer_timeout(): + $Sprite.visible = !$Sprite.visible + + .. code-tab:: csharp + + public class TimerExample : Node + { + public void _on_Timer_timeout() + { + var sprite = (Sprite) GetNode("Sprite"); + sprite.Visible = !sprite.Visible; + } + } + +Run the scene and you'll see the Sprite blinking on and off every second. You can +change the Timer's *Wait Time* property to alter this. + +Connecting signals in code +~~~~~~~~~~~~~~~~~~~~~~~~~~ + +You can also make the signal connection in code rather than with the editor. This +is usually necessary when you're instancing nodes via code and so you can't use +the editor to make the connection. + +First, disconnect the signal by selecting the connection in the Timer's "Node" +tab and clicking disconnect. + +.. image:: img/signals_disconnect_timer.png + +To make the connection in code, we can use the ``connect`` function. We'll put it +in ``_ready()`` so that the connection will be made on run. The syntax of the +function is ``.connect(, , )``. +Here is the code for our Timer connection: + +.. tabs:: + .. code-tab:: gdscript GDScript + + extends Node + + func _ready(): + $Timer.connect("timeout", self, "_on_Timer_timeout") + + func _on_Timer_timeout(): + $Sprite.visible = !$Sprite.visible + + .. code-tab:: csharp + + public class TimerExample : Node + { + public override void _Ready() + { + var timer = (Timer) GetNode("Timer"); + timer.Connect("timeout", this, nameof(_on_Timer_timeout)); + } + + public void _on_Timer_timeout() + { + var sprite = (Sprite) GetNode("Sprite"); + sprite.Visible = !sprite.Visible; + } + } + +Custom signals +-------------- + +You can also declare your own custom signals in Godot: + + +.. tabs:: + .. code-tab:: gdscript GDScript + + extends Node + + signal my_signal + + .. code-tab:: csharp + + public class Main : Node + { + [Signal] + public delegate void MySignal(); + } + +Once declared, your custom signals will appear in the Inspector and can be connected +in the same way as a node's built-in signals. + +To emit a signal via code, use the ``emit`` function: + +.. tabs:: + .. code-tab:: gdscript GDScript + + extends Node + + signal my_signal + + func _ready(): + emit("my_signal") + + .. code-tab:: csharp + + public class Main : Node + { + [Signal] + public delegate void MySignal(); + + public override void _Ready() + { + EmitSignal(nameof(MySignal)); + } + } + +Shooting example +---------------- + +As another example of signal usage, let's 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 :ref:`doc_instancing` +for details. + +However, if the bullets are added as children of the player, then they will +remain "attached" to the player as it rotates: + +.. image:: img/signals_shoot1.gif + +Instead, we need the bullets to be independent of the player's movement - once +fired, they should continue traveling in a straight line and the player can no +longer affect them. Instead of being added to the scne tree as a child of the +player, it makes more sense to add the bullet as a child of the "main" game +scene, which may be the player's parent or even further up the tree. + +You could do this by adding the bullet directly: + +.. tabs:: + .. code-tab:: gdscript GDScript + + var bullet_instance = Bullet.instance() + get_parent().add_child(bullet_instance) + + .. code-tab:: csharp + + var bulletInstance = (Sprite) Bullet.Instance(); + GetParent().AddChild(bulletInstance); + +However, this will lead to a different problem. Now if you try and 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. + +The solution to this is to use a signal to "emit" the bullets from the player. +The player then has no need to "know" what happens to the bullets after that - +whatever node is connected to the signal can "receive" the bullets and take the +appropriate action to spawn them. + + +Here is the code for the player using signals to emit the bullet: + +.. tabs:: + .. code-tab:: gdscript GDScript + + 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()) + + .. code-tab:: csharp + + public class Player : Sprite + { + [Signal] + delegate void Shoot(PackedScene bullet, Vector2 direction, Vector2 location); + + var Bullet = (PackedScene) ResourceLoader.Load("res://Bullet.tscn"); + + public override void _Input(InputEvent event) + { + if (input is InputEventMouseButton && Input.IsMouseButtonPressed((int) ButtonList.Left)) + { + EmitSignal(nameof(Shoot), Bullet, Rotation, Position); + } + } + + public override _Process(float delta) + { + LookAt(GetGlobalMousePosition()); + } + } + + +In the main scene, we then connect the player's signal (it will appear in the +"Node" tab). + +.. tabs:: + .. code-tab:: gdscript GDScript + + 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) + + .. code-tab:: csharp + + public void _on_Player_Shoot(PackedScene Bullet, Vector2 direction, Vector2 location) + { + var bulletInstance = (Area2D) Bullet.Instance(); + AddChild(bulletInstance); + bulletInstance.Rotation = direction; + bulletInstance.Position = location; + bulletInstance.Velocity = bulletInstance.Velocity.Rotated(direction); + } + +Now the bullets will maintain their own movement independent of the player's +rotation: + +.. image:: img/signals_shoot2.gif + +Conclusion +---------- + +Many of Godot's built-in node types provide signals you can use to detect +events. For example, an :ref:`Area2D ` representing a coin emits +a ``body_entered`` signal whenever the player's physics body enters its collision +shape, allowing you to know when the player collected it. + +In the next section, :ref:`doc_your_first_game`, you'll build a complete game +including several uses of signals to connect different game components. diff --git a/getting_started/step_by_step/your_first_game.rst b/getting_started/step_by_step/your_first_game.rst index c183f81c3..fbb27bb84 100644 --- a/getting_started/step_by_step/your_first_game.rst +++ b/getting_started/step_by_step/your_first_game.rst @@ -351,24 +351,24 @@ Let's place this code at the end of our ``_process()`` function: } .. Note:: The boolean assignments in the code above are a common shorthand - for programmers. Consider this code versus the shortened + for programmers. Consider this code versus the shortened boolean assignment above: - + .. tabs:: .. code-tab :: gdscript GDScript - + if velocity.x < 0: $AnimatedSprite.flip_h = true else: $AnimatedSprite.flip_h = false - + .. code-tab:: csharp - + if velocity.x < 0: animatedSprite.FlipH = true else: animatedSprite.FlipH = false - + Play the scene again and check that the animations are correct in each of the directions. When you're sure the movement is working correctly, add this line to ``_ready()`` so the player will be hidden when the game