mirror of
https://github.com/godotengine/godot-docs.git
synced 2026-01-05 22:09:56 +03:00
Update getting started C# code, remove randomize (#6811)
* Update getting started C# code, remove randomize - Sync C# code with GDScript. - Remove `randomize` calls. - Rename members that have been renamed in Godot's C# API for 4.0. - Change `delta` parameter type to `double` in C#. - Follow our C# code style more closely. - Other minor code fixes. --------- Co-authored-by: RedworkDE <10944644+RedworkDE@users.noreply.github.com>
This commit is contained in:
@@ -25,7 +25,7 @@ Launch Godot and create a new project.
|
||||
and ``fonts/`` directories to your project's directory.
|
||||
|
||||
Ensure that you have the required dependencies to use C# in Godot.
|
||||
You need the .NET Core 3.1 SDK, and an editor such as VS Code.
|
||||
You need the latest stable .NET SDK, and an editor such as VS Code.
|
||||
See :ref:`doc_c_sharp_setup`.
|
||||
|
||||
.. tab:: C++
|
||||
|
||||
@@ -89,11 +89,18 @@ node and you'll see the property now appears in the "Script Variables" section
|
||||
of the Inspector. Remember, if you change the value here, it will override the
|
||||
value written in the script.
|
||||
|
||||
.. warning:: If you're using C#, you need to (re)build the project assemblies
|
||||
whenever you want to see new export variables or signals. This
|
||||
build can be manually triggered by clicking the word "Mono" at the
|
||||
bottom of the editor window to reveal the Mono Panel, then clicking
|
||||
the "Build Project" button.
|
||||
.. warning::
|
||||
|
||||
If you're using C#, you need to (re)build the project assemblies
|
||||
whenever you want to see new export variables or signals. This
|
||||
build can be manually triggered by clicking the "Build" button at
|
||||
the top right of the editor.
|
||||
|
||||
.. image:: img/build_dotnet.webp
|
||||
|
||||
A manual build can also be triggered from the MSBuild Panel. Click
|
||||
the word "MSBuild" at the bottom of the editor window to reveal the
|
||||
MSBuild Panel, then click the "Build" button.
|
||||
|
||||
.. image:: img/export_variable.webp
|
||||
|
||||
@@ -390,7 +397,7 @@ movement. Let's place this code at the end of the ``_process()`` function:
|
||||
|
||||
.. code-tab:: csharp
|
||||
|
||||
if (velocity.x < 0)
|
||||
if (velocity.X < 0)
|
||||
{
|
||||
animatedSprite2D.FlipH = true;
|
||||
}
|
||||
@@ -490,12 +497,12 @@ this code to the function:
|
||||
|
||||
.. code-tab:: csharp
|
||||
|
||||
public void OnBodyEntered(PhysicsBody2D body)
|
||||
private void OnBodyEntered(PhysicsBody2D body)
|
||||
{
|
||||
Hide(); // Player disappears after being hit.
|
||||
EmitSignal(SignalName.Hit);
|
||||
// Must be deferred as we can't change physics properties on a physics callback.
|
||||
GetNode<CollisionShape2D>("CollisionShape2D").SetDeferred("disabled", true);
|
||||
GetNode<CollisionShape2D>("CollisionShape2D").SetDeferred(CollisionShape2D.PropertyName.Disabled, true);
|
||||
}
|
||||
|
||||
.. code-tab:: cpp
|
||||
@@ -530,9 +537,9 @@ starting a new game.
|
||||
|
||||
.. code-tab:: csharp
|
||||
|
||||
public void Start(Vector2 pos)
|
||||
public void Start(Vector2 position)
|
||||
{
|
||||
Position = pos;
|
||||
Position = position;
|
||||
Show();
|
||||
GetNode<CollisionShape2D>("CollisionShape2D").Disabled = false;
|
||||
}
|
||||
|
||||
@@ -108,18 +108,16 @@ and randomly choose one of the three animation types:
|
||||
.. code-tab:: gdscript GDScript
|
||||
|
||||
func _ready():
|
||||
$AnimatedSprite2D.play()
|
||||
var mob_types = $AnimatedSprite2D.get_sprite_frames().get_animation_names()
|
||||
$AnimatedSprite2D.animation = mob_types[randi() % mob_types.size()]
|
||||
var mob_types = $AnimatedSprite2D.sprite_frames.get_animation_names()
|
||||
$AnimatedSprite2D.play(mob_types[randi() % mob_types.size()])
|
||||
|
||||
.. code-tab:: csharp
|
||||
|
||||
public override void _Ready()
|
||||
{
|
||||
var animSprite2D = GetNode<AnimatedSprite2D>("AnimatedSprite2D");
|
||||
animSprite2D.Play();
|
||||
string[] mobTypes = animSprite2D.SpriteFrames.GetAnimationNames();
|
||||
animSprite2D.Animation = mobTypes[GD.Randi() % mobTypes.Length];
|
||||
var animatedSprite2D = GetNode<AnimatedSprite2D>("AnimatedSprite2D");
|
||||
string[] mobTypes = animatedSprite2D.SpriteFrames.GetAnimationNames();
|
||||
animatedSprite2D.Play(mobTypes[GD.Randi() % mobTypes.Length]);
|
||||
}
|
||||
|
||||
.. code-tab:: cpp
|
||||
@@ -132,7 +130,6 @@ and randomly choose one of the three animation types:
|
||||
|
||||
void Mob::_ready() {
|
||||
godot::Ref<godot::RandomNumberGenerator> random = godot::RandomNumberGenerator::_new();
|
||||
random->randomize();
|
||||
_animated_sprite = get_node<godot::AnimatedSprite2D>("AnimatedSprite2D");
|
||||
_animated_sprite->set_playing(true);
|
||||
godot::PoolStringArray mob_types = _animated_sprite->get_sprite_frames()->get_animation_names();
|
||||
@@ -147,10 +144,6 @@ We then need to pick a random number between ``0`` and ``2`` to select one of
|
||||
these names from the list (array indices start at ``0``). ``randi() % n``
|
||||
selects a random integer between ``0`` and ``n-1``.
|
||||
|
||||
.. 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, so we won't need it here.
|
||||
|
||||
The last piece is to make the mobs delete themselves when they leave the screen.
|
||||
Connect the ``screen_exited()`` signal of the ``VisibleOnScreenNotifier2D`` node and
|
||||
add this code:
|
||||
@@ -163,7 +156,7 @@ add this code:
|
||||
|
||||
.. code-tab:: csharp
|
||||
|
||||
public void OnVisibleOnScreenNotifier2DScreenExited()
|
||||
private void OnVisibleOnScreenNotifier2DScreenExited()
|
||||
{
|
||||
QueueFree();
|
||||
}
|
||||
|
||||
@@ -93,13 +93,10 @@ to 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 MobScene;
|
||||
#pragma warning restore 649
|
||||
public PackedScene MobScene { get; set; }
|
||||
|
||||
public int Score;
|
||||
private int _score;
|
||||
}
|
||||
|
||||
.. code-tab:: cpp
|
||||
@@ -219,7 +216,7 @@ new game:
|
||||
|
||||
public void NewGame()
|
||||
{
|
||||
Score = 0;
|
||||
_score = 0;
|
||||
|
||||
var player = GetNode<Player>("Player");
|
||||
var startPosition = GetNode<Marker2D>("StartPosition");
|
||||
@@ -258,12 +255,12 @@ the other two timers. ``ScoreTimer`` will increment the score by 1.
|
||||
|
||||
.. code-tab:: csharp
|
||||
|
||||
public void OnScoreTimerTimeout()
|
||||
private void OnScoreTimerTimeout()
|
||||
{
|
||||
Score++;
|
||||
_score++;
|
||||
}
|
||||
|
||||
public void OnStartTimerTimeout()
|
||||
private void OnStartTimerTimeout()
|
||||
{
|
||||
GetNode<Timer>("MobTimer").Start();
|
||||
GetNode<Timer>("ScoreTimer").Start();
|
||||
@@ -332,14 +329,14 @@ Note that a new instance must be added to the scene using ``add_child()``.
|
||||
|
||||
.. code-tab:: csharp
|
||||
|
||||
public void OnMobTimerTimeout()
|
||||
private 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 Mob and PathFollow2D, since they appear later on the line.
|
||||
|
||||
// Create a new instance of the Mob scene.
|
||||
var mob = MobScene.Instantiate<Mob>();
|
||||
Mob mob = MobScene.Instantiate<Mob>();
|
||||
|
||||
// Choose a random location on Path2D.
|
||||
var mobSpawnLocation = GetNode<PathFollow2D>("MobPath/MobSpawnLocation");
|
||||
|
||||
@@ -233,13 +233,13 @@ We also need to process what happens when the player loses. The code below will
|
||||
ShowMessage("Game Over");
|
||||
|
||||
var messageTimer = GetNode<Timer>("MessageTimer");
|
||||
await ToSignal(messageTimer, "timeout");
|
||||
await ToSignal(messageTimer, Timer.SignalName.Timeout);
|
||||
|
||||
var message = GetNode<Label>("Message");
|
||||
message.Text = "Dodge the\nCreeps!";
|
||||
message.Show();
|
||||
|
||||
await ToSignal(GetTree().CreateTimer(1), "timeout");
|
||||
await ToSignal(GetTree().CreateTimer(1.0), SceneTreeTimer.SignalName.Timeout);
|
||||
GetNode<Button>("StartButton").Show();
|
||||
}
|
||||
|
||||
@@ -306,13 +306,13 @@ signal of ``StartButton``, and add the following code to the new functions:
|
||||
|
||||
.. code-tab:: csharp
|
||||
|
||||
public void OnStartButtonPressed()
|
||||
private void OnStartButtonPressed()
|
||||
{
|
||||
GetNode<Button>("StartButton").Hide();
|
||||
EmitSignal(SignalName.StartGame);
|
||||
}
|
||||
|
||||
public void OnMessageTimerTimeout()
|
||||
private void OnMessageTimerTimeout()
|
||||
{
|
||||
GetNode<Label>("Message").Hide();
|
||||
}
|
||||
@@ -364,7 +364,7 @@ In ``new_game()``, update the score display and show the "Get Ready" message:
|
||||
.. code-tab:: csharp
|
||||
|
||||
var hud = GetNode<HUD>("HUD");
|
||||
hud.UpdateScore(Score);
|
||||
hud.UpdateScore(_score);
|
||||
hud.ShowMessage("Get Ready!");
|
||||
|
||||
.. code-tab:: cpp
|
||||
@@ -400,7 +400,7 @@ with the changing score:
|
||||
|
||||
.. code-tab:: csharp
|
||||
|
||||
GetNode<HUD>("HUD").UpdateScore(Score);
|
||||
GetNode<HUD>("HUD").UpdateScore(_score);
|
||||
|
||||
.. code-tab:: cpp
|
||||
|
||||
@@ -435,7 +435,7 @@ the ``new_game()`` function in ``Main``:
|
||||
|
||||
// Note that for calling Godot-provided methods with strings,
|
||||
// we have to use the original Godot snake_case name.
|
||||
GetTree().CallGroup("mobs", "queue_free");
|
||||
GetTree().CallGroup("mobs", Node.MethodName.QueueFree);
|
||||
|
||||
.. code-tab:: cpp
|
||||
|
||||
|
||||
BIN
getting_started/first_2d_game/img/build_dotnet.webp
Normal file
BIN
getting_started/first_2d_game/img/build_dotnet.webp
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 3.7 KiB |
@@ -38,12 +38,12 @@ character.
|
||||
|
||||
// How fast the player moves in meters per second.
|
||||
[Export]
|
||||
public int Speed = 14;
|
||||
public int Speed { get; set; } = 14;
|
||||
// The downward acceleration when in the air, in meters per second squared.
|
||||
[Export]
|
||||
public int FallAcceleration = 75;
|
||||
public int FallAcceleration { get; set; } = 75;
|
||||
|
||||
private Vector3 _velocity = Vector3.Zero;
|
||||
private Vector3 _targetVelocity = Vector3.Zero;
|
||||
}
|
||||
|
||||
|
||||
@@ -81,29 +81,29 @@ using the global ``Input`` object, in ``_physics_process()``.
|
||||
|
||||
.. code-tab:: csharp
|
||||
|
||||
public override void _PhysicsProcess(float delta)
|
||||
public override void _PhysicsProcess(double delta)
|
||||
{
|
||||
// We create a local variable to store the input direction.
|
||||
var direction = Vector3.Zero;
|
||||
|
||||
// We check for each move input and update the direction accordingly
|
||||
// We check for each move input and update the direction accordingly.
|
||||
if (Input.IsActionPressed("move_right"))
|
||||
{
|
||||
direction.x += 1f;
|
||||
direction.X += 1.0f;
|
||||
}
|
||||
if (Input.IsActionPressed("move_left"))
|
||||
{
|
||||
direction.x -= 1f;
|
||||
direction.X -= 1.0f;
|
||||
}
|
||||
if (Input.IsActionPressed("move_back"))
|
||||
{
|
||||
// Notice how we are working with the vector's x and z axes.
|
||||
// Notice how we are working with the vector's X and Z axes.
|
||||
// In 3D, the XZ plane is the ground plane.
|
||||
direction.z += 1f;
|
||||
direction.Z += 1.0f;
|
||||
}
|
||||
if (Input.IsActionPressed("move_forward"))
|
||||
{
|
||||
direction.z -= 1f;
|
||||
direction.Z -= 1.0f;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -141,14 +141,14 @@ call its ``normalize()`` method.
|
||||
|
||||
.. code-tab:: csharp
|
||||
|
||||
public override void _PhysicsProcess(float delta)
|
||||
public override void _PhysicsProcess(double delta)
|
||||
{
|
||||
// ...
|
||||
|
||||
if (direction != Vector3.Zero)
|
||||
{
|
||||
direction = direction.Normalized();
|
||||
GetNode<Node3D>("Pivot").LookAt(position + direction, Vector3.Up);
|
||||
GetNode<Node3D>("Pivot").LookAt(Position + direction, Vector3.Up);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -192,19 +192,30 @@ fall speed separately. Be sure to go back one tab so the lines are inside the
|
||||
# Moving the Character
|
||||
velocity = target_velocity
|
||||
move_and_slide()
|
||||
|
||||
.. code-tab:: csharp
|
||||
|
||||
public override void _PhysicsProcess(float delta)
|
||||
public override void _PhysicsProcess(double delta)
|
||||
{
|
||||
// ...
|
||||
if (direction != Vector3.Zero)
|
||||
{
|
||||
// ...
|
||||
}
|
||||
|
||||
// Ground velocity
|
||||
_velocity.x = direction.x * Speed;
|
||||
_velocity.z = direction.z * Speed;
|
||||
_targetVelocity.X = direction.X * Speed;
|
||||
_targetVelocity.Z = direction.Z * Speed;
|
||||
|
||||
// Vertical velocity
|
||||
_velocity.y -= FallAcceleration * delta;
|
||||
if (!IsOnFloor()) // If in the air, fall towards the floor. Literally gravity
|
||||
{
|
||||
_targetVelocity.Y -= FallAcceleration * (float)delta;
|
||||
}
|
||||
|
||||
// Moving the character
|
||||
_velocity = MoveAndSlide(_velocity, Vector3.Up);
|
||||
Velocity = _targetVelocity;
|
||||
MoveAndSlide();
|
||||
}
|
||||
|
||||
The ``CharacterBody3D.is_on_floor()`` function returns ``true`` if the body collided with the floor in this frame. That's why
|
||||
@@ -269,11 +280,12 @@ Here is the complete ``Player.gd`` code for reference.
|
||||
|
||||
# Vertical Velocity
|
||||
if not is_on_floor(): # If in the air, fall towards the floor. Literally gravity
|
||||
target_velocity.y = target_velocity.y - (fall_acceleration * delta)
|
||||
target_velocity.y = target_velocity.y - (fall_acceleration * delta)
|
||||
|
||||
# Moving the Character
|
||||
velocity = target_velocity
|
||||
move_and_slide()
|
||||
|
||||
.. code-tab:: csharp
|
||||
|
||||
using Godot;
|
||||
@@ -282,51 +294,53 @@ Here is the complete ``Player.gd`` code for reference.
|
||||
{
|
||||
// How fast the player moves in meters per second.
|
||||
[Export]
|
||||
public int Speed = 14;
|
||||
public int Speed { get; set; } = 14;
|
||||
// The downward acceleration when in the air, in meters per second squared.
|
||||
[Export]
|
||||
public int FallAcceleration = 75;
|
||||
public int FallAcceleration { get; set; } = 75;
|
||||
|
||||
private Vector3 _velocity = Vector3.Zero;
|
||||
private Vector3 _targetVelocity = Vector3.Zero;
|
||||
|
||||
public override void _PhysicsProcess(float delta)
|
||||
public override void _PhysicsProcess(double delta)
|
||||
{
|
||||
// We create a local variable to store the input direction.
|
||||
var direction = Vector3.Zero;
|
||||
|
||||
// We check for each move input and update the direction accordingly
|
||||
if (Input.IsActionPressed("move_right"))
|
||||
{
|
||||
direction.x += 1f;
|
||||
direction.X += 1.0f;
|
||||
}
|
||||
if (Input.IsActionPressed("move_left"))
|
||||
{
|
||||
direction.x -= 1f;
|
||||
direction.X -= 1.0f;
|
||||
}
|
||||
if (Input.IsActionPressed("move_back"))
|
||||
{
|
||||
// Notice how we are working with the vector's x and z axes.
|
||||
// In 3D, the XZ plane is the ground plane.
|
||||
direction.z += 1f;
|
||||
direction.Z += 1.0f;
|
||||
}
|
||||
if (Input.IsActionPressed("move_forward"))
|
||||
{
|
||||
direction.z -= 1f;
|
||||
direction.Z -= 1.0f;
|
||||
}
|
||||
|
||||
if (direction != Vector3.Zero)
|
||||
{
|
||||
direction = direction.Normalized();
|
||||
GetNode<Node3D>("Pivot").LookAt(Translation + direction, Vector3.Up);
|
||||
GetNode<Node3D>("Pivot").LookAt(Position + direction, Vector3.Up);
|
||||
}
|
||||
|
||||
// Ground velocity
|
||||
_velocity.x = direction.x * Speed;
|
||||
_velocity.z = direction.z * Speed;
|
||||
_targetVelocity.X = direction.X * Speed;
|
||||
_targetVelocity.Z = direction.Z * Speed;
|
||||
|
||||
// Vertical velocity
|
||||
_velocity.y -= FallAcceleration * delta;
|
||||
if (!IsOnFloor()) // If in the air, fall towards the floor. Literally gravity
|
||||
{
|
||||
_targetVelocity.Y -= FallAcceleration * (float)delta;
|
||||
}
|
||||
|
||||
// Moving the character
|
||||
_velocity = MoveAndSlide(_velocity, Vector3.Up);
|
||||
Velocity = _targetVelocity;
|
||||
MoveAndSlide();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -125,12 +125,12 @@ and ``max_speed``, to define a random speed range, which we will later use to de
|
||||
|
||||
// Minimum speed of the mob in meters per second
|
||||
[Export]
|
||||
public int MinSpeed = 10;
|
||||
public int MinSpeed { get; set; } = 10;
|
||||
// Maximum speed of the mob in meters per second
|
||||
[Export]
|
||||
public int MaxSpeed = 18;
|
||||
public int MaxSpeed { get; set; } = 18;
|
||||
|
||||
public override void _PhysicsProcess(float delta)
|
||||
public override void _PhysicsProcess(double delta)
|
||||
{
|
||||
MoveAndSlide();
|
||||
}
|
||||
@@ -161,17 +161,20 @@ between ``-PI / 4`` radians and ``PI / 4`` radians.
|
||||
# We position the mob by placing it at start_position
|
||||
# and rotate it towards player_position, so it looks at the player.
|
||||
look_at_from_position(start_position, player_position, Vector3.UP)
|
||||
# In this rotation^, the mob will move directly towards the player
|
||||
# so we rotate it randomly within range of -90 and +90 degrees.
|
||||
# Rotate this mob randomly within range of -90 and +90 degrees,
|
||||
# so that it doesn't move directly towards the player.
|
||||
rotate_y(randf_range(-PI / 4, PI / 4))
|
||||
|
||||
.. code-tab:: csharp
|
||||
|
||||
// We will call this function from the Main scene
|
||||
// This function will be called from the Main scene.
|
||||
public void Initialize(Vector3 startPosition, Vector3 playerPosition)
|
||||
{
|
||||
// We position the mob and turn it so that it looks at the player.
|
||||
// We position the mob by placing it at startPosition
|
||||
// and rotate it towards playerPosition, so it looks at the player.
|
||||
LookAtFromPosition(startPosition, playerPosition, Vector3.Up);
|
||||
// And rotate it randomly so it doesn't move exactly toward the player.
|
||||
// Rotate this mob randomly within range of -90 and +90 degrees,
|
||||
// so that it doesn't move directly towards the player.
|
||||
RotateY((float)GD.RandRange(-Mathf.Pi / 4.0, Mathf.Pi / 4.0));
|
||||
}
|
||||
|
||||
@@ -198,12 +201,13 @@ We got a random position, now we need a ``random_speed``. ``randi_range()`` will
|
||||
{
|
||||
// ...
|
||||
|
||||
// We calculate a random speed.
|
||||
float randomSpeed = (float)GD.RandRange(MinSpeed, MaxSpeed);
|
||||
// We calculate a random speed (integer).
|
||||
int randomSpeed = GD.RandRange(MinSpeed, MaxSpeed);
|
||||
// We calculate a forward velocity that represents the speed.
|
||||
_velocity = Vector3.Forward * randomSpeed;
|
||||
// We then rotate the vector based on the mob's Y rotation to move in the direction it's looking
|
||||
_velocity = _velocity.Rotated(Vector3.Up, Rotation.y);
|
||||
Velocity = Vector3.Forward * randomSpeed;
|
||||
// We then rotate the velocity vector based on the mob's Y rotation
|
||||
// in order to move in the direction the mob is looking.
|
||||
Velocity = Velocity.Rotated(Vector3.Up, Rotation.Y);
|
||||
}
|
||||
|
||||
Leaving the screen
|
||||
@@ -239,7 +243,7 @@ method. This function destroy the instance it's called on.
|
||||
.. code-tab:: csharp
|
||||
|
||||
// We also specified this function name in PascalCase in the editor's connection window
|
||||
public void OnVisibilityNotifierScreenExited()
|
||||
private void OnVisibilityNotifierScreenExited()
|
||||
{
|
||||
QueueFree();
|
||||
}
|
||||
@@ -268,8 +272,8 @@ Here is the complete ``Mob.gd`` script for reference.
|
||||
# We position the mob by placing it at start_position
|
||||
# and rotate it towards player_position, so it looks at the player.
|
||||
look_at_from_position(start_position, player_position, Vector3.UP)
|
||||
# In this rotation^, the mob will move directly towards the player
|
||||
# so we rotate it randomly within range of -90 and +90 degrees.
|
||||
# Rotate this mob randomly within range of -90 and +90 degrees,
|
||||
# so that it doesn't move directly towards the player.
|
||||
rotate_y(randf_range(-PI / 4, PI / 4))
|
||||
|
||||
# We calculate a random speed (integer)
|
||||
@@ -282,39 +286,46 @@ Here is the complete ``Mob.gd`` script for reference.
|
||||
|
||||
func _on_visible_on_screen_notifier_3d_screen_exited():
|
||||
queue_free()
|
||||
|
||||
.. code-tab:: csharp
|
||||
|
||||
using Godot;
|
||||
|
||||
public partial class Mob : CharacterBody3D
|
||||
{
|
||||
// Minimum speed of the mob in meters per second
|
||||
// Minimum speed of the mob in meters per second.
|
||||
[Export]
|
||||
public int MinSpeed = 10;
|
||||
// Maximum speed of the mob in meters per second
|
||||
public int MinSpeed { get; set; } = 10;
|
||||
// Maximum speed of the mob in meters per second.
|
||||
[Export]
|
||||
public int MaxSpeed = 18;
|
||||
public int MaxSpeed { get; set; } = 18;
|
||||
|
||||
private Vector3 _velocity = Vector3.Zero;
|
||||
|
||||
public override void _PhysicsProcess(float delta)
|
||||
public override void _PhysicsProcess(double delta)
|
||||
{
|
||||
MoveAndSlide(_velocity);
|
||||
MoveAndSlide();
|
||||
}
|
||||
|
||||
// We will call this function from the Main scene
|
||||
// This function will be called from the Main scene.
|
||||
public void Initialize(Vector3 startPosition, Vector3 playerPosition)
|
||||
{
|
||||
// We position the mob by placing it at startPosition
|
||||
// and rotate it towards playerPosition, so it looks at the player.
|
||||
LookAtFromPosition(startPosition, playerPosition, Vector3.Up);
|
||||
// Rotate this mob randomly within range of -90 and +90 degrees,
|
||||
// so that it doesn't move directly towards the player.
|
||||
RotateY((float)GD.RandRange(-Mathf.Pi / 4.0, Mathf.Pi / 4.0));
|
||||
|
||||
var randomSpeed = (float)GD.RandRange(MinSpeed, MaxSpeed);
|
||||
_velocity = Vector3.Forward * randomSpeed;
|
||||
_velocity = _velocity.Rotated(Vector3.Up, Rotation.y);
|
||||
// We calculate a random speed (integer).
|
||||
int randomSpeed = GD.RandRange(MinSpeed, MaxSpeed);
|
||||
// We calculate a forward velocity that represents the speed.
|
||||
Velocity = Vector3.Forward * randomSpeed;
|
||||
// We then rotate the velocity vector based on the mob's Y rotation
|
||||
// in order to move in the direction the mob is looking.
|
||||
Velocity = Velocity.Rotated(Vector3.Up, Rotation.Y);
|
||||
}
|
||||
|
||||
// We also specified this function name in PascalCase in the editor's connection window
|
||||
public void OnVisibilityNotifierScreenExited()
|
||||
private void OnVisibilityNotifierScreenExited()
|
||||
{
|
||||
QueueFree();
|
||||
}
|
||||
|
||||
@@ -143,7 +143,7 @@ Your path should look like this.
|
||||
|image18|
|
||||
|
||||
To sample random positions on it, we need a :ref:`PathFollow3D <class_PathFollow3D>` node. Add a
|
||||
:ref:`PathFollow3D <class_PathFollow3D>` as a child of the ``Path3d``. Rename the two nodes to ``SpawnPath`` and
|
||||
:ref:`PathFollow3D <class_PathFollow3D>` as a child of the ``Path3D``. Rename the two nodes to ``SpawnPath`` and
|
||||
``SpawnLocation``, respectively. It's more descriptive of what we'll use them for.
|
||||
|
||||
|image19|
|
||||
@@ -158,10 +158,6 @@ Right-click on the ``Main`` node and attach a new script to it.
|
||||
We first export a variable to the *Inspector* so that we can assign ``Mob.tscn``
|
||||
or any other monster to it.
|
||||
|
||||
Then, as we're going to spawn the monsters procedurally, we want to randomize
|
||||
numbers every time we play the game. If we don't do that, the monsters will
|
||||
always spawn following the same sequence.
|
||||
|
||||
.. tabs::
|
||||
.. code-tab:: gdscript GDScript
|
||||
|
||||
@@ -169,10 +165,6 @@ always spawn following the same sequence.
|
||||
|
||||
@export var mob_scene: PackedScene
|
||||
|
||||
|
||||
func _ready():
|
||||
randomize()
|
||||
|
||||
.. code-tab:: csharp
|
||||
|
||||
using Godot;
|
||||
@@ -181,16 +173,8 @@ always spawn following the same sequence.
|
||||
{
|
||||
// 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 MobScene;
|
||||
#pragma warning restore 649
|
||||
|
||||
public override void _Ready()
|
||||
{
|
||||
GD.Randomize();
|
||||
}
|
||||
public PackedScene MobScene { get; set; }
|
||||
}
|
||||
|
||||
We want to spawn mobs at regular time intervals. To do this, we need to go back
|
||||
@@ -259,22 +243,22 @@ Let's code the mob spawning logic. We're going to:
|
||||
.. code-tab:: csharp
|
||||
|
||||
// We also specified this function name in PascalCase in the editor's connection window
|
||||
public void OnMobTimerTimeout()
|
||||
private void OnMobTimerTimeout()
|
||||
{
|
||||
// Create a new instance of the Mob scene.
|
||||
Mob mob = (Mob)MobScene.Instantiate();
|
||||
Mob mob = MobScene.Instantiate<Mob>();
|
||||
|
||||
// Choose a random location on the SpawnPath.
|
||||
// We store the reference to the SpawnLocation node.
|
||||
var mobSpawnLocation = GetNode<PathFollow>("SpawnPath/SpawnLocation");
|
||||
// And give it a random offset.
|
||||
mobSpawnLocation.UnitOffset = GD.Randf();
|
||||
mobSpawnLocation.ProgressRatio = GD.Randf();
|
||||
|
||||
Vector3 playerPosition = GetNode<Player>("Player").position;
|
||||
mob.Initialize(mobSpawnLocation.Translation, playerPosition);
|
||||
Vector3 playerPosition = GetNode<Player>("Player").Position;
|
||||
mob.Initialize(mobSpawnLocation.Position, playerPosition);
|
||||
|
||||
// Spawn the mob by adding it to the Main scene.
|
||||
AddChild(mob);
|
||||
|
||||
}
|
||||
|
||||
Above, ``randf()`` produces a random value between ``0`` and ``1``, which is
|
||||
@@ -292,9 +276,6 @@ Here is the complete ``Main.gd`` script so far, for reference.
|
||||
|
||||
@export var mob_scene: PackedScene
|
||||
|
||||
func _ready():
|
||||
randomize()
|
||||
|
||||
|
||||
func _on_mob_timer_timeout():
|
||||
# Create a new instance of the Mob scene.
|
||||
@@ -318,26 +299,24 @@ Here is the complete ``Main.gd`` script so far, for reference.
|
||||
|
||||
public partial class Main : Node
|
||||
{
|
||||
#pragma warning disable 649
|
||||
[Export]
|
||||
public PackedScene MobScene;
|
||||
#pragma warning restore 649
|
||||
public PackedScene MobScene { get; set; }
|
||||
|
||||
public override void _Ready()
|
||||
private void OnMobTimerTimeout()
|
||||
{
|
||||
GD.Randomize();
|
||||
}
|
||||
// Create a new instance of the Mob scene.
|
||||
Mob mob = MobScene.Instantiate<Mob>();
|
||||
|
||||
public void OnMobTimerTimeout()
|
||||
{
|
||||
Mob mob = (Mob)MobScene.Instantiate();
|
||||
// Choose a random location on the SpawnPath.
|
||||
// We store the reference to the SpawnLocation node.
|
||||
var mobSpawnLocation = GetNode<PathFollow3D>("SpawnPath/SpawnLocation");
|
||||
// And give it a random offset.
|
||||
mobSpawnLocation.ProgressRatio = GD.Randf();
|
||||
|
||||
var mobSpawnLocation = GetNode<PathFollow>("SpawnPath/SpawnLocation");
|
||||
mobSpawnLocation.UnitOffset = GD.Randf();
|
||||
|
||||
Vector3 playerPosition = GetNode<Player>("Player").position;
|
||||
mob.Initialize(mobSpawnLocation.Translation, playerPosition);
|
||||
Vector3 playerPosition = GetNode<Player>("Player").Position;
|
||||
mob.Initialize(mobSpawnLocation.Position, playerPosition);
|
||||
|
||||
// Spawn the mob by adding it to the Main scene.
|
||||
AddChild(mob);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -122,7 +122,7 @@ the ``jump_impulse``.
|
||||
// ...
|
||||
// Vertical impulse applied to the character upon jumping in meters per second.
|
||||
[Export]
|
||||
public int JumpImpulse = 20;
|
||||
public int JumpImpulse { get; set; } = 20;
|
||||
|
||||
Inside ``_physics_process()``, add the following code before the ``move_and_slide()`` codeblock.
|
||||
|
||||
@@ -140,14 +140,14 @@ Inside ``_physics_process()``, add the following code before the ``move_and_slid
|
||||
|
||||
.. code-tab:: csharp
|
||||
|
||||
public override void _PhysicsProcess(float delta)
|
||||
public override void _PhysicsProcess(double delta)
|
||||
{
|
||||
// ...
|
||||
|
||||
// Jumping.
|
||||
if (IsOnFloor() && Input.IsActionJustPressed("jump"))
|
||||
{
|
||||
_velocity.y = JumpImpulse;
|
||||
_targetVelocity.Y = JumpImpulse;
|
||||
}
|
||||
|
||||
// ...
|
||||
@@ -218,7 +218,7 @@ when jumping.
|
||||
|
||||
// Vertical impulse applied to the character upon bouncing over a mob in meters per second.
|
||||
[Export]
|
||||
public int BounceImpulse = 16;
|
||||
public int BounceImpulse { get; set; } = 16;
|
||||
|
||||
Then, after the **Jumping** codeblock we added above in ``_physics_process()``, add the following loop. With
|
||||
``move_and_slide()``, Godot makes the body move sometimes multiple times in a
|
||||
@@ -235,6 +235,7 @@ With this code, if no collisions occurred on a given frame, the loop won't run.
|
||||
|
||||
func _physics_process(delta):
|
||||
#...
|
||||
|
||||
# Iterate through all collisions that occurred this frame
|
||||
# in C this would be for(int i = 0; i < collisions.Count; i++)
|
||||
for index in range(get_slide_collision_count()):
|
||||
@@ -256,23 +257,25 @@ With this code, if no collisions occurred on a given frame, the loop won't run.
|
||||
|
||||
.. code-tab:: csharp
|
||||
|
||||
public override void _PhysicsProcess(float delta)
|
||||
public override void _PhysicsProcess(double delta)
|
||||
{
|
||||
// ...
|
||||
|
||||
for (int index = 0; index < GetSlideCount(); index++)
|
||||
// Iterate through all collisions that occurred this frame.
|
||||
for (int index = 0; index < GetSlideCollisionCount(); index++)
|
||||
{
|
||||
// We check every collision that occurred this frame.
|
||||
// We get one of the collisions with the player.
|
||||
KinematicCollision3D collision = GetSlideCollision(index);
|
||||
// If we collide with a monster...
|
||||
if (collision.Collider is Mob mob && mob.IsInGroup("mob"))
|
||||
|
||||
// If the collision is with a mob.
|
||||
if (collision.GetCollider() is Mob mob)
|
||||
{
|
||||
// ...we check that we are hitting it from above.
|
||||
if (Vector3.Up.Dot(collision.Normal) > 0.1f)
|
||||
// We check that we are hitting it from above.
|
||||
if (Vector3.Up.Dot(collision.GetNormal()) > 0.1f)
|
||||
{
|
||||
// If so, we squash it and bounce.
|
||||
mob.Squash();
|
||||
_velocity.y = BounceImpulse;
|
||||
_targetVelocity.Y = BounceImpulse;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -328,7 +331,7 @@ destroy the mob.
|
||||
|
||||
// Don't forget to rebuild the project so the editor knows about the new signal.
|
||||
|
||||
// Emitted when the played jumped on the mob.
|
||||
// Emitted when the player jumped on the mob.
|
||||
[Signal]
|
||||
public delegate void SquashedEventHandler();
|
||||
|
||||
@@ -336,7 +339,7 @@ destroy the mob.
|
||||
|
||||
public void Squash()
|
||||
{
|
||||
EmitSignal(nameof(Squashed));
|
||||
EmitSignal(SignalName.Squashed);
|
||||
QueueFree();
|
||||
}
|
||||
|
||||
|
||||
@@ -90,12 +90,12 @@ a ``die()`` function that helps us put a descriptive label on the code.
|
||||
|
||||
private void Die()
|
||||
{
|
||||
EmitSignal(nameof(Hit));
|
||||
EmitSignal(SignalName.Hit);
|
||||
QueueFree();
|
||||
}
|
||||
|
||||
// We also specified this function name in PascalCase in the editor's connection window
|
||||
public void OnMobDetectorBodyEntered(Node body)
|
||||
private void OnMobDetectorBodyEntered(Node3D body)
|
||||
{
|
||||
Die();
|
||||
}
|
||||
@@ -108,6 +108,11 @@ the character should die when an enemy runs into the collider. Note that without
|
||||
|
||||
var player_position = $Player.position
|
||||
|
||||
.. code-tab:: csharp
|
||||
|
||||
Vector3 playerPosition = GetNode<Player>("Player").Position;
|
||||
|
||||
|
||||
gives error because there is no $Player!
|
||||
|
||||
Also note that the enemy colliding with the player and dying depends on the size and position of the
|
||||
@@ -137,7 +142,7 @@ Get the timer, and stop it, in the ``_on_player_hit()`` function.
|
||||
.. code-tab:: csharp
|
||||
|
||||
// We also specified this function name in PascalCase in the editor's connection window
|
||||
public void OnPlayerHit()
|
||||
private void OnPlayerHit()
|
||||
{
|
||||
GetNode<Timer>("MobTimer").Stop();
|
||||
}
|
||||
@@ -167,9 +172,6 @@ Starting with ``Main.gd``.
|
||||
|
||||
@export var mob_scene: PackedScene
|
||||
|
||||
func _ready():
|
||||
randomize()
|
||||
|
||||
|
||||
func _on_mob_timer_timeout():
|
||||
# Create a new instance of the Mob scene.
|
||||
@@ -189,42 +191,35 @@ Starting with ``Main.gd``.
|
||||
|
||||
func _on_player_hit():
|
||||
$MobTimer.stop()
|
||||
|
||||
.. code-tab:: csharp
|
||||
|
||||
using Godot;
|
||||
|
||||
public partial class Main : Node
|
||||
{
|
||||
#pragma warning disable 649
|
||||
[Export]
|
||||
public PackedScene MobScene;
|
||||
#pragma warning restore 649
|
||||
public PackedScene MobScene { get; set; }
|
||||
|
||||
public override void _Ready()
|
||||
{
|
||||
GD.Randomize();
|
||||
}
|
||||
|
||||
public void OnMobTimerTimeout()
|
||||
private void OnMobTimerTimeout()
|
||||
{
|
||||
// Create a new instance of the Mob scene.
|
||||
var mob = (Mob)MobScene.Instantiate();
|
||||
Mob mob = MobScene.Instantiate<Mob>();
|
||||
|
||||
// Choose a random location on the SpawnPath.
|
||||
// We store the reference to the SpawnLocation node.
|
||||
var mobSpawnLocation = GetNode<PathFollow>("SpawnPath/SpawnLocation");
|
||||
// And give it a random offset.
|
||||
mobSpawnLocation.UnitOffset = GD.Randf();
|
||||
mobSpawnLocation.ProgressRatio = GD.Randf();
|
||||
|
||||
// Communicate the spawn location and the player's location to the mob.
|
||||
Vector3 playerPosition = GetNode<Player>("Player").position;
|
||||
mob.Initialize(mobSpawnLocation.Translation, playerPosition);
|
||||
Vector3 playerPosition = GetNode<Player>("Player").Position;
|
||||
mob.Initialize(mobSpawnLocation.Position, playerPosition);
|
||||
|
||||
// Spawn the mob by adding it to the Main scene.
|
||||
AddChild(mob);
|
||||
}
|
||||
|
||||
public void OnPlayerHit()
|
||||
private void OnPlayerHit()
|
||||
{
|
||||
GetNode<Timer>("MobTimer").Stop();
|
||||
}
|
||||
@@ -253,8 +248,8 @@ Next is ``Mob.gd``.
|
||||
# We position the mob by placing it at start_position
|
||||
# and rotate it towards player_position, so it looks at the player.
|
||||
look_at_from_position(start_position, player_position, Vector3.UP)
|
||||
# In this rotation^, the mob will move directly towards the player
|
||||
# so we rotate it randomly within range of -90 and +90 degrees.
|
||||
# Rotate this mob randomly within range of -90 and +90 degrees,
|
||||
# so that it doesn't move directly towards the player.
|
||||
rotate_y(randf_range(-PI / 4, PI / 4))
|
||||
|
||||
# We calculate a random speed (integer)
|
||||
@@ -271,6 +266,7 @@ Next is ``Mob.gd``.
|
||||
func squash():
|
||||
squashed.emit()
|
||||
queue_free() # Destroy this node
|
||||
|
||||
.. code-tab:: csharp
|
||||
|
||||
using Godot;
|
||||
@@ -283,35 +279,42 @@ Next is ``Mob.gd``.
|
||||
|
||||
// Minimum speed of the mob in meters per second
|
||||
[Export]
|
||||
public int MinSpeed = 10;
|
||||
public int MinSpeed { get; set; } = 10;
|
||||
// Maximum speed of the mob in meters per second
|
||||
[Export]
|
||||
public int MaxSpeed = 18;
|
||||
public int MaxSpeed { get; set; } = 18;
|
||||
|
||||
private Vector3 _velocity = Vector3.Zero;
|
||||
|
||||
public override void _PhysicsProcess(float delta)
|
||||
public override void _PhysicsProcess(double delta)
|
||||
{
|
||||
MoveAndSlide(_velocity);
|
||||
MoveAndSlide();
|
||||
}
|
||||
|
||||
// This function will be called from the Main scene.
|
||||
public void Initialize(Vector3 startPosition, Vector3 playerPosition)
|
||||
{
|
||||
// We position the mob by placing it at startPosition
|
||||
// and rotate it towards playerPosition, so it looks at the player.
|
||||
LookAtFromPosition(startPosition, playerPosition, Vector3.Up);
|
||||
// Rotate this mob randomly within range of -90 and +90 degrees,
|
||||
// so that it doesn't move directly towards the player.
|
||||
RotateY((float)GD.RandRange(-Mathf.Pi / 4.0, Mathf.Pi / 4.0));
|
||||
|
||||
float randomSpeed = (float)GD.RandRange(MinSpeed, MaxSpeed);
|
||||
_velocity = Vector3.Forward * randomSpeed;
|
||||
_velocity = _velocity.Rotated(Vector3.Up, Rotation.y);
|
||||
// We calculate a random speed (integer)
|
||||
int randomSpeed = GD.RandRange(MinSpeed, MaxSpeed);
|
||||
// We calculate a forward velocity that represents the speed.
|
||||
Velocity = Vector3.Forward * randomSpeed;
|
||||
// We then rotate the velocity vector based on the mob's Y rotation
|
||||
// in order to move in the direction the mob is looking.
|
||||
Velocity = Velocity.Rotated(Vector3.Up, Rotation.Y);
|
||||
}
|
||||
|
||||
public void Squash()
|
||||
{
|
||||
EmitSignal(nameof(Squashed));
|
||||
QueueFree();
|
||||
EmitSignal(SignalName.Squashed);
|
||||
QueueFree(); // Destroy this node
|
||||
}
|
||||
|
||||
public void OnVisibilityNotifierScreenExited()
|
||||
private void OnVisibilityNotifierScreenExited()
|
||||
{
|
||||
QueueFree();
|
||||
}
|
||||
@@ -402,6 +405,7 @@ Finally, the longest script, ``Player.gd``:
|
||||
|
||||
func _on_mob_detector_body_entered(body):
|
||||
die()
|
||||
|
||||
.. code-tab:: csharp
|
||||
|
||||
using Godot;
|
||||
@@ -414,79 +418,98 @@ Finally, the longest script, ``Player.gd``:
|
||||
|
||||
// How fast the player moves in meters per second.
|
||||
[Export]
|
||||
public int Speed = 14;
|
||||
public int Speed { get; set; } = 14;
|
||||
// The downward acceleration when in the air, in meters per second squared.
|
||||
[Export]
|
||||
public int FallAcceleration = 75;
|
||||
public int FallAcceleration { get; set; } = 75;
|
||||
// Vertical impulse applied to the character upon jumping in meters per second.
|
||||
[Export]
|
||||
public int JumpImpulse = 20;
|
||||
public int JumpImpulse { get; set; } = 20;
|
||||
// Vertical impulse applied to the character upon bouncing over a mob in meters per second.
|
||||
[Export]
|
||||
public int BounceImpulse = 16;
|
||||
public int BounceImpulse { get; set; } = 16;
|
||||
|
||||
private Vector3 _velocity = Vector3.Zero;
|
||||
private Vector3 _targetVelocity = Vector3.Zero;
|
||||
|
||||
public override void _PhysicsProcess(float delta)
|
||||
public override void _PhysicsProcess(double delta)
|
||||
{
|
||||
// We create a local variable to store the input direction.
|
||||
var direction = Vector3.Zero;
|
||||
|
||||
// We check for each move input and update the direction accordingly.
|
||||
if (Input.IsActionPressed("move_right"))
|
||||
{
|
||||
direction.x += 1f;
|
||||
direction.X += 1.0f;
|
||||
}
|
||||
if (Input.IsActionPressed("move_left"))
|
||||
{
|
||||
direction.x -= 1f;
|
||||
direction.X -= 1.0f;
|
||||
}
|
||||
if (Input.IsActionPressed("move_back"))
|
||||
{
|
||||
direction.z += 1f;
|
||||
// Notice how we are working with the vector's X and Z axes.
|
||||
// In 3D, the XZ plane is the ground plane.
|
||||
direction.Z += 1.0f;
|
||||
}
|
||||
if (Input.IsActionPressed("move_forward"))
|
||||
{
|
||||
direction.z -= 1f;
|
||||
direction.Z -= 1.0f;
|
||||
}
|
||||
|
||||
// Prevent diagonal moving fast af
|
||||
if (direction != Vector3.Zero)
|
||||
{
|
||||
direction = direction.Normalized();
|
||||
GetNode<Node3D>("Pivot").LookAt(Translation + direction, Vector3.Up);
|
||||
GetNode<Node3D>("Pivot").LookAt(Position + direction, Vector3.Up);
|
||||
}
|
||||
|
||||
_velocity.x = direction.x * Speed;
|
||||
_velocity.z = direction.z * Speed;
|
||||
// Ground Velocity
|
||||
_targetVelocity.X = direction.X * Speed;
|
||||
_targetVelocity.Z = direction.Z * Speed;
|
||||
|
||||
// Vertical Velocity
|
||||
if (!IsOnFloor()) // If in the air, fall towards the floor. Literally gravity
|
||||
{
|
||||
_targetVelocity.Y -= FallAcceleration * (float)delta;
|
||||
}
|
||||
|
||||
// Jumping.
|
||||
if (IsOnFloor() && Input.IsActionJustPressed("jump"))
|
||||
{
|
||||
_velocity.y += JumpImpulse;
|
||||
_targetVelocity.Y = JumpImpulse;
|
||||
}
|
||||
|
||||
_velocity.y -= FallAcceleration * delta;
|
||||
_velocity = MoveAndSlide(_velocity, Vector3.Up);
|
||||
|
||||
// Iterate through all collisions that occurred this frame.
|
||||
for (int index = 0; index < GetSlideCount(); index++)
|
||||
{
|
||||
// We get one of the collisions with the player.
|
||||
KinematicCollision3D collision = GetSlideCollision(index);
|
||||
if (collision.Collider is Mob mob && mob.IsInGroup("mob"))
|
||||
|
||||
// If the collision is with a mob.
|
||||
if (collision.GetCollider() is Mob mob)
|
||||
{
|
||||
if (Vector3.Up.Dot(collision.Normal) > 0.1f)
|
||||
// We check that we are hitting it from above.
|
||||
if (Vector3.Up.Dot(collision.GetNormal()) > 0.1f)
|
||||
{
|
||||
// If so, we squash it and bounce.
|
||||
mob.Squash();
|
||||
_velocity.y = BounceImpulse;
|
||||
_targetVelocity.Y = BounceImpulse;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Moving the Character
|
||||
Velocity = _targetVelocity;
|
||||
MoveAndSlide();
|
||||
}
|
||||
|
||||
private void Die()
|
||||
{
|
||||
EmitSignal(nameof(Hit));
|
||||
EmitSignal(SignalName.Hit);
|
||||
QueueFree();
|
||||
}
|
||||
|
||||
public void OnMobDetectorBodyEntered(Node body)
|
||||
private void OnMobDetectorBodyEntered(Node3D body)
|
||||
{
|
||||
Die();
|
||||
}
|
||||
|
||||
@@ -121,7 +121,7 @@ line:
|
||||
|
||||
.. code-tab:: csharp
|
||||
|
||||
public void OnMobTimerTimeout()
|
||||
private void OnMobTimerTimeout()
|
||||
{
|
||||
// ...
|
||||
// We connect the mob to the score label to update the score upon squashing one.
|
||||
@@ -148,7 +148,7 @@ There, we increment the score and update the displayed text.
|
||||
public void OnMobSquashed()
|
||||
{
|
||||
_score += 1;
|
||||
Text = string.Format("Score: {0}", _score);
|
||||
Text = $"Score: {_score}";
|
||||
}
|
||||
|
||||
The second line uses the value of the ``score`` variable to replace the
|
||||
@@ -159,6 +159,7 @@ function.
|
||||
.. seealso::
|
||||
|
||||
You can learn more about string formatting here: :ref:`doc_gdscript_printf`.
|
||||
In C#, consider using `string interpolation with "$" <https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/tokens/interpolated>`_.
|
||||
|
||||
|
||||
.. note::
|
||||
@@ -237,15 +238,13 @@ the game. Add this line to the ``_ready()`` function.
|
||||
.. tabs::
|
||||
.. code-tab:: gdscript GDScript
|
||||
|
||||
func _ready():
|
||||
#...
|
||||
$UserInterface/Retry.hide()
|
||||
func _ready():
|
||||
$UserInterface/Retry.hide()
|
||||
|
||||
.. code-tab:: csharp
|
||||
|
||||
public override void _Ready()
|
||||
{
|
||||
// ...
|
||||
GetNode<Control>("UserInterface/Retry").Hide();
|
||||
}
|
||||
|
||||
@@ -260,7 +259,7 @@ Then, when the player gets hit, we show the overlay.
|
||||
|
||||
.. code-tab:: csharp
|
||||
|
||||
public void OnPlayerHit()
|
||||
private void OnPlayerHit()
|
||||
{
|
||||
//...
|
||||
GetNode<Control>("UserInterface/Retry").Show();
|
||||
@@ -316,7 +315,7 @@ or by using the *+* icon next to your currently opened scene.
|
||||
|
||||
|image15|
|
||||
|
||||
Click the *Other Node* button to create an :ref:`AudioStreamPlayer2D <class_AudioStreamPlayer2D>` and rename it to
|
||||
Click the *Other Node* button to create an :ref:`AudioStreamPlayer <class_AudioStreamPlayer>` and rename it to
|
||||
``MusicPlayer``.
|
||||
|
||||
|image16|
|
||||
@@ -370,7 +369,6 @@ Here is the complete ``Main.gd`` script for reference.
|
||||
@export var mob_scene: PackedScene
|
||||
|
||||
func _ready():
|
||||
randomize()
|
||||
$UserInterface/Retry.hide()
|
||||
|
||||
|
||||
@@ -401,20 +399,18 @@ Here is the complete ``Main.gd`` script for reference.
|
||||
if event.is_action_pressed("ui_accept") and $UserInterface/Retry.visible:
|
||||
# This restarts the current scene.
|
||||
get_tree().reload_current_scene()
|
||||
.. code-tab:: csharp
|
||||
|
||||
.. code-tab:: csharp
|
||||
|
||||
using Godot;
|
||||
|
||||
public partial class Main : Node
|
||||
{
|
||||
#pragma warning disable 649
|
||||
[Export]
|
||||
public PackedScene MobScene;
|
||||
#pragma warning restore 649
|
||||
public PackedScene MobScene { get; set; }
|
||||
|
||||
public override void _Ready()
|
||||
{
|
||||
GD.Randomize();
|
||||
GetNode<Control>("UserInterface/Retry").Hide();
|
||||
}
|
||||
|
||||
@@ -422,25 +418,33 @@ Here is the complete ``Main.gd`` script for reference.
|
||||
{
|
||||
if (@event.IsActionPressed("ui_accept") && GetNode<Control>("UserInterface/Retry").Visible)
|
||||
{
|
||||
// This restarts the current scene.
|
||||
GetTree().ReloadCurrentScene();
|
||||
}
|
||||
}
|
||||
|
||||
public void OnMobTimerTimeout()
|
||||
private void OnMobTimerTimeout()
|
||||
{
|
||||
Mob mob = (Mob)MobScene.Instantiate();
|
||||
// Create a new instance of the Mob scene.
|
||||
Mob mob = MobScene.Instantiate<Mob>();
|
||||
|
||||
var mobSpawnLocation = GetNode<PathFollow>("SpawnPath/SpawnLocation");
|
||||
mobSpawnLocation.UnitOffset = GD.Randf();
|
||||
// Choose a random location on the SpawnPath.
|
||||
// We store the reference to the SpawnLocation node.
|
||||
var mobSpawnLocation = GetNode<PathFollow3D>("SpawnPath/SpawnLocation");
|
||||
// And give it a random offset.
|
||||
mobSpawnLocation.ProgressRatio = GD.Randf();
|
||||
|
||||
Vector3 playerPosition = GetNode<Player>("Player").position;
|
||||
mob.Initialize(mobSpawnLocation.Translation, playerPosition);
|
||||
mob.Initialize(mobSpawnLocation.Position, playerPosition);
|
||||
|
||||
// Spawn the mob by adding it to the Main scene.
|
||||
AddChild(mob);
|
||||
|
||||
// We connect the mob to the score label to update the score upon squashing one.
|
||||
mob.Squashed += GetNode<ScoreLabel>("UserInterface/ScoreLabel").OnMobSquashed;
|
||||
}
|
||||
|
||||
public void OnPlayerHit()
|
||||
private void OnPlayerHit()
|
||||
{
|
||||
GetNode<Timer>("MobTimer").Stop();
|
||||
GetNode<Control>("UserInterface/Retry").Show();
|
||||
|
||||
@@ -77,7 +77,7 @@ click any of them to create a keyframe, a time and value pair for the
|
||||
corresponding property. The keyframe gets inserted where your time cursor is in
|
||||
the timeline.
|
||||
|
||||
Let's insert our first keys. Here, we will animate both the translation and the
|
||||
Let's insert our first keys. Here, we will animate both the position and the
|
||||
rotation of the ``Character`` node.
|
||||
|
||||
Select the ``Character`` and in the *Inspector* expand the *Transform* section. Click the key icon next to *Position*, and *Rotation*.
|
||||
@@ -93,7 +93,7 @@ Two tracks appear in the editor with a diamond icon representing each keyframe.
|
||||
|image11|
|
||||
|
||||
You can click and drag on the diamonds to move them in time. Move the
|
||||
translation key to ``0.2`` seconds and the rotation key to ``0.1`` seconds.
|
||||
position key to ``0.2`` seconds and the rotation key to ``0.1`` seconds.
|
||||
|
||||
|image12|
|
||||
|
||||
@@ -163,7 +163,7 @@ Apply an ease-out to the second keyframe in the rotation track.
|
||||
|
||||
|image19|
|
||||
|
||||
Do the opposite for the second translation keyframe, dragging it to the right.
|
||||
Do the opposite for the second position keyframe, dragging it to the right.
|
||||
|
||||
|image20|
|
||||
|
||||
@@ -211,7 +211,7 @@ vector, add the following code.
|
||||
|
||||
.. code-tab:: csharp
|
||||
|
||||
public override void _PhysicsProcess(float delta)
|
||||
public override void _PhysicsProcess(double delta)
|
||||
{
|
||||
// ...
|
||||
if (direction != Vector3.Zero)
|
||||
@@ -241,11 +241,11 @@ at the end of ``_physics_process()``.
|
||||
|
||||
.. code-tab:: csharp
|
||||
|
||||
public override void _PhysicsProcess(float delta)
|
||||
public override void _PhysicsProcess(double delta)
|
||||
{
|
||||
// ...
|
||||
var pivot = GetNode<Node3D>("Pivot");
|
||||
pivot.Rotation = new Vector3(Mathf.Pi / 6f * _velocity.y / JumpImpulse, pivot.Rotation.y, pivot.Rotation.z);
|
||||
pivot.Rotation = new Vector3(Mathf.Pi / 6.0f * Velocity.Y / JumpImpulse, pivot.Rotation.Y, pivot.Rotation.Z);
|
||||
}
|
||||
|
||||
Animating the mobs
|
||||
@@ -293,15 +293,16 @@ Here's the *Player* script.
|
||||
|
||||
.. tabs::
|
||||
.. code-tab:: gdscript GDScript
|
||||
|
||||
|
||||
extends CharacterBody3D
|
||||
|
||||
signal hit
|
||||
|
||||
# How fast the player moves in meters per second
|
||||
# How fast the player moves in meters per second.
|
||||
@export var speed = 14
|
||||
# The downward acceleration while in the air, in meters per second squared.
|
||||
@export var fall_acceleration = 75
|
||||
# Vertical impulse applied to the character upon jumping in meters per second.
|
||||
@export var jump_impulse = 20
|
||||
# Vertical impulse applied to the character upon bouncing over a mob
|
||||
# in meters per second.
|
||||
@@ -330,9 +331,9 @@ Here's the *Player* script.
|
||||
if direction != Vector3.ZERO:
|
||||
direction = direction.normalized()
|
||||
$Pivot.look_at(position + direction,Vector3.UP)
|
||||
$AnimationPlayer.playback_speed = 4
|
||||
$AnimationPlayer.speed_scale = 4
|
||||
else:
|
||||
$AnimationPlayer.playback_speed = 1
|
||||
$AnimationPlayer.speed_scale = 1
|
||||
|
||||
# Ground Velocity
|
||||
target_velocity.x = direction.x * speed
|
||||
@@ -378,6 +379,7 @@ Here's the *Player* script.
|
||||
|
||||
func _on_mob_detector_body_entered(body):
|
||||
die()
|
||||
|
||||
.. code-tab:: csharp
|
||||
|
||||
using Godot;
|
||||
@@ -390,44 +392,49 @@ Here's the *Player* script.
|
||||
|
||||
// How fast the player moves in meters per second.
|
||||
[Export]
|
||||
public int Speed = 14;
|
||||
public int Speed { get; set; } = 14;
|
||||
// The downward acceleration when in the air, in meters per second squared.
|
||||
[Export]
|
||||
public int FallAcceleration = 75;
|
||||
public int FallAcceleration { get; set; } = 75;
|
||||
// Vertical impulse applied to the character upon jumping in meters per second.
|
||||
[Export]
|
||||
public int JumpImpulse = 20;
|
||||
public int JumpImpulse { get; set; } = 20;
|
||||
// Vertical impulse applied to the character upon bouncing over a mob in meters per second.
|
||||
[Export]
|
||||
public int BounceImpulse = 16;
|
||||
public int BounceImpulse { get; set; } = 16;
|
||||
|
||||
private Vector3 _velocity = Vector3.Zero;
|
||||
private Vector3 _targetVelocity = Vector3.Zero;
|
||||
|
||||
public override void _PhysicsProcess(float delta)
|
||||
public override void _PhysicsProcess(double delta)
|
||||
{
|
||||
// We create a local variable to store the input direction.
|
||||
var direction = Vector3.Zero;
|
||||
|
||||
// We check for each move input and update the direction accordingly.
|
||||
if (Input.IsActionPressed("move_right"))
|
||||
{
|
||||
direction.x += 1f;
|
||||
direction.X += 1.0f;
|
||||
}
|
||||
if (Input.IsActionPressed("move_left"))
|
||||
{
|
||||
direction.x -= 1f;
|
||||
direction.X -= 1.0f;
|
||||
}
|
||||
if (Input.IsActionPressed("move_back"))
|
||||
{
|
||||
direction.z += 1f;
|
||||
// Notice how we are working with the vector's X and Z axes.
|
||||
// In 3D, the XZ plane is the ground plane.
|
||||
direction.Z += 1.0f;
|
||||
}
|
||||
if (Input.IsActionPressed("move_forward"))
|
||||
{
|
||||
direction.z -= 1f;
|
||||
direction.Z -= 1.0f;
|
||||
}
|
||||
|
||||
// Prevent diagonal movement being very fast.
|
||||
if (direction != Vector3.Zero)
|
||||
{
|
||||
direction = direction.Normalized();
|
||||
GetNode<Node3D>("Pivot").LookAt(Translation + direction, Vector3.Up);
|
||||
GetNode<Node3D>("Pivot").LookAt(Position + direction, Vector3.Up);
|
||||
GetNode<AnimationPlayer>("AnimationPlayer").PlaybackSpeed = 4;
|
||||
}
|
||||
else
|
||||
@@ -435,42 +442,56 @@ Here's the *Player* script.
|
||||
GetNode<AnimationPlayer>("AnimationPlayer").PlaybackSpeed = 1;
|
||||
}
|
||||
|
||||
_velocity.x = direction.x * Speed;
|
||||
_velocity.z = direction.z * Speed;
|
||||
// Ground velocity
|
||||
_targetVelocity.X = direction.X * Speed;
|
||||
_targetVelocity.Z = direction.Z * Speed;
|
||||
|
||||
// Vertical velocity
|
||||
if (!IsOnFloor())
|
||||
{
|
||||
_targetVelocity.Y -= FallAcceleration * (float)delta;
|
||||
}
|
||||
|
||||
// Jumping.
|
||||
if (IsOnFloor() && Input.IsActionJustPressed("jump"))
|
||||
{
|
||||
_velocity.y += JumpImpulse;
|
||||
_targetVelocity.y += JumpImpulse;
|
||||
}
|
||||
|
||||
_velocity.y -= FallAcceleration * delta;
|
||||
_velocity = MoveAndSlide(_velocity, Vector3.Up);
|
||||
|
||||
for (int index = 0; index < GetSlideCount(); index++)
|
||||
// Iterate through all collisions that occurred this frame.
|
||||
for (int index = 0; index < GetSlideCollisionCount(); index++)
|
||||
{
|
||||
// We get one of the collisions with the player.
|
||||
KinematicCollision3D collision = GetSlideCollision(index);
|
||||
if (collision.Collider is Mob mob && mob.IsInGroup("mob"))
|
||||
|
||||
// If the collision is with a mob.
|
||||
if (collision.GetCollider() is Mob mob)
|
||||
{
|
||||
if (Vector3.Up.Dot(collision.Normal) > 0.1f)
|
||||
// We check that we are hitting it from above.
|
||||
if (Vector3.Up.Dot(collision.GetNormal()) > 0.1f)
|
||||
{
|
||||
// If so, we squash it and bounce.
|
||||
mob.Squash();
|
||||
_velocity.y = BounceImpulse;
|
||||
_targetVelocity.Y = BounceImpulse;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Moving the character
|
||||
Velocity = _targetVelocity;
|
||||
MoveAndSlide();
|
||||
|
||||
var pivot = GetNode<Node3D>("Pivot");
|
||||
pivot.Rotation = new Vector3(Mathf.Pi / 6f * _velocity.y / JumpImpulse, pivot.Rotation.y, pivot.Rotation.z);
|
||||
pivot.Rotation = new Vector3(Mathf.Pi / 6.0f * Velocity.Y / JumpImpulse, pivot.Rotation.Y, pivot.Rotation.Z);
|
||||
}
|
||||
|
||||
private void Die()
|
||||
{
|
||||
EmitSignal(nameof(Hit));
|
||||
EmitSignal(SignalName.Hit);
|
||||
QueueFree();
|
||||
}
|
||||
|
||||
public void OnMobDetectorBodyEntered(Node body)
|
||||
private void OnMobDetectorBodyEntered(Node body)
|
||||
{
|
||||
Die();
|
||||
}
|
||||
@@ -500,8 +521,8 @@ And the *Mob*'s script.
|
||||
# We position the mob by placing it at start_position
|
||||
# and rotate it towards player_position, so it looks at the player.
|
||||
look_at_from_position(start_position, player_position, Vector3.UP)
|
||||
# In this rotation^, the mob will move directly towards the player
|
||||
# so we rotate it randomly within range of -90 and +90 degrees.
|
||||
# Rotate this mob randomly within range of -90 and +90 degrees,
|
||||
# so that it doesn't move directly towards the player.
|
||||
rotate_y(randf_range(-PI / 4, PI / 4))
|
||||
|
||||
# We calculate a random speed (integer)
|
||||
@@ -512,7 +533,7 @@ And the *Mob*'s script.
|
||||
# in order to move in the direction the mob is looking.
|
||||
velocity = velocity.rotated(Vector3.UP, rotation.y)
|
||||
|
||||
$AnimationPlayer.playback_speed = random_speed / min_speed
|
||||
$AnimationPlayer.speed_scale = random_speed / min_speed
|
||||
|
||||
func _on_visible_on_screen_notifier_3d_screen_exited():
|
||||
queue_free()
|
||||
@@ -520,6 +541,7 @@ And the *Mob*'s script.
|
||||
func squash():
|
||||
squashed.emit()
|
||||
queue_free() # Destroy this node
|
||||
|
||||
.. code-tab:: csharp
|
||||
|
||||
using Godot;
|
||||
@@ -532,37 +554,44 @@ And the *Mob*'s script.
|
||||
|
||||
// Minimum speed of the mob in meters per second
|
||||
[Export]
|
||||
public int MinSpeed = 10;
|
||||
public int MinSpeed { get; set; } = 10;
|
||||
// Maximum speed of the mob in meters per second
|
||||
[Export]
|
||||
public int MaxSpeed = 18;
|
||||
public int MaxSpeed { get; set; } = 18;
|
||||
|
||||
private Vector3 _velocity = Vector3.Zero;
|
||||
|
||||
public override void _PhysicsProcess(float delta)
|
||||
public override void _PhysicsProcess(double delta)
|
||||
{
|
||||
MoveAndSlide(_velocity);
|
||||
MoveAndSlide();
|
||||
}
|
||||
|
||||
// This function will be called from the Main scene.
|
||||
public void Initialize(Vector3 startPosition, Vector3 playerPosition)
|
||||
{
|
||||
// We position the mob by placing it at startPosition
|
||||
// and rotate it towards playerPosition, so it looks at the player.
|
||||
LookAtFromPosition(startPosition, playerPosition, Vector3.Up);
|
||||
// Rotate this mob randomly within range of -90 and +90 degrees,
|
||||
// so that it doesn't move directly towards the player.
|
||||
RotateY((float)GD.RandRange(-Mathf.Pi / 4.0, Mathf.Pi / 4.0));
|
||||
|
||||
float randomSpeed = (float)GD.RandRange(MinSpeed, MaxSpeed);
|
||||
_velocity = Vector3.Forward * randomSpeed;
|
||||
_velocity = _velocity.Rotated(Vector3.Up, Rotation.y);
|
||||
// We calculate a random speed (integer).
|
||||
int randomSpeed = GD.RandRange(MinSpeed, MaxSpeed);
|
||||
// We calculate a forward velocity that represents the speed.
|
||||
Velocity = Vector3.Forward * randomSpeed;
|
||||
// We then rotate the velocity vector based on the mob's Y rotation
|
||||
// in order to move in the direction the mob is looking.
|
||||
Velocity = Velocity.Rotated(Vector3.Up, Rotation.Y);
|
||||
|
||||
GetNode<AnimationPlayer>("AnimationPlayer").PlaybackSpeed = randomSpeed / MinSpeed;
|
||||
GetNode<AnimationPlayer>("AnimationPlayer").SpeedScale = randomSpeed / MinSpeed;
|
||||
}
|
||||
|
||||
public void Squash()
|
||||
{
|
||||
EmitSignal(nameof(Squashed));
|
||||
QueueFree();
|
||||
EmitSignal(SignalName.Squashed);
|
||||
QueueFree(); // Destroy this node
|
||||
}
|
||||
|
||||
public void OnVisibilityNotifierScreenExited()
|
||||
private void OnVisibilityNotifierScreenExited()
|
||||
{
|
||||
QueueFree();
|
||||
}
|
||||
|
||||
@@ -157,7 +157,7 @@ the ``not`` keyword to invert the value.
|
||||
|
||||
.. code-tab:: csharp C#
|
||||
|
||||
public void OnButtonPressed()
|
||||
private void OnButtonPressed()
|
||||
{
|
||||
SetProcess(!IsProcessing());
|
||||
}
|
||||
@@ -181,8 +181,8 @@ following code, which we saw two lessons ago:
|
||||
|
||||
public override void _Process(double delta)
|
||||
{
|
||||
Rotation += AngularSpeed * (float)delta;
|
||||
var velocity = Vector2.Up.Rotated(Rotation) * Speed;
|
||||
Rotation += _angularSpeed * (float)delta;
|
||||
var velocity = Vector2.Up.Rotated(Rotation) * _speed;
|
||||
Position += velocity * (float)delta;
|
||||
}
|
||||
|
||||
@@ -210,19 +210,19 @@ Your complete ``Sprite2D.gd`` code should look like the following.
|
||||
|
||||
using Godot;
|
||||
|
||||
public partial class Sprite : Sprite2D
|
||||
public partial class MySprite2D : Sprite2D
|
||||
{
|
||||
private float Speed = 400;
|
||||
private float AngularSpeed = Mathf.Pi;
|
||||
private float _speed = 400;
|
||||
private float _angularSpeed = Mathf.Pi;
|
||||
|
||||
public override void _Process(double delta)
|
||||
{
|
||||
Rotation += AngularSpeed * (float)delta;
|
||||
var velocity = Vector2.Up.Rotated(Rotation) * Speed;
|
||||
Rotation += _angularSpeed * (float)delta;
|
||||
var velocity = Vector2.Up.Rotated(Rotation) * _speed;
|
||||
Position += velocity * (float)delta;
|
||||
}
|
||||
|
||||
public void OnButtonPressed()
|
||||
private void OnButtonPressed()
|
||||
{
|
||||
SetProcess(!IsProcessing());
|
||||
}
|
||||
@@ -323,7 +323,7 @@ bottom of our script and use it to toggle our sprite's visibility.
|
||||
|
||||
.. code-tab:: csharp C#
|
||||
|
||||
public void OnTimerTimeout()
|
||||
private void OnTimerTimeout()
|
||||
{
|
||||
Visible = !Visible;
|
||||
}
|
||||
@@ -372,10 +372,10 @@ Here is the complete ``Sprite2D.gd`` file for reference.
|
||||
|
||||
using Godot;
|
||||
|
||||
public partial class Sprite : Sprite2D
|
||||
public partial class MySprite2D : Sprite2D
|
||||
{
|
||||
private float Speed = 400;
|
||||
private float AngularSpeed = Mathf.Pi;
|
||||
private float _speed = 400;
|
||||
private float _angularSpeed = Mathf.Pi;
|
||||
|
||||
public override void _Ready()
|
||||
{
|
||||
@@ -385,17 +385,17 @@ Here is the complete ``Sprite2D.gd`` file for reference.
|
||||
|
||||
public override void _Process(double delta)
|
||||
{
|
||||
Rotation += AngularSpeed * (float)delta;
|
||||
var velocity = Vector2.Up.Rotated(Rotation) * Speed;
|
||||
Rotation += _angularSpeed * (float)delta;
|
||||
var velocity = Vector2.Up.Rotated(Rotation) * _speed;
|
||||
Position += velocity * (float)delta;
|
||||
}
|
||||
|
||||
public void OnButtonPressed()
|
||||
private void OnButtonPressed()
|
||||
{
|
||||
SetProcess(!IsProcessing());
|
||||
}
|
||||
|
||||
public void OnTimerTimeout()
|
||||
private void OnTimerTimeout()
|
||||
{
|
||||
Visible = !Visible;
|
||||
}
|
||||
@@ -425,12 +425,12 @@ reaches 0.
|
||||
|
||||
using Godot;
|
||||
|
||||
public partial class CustomSignal : Node2D
|
||||
public partial class MyNode2D : Node2D
|
||||
{
|
||||
[Signal]
|
||||
public delegate void HealthDepletedEventHandler();
|
||||
|
||||
private int Health = 10;
|
||||
private int _health = 10;
|
||||
}
|
||||
|
||||
.. note:: As signals represent events that just occurred, we generally use an
|
||||
@@ -455,9 +455,9 @@ To emit a signal in your scripts, call ``emit()`` on the signal.
|
||||
|
||||
public void TakeDamage(int amount)
|
||||
{
|
||||
Health -= amount;
|
||||
_health -= amount;
|
||||
|
||||
if (Health < 0)
|
||||
if (_health <= 0)
|
||||
{
|
||||
EmitSignal(SignalName.HealthDepleted);
|
||||
}
|
||||
@@ -479,12 +479,12 @@ names between parentheses:
|
||||
|
||||
using Godot;
|
||||
|
||||
public partial class CustomSignal : Node
|
||||
public partial class MyNode : Node
|
||||
{
|
||||
[Signal]
|
||||
public delegate void HealthChangedEventHandler(int oldValue, int newValue);
|
||||
|
||||
private int Health = 10;
|
||||
private int _health = 10;
|
||||
}
|
||||
|
||||
.. note::
|
||||
@@ -509,9 +509,9 @@ To emit values along with the signal, add them as extra arguments to the
|
||||
|
||||
public void TakeDamage(int amount)
|
||||
{
|
||||
var oldHealth = Health;
|
||||
Health -= amount;
|
||||
EmitSignal(SignalName.HealthChanged, oldHealth, Health);
|
||||
int oldHealth = _health;
|
||||
_health -= amount;
|
||||
EmitSignal(SignalName.HealthChanged, oldHealth, _health);
|
||||
}
|
||||
|
||||
Summary
|
||||
|
||||
Reference in New Issue
Block a user