Преглед изворни кода

Add Signals tutorial to "Step by step"

Chris Bradfield пре 6 година
родитељ
комит
85d4e8757a

+ 4 - 4
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
 

BIN
getting_started/step_by_step/img/signals_connect_dialog_timer.png


BIN
getting_started/step_by_step/img/signals_disconnect_timer.png


BIN
getting_started/step_by_step/img/signals_node_setup.png


BIN
getting_started/step_by_step/img/signals_node_tab_timer.png


BIN
getting_started/step_by_step/img/signals_shoot1.gif


BIN
getting_started/step_by_step/img/signals_shoot2.gif


+ 1 - 0
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

+ 329 - 0
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 <class_Timer>` node. Create
+a new scene with a Node and two children: a Timer and a :ref:`Sprite <class_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_<node_name>_<signal_name>``
+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 ``<source_node>.connect(<signal_name>, <target_node>, <target_function_name>)``.
+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 <class_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.

+ 6 - 6
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