Forráskód Böngészése

Add Signals tutorial to "Step by step"

Chris Bradfield 6 éve
szülő
commit
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**:
 **Bad**:
 
 
 ::
 ::
-    
+
     for i in range(10):
     for i in range(10):
       print("hello")
       print("hello")
-      
+
     for i in range(10):
     for i in range(10):
             print("hello")
             print("hello")
 
 
@@ -167,7 +167,7 @@ Use PascalCase: ``extends KinematicBody``
 Also when loading a class into a constant or variable:
 Also when loading a class into a constant or variable:
 
 
 ::
 ::
-    
+
     const MyCoolNode = preload('res://my_cool_node.gd')
     const MyCoolNode = preload('res://my_cool_node.gd')
 
 
 Functions and Variables
 Functions and Variables
@@ -185,7 +185,7 @@ Signals
 Use past tense:
 Use past tense:
 
 
 ::
 ::
-    
+
     signal door_opened
     signal door_opened
     signal score_changed
     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
    instancing_continued
    scripting
    scripting
    scripting_continued
    scripting_continued
+   signals
    your_first_game
    your_first_game
    exporting
    exporting
    godot_design_philosophy
    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
 .. 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:
           boolean assignment above:
-          
+
           .. tabs::
           .. tabs::
            .. code-tab :: gdscript GDScript
            .. code-tab :: gdscript GDScript
-           
+
              if velocity.x < 0:
              if velocity.x < 0:
                  $AnimatedSprite.flip_h = true
                  $AnimatedSprite.flip_h = true
              else:
              else:
                  $AnimatedSprite.flip_h = false
                  $AnimatedSprite.flip_h = false
-              
+
            .. code-tab:: csharp
            .. code-tab:: csharp
-           
+
              if velocity.x < 0:
              if velocity.x < 0:
                  animatedSprite.FlipH = true
                  animatedSprite.FlipH = true
              else:
              else:
                  animatedSprite.FlipH = false
                  animatedSprite.FlipH = false
-          
+
 Play the scene again and check that the animations are correct in each
 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,
 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
 add this line to ``_ready()`` so the player will be hidden when the game