Browse Source

Added C# examples & info for the Your First Game example (#1297)

* Added C# examples & information for the your first game example
Lamonte 7 năm trước cách đây
mục cha
commit
753e45479d

+ 353 - 25
getting_started/step_by_step/your_first_game.rst

@@ -140,6 +140,9 @@ node, so we'll add a script. Click the ``Player`` node and click the
 In the script settings window, you can leave the default settings alone. Just
 In the script settings window, you can leave the default settings alone. Just
 click "Create":
 click "Create":
 
 
+.. note:: If you're creating a C# script or other languages, select the 
+            language from the `language` drop down menu before hitting create.
+
 .. image:: img/attach_node_window.png
 .. image:: img/attach_node_window.png
 
 
 .. note:: If this is your first time encountering GDScript, please read
 .. note:: If this is your first time encountering GDScript, please read
@@ -147,28 +150,51 @@ click "Create":
 
 
 Start by declaring the member variables this object will need:
 Start by declaring the member variables this object will need:
 
 
-::
+.. tabs::
+ .. code-tab:: gdscript GDScript
 
 
     extends Area2D
     extends Area2D
 
 
     export (int) var SPEED  # how fast the player will move (pixels/sec)
     export (int) var SPEED  # how fast the player will move (pixels/sec)
     var screensize  # size of the game window
     var screensize  # size of the game window
+ 
+ .. code-tab:: csharp
+
+    public class Player : Area2D
+    {
+        [Export] 
+        public int Speed = 0;
+        
+        private Vector2 _screenSize;
+    }
+
 
 
 Using the ``export`` keyword on the first variable ``SPEED`` allows us to
 Using the ``export`` keyword on the first variable ``SPEED`` allows us to
 set its value in the Inspector. This can be very handy for values that you
 set its value in the Inspector. This can be very handy for values that you
 want to be able to adjust just like a node's built-in properties. Click on
 want to be able to adjust just like a node's built-in properties. Click on
 the ``Player`` node and set the speed property to ``400``.
 the ``Player`` node and set the speed property to ``400``.
 
 
+.. warning:: If you're using C#, you need to restart godot editor temporarily to see
+            exported variables in the editor until it's fixed.
+
 .. image:: img/export_variable.png
 .. image:: img/export_variable.png
 
 
 The ``_ready()`` function is called when a node enters the scene tree, 
 The ``_ready()`` function is called when a node enters the scene tree, 
 which is a good time to find the size of the game window:
 which is a good time to find the size of the game window:
 
 
-::
+.. tabs::
+ .. code-tab:: gdscript GDScript
 
 
     func _ready():
     func _ready():
         screensize = get_viewport_rect().size
         screensize = get_viewport_rect().size
 
 
+ .. code-tab:: csharp
+
+    public override void _Ready()
+    {
+        _screenSize = GetViewport().GetSize();
+    }   
+
 Now we can use the ``_process()`` function to define what the player will do.
 Now we can use the ``_process()`` function to define what the player will do.
 ``_process()`` is called every frame, so we'll use it to update
 ``_process()`` is called every frame, so we'll use it to update
 elements of our game which we expect will change often. Here we'll make it:
 elements of our game which we expect will change often. Here we'll make it:
@@ -188,7 +214,8 @@ You can detect whether a key is pressed using
 ``Input.is_action_pressed()``, which returns ``true`` if it is pressed
 ``Input.is_action_pressed()``, which returns ``true`` if it is pressed
 or ``false`` if it isn't.
 or ``false`` if it isn't.
 
 
-::
+.. tabs::
+ .. code-tab:: gdscript GDScript
 
 
     func _process(delta):
     func _process(delta):
         var velocity = Vector2() # the player's movement vector
         var velocity = Vector2() # the player's movement vector
@@ -206,6 +233,36 @@ or ``false`` if it isn't.
         else:
         else:
             $AnimatedSprite.stop()
             $AnimatedSprite.stop()
 
 
+ .. code-tab:: csharp
+
+    public override void _Process(float delta)
+    {
+        var velocity = new Vector2();
+        if (Input.IsActionPressed("ui_right")) {
+            velocity.x += 1;
+        }
+    
+        if (Input.IsActionPressed("ui_left")) {
+            velocity.x -= 1;
+        }
+    
+        if (Input.IsActionPressed("ui_down")) {
+            velocity.y += 1;
+        }
+    
+        if (Input.IsActionPressed("ui_up")) {
+            velocity.y -= 1;
+        }
+    
+        var animatedSprite = (AnimatedSprite) GetNode("AnimatedSprite");
+        if (velocity.Length() > 0) {
+            velocity = velocity.Normalized() * Speed;
+            animatedSprite.Play();
+        } else {
+            animatedSprite.Stop();
+        }
+    }   
+
 We check each input and add/subtract from the ``velocity`` to obtain a
 We check each input and add/subtract from the ``velocity`` to obtain a
 total direction. For example, if you hold ``right`` and ``down`` at
 total direction. For example, if you hold ``right`` and ``down`` at
 the same time, the resulting ``velocity`` vector will be ``(1, 1)``. In
 the same time, the resulting ``velocity`` vector will be ``(1, 1)``. In
@@ -230,14 +287,24 @@ AnimatedSprite animation.
          So in the code above, ``$AnimatedSprite.play()`` is the same as ``get_node("AnimatedSprite").play()``.
          So in the code above, ``$AnimatedSprite.play()`` is the same as ``get_node("AnimatedSprite").play()``.
 
 
 Now that we have a movement direction, we can update ``Player``'s position
 Now that we have a movement direction, we can update ``Player``'s position
-and use ``clamp()`` to prevent it from leaving the screen:
+and use ``clamp()`` to prevent it from leaving the screen by adding the following
+to the bottom of the ``_process`` function:
 
 
-::
+.. tabs::
+ .. code-tab:: gdscript GDScript
 
 
         position += velocity * delta
         position += velocity * delta
         position.x = clamp(position.x, 0, screensize.x)
         position.x = clamp(position.x, 0, screensize.x)
         position.y = clamp(position.y, 0, screensize.y)
         position.y = clamp(position.y, 0, screensize.y)
 
 
+ .. code-tab:: csharp
+
+        Position += velocity * delta;
+        Position = new Vector2(
+            Mathf.Clamp(Position.x, 0, _screenSize.x),
+            Mathf.Clamp(Position.y, 0, _screenSize.y)
+        );
+
 
 
 .. tip:: *Clamping* a value means restricting it to a given range.
 .. tip:: *Clamping* a value means restricting it to a given range.
 
 
@@ -258,7 +325,8 @@ property for left movement, and an "up" animation, which should be
 flipped vertically with ``flip_v`` for downward movement.
 flipped vertically with ``flip_v`` for downward movement.
 Let's place this code at the end of our ``_process()`` function:
 Let's place this code at the end of our ``_process()`` function:
 
 
-::
+.. tabs::
+ .. code-tab:: gdscript GDScript
 
 
         if velocity.x != 0:
         if velocity.x != 0:
             $AnimatedSprite.animation = "right"
             $AnimatedSprite.animation = "right"
@@ -267,16 +335,32 @@ Let's place this code at the end of our ``_process()`` function:
         elif velocity.y != 0:
         elif velocity.y != 0:
             $AnimatedSprite.animation = "up"
             $AnimatedSprite.animation = "up"
             $AnimatedSprite.flip_v = velocity.y > 0
             $AnimatedSprite.flip_v = velocity.y > 0
+ 
+ .. code-tab:: csharp
+
+        if (velocity.x != 0) {
+            animatedSprite.Animation = "right";
+            animatedSprite.FlipH = velocity.x < 0;
+            animatedSprite.FlipV = false;
+        } else if(velocity.y != 0) {
+            animatedSprite.Animation = "up";
+            animatedSprite.FlipV = velocity.y > 0;
+        }
 
 
 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
 starts:
 starts:
 
 
-::
+.. tabs::
+ .. code-tab:: gdscript GDScript
     
     
     hide()
     hide()
 
 
+ .. code-tab:: csharp
+
+    Hide();
+
 Preparing for Collisions
 Preparing for Collisions
 ~~~~~~~~~~~~~~~~~~~~~~~~
 ~~~~~~~~~~~~~~~~~~~~~~~~
 
 
@@ -286,10 +370,16 @@ made any enemies yet! That's OK, because we're going to use Godot's
 
 
 Add the following at the top of the script, after ``extends Area2d``:
 Add the following at the top of the script, after ``extends Area2d``:
 
 
-::
+.. tabs::
+ .. code-tab:: gdscript GDScript
 
 
     signal hit
     signal hit
 
 
+ .. code-tab:: csharp
+
+    [Signal] 
+    public delegate void Hit();
+
 This defines a custom signal called "hit" that we will have our player
 This defines a custom signal called "hit" that we will have our player
 emit (send out) when it collides with an enemy. We will use ``Area2D`` to
 emit (send out) when it collides with an enemy. We will use ``Area2D`` to
 detect the collision. Select the ``Player`` node and click the "Node" tab
 detect the collision. Select the ``Player`` node and click the "Node" tab
@@ -311,13 +401,27 @@ settings - Godot will automatically create a function called
 
 
 Add this code to the function:
 Add this code to the function:
 
 
-::
+.. tabs::
+ .. code-tab:: gdscript GDScript
 
 
     func _on_Player_body_entered( body ):
     func _on_Player_body_entered( body ):
         hide() # Player disappears after being hit
         hide() # Player disappears after being hit
         emit_signal("hit")
         emit_signal("hit")
         $CollisionShape2D.disabled = true
         $CollisionShape2D.disabled = true
 
 
+ .. code-tab:: csharp
+
+    public void OnPlayerBodyEntered(Godot.Object body)
+    {
+        Hide();
+        EmitSignal("Hit");
+        
+        // for the sake of this example, but it's better to create a class var
+        // then assign the variable inside _Ready()
+        var collisionShape2D = (CollisionShape2D) GetNode("CollisionShape2D");
+        collisionShape2D.Disabled = true;
+    }
+
 .. Note:: Disabling the area's collision shape means
 .. Note:: Disabling the area's collision shape means
           it won't detect collisions. By turning it off, we make
           it won't detect collisions. By turning it off, we make
           sure we don't trigger the ``hit`` signal more than once.
           sure we don't trigger the ``hit`` signal more than once.
@@ -326,13 +430,25 @@ Add this code to the function:
 The last piece for our player is to add a function we can call to reset
 The last piece for our player is to add a function we can call to reset
 the player when starting a new game.
 the player when starting a new game.
 
 
-::
+.. tabs::
+ .. code-tab:: gdscript GDScript
 
 
     func start(pos):
     func start(pos):
         position = pos
         position = pos
         show()
         show()
         $CollisionShape2D.disabled = false
         $CollisionShape2D.disabled = false
 
 
+ .. code-tab:: csharp
+
+    public void Start(Vector2 pos)
+    {
+        Position = pos;
+        Show();
+        
+        var collisionShape2D = (CollisionShape2D) GetNode("CollisionShape2D");
+        collisionShape2D.Disabled = false;
+    }
+
 Enemy Scene
 Enemy Scene
 -----------
 -----------
 
 
@@ -388,7 +504,8 @@ Enemy Script
 
 
 Add a script to the ``Mob`` and add the following member variables:
 Add a script to the ``Mob`` and add the following member variables:
 
 
-::
+.. tabs::
+ .. code-tab:: gdscript GDScript
 
 
     extends RigidBody2D
     extends RigidBody2D
 
 
@@ -396,6 +513,19 @@ Add a script to the ``Mob`` and add the following member variables:
     export (int) var MAX_SPEED # maximum speed range
     export (int) var MAX_SPEED # maximum speed range
     var mob_types = ["walk", "swim", "fly"]
     var mob_types = ["walk", "swim", "fly"]
 
 
+ .. code-tab:: csharp
+
+    public class Mob : RigidBody2D
+    {
+        [Export] 
+        public int MinSpeed = 150;
+
+        [Export] 
+        public int MaxSpeed = 250;
+
+        private String[] _mobTypes = {"walk", "swim", "fly"};
+    }
+
 We'll pick a random value between ``MIN_SPEED`` and ``MAX_SPEED`` for
 We'll pick a random value between ``MIN_SPEED`` and ``MAX_SPEED`` for
 how fast each mob will move (it would be boring if they were all moving
 how fast each mob will move (it would be boring if they were all moving
 at the same speed). Set them to ``150`` and ``250`` in the Inspector. We
 at the same speed). Set them to ``150`` and ``250`` in the Inspector. We
@@ -405,11 +535,27 @@ we'll use to select a random one.
 Now let's look at the rest of the script. In ``_ready()`` we randomly 
 Now let's look at the rest of the script. In ``_ready()`` we randomly 
 choose one of the three animation types:
 choose one of the three animation types:
 
 
-::
+.. tabs::
+ .. code-tab:: gdscript GDScript
 
 
     func _ready():
     func _ready():
         $AnimatedSprite.animation = mob_types[randi() % mob_types.size()]
         $AnimatedSprite.animation = mob_types[randi() % mob_types.size()]
 
 
+ .. code-tab:: csharp
+
+    public override void _Ready()
+    {
+        var animatedSprite = (AnimatedSprite) GetNode("AnimatedSprite");
+    
+        // C# doesn't implement gdscript's random methods, so we use Random
+        // as an alternative.
+        //
+        // Note: Never define random multiple times in real projects. Create a
+        // class memory and reuse it to get true random numbers.
+        var randomMob = new Random();
+        animatedSprite.Animation = _mobTypes[randomMob.Next(0, _mobTypes.Length)];
+    }
+
 .. note:: You must use ``randomize()`` if you want
 .. note:: You must use ``randomize()`` if you want
           your sequence of "random" numbers to be different every time you run
           your sequence of "random" numbers to be different every time you run
           the scene. We're going to use ``randomize()`` in our ``Main`` scene,
           the scene. We're going to use ``randomize()`` in our ``Main`` scene,
@@ -420,11 +566,19 @@ The last piece is to make the mobs delete themselves when they leave the
 screen. Connect the ``screen_exited()`` signal of the ``Visibility``
 screen. Connect the ``screen_exited()`` signal of the ``Visibility``
 node and add this code:
 node and add this code:
 
 
-::
+.. tabs::
+ .. code-tab:: gdscript GDScript
 
 
     func _on_Visibility_screen_exited():
     func _on_Visibility_screen_exited():
         queue_free()
         queue_free()
 
 
+ .. code-tab:: csharp
+
+    public void onVisibilityScreenExited()
+    {
+        QueueFree();
+    }
+
 This completes the `Mob` scene.
 This completes the `Mob` scene.
 
 
 Main Scene
 Main Scene
@@ -492,7 +646,8 @@ Add a script to ``Main``. At the top of the script we use
 ``export (PackedScene)`` to allow us to choose the Mob scene we want to
 ``export (PackedScene)`` to allow us to choose the Mob scene we want to
 instance.
 instance.
 
 
-::
+.. tabs::
+ .. code-tab:: gdscript GDScript
 
 
     extends Node
     extends Node
 
 
@@ -502,6 +657,30 @@ instance.
     func _ready():
     func _ready():
         randomize()
         randomize()
 
 
+ .. code-tab:: csharp
+
+    public class Main : Node
+    {
+        [Export] 
+        public PackedScene Mob;
+
+        public int Score = 0;
+
+        // note: we're going to use this many times, so instantiating it
+        // allows our numbers to consistently be random
+        private Random rand = new Random();
+
+        public override void _Ready()
+        {
+        }
+
+        // we'll use this later because c# doesn't support gdscript's randi()
+        private float RandRand(float min, float max)
+        {
+            return (float) (rand.NextDouble() * (max - min) + min);
+        }
+    }
+
 Drag ``Mob.tscn`` from the "FileSystem" panel and drop it in the
 Drag ``Mob.tscn`` from the "FileSystem" panel and drop it in the
 ``Mob`` property under the Script Variables of the ``Main`` node.
 ``Mob`` property under the Script Variables of the ``Main`` node.
 
 
@@ -511,7 +690,8 @@ game ends. Type "game_over" in the "Method In Node" box at the bottom of the
 "Connecting Signal" window. Add the following code, as well as a ``new_game``
 "Connecting Signal" window. Add the following code, as well as a ``new_game``
 function to set everything up for a new game:
 function to set everything up for a new game:
 
 
-::
+.. tabs::
+ .. code-tab:: gdscript GDScript
 
 
     func game_over():
     func game_over():
         $ScoreTimer.stop()
         $ScoreTimer.stop()
@@ -522,12 +702,36 @@ function to set everything up for a new game:
         $Player.start($StartPosition.position)
         $Player.start($StartPosition.position)
         $StartTimer.start()
         $StartTimer.start()
 
 
+ .. code-tab:: csharp
+
+    public void GameOver()
+    {
+        //timers
+        var mobTimer = (Timer) GetNode("MobTimer");
+        var scoreTimer = (Timer) GetNode("ScoreTimer");
+    
+        scoreTimer.Stop();
+        mobTimer.Stop();
+    }
+
+    public void NewGame()
+    {
+        Score = 0;
+    
+        var player = (Player) GetNode("Player");
+        var startTimer = (Timer) GetNode("StartTimer");
+        var startPosition = (Position2D) GetNode("StartPosition");
+        
+        player.Start(startPosition.Position);
+        startTimer.Start();
+    }
 
 
 Now connect the ``timeout()`` signal of each of the Timer nodes.
 Now connect the ``timeout()`` signal of each of the Timer nodes.
 ``StartTimer`` will start the other two timers. ``ScoreTimer`` will
 ``StartTimer`` will start the other two timers. ``ScoreTimer`` will
 increment the score by 1.
 increment the score by 1.
 
 
-::
+.. tabs::
+ .. code-tab:: gdscript GDScript
 
 
     func _on_StartTimer_timeout():
     func _on_StartTimer_timeout():
         $MobTimer.start()
         $MobTimer.start()
@@ -536,6 +740,23 @@ increment the score by 1.
     func _on_ScoreTimer_timeout():
     func _on_ScoreTimer_timeout():
         score += 1
         score += 1
 
 
+ .. code-tab:: csharp
+
+    public void OnStartTimerTimeout()
+    {
+        //timers
+        var mobTimer = (Timer) GetNode("MobTimer");
+        var scoreTimer = (Timer) GetNode("ScoreTimer");
+    
+        mobTimer.Start();
+        scoreTimer.Start();
+    }
+
+    public void OnScoreTimerTimeout()
+    {
+        Score += 1;
+    }
+
 In ``_on_MobTimer_timeout()`` we will create a mob instance, pick a
 In ``_on_MobTimer_timeout()`` we will create a mob instance, pick a
 random starting location along the ``Path2D``, and set the mob in
 random starting location along the ``Path2D``, and set the mob in
 motion. The ``PathFollow2D`` node will automatically rotate as it
 motion. The ``PathFollow2D`` node will automatically rotate as it
@@ -545,7 +766,8 @@ well as its position.
 Note that a new instance must be added to the scene using
 Note that a new instance must be added to the scene using
 ``add_child()``.
 ``add_child()``.
 
 
-::
+.. tabs::
+ .. code-tab:: gdscript GDScript
 
 
     func _on_MobTimer_timeout():
     func _on_MobTimer_timeout():
         # choose a random location on Path2D
         # choose a random location on Path2D
@@ -563,6 +785,27 @@ Note that a new instance must be added to the scene using
         # choose the velocity
         # choose the velocity
         mob.set_linear_velocity(Vector2(rand_range(mob.MIN_SPEED, mob.MAX_SPEED), 0).rotated(direction))
         mob.set_linear_velocity(Vector2(rand_range(mob.MIN_SPEED, mob.MAX_SPEED), 0).rotated(direction))
 
 
+ .. code-tab:: csharp
+
+    public void OnMobTimerTimeout()
+    {
+        //choose random location on path2d
+        var mobSpawnLocation = (PathFollow2D) GetNode("MobPath/MobSpawnLocation");
+        mobSpawnLocation.SetOffset(rand.Next());
+    
+        //set direction
+        var direction = mobSpawnLocation.Rotation + Mathf.PI/2;
+        direction += RandRand(-Mathf.PI/4, Mathf.PI/4);
+    
+        //create mob instance and add it to scene
+        var mobInstance = (RigidBody2D) Mob.Instance();
+        mobInstance.Position = mobSpawnLocation.Position;
+        mobInstance.Rotation = direction;
+        mobInstance.SetLinearVelocity(new Vector2(RandRand(150f, 250f), 0).Rotated(direction));
+    
+        AddChild(mobInstance);
+    }
+
 .. important:: In functions requiring angles, GDScript uses *radians*,
 .. important:: In functions requiring angles, GDScript uses *radians*,
                not degrees. If you're more comfortable working with
                not degrees. If you're more comfortable working with
                degrees, you'll need to use the ``deg2rad()`` and
                degrees, you'll need to use the ``deg2rad()`` and
@@ -669,27 +912,50 @@ the three ``Control`` nodes:
 
 
 Now add this script to ``HUD``:
 Now add this script to ``HUD``:
 
 
-::
+.. tabs::
+ .. code-tab:: gdscript GDScript
 
 
     extends CanvasLayer
     extends CanvasLayer
 
 
     signal start_game
     signal start_game
 
 
+ .. code-tab:: csharp
+
+    public class HUD : CanvasLayer
+    {
+        [Signal] 
+        public delegate void StartGame();
+    }
+
 The ``start_game`` signal tells the ``Main`` node that the button
 The ``start_game`` signal tells the ``Main`` node that the button
 has been pressed.
 has been pressed.
 
 
-::
+.. tabs::
+ .. code-tab:: gdscript GDScript
 
 
     func show_message(text):
     func show_message(text):
         $MessageLabel.text = text
         $MessageLabel.text = text
         $MessageLabel.show()
         $MessageLabel.show()
         $MessageTimer.start()
         $MessageTimer.start()
 
 
+ .. code-tab:: csharp
+
+    public void ShowMessage(string text)
+    {
+        var messageTimer = (Timer) GetNode("MessageTimer");
+        var messageLabel = (Label) GetNode("MessageLabel");
+    
+        messageLabel.Text = text;
+        messageLabel.Show();
+        messageTimer.Start();
+    }
+
 This function is called when we want to display a message
 This function is called when we want to display a message
 temporarily, such as "Get Ready". On the ``MessageTimer``, set the
 temporarily, such as "Get Ready". On the ``MessageTimer``, set the
 ``Wait Time`` to ``2`` and set the ``One Shot`` property to "On".
 ``Wait Time`` to ``2`` and set the ``One Shot`` property to "On".
 
 
-::
+.. tabs::
+ .. code-tab:: gdscript GDScript
 
 
     func show_game_over():
     func show_game_over():
         show_message("Game Over")
         show_message("Game Over")
@@ -698,21 +964,48 @@ temporarily, such as "Get Ready". On the ``MessageTimer``, set the
         $MessageLabel.text = "Dodge the\nCreeps!"
         $MessageLabel.text = "Dodge the\nCreeps!"
         $MessageLabel.show()
         $MessageLabel.show()
 
 
+ .. code-tab:: csharp
+
+    async public void ShowGameOver()
+    {
+        ShowMessage("Game Over");
+    
+        var startButton = (Button) GetNode("StartButton");
+        var messageTimer = (Timer) GetNode("MessageTimer");
+        var messageLabel = (Label) GetNode("MessageLabel");
+
+        //work around for gdscript's yield
+        await Task.Delay((int) messageTimer.WaitTime * 1000);
+        messageLabel.Text = "Dodge the\nCreeps!";
+        messageLabel.Show();
+        startButton.Show();
+    }
+
 This function is called when the player loses. It will show "Game
 This function is called when the player loses. It will show "Game
 Over" for 2 seconds, then return to the title screen and show the
 Over" for 2 seconds, then return to the title screen and show the
 "Start" button.
 "Start" button.
 
 
-::
+.. tabs::
+ .. code-tab:: gdscript GDScript
 
 
     func update_score(score):
     func update_score(score):
         $ScoreLabel.text = str(score)
         $ScoreLabel.text = str(score)
 
 
+ .. code-tab:: csharp
+
+    public void UpdateScore(int score)
+    {
+        var scoreLabel = (Label) GetNode("ScoreLabel");
+        scoreLabel.Text = score.ToString();
+    }
+
 This function is called in ``Main`` whenever the score changes.
 This function is called in ``Main`` whenever the score changes.
 
 
 Connect the ``timeout()`` signal of ``MessageTimer`` and the
 Connect the ``timeout()`` signal of ``MessageTimer`` and the
 ``pressed()`` signal of ``StartButton``.
 ``pressed()`` signal of ``StartButton``.
 
 
-::
+.. tabs::
+ .. code-tab:: gdscript GDScript
 
 
     func _on_StartButton_pressed():
     func _on_StartButton_pressed():
         $StartButton.hide()
         $StartButton.hide()
@@ -721,6 +1014,22 @@ Connect the ``timeout()`` signal of ``MessageTimer`` and the
     func _on_MessageTimer_timeout():
     func _on_MessageTimer_timeout():
         $MessageLabel.hide()
         $MessageLabel.hide()
 
 
+ .. code-tab:: csharp
+
+    public void OnStartButtonPressed()
+    {
+        var startButton = (Button) GetNode("StartButton");
+        startButton.Hide();
+    
+        EmitSignal("StartGame");
+    }
+
+    public void OnMessageTimerTimeout()
+    {
+        var messageLabel = (Label) GetNode("MessageLabel");
+        messageLabel.Hide();
+    }
+
 Connecting HUD to Main
 Connecting HUD to Main
 ~~~~~~~~~~~~~~~~~~~~~~
 ~~~~~~~~~~~~~~~~~~~~~~
 
 
@@ -740,24 +1049,43 @@ In the Node tab, connect the HUD's ``start_game`` signal to the
 In ``new_game()``, update the score display and show the "Get Ready"
 In ``new_game()``, update the score display and show the "Get Ready"
 message:
 message:
 
 
-::
+.. tabs::
+ .. code-tab:: gdscript GDScript
 
 
         $HUD.update_score(score)
         $HUD.update_score(score)
         $HUD.show_message("Get Ready")
         $HUD.show_message("Get Ready")
 
 
+ .. code-tab:: csharp
+
+        var hud = (HUD) GetNode("HUD");
+        hud.UpdateScore(Score);
+        hud.ShowMessage("Get Ready!");
+
 In ``game_over()`` we need to call the corresponding ``HUD`` function:
 In ``game_over()`` we need to call the corresponding ``HUD`` function:
 
 
-::
+.. tabs::
+ .. code-tab:: gdscript GDScript
 
 
         $HUD.show_game_over()
         $HUD.show_game_over()
 
 
+ .. code-tab:: csharp
+
+        var hud = (HUD) GetNode("HUD");
+        hud.ShowGameOver();
+
 Finally, add this to ``_on_ScoreTimer_timeout()`` to keep the display in
 Finally, add this to ``_on_ScoreTimer_timeout()`` to keep the display in
 sync with the changing score:
 sync with the changing score:
 
 
-::
+.. tabs::
+ .. code-tab:: gdscript GDScript
 
 
         $HUD.update_score(score)
         $HUD.update_score(score)
 
 
+ .. code-tab:: csharp
+
+        var hud = (HUD) GetNode("HUD");
+        hud.UpdateScore(Score);
+
 Now you're ready to play! Click the "Play the Project" button. You will
 Now you're ready to play! Click the "Play the Project" button. You will
 be asked to select a main scene, so choose ``Main.tscn``.
 be asked to select a main scene, so choose ``Main.tscn``.