diff --git a/getting_started/step_by_step/your_first_game.rst b/getting_started/step_by_step/your_first_game.rst index 6e99a0dc0..38e24b537 100644 --- a/getting_started/step_by_step/your_first_game.rst +++ b/getting_started/step_by_step/your_first_game.rst @@ -176,17 +176,17 @@ Start by declaring the member variables this object will need: extends Area2D - export var speed = 400 # How fast the player will move (pixels/sec). - var screen_size # Size of the game window. + export var speed = 400 # How fast the player will move (pixels/sec). + var screen_size # Size of the game window. .. code-tab:: csharp public class Player : Area2D { [Export] - public int Speed = 400; // How fast the player will move (pixels/sec). + public int speed = 400; // How fast the player will move (pixels/sec). - private Vector2 _screenSize; // Size of the game window. + public Vector2 screenSize; // Size of the game window. } @@ -213,12 +213,14 @@ which is a good time to find the size of the game window: func _ready(): screen_size = get_viewport_rect().size + hide() # Start the player hidden. .. code-tab:: csharp public override void _Ready() { - _screenSize = GetViewport().Size; + screenSize = GetViewportRect().Size; + Hide(); // Start the player hidden. } Now we can use the ``_process()`` function to define what the player will do. @@ -233,9 +235,9 @@ need to do the following: First, we need to check for input - is the player pressing a key? For this game, we have 4 direction inputs to check. Input actions are defined in the Project Settings under "Input Map". Here, you can define custom events and -assign different keys, mouse events, or other inputs to them. For this demo, -we will use the default events that are assigned to the arrow keys on the -keyboard. +assign different keys, mouse events, or other inputs to them. +For this game, we will just use the default events called +"ui_right" etc that are assigned to the arrow keys on the keyboard. You can detect whether a key is pressed using ``Input.is_action_pressed()``, which returns ``true`` if it's pressed @@ -245,7 +247,7 @@ or ``false`` if it isn't. .. code-tab:: gdscript GDScript func _process(delta): - var velocity = Vector2() # The player's movement vector. + var velocity = Vector2.ZERO # The player's movement vector. if Input.is_action_pressed("ui_right"): velocity.x += 1 if Input.is_action_pressed("ui_left"): @@ -254,6 +256,7 @@ or ``false`` if it isn't. velocity.y += 1 if Input.is_action_pressed("ui_up"): velocity.y -= 1 + if velocity.length() > 0: velocity = velocity.normalized() * speed $AnimatedSprite.play() @@ -264,7 +267,7 @@ or ``false`` if it isn't. public override void _Process(float delta) { - var velocity = new Vector2(); // The player's movement vector. + var velocity = Vector2.Zero; // The player's movement vector. if (Input.IsActionPressed("ui_right")) { @@ -290,7 +293,7 @@ or ``false`` if it isn't. if (velocity.Length() > 0) { - velocity = velocity.Normalized() * Speed; + velocity = velocity.Normalized() * speed; animatedSprite.Play(); } else @@ -339,8 +342,8 @@ the ``_process`` function (make sure it's not indented under the `else`): Position += velocity * delta; Position = new Vector2( - x: Mathf.Clamp(Position.x, 0, _screenSize.x), - y: Mathf.Clamp(Position.y, 0, _screenSize.y) + x: Mathf.Clamp(Position.x, 0, screenSize.x), + y: Mathf.Clamp(Position.y, 0, screenSize.y) ); @@ -377,7 +380,7 @@ function: if velocity.x != 0: $AnimatedSprite.animation = "walk" $AnimatedSprite.flip_v = false - # See the note below about boolean assignment + # See the note below about boolean assignment. $AnimatedSprite.flip_h = velocity.x < 0 elif velocity.y != 0: $AnimatedSprite.animation = "up" @@ -389,7 +392,7 @@ function: { animatedSprite.Animation = "walk"; animatedSprite.FlipV = false; - // See the note below about boolean assignment + // See the note below about boolean assignment. animatedSprite.FlipH = velocity.x < 0; } else if (velocity.y != 0) @@ -486,8 +489,9 @@ this code to the function: .. code-tab:: gdscript GDScript func _on_Player_body_entered(body): - hide() # Player disappears after being hit. + hide() # Player disappears after being hit. emit_signal("hit") + # Must be deferred as we can't change physics properties on a physics callback. $CollisionShape2D.set_deferred("disabled", true) .. code-tab:: csharp @@ -495,7 +499,8 @@ this code to the function: public void OnPlayerBodyEntered(PhysicsBody2D body) { Hide(); // Player disappears after being hit. - EmitSignal("Hit"); + EmitSignal(nameof(Hit)); + // Must be deferred as we can't change physics properties on a physics callback. GetNode("CollisionShape2D").SetDeferred("disabled", true); } @@ -605,33 +610,31 @@ Add a script to the ``Mob`` and add the following member variables: [Export] public int MaxSpeed = 250; // Maximum speed range. - } When we spawn a mob, 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). -Now let's look at the rest of the script. In ``_ready()`` we randomly -choose one of the three animation types: +Now let's look at the rest of the script. In ``_ready()`` we play the +animation and randomly choose one of the three animation types: .. tabs:: .. code-tab:: gdscript GDScript func _ready(): + $AnimatedSprite.playing = true var mob_types = $AnimatedSprite.frames.get_animation_names() $AnimatedSprite.animation = mob_types[randi() % mob_types.size()] .. code-tab:: csharp - // C# doesn't implement GDScript's random methods, so we use 'System.Random' as an alternative. - static private Random _random = new Random(); - public override void _Ready() { var animSprite = GetNode("AnimatedSprite"); - var mobTypes = animSprite.Frames.GetAnimationNames(); - animSprite.Animation = mobTypes[_random.Next(0, mobTypes.Length)]; + animSprite.Playing = true; + string[] mobTypes = animSprite.Frames.GetAnimationNames(); + animSprite.Animation = mobTypes[GD.Randi() % mobTypes.Length]; } First, we get the list of animation names from the AnimatedSprite's ``frames`` @@ -740,7 +743,7 @@ instance. extends Node - export (PackedScene) var Mob + export(PackedScene) var mob_scene var score func _ready(): @@ -752,22 +755,17 @@ instance. { // Don't forget to rebuild the project so the editor knows about the new export variable. + #pragma warning disable 649 + // We assign this in the editor, so we don't need the warning about not being assigned. [Export] - public PackedScene Mob; + public PackedScene mobScene; + #pragma warning restore 649 - private int _score; - - // We use 'System.Random' as an alternative to GDScript's random methods. - private Random _random = new Random(); + public int score; public override void _Ready() { - } - - // We'll use this later because C# doesn't support GDScript's randi(). - private float RandRange(float min, float max) - { - return (float)_random.NextDouble() * (max - min) + min; + GD.Randomize(); } } @@ -816,7 +814,7 @@ for a new game: public void NewGame() { - _score = 0; + score = 0; var player = GetNode("Player"); var startPosition = GetNode("StartPosition"); @@ -849,7 +847,7 @@ the other two timers. ``ScoreTimer`` will increment the score by 1. public void OnScoreTimerTimeout() { - _score++; + score++; } In ``_on_MobTimer_timeout()``, we will create a mob instance, pick a @@ -865,49 +863,62 @@ Note that a new instance must be added to the scene using ``add_child()``. func _on_MobTimer_timeout(): # Choose a random location on Path2D. - $MobPath/MobSpawnLocation.offset = randi() + var mob_spawn_location = get_node("MobPath/MobSpawnLocation"); + mob_spawn_location.offset = randi() + # Create a Mob instance and add it to the scene. - var mob = Mob.instance() + var mob = mob_scene.instance() add_child(mob) + # Set the mob's direction perpendicular to the path direction. - var direction = $MobPath/MobSpawnLocation.rotation + PI / 2 + var direction = mob_spawn_location.rotation + PI / 2 + # Set the mob's position to a random location. - mob.position = $MobPath/MobSpawnLocation.position + mob.position = mob_spawn_location.position + # Add some randomness to the direction. direction += rand_range(-PI / 4, PI / 4) mob.rotation = direction - # Set the velocity (speed & direction). - mob.linear_velocity = Vector2(rand_range(mob.min_speed, mob.max_speed), 0) - mob.linear_velocity = mob.linear_velocity.rotated(direction) + + # Choose the velocity. + var velocity = Vector2(rand_range(mob.min_speed, mob.max_speed), 0) + mob.linear_velocity = velocity.rotated(direction) .. code-tab:: csharp public void OnMobTimerTimeout() { + // Note: Normally it is best to use explicit types rather than the `var` + // keyword. However, var is acceptable to use here because the types are + // obviously PathFollow2D and Mob, since they appear later on the line. + // Choose a random location on Path2D. var mobSpawnLocation = GetNode("MobPath/MobSpawnLocation"); - mobSpawnLocation.Offset = _random.Next(); + mobSpawnLocation.Offset = GD.Randi(); // Create a Mob instance and add it to the scene. - var mobInstance = (RigidBody2D)Mob.Instance(); - AddChild(mobInstance); + var mob = (Mob)mobScene.Instance(); + AddChild(mob); // Set the mob's direction perpendicular to the path direction. float direction = mobSpawnLocation.Rotation + Mathf.Pi / 2; // Set the mob's position to a random location. - mobInstance.Position = mobSpawnLocation.Position; + mob.Position = mobSpawnLocation.Position; // Add some randomness to the direction. - direction += RandRange(-Mathf.Pi / 4, Mathf.Pi / 4); - mobInstance.Rotation = direction; + direction += (float)GD.RandRange(-Mathf.Pi / 4, Mathf.Pi / 4); + mob.Rotation = direction; // Choose the velocity. - mobInstance.LinearVelocity = new Vector2(RandRange(150f, 250f), 0).Rotated(direction); + var velocity = new Vector2((float)GD.RandRange(mob.minSpeed, mob.maxSpeed), 0); + mob.LinearVelocity = velocity.Rotated(direction); } -.. important:: Why ``PI``? In functions requiring angles, GDScript uses *radians*, - not degrees. If you're more comfortable working with +.. important:: Why ``PI``? In functions requiring angles, Godot uses *radians*, + not degrees. Pi represents a half turn in radians, about + ``3.1415`` (there is also ``TAU`` which is equal to ``2 * PI``). + If you're more comfortable working with degrees, you'll need to use the ``deg2rad()`` and ``rad2deg()`` functions to convert between the two. @@ -925,10 +936,9 @@ Let's test the scene to make sure everything is working. Add this to ``_ready()` .. code-tab:: csharp - public override void _Ready() - { - NewGame(); - } + public override void _Ready() + { + NewGame(); } Let's also assign ``Main`` as our "Main Scene" - the one that runs automatically @@ -1187,7 +1197,7 @@ message: .. code-tab:: csharp var hud = GetNode("HUD"); - hud.UpdateScore(_score); + hud.UpdateScore(score); hud.ShowMessage("Get Ready!"); In ``game_over()`` we need to call the corresponding ``HUD`` function: @@ -1211,7 +1221,7 @@ sync with the changing score: .. code-tab:: csharp - GetNode("HUD").UpdateScore(_score); + GetNode("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``. @@ -1231,16 +1241,17 @@ click "Groups" and you can type a new group name and click "Add". .. image:: img/group_tab.png Now all mobs will be in the "mobs" group. We can then add the following line to -the ``game_over()`` function in ``Main``: +the ``new_game()`` function in ``Main``: .. tabs:: .. code-tab:: gdscript GDScript get_tree().call_group("mobs", "queue_free") - .. code-tab:: csharp + // Note that for calling Godot-provided methods with strings, + // we have to use the original Godot snake_case name. GetTree().CallGroup("mobs", "queue_free"); The ``call_group()`` function calls the named function on every node in a group -