Просмотр исходного кода

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

* Added C# examples & information for the your first game example
Lamonte 7 лет назад
Родитель
Сommit
753e45479d
1 измененных файлов с 353 добавлено и 25 удалено
  1. 353 25
      getting_started/step_by_step/your_first_game.rst

+ 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
 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
 
 .. 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:
 
-::
+.. tabs::
+ .. code-tab:: gdscript GDScript
 
     extends Area2D
 
     export (int) var SPEED  # how fast the player will move (pixels/sec)
     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
 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
 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
 
 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:
 
-::
+.. tabs::
+ .. code-tab:: gdscript GDScript
 
     func _ready():
         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.
 ``_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:
@@ -188,7 +214,8 @@ You can detect whether a key is pressed using
 ``Input.is_action_pressed()``, which returns ``true`` if it is pressed
 or ``false`` if it isn't.
 
-::
+.. tabs::
+ .. code-tab:: gdscript GDScript
 
     func _process(delta):
         var velocity = Vector2() # the player's movement vector
@@ -206,6 +233,36 @@ or ``false`` if it isn't.
         else:
             $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
 total direction. For example, if you hold ``right`` and ``down`` at
 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()``.
 
 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.x = clamp(position.x, 0, screensize.x)
         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.
 
@@ -258,7 +325,8 @@ property for left movement, and an "up" animation, which should be
 flipped vertically with ``flip_v`` for downward movement.
 Let's place this code at the end of our ``_process()`` function:
 
-::
+.. tabs::
+ .. code-tab:: gdscript GDScript
 
         if velocity.x != 0:
             $AnimatedSprite.animation = "right"
@@ -267,16 +335,32 @@ Let's place this code at the end of our ``_process()`` function:
         elif velocity.y != 0:
             $AnimatedSprite.animation = "up"
             $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
 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
 starts:
 
-::
+.. tabs::
+ .. code-tab:: gdscript GDScript
     
     hide()
 
+ .. code-tab:: csharp
+
+    Hide();
+
 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``:
 
-::
+.. tabs::
+ .. code-tab:: gdscript GDScript
 
     signal hit
 
+ .. code-tab:: csharp
+
+    [Signal] 
+    public delegate void Hit();
+
 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
 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:
 
-::
+.. tabs::
+ .. code-tab:: gdscript GDScript
 
     func _on_Player_body_entered( body ):
         hide() # Player disappears after being hit
         emit_signal("hit")
         $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
           it won't detect collisions. By turning it off, we make
           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 player when starting a new game.
 
-::
+.. tabs::
+ .. code-tab:: gdscript GDScript
 
     func start(pos):
         position = pos
         show()
         $CollisionShape2D.disabled = false
 
+ .. code-tab:: csharp
+
+    public void Start(Vector2 pos)
+    {
+        Position = pos;
+        Show();
+        
+        var collisionShape2D = (CollisionShape2D) GetNode("CollisionShape2D");
+        collisionShape2D.Disabled = false;
+    }
+
 Enemy Scene
 -----------
 
@@ -388,7 +504,8 @@ Enemy Script
 
 Add a script to the ``Mob`` and add the following member variables:
 
-::
+.. tabs::
+ .. code-tab:: gdscript GDScript
 
     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
     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
 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
@@ -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 
 choose one of the three animation types:
 
-::
+.. tabs::
+ .. code-tab:: gdscript GDScript
 
     func _ready():
         $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
           your sequence of "random" numbers to be different every time you run
           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``
 node and add this code:
 
-::
+.. tabs::
+ .. code-tab:: gdscript GDScript
 
     func _on_Visibility_screen_exited():
         queue_free()
 
+ .. code-tab:: csharp
+
+    public void onVisibilityScreenExited()
+    {
+        QueueFree();
+    }
+
 This completes the `Mob` 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
 instance.
 
-::
+.. tabs::
+ .. code-tab:: gdscript GDScript
 
     extends Node
 
@@ -502,6 +657,30 @@ instance.
     func _ready():
         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
 ``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``
 function to set everything up for a new game:
 
-::
+.. tabs::
+ .. code-tab:: gdscript GDScript
 
     func game_over():
         $ScoreTimer.stop()
@@ -522,12 +702,36 @@ function to set everything up for a new game:
         $Player.start($StartPosition.position)
         $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.
 ``StartTimer`` will start the other two timers. ``ScoreTimer`` will
 increment the score by 1.
 
-::
+.. tabs::
+ .. code-tab:: gdscript GDScript
 
     func _on_StartTimer_timeout():
         $MobTimer.start()
@@ -536,6 +740,23 @@ increment the score by 1.
     func _on_ScoreTimer_timeout():
         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
 random starting location along the ``Path2D``, and set the mob in
 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
 ``add_child()``.
 
-::
+.. tabs::
+ .. code-tab:: gdscript GDScript
 
     func _on_MobTimer_timeout():
         # 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
         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*,
                not degrees. If you're more comfortable working with
                degrees, you'll need to use the ``deg2rad()`` and
@@ -669,27 +912,50 @@ the three ``Control`` nodes:
 
 Now add this script to ``HUD``:
 
-::
+.. tabs::
+ .. code-tab:: gdscript GDScript
 
     extends CanvasLayer
 
     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
 has been pressed.
 
-::
+.. tabs::
+ .. code-tab:: gdscript GDScript
 
     func show_message(text):
         $MessageLabel.text = text
         $MessageLabel.show()
         $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
 temporarily, such as "Get Ready". On the ``MessageTimer``, set the
 ``Wait Time`` to ``2`` and set the ``One Shot`` property to "On".
 
-::
+.. tabs::
+ .. code-tab:: gdscript GDScript
 
     func show_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.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
 Over" for 2 seconds, then return to the title screen and show the
 "Start" button.
 
-::
+.. tabs::
+ .. code-tab:: gdscript GDScript
 
     func update_score(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.
 
 Connect the ``timeout()`` signal of ``MessageTimer`` and the
 ``pressed()`` signal of ``StartButton``.
 
-::
+.. tabs::
+ .. code-tab:: gdscript GDScript
 
     func _on_StartButton_pressed():
         $StartButton.hide()
@@ -721,6 +1014,22 @@ Connect the ``timeout()`` signal of ``MessageTimer`` and the
     func _on_MessageTimer_timeout():
         $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
 ~~~~~~~~~~~~~~~~~~~~~~
 
@@ -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"
 message:
 
-::
+.. tabs::
+ .. code-tab:: gdscript GDScript
 
         $HUD.update_score(score)
         $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:
 
-::
+.. tabs::
+ .. code-tab:: gdscript GDScript
 
         $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
 sync with the changing score:
 
-::
+.. tabs::
+ .. code-tab:: gdscript GDScript
 
         $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
 be asked to select a main scene, so choose ``Main.tscn``.