Removed tabs.

This commit is contained in:
Relintai 2023-01-12 18:31:02 +01:00
parent 108cb20c13
commit 3beb0ac5d1
85 changed files with 1127 additions and 6313 deletions

View File

@ -203,20 +203,12 @@ workflow.
The above will display as:
.. tabs::
.. code-tab:: gdscript GDScript
gdscript GDScript
```
func _ready():
var sprite = get_node("Sprite")
print(sprite.get_pos())
.. code-tab:: csharp
public override void _Ready()
{
var sprite = GetNode("Sprite");
GD.Print(sprite.GetPos());
}
```
To denote important information, add a paragraph starting with "[b]Note:[/b]" at
the end of the description:

View File

@ -47,18 +47,18 @@ Step by step
2. Set this key as environment variable in the console that you will use to
compile Godot, like this:
.. tabs::
.. code-tab:: bash Linux/macOS
export SCRIPT_AES256_ENCRYPTION_KEY="your_generated_key"
bash Linux/macOS
.. code-tab:: bat Windows (cmd)
``` export SCRIPT_AES256_ENCRYPTION_KEY="your_generated_key" ```
set SCRIPT_AES256_ENCRYPTION_KEY=your_generated_key
bat Windows (cmd)
.. code-tab:: bat Windows (PowerShell)
``` set SCRIPT_AES256_ENCRYPTION_KEY=your_generated_key ````
$env:SCRIPT_AES256_ENCRYPTION_KEY="your_generated_key"
bat Windows (PowerShell)
``` $env:SCRIPT_AES256_ENCRYPTION_KEY="your_generated_key" ```
3. Compile Godot export templates and set them as custom export templates
in the export preset options.

View File

@ -297,18 +297,17 @@ options automatically without having to supply them via the command line.
For instance, you may want to build Godot in parallel with the aforementioned
``-j`` option for all the future builds:
.. tabs::
.. code-tab:: bash Linux/macOS
bash Linux/macOS
export SCONSFLAGS="-j4"
``` export SCONSFLAGS="-j4" ```
.. code-tab:: bat Windows (cmd)
bat Windows (cmd)
set SCONSFLAGS=-j4
``` set SCONSFLAGS=-j4 ```
.. code-tab:: powershell Windows (powershell)
powershell Windows (powershell)
$env:SCONSFLAGS="-j4"
``` $env:SCONSFLAGS="-j4" ```
Export templates
----------------

View File

@ -33,9 +33,9 @@ Importing the project
- Within the ``tasks.json`` file find the ``"tasks"`` array and add a new section to it:
.. tabs::
.. code-tab:: js Linux/X11
js Linux/X11
```
{
"label": "build",
"group": "build",
@ -46,9 +46,11 @@ Importing the project
],
"problemMatcher": "$msCompile"
}
```
.. code-tab:: js Windows
js Windows
```
{
"label": "build",
"group": "build",
@ -62,6 +64,7 @@ Importing the project
],
"problemMatcher": "$msCompile"
}
```
.. figure:: img/vscode_3_tasks.json.png
:figclass: figure-w480
@ -87,9 +90,9 @@ To run and debug the project you need to create a new configuration in the ``lau
adjust the configuration example provided accordingly.
- Within the ``launch.json`` file find the ``"configurations"`` array and add a new section to it:
.. tabs::
.. code-tab:: js X11
js X11
```
{
"name": "Launch Project",
"type": "lldb",
@ -105,8 +108,12 @@ To run and debug the project you need to create a new configuration in the ``lau
"externalConsole": false,
"preLaunchTask": "build"
}
.. code-tab:: js X11_gdb
```
js X11_gdb
```
{
"name": "Launch Project",
"type": "cppdbg",
@ -130,9 +137,11 @@ To run and debug the project you need to create a new configuration in the ``lau
],
"preLaunchTask": "build"
}
```
.. code-tab:: js Windows
js Windows
```
{
"name": "Launch Project",
"type": "cppvsdbg",
@ -148,6 +157,7 @@ To run and debug the project you need to create a new configuration in the ``lau
"visualizerFile": "${workspaceFolder}/platform/windows/godot.natvis",
"preLaunchTask": "build"
}
```
.. figure:: img/vscode_2_launch.json.png
:figclass: figure-w480

View File

@ -230,15 +230,14 @@ Using the module
You can now use your newly created module from any script:
.. tabs::
.. code-tab:: gdscript GDScript
```
var s = Summator.new()
s.add(10)
s.add(20)
s.add(30)
print(s.get_total())
s.reset()
```
The output will be ``60``.

View File

@ -9,40 +9,14 @@ Launch Godot and create a new project.
.. image:: img/new-project-button.png
.. tabs::
.. tab:: GDScript
GDScript
```
Download :download:`dodge_assets.zip <files/dodge_assets.zip>`.
The archive contains the images and sounds you'll be using
to make the game. Extract the archive and move the ``art/``
and ``fonts/`` directories to your project's directory.
.. tab:: C#
Download :download:`dodge_assets.zip <files/dodge_assets.zip>`.
The archive contains the images and sounds you'll be using
to make the game. Extract the archive and move the ``art/``
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.
See :ref:`doc_c_sharp_setup`.
.. tab:: GDNative C++
Download :download:`dodge_assets_with_gdnative.zip
<files/dodge_assets_with_gdnative.zip>`.
The archive contains the images and sounds you'll be using
to make the game. It also contains a starter GDNative project
including a ``SConstruct`` file, a ``dodge_the_creeps.gdnlib``
file, a ``player.gdns`` file, and an ``entry.cpp`` file.
Ensure that you have the required dependencies to use GDNative C++.
You need a C++ compiler such as GCC or Clang or MSVC that supports C++14.
On Windows you can download Visual Studio 2019 and select the C++ workload.
You also need SCons to use the build system (the SConstruct file).
Then you need to `download the Godot C++ bindings <https://github.com/godotengine/godot-cpp>`_
and place them in your project.
```
Your project folder should look like this.

View File

@ -25,63 +25,14 @@ click "Create":
Start by declaring the member variables this object will need:
.. tabs::
.. code-tab:: gdscript GDScript
gdscript GDScript
```
extends Area2D
export var speed = 400 # How fast the player will move (pixels/sec).
var screen_size # Size of the game window.
.. code-tab:: csharp
using Godot;
using System;
public class Player : Area2D
{
[Export]
public int Speed = 400; // How fast the player will move (pixels/sec).
public Vector2 ScreenSize; // Size of the game window.
}
.. code-tab:: cpp
// A `player.gdns` file has already been created for you. Attach it to the Player node.
// Create two files `player.cpp` and `player.hpp` next to `entry.cpp` in `src`.
// This code goes in `player.hpp`. We also define the methods we'll be using here.
#ifndef PLAYER_H
#define PLAYER_H
#include <AnimatedSprite.hpp>
#include <Area2D.hpp>
#include <CollisionShape2D.hpp>
#include <Godot.hpp>
#include <Input.hpp>
class Player : public godot::Area2D {
GODOT_CLASS(Player, godot::Area2D)
godot::AnimatedSprite *_animated_sprite;
godot::CollisionShape2D *_collision_shape;
godot::Input *_input;
godot::Vector2 _screen_size; // Size of the game window.
public:
real_t speed = 400; // How fast the player will move (pixels/sec).
void _init() {}
void _ready();
void _process(const double p_delta);
void start(const godot::Vector2 p_position);
void _on_Player_body_entered(godot::Node2D *_body);
static void _register_methods();
};
#endif // PLAYER_H
```
Using the ``export`` keyword on the first variable ``speed`` allows us to set
its value in the Inspector. This can be handy for values that you want to be
@ -90,41 +41,15 @@ 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.
.. 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():
screen_size = get_viewport_rect().size
.. code-tab:: csharp
public override void _Ready()
{
ScreenSize = GetViewportRect().Size;
}
.. code-tab:: cpp
// This code goes in `player.cpp`.
#include "player.hpp"
void Player::_ready() {
_animated_sprite = get_node<godot::AnimatedSprite>("AnimatedSprite");
_collision_shape = get_node<godot::CollisionShape2D>("CollisionShape2D");
_input = godot::Input::get_singleton();
_screen_size = get_viewport_rect().size;
}
```
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
@ -173,9 +98,8 @@ Click the "Close" button to close the project settings.
You can detect whether a key is pressed using ``Input.is_action_pressed()``,
which returns ``true`` if it's pressed or ``false`` if it isn't.
.. tabs::
.. code-tab:: gdscript GDScript
gdscript GDScript
```
func _process(delta):
var velocity = Vector2.ZERO # The player's movement vector.
if Input.is_action_pressed("move_right"):
@ -192,62 +116,7 @@ which returns ``true`` if it's pressed or ``false`` if it isn't.
$AnimatedSprite.play()
else:
$AnimatedSprite.stop()
.. code-tab:: csharp
public override void _Process(float delta)
{
var velocity = Vector2.Zero; // The player's movement vector.
if (Input.IsActionPressed("move_right"))
{
velocity.x += 1;
}
if (Input.IsActionPressed("move_left"))
{
velocity.x -= 1;
}
if (Input.IsActionPressed("move_down"))
{
velocity.y += 1;
}
if (Input.IsActionPressed("move_up"))
{
velocity.y -= 1;
}
var animatedSprite = GetNode<AnimatedSprite>("AnimatedSprite");
if (velocity.Length() > 0)
{
velocity = velocity.Normalized() * Speed;
animatedSprite.Play();
}
else
{
animatedSprite.Stop();
}
}
.. code-tab:: cpp
// This code goes in `player.cpp`.
void Player::_process(const double p_delta) {
godot::Vector2 velocity(0, 0);
velocity.x = _input->get_action_strength("move_right") - _input->get_action_strength("move_left");
velocity.y = _input->get_action_strength("move_down") - _input->get_action_strength("move_up");
if (velocity.length() > 0) {
velocity = velocity.normalized() * speed;
_animated_sprite->play();
} else {
_animated_sprite->stop();
}
}
```
We start by setting the ``velocity`` to ``(0, 0)`` - by default, the player
should not be moving. Then we check each input and add/subtract from the
@ -281,28 +150,13 @@ can also use ``clamp()`` to prevent it from leaving the screen. *Clamping* a
value means restricting it to a given range. Add the following to the bottom of
the ``_process`` function (make sure it's not indented under the `else`):
.. tabs::
.. code-tab:: gdscript GDScript
gdscript GDScript
```
position += velocity * delta
position.x = clamp(position.x, 0, screen_size.x)
position.y = clamp(position.y, 0, screen_size.y)
.. code-tab:: csharp
Position += velocity * delta;
Position = new Vector2(
x: Mathf.Clamp(Position.x, 0, ScreenSize.x),
y: Mathf.Clamp(Position.y, 0, ScreenSize.y)
);
.. code-tab:: cpp
godot::Vector2 position = get_position();
position += velocity * (real_t)p_delta;
position.x = godot::Math::clamp(position.x, (real_t)0.0, _screen_size.x);
position.y = godot::Math::clamp(position.y, (real_t)0.0, _screen_size.y);
set_position(position);
```
.. tip:: The `delta` parameter in the `_process()` function refers to the *frame
length* - the amount of time that the previous frame took to complete.
@ -331,9 +185,9 @@ horizontally using the ``flip_h`` property for left movement. We also have the
"up" animation, which should be flipped vertically with ``flip_v`` for downward
movement. Let's place this code at the end of the ``_process()`` function:
.. tabs::
.. code-tab:: gdscript GDScript
gdscript GDScript
```
if velocity.x != 0:
$AnimatedSprite.animation = "walk"
$AnimatedSprite.flip_v = false
@ -342,57 +196,21 @@ movement. Let's place this code at the end of the ``_process()`` function:
elif velocity.y != 0:
$AnimatedSprite.animation = "up"
$AnimatedSprite.flip_v = velocity.y > 0
.. code-tab:: csharp
if (velocity.x != 0)
{
animatedSprite.Animation = "walk";
animatedSprite.FlipV = false;
// See the note below about boolean assignment.
animatedSprite.FlipH = velocity.x < 0;
}
else if (velocity.y != 0)
{
animatedSprite.Animation = "up";
animatedSprite.FlipV = velocity.y > 0;
}
.. code-tab:: cpp
if (velocity.x != 0) {
_animated_sprite->set_animation("walk");
_animated_sprite->set_flip_v(false);
// See the note below about boolean assignment.
_animated_sprite->set_flip_h(velocity.x < 0);
} else if (velocity.y != 0) {
_animated_sprite->set_animation("up");
_animated_sprite->set_flip_v(velocity.y > 0);
}
```
.. Note:: The boolean assignments in the code above are a common shorthand for
programmers. Since we're doing a comparison test (boolean) and also
*assigning* a boolean value, we can do both at the same time. Consider
this code versus the one-line boolean assignment above:
.. tabs::
.. code-tab :: gdscript GDScript
```
if velocity.x < 0:
$AnimatedSprite.flip_h = true
else:
$AnimatedSprite.flip_h = false
.. code-tab:: csharp
if (velocity.x < 0)
{
animatedSprite.FlipH = true;
}
else
{
animatedSprite.FlipH = false;
}
```
Play the scene again and check that the animations are correct in each of the
directions.
@ -405,18 +223,11 @@ 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
gdscript GDScript
```
hide()
.. code-tab:: csharp
Hide();
.. code-tab:: cpp
hide();
```
Preparing for collisions
~~~~~~~~~~~~~~~~~~~~~~~~
@ -427,32 +238,11 @@ functionality to make it work.
Add the following at the top of the script, after ``extends Area2D``:
.. tabs::
.. code-tab:: gdscript GDScript
gdscript GDScript
```
signal hit
.. code-tab:: csharp
// Don't forget to rebuild the project so the editor knows about the new signal.
[Signal]
public delegate void Hit();
.. code-tab:: cpp
// This code goes in `player.cpp`.
// We need to register the signal here, and while we're here, we can also
// register the other methods and register the speed property.
void Player::_register_methods() {
godot::register_method("_ready", &Player::_ready);
godot::register_method("_process", &Player::_process);
godot::register_method("start", &Player::start);
godot::register_method("_on_Player_body_entered", &Player::_on_Player_body_entered);
godot::register_property("speed", &Player::speed, (real_t)400.0);
// This below line is the signal.
godot::register_signal<Player>("hit", godot::Dictionary());
}
```
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
@ -473,34 +263,15 @@ your player's script.
Note the green icon indicating that a signal is connected to this function. Add
this code to the function:
.. tabs::
.. code-tab:: gdscript GDScript
gdscript GDScript
```
func _on_Player_body_entered(body):
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
public void OnPlayerBodyEntered(PhysicsBody2D body)
{
Hide(); // Player disappears after being hit.
EmitSignal(nameof(Hit));
// Must be deferred as we can't change physics properties on a physics callback.
GetNode<CollisionShape2D>("CollisionShape2D").SetDeferred("disabled", true);
}
.. code-tab:: cpp
// This code goes in `player.cpp`.
void Player::_on_Player_body_entered(godot::Node2D *_body) {
hide(); // Player disappears after being hit.
emit_signal("hit");
// Must be deferred as we can't change physics properties on a physics callback.
_collision_shape->set_deferred("disabled", true);
}
```
Each time an enemy hits the player, the signal is going to be emitted. We need
to disable the player's collision so that we don't trigger the ``hit`` signal
@ -514,30 +285,13 @@ more than once.
The last piece is to add a function we can call to reset the player when
starting a new game.
.. tabs::
.. code-tab:: gdscript GDScript
gdscript GDScript
```
func start(pos):
position = pos
show()
$CollisionShape2D.disabled = false
.. code-tab:: csharp
public void Start(Vector2 pos)
{
Position = pos;
Show();
GetNode<CollisionShape2D>("CollisionShape2D").Disabled = false;
}
.. code-tab:: cpp
// This code goes in `player.cpp`.
void Player::start(const godot::Vector2 p_position) {
set_position(p_position);
show();
_collision_shape->set_disabled(false);
}
```
With the player working, we'll work on the enemy in the next lesson.

View File

@ -58,84 +58,23 @@ Enemy script
Add a script to the ``Mob`` like this:
.. tabs::
.. code-tab:: gdscript GDScript
gdscript GDScript
```
extends RigidBody2D
.. code-tab:: csharp
public class Mob : RigidBody2D
{
// Don't forget to rebuild the project.
}
.. code-tab:: cpp
// Copy `player.gdns` to `mob.gdns` and replace `Player` with `Mob`.
// Attach the `mob.gdns` file to the Mob node.
// Create two files `mob.cpp` and `mob.hpp` next to `entry.cpp` in `src`.
// This code goes in `mob.hpp`. We also define the methods we'll be using here.
#ifndef MOB_H
#define MOB_H
#include <AnimatedSprite.hpp>
#include <Godot.hpp>
#include <RigidBody2D.hpp>
class Mob : public godot::RigidBody2D {
GODOT_CLASS(Mob, godot::RigidBody2D)
godot::AnimatedSprite *_animated_sprite;
public:
void _init() {}
void _ready();
void _on_VisibilityNotifier2D_screen_exited();
static void _register_methods();
};
#endif // MOB_H
```
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
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
public override void _Ready()
{
var animSprite = GetNode<AnimatedSprite>("AnimatedSprite");
animSprite.Playing = true;
string[] mobTypes = animSprite.Frames.GetAnimationNames();
animSprite.Animation = mobTypes[GD.Randi() % mobTypes.Length];
}
.. code-tab:: cpp
// This code goes in `mob.cpp`.
#include "mob.hpp"
#include <RandomNumberGenerator.hpp>
#include <SpriteFrames.hpp>
void Mob::_ready() {
godot::Ref<godot::RandomNumberGenerator> random = godot::RandomNumberGenerator::_new();
random->randomize();
_animated_sprite = get_node<godot::AnimatedSprite>("AnimatedSprite");
_animated_sprite->_set_playing(true);
godot::PoolStringArray mob_types = _animated_sprite->get_sprite_frames()->get_animation_names();
_animated_sprite->set_animation(mob_types[random->randi() % mob_types.size()]);
}
```
First, we get the list of animation names from the AnimatedSprite's ``frames``
property. This returns an Array containing all three animation names: ``["walk",
@ -153,25 +92,12 @@ The last piece is to make the mobs delete themselves when they leave the screen.
Connect the ``screen_exited()`` signal of the ``VisibilityNotifier2D`` node and
add this code:
.. tabs::
.. code-tab:: gdscript GDScript
gdscript GDScript
```
func _on_VisibilityNotifier2D_screen_exited():
queue_free()
.. code-tab:: csharp
public void OnVisibilityNotifier2DScreenExited()
{
QueueFree();
}
.. code-tab:: cpp
// This code goes in `mob.cpp`.
void Mob::_on_VisibilityNotifier2D_screen_exited() {
queue_free();
}
```
This completes the `Mob` scene.

View File

@ -76,121 +76,24 @@ Main script
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
gdscript GDScript
```
extends Node
export(PackedScene) var mob_scene
var score
.. code-tab:: csharp
public class Main : Node
{
// 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 int Score;
}
.. code-tab:: cpp
// Copy `player.gdns` to `main.gdns` and replace `Player` with `Main`.
// Attach the `main.gdns` file to the Main node.
// Create two files `main.cpp` and `main.hpp` next to `entry.cpp` in `src`.
// This code goes in `main.hpp`. We also define the methods we'll be using here.
#ifndef MAIN_H
#define MAIN_H
#include <AudioStreamPlayer.hpp>
#include <CanvasLayer.hpp>
#include <Godot.hpp>
#include <Node.hpp>
#include <PackedScene.hpp>
#include <PathFollow2D.hpp>
#include <RandomNumberGenerator.hpp>
#include <Timer.hpp>
#include "hud.hpp"
#include "player.hpp"
class Main : public godot::Node {
GODOT_CLASS(Main, godot::Node)
int score;
HUD *_hud;
Player *_player;
godot::Node2D *_start_position;
godot::PathFollow2D *_mob_spawn_location;
godot::Timer *_mob_timer;
godot::Timer *_score_timer;
godot::Timer *_start_timer;
godot::AudioStreamPlayer *_music;
godot::AudioStreamPlayer *_death_sound;
godot::Ref<godot::RandomNumberGenerator> _random;
public:
godot::Ref<godot::PackedScene> mob_scene;
void _init() {}
void _ready();
void game_over();
void new_game();
void _on_MobTimer_timeout();
void _on_ScoreTimer_timeout();
void _on_StartTimer_timeout();
static void _register_methods();
};
#endif // MAIN_H
```
We also add a call to ``randomize()`` here so that the random number
generator generates different random numbers each time the game is run:
.. tabs::
.. code-tab:: gdscript GDScript
gdscript GDScript
```
func _ready():
randomize()
.. code-tab:: csharp
public override void _Ready()
{
GD.Randomize();
}
.. code-tab:: cpp
// This code goes in `main.cpp`.
#include "main.hpp"
#include <SceneTree.hpp>
#include "mob.hpp"
void Main::_ready() {
_hud = get_node<HUD>("HUD");
_player = get_node<Player>("Player");
_start_position = get_node<godot::Node2D>("StartPosition");
_mob_spawn_location = get_node<godot::PathFollow2D>("MobPath/MobSpawnLocation");
_mob_timer = get_node<godot::Timer>("MobTimer");
_score_timer = get_node<godot::Timer>("ScoreTimer");
_start_timer = get_node<godot::Timer>("StartTimer");
// Uncomment these after adding the nodes in the "Sound effects" section of "Finishing up".
//_music = get_node<godot::AudioStreamPlayer>("Music");
//_death_sound = get_node<godot::AudioStreamPlayer>("DeathSound");
_random = (godot::Ref<godot::RandomNumberGenerator>)godot::RandomNumberGenerator::_new();
_random->randomize();
}
```
Click the ``Main`` node and you will see the ``Mob Scene`` property in the Inspector
under "Script Variables".
@ -213,9 +116,9 @@ signal connection dialog and click "Connect". Add the following code to the new
function, as well as a ``new_game`` function that will set everything up for a
new game:
.. tabs::
.. code-tab:: gdscript GDScript
gdscript GDScript
```
func game_over():
$ScoreTimer.stop()
$MobTimer.stop()
@ -224,89 +127,22 @@ new game:
score = 0
$Player.start($StartPosition.position)
$StartTimer.start()
.. code-tab:: csharp
public void GameOver()
{
GetNode<Timer>("MobTimer").Stop();
GetNode<Timer>("ScoreTimer").Stop();
}
public void NewGame()
{
Score = 0;
var player = GetNode<Player>("Player");
var startPosition = GetNode<Position2D>("StartPosition");
player.Start(startPosition.Position);
GetNode<Timer>("StartTimer").Start();
}
.. code-tab:: cpp
// This code goes in `main.cpp`.
void Main::game_over() {
_score_timer->stop();
_mob_timer->stop();
}
void Main::new_game() {
score = 0;
_player->start(_start_position->get_position());
_start_timer->start();
}
```
Now connect the ``timeout()`` signal of each of the Timer nodes (``StartTimer``,
``ScoreTimer`` , and ``MobTimer``) to the main script. ``StartTimer`` will start
the other two timers. ``ScoreTimer`` will increment the score by 1.
.. tabs::
.. code-tab:: gdscript GDScript
gdscript GDScript
```
func _on_ScoreTimer_timeout():
score += 1
func _on_StartTimer_timeout():
$MobTimer.start()
$ScoreTimer.start()
.. code-tab:: csharp
public void OnScoreTimerTimeout()
{
Score++;
}
public void OnStartTimerTimeout()
{
GetNode<Timer>("MobTimer").Start();
GetNode<Timer>("ScoreTimer").Start();
}
.. code-tab:: cpp
// This code goes in `main.cpp`.
void Main::_on_ScoreTimer_timeout() {
score += 1;
}
void Main::_on_StartTimer_timeout() {
_mob_timer->start();
_score_timer->start();
}
// Also add this to register all methods and the mob scene property.
void Main::_register_methods() {
godot::register_method("_ready", &Main::_ready);
godot::register_method("game_over", &Main::game_over);
godot::register_method("new_game", &Main::new_game);
godot::register_method("_on_MobTimer_timeout", &Main::_on_MobTimer_timeout);
godot::register_method("_on_ScoreTimer_timeout", &Main::_on_ScoreTimer_timeout);
godot::register_method("_on_StartTimer_timeout", &Main::_on_StartTimer_timeout);
godot::register_property("mob_scene", &Main::mob_scene, (godot::Ref<godot::PackedScene>)nullptr);
}
```
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
@ -318,9 +154,9 @@ all moving at the same speed).
Note that a new instance must be added to the scene using ``add_child()``.
.. tabs::
.. code-tab:: gdscript GDScript
gdscript GDScript
```
func _on_MobTimer_timeout():
# Create a new instance of the Mob scene.
var mob = mob_scene.instance()
@ -345,67 +181,7 @@ Note that a new instance must be added to the scene using ``add_child()``.
# Spawn the mob by adding it to the Main scene.
add_child(mob)
.. 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 Mob and PathFollow2D, since they appear later on the line.
// Create a new instance of the Mob scene.
var mob = (Mob)MobScene.Instance();
// Choose a random location on Path2D.
var mobSpawnLocation = GetNode<PathFollow2D>("MobPath/MobSpawnLocation");
mobSpawnLocation.Offset = GD.Randi();
// 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.
mob.Position = mobSpawnLocation.Position;
// Add some randomness to the direction.
direction += (float)GD.RandRange(-Mathf.Pi / 4, Mathf.Pi / 4);
mob.Rotation = direction;
// Choose the velocity.
var velocity = new Vector2((float)GD.RandRange(150.0, 250.0), 0);
mob.LinearVelocity = velocity.Rotated(direction);
// Spawn the mob by adding it to the Main scene.
AddChild(mob);
}
.. code-tab:: cpp
// This code goes in `main.cpp`.
void Main::_on_MobTimer_timeout() {
// Create a new instance of the Mob scene.
godot::Node *mob = mob_scene->instance();
// Choose a random location on Path2D.
_mob_spawn_location->set_offset((real_t)_random->randi());
// Set the mob's direction perpendicular to the path direction.
real_t direction = _mob_spawn_location->get_rotation() + (real_t)Math_PI / 2;
// Set the mob's position to a random location.
mob->set("position", _mob_spawn_location->get_position());
// Add some randomness to the direction.
direction += _random->randf_range((real_t)-Math_PI / 4, (real_t)Math_PI / 4);
mob->set("rotation", direction);
// Choose the velocity for the mob.
godot::Vector2 velocity = godot::Vector2(_random->randf_range(150.0, 250.0), 0.0);
mob->set("linear_velocity", velocity.rotated(direction));
// Spawn the mob by adding it to the Main scene.
add_child(mob);
}
```
.. important:: Why ``PI``? In functions requiring angles, Godot uses *radians*,
not degrees. Pi represents a half turn in radians, about
@ -420,26 +196,13 @@ Testing the scene
Let's test the scene to make sure everything is working. Add this ``new_game``
call to ``_ready()``:
.. tabs::
.. code-tab:: gdscript GDScript
gdscript GDScript
```
func _ready():
randomize()
new_game()
.. code-tab:: csharp
public override void _Ready()
{
NewGame();
}
.. code-tab:: cpp
// This code goes in `main.cpp`.
void Main::_ready() {
new_game();
}
```
Let's also assign ``Main`` as our "Main Scene" - the one that runs automatically
when the game launches. Press the "Play" button and select ``Main.tscn`` when

View File

@ -97,117 +97,32 @@ property to "On".
Now add this script to ``HUD``:
.. tabs::
.. code-tab:: gdscript GDScript
gdscript GDScript
```
extends CanvasLayer
signal start_game
.. code-tab:: csharp
public class HUD : CanvasLayer
{
// Don't forget to rebuild the project so the editor knows about the new signal.
[Signal]
public delegate void StartGame();
}
.. code-tab:: cpp
// Copy `player.gdns` to `hud.gdns` and replace `Player` with `HUD`.
// Attach the `hud.gdns` file to the HUD node.
// Create two files `hud.cpp` and `hud.hpp` next to `entry.cpp` in `src`.
// This code goes in `hud.hpp`. We also define the methods we'll be using here.
#ifndef HUD_H
#define HUD_H
#include <Button.hpp>
#include <CanvasLayer.hpp>
#include <Godot.hpp>
#include <Label.hpp>
#include <Timer.hpp>
class HUD : public godot::CanvasLayer {
GODOT_CLASS(HUD, godot::CanvasLayer)
godot::Label *_score_label;
godot::Label *_message_label;
godot::Timer *_start_message_timer;
godot::Timer *_get_ready_message_timer;
godot::Button *_start_button;
godot::Timer *_start_button_timer;
public:
void _init() {}
void _ready();
void show_get_ready();
void show_game_over();
void update_score(const int score);
void _on_StartButton_pressed();
void _on_StartMessageTimer_timeout();
void _on_GetReadyMessageTimer_timeout();
static void _register_methods();
};
#endif // HUD_H
```
The ``start_game`` signal tells the ``Main`` node that the button
has been pressed.
.. tabs::
.. code-tab:: gdscript GDScript
gdscript GDScript
```
func show_message(text):
$Message.text = text
$Message.show()
$MessageTimer.start()
.. code-tab:: csharp
public void ShowMessage(string text)
{
var message = GetNode<Label>("Message");
message.Text = text;
message.Show();
GetNode<Timer>("MessageTimer").Start();
}
.. code-tab:: cpp
// This code goes in `hud.cpp`.
#include "hud.hpp"
void HUD::_ready() {
_score_label = get_node<godot::Label>("ScoreLabel");
_message_label = get_node<godot::Label>("MessageLabel");
_start_message_timer = get_node<godot::Timer>("StartMessageTimer");
_get_ready_message_timer = get_node<godot::Timer>("GetReadyMessageTimer");
_start_button = get_node<godot::Button>("StartButton");
_start_button_timer = get_node<godot::Timer>("StartButtonTimer");
}
void HUD::_register_methods() {
godot::register_method("_ready", &HUD::_ready);
godot::register_method("show_get_ready", &HUD::show_get_ready);
godot::register_method("show_game_over", &HUD::show_game_over);
godot::register_method("update_score", &HUD::update_score);
godot::register_method("_on_StartButton_pressed", &HUD::_on_StartButton_pressed);
godot::register_method("_on_StartMessageTimer_timeout", &HUD::_on_StartMessageTimer_timeout);
godot::register_method("_on_GetReadyMessageTimer_timeout", &HUD::_on_GetReadyMessageTimer_timeout);
godot::register_signal<HUD>("start_game", godot::Dictionary());
}
```
This function is called when we want to display a message
temporarily, such as "Get Ready".
.. tabs::
.. code-tab:: gdscript GDScript
gdscript GDScript
```
func show_game_over():
show_message("Game Over")
# Wait until the MessageTimer has counted down.
@ -218,40 +133,7 @@ temporarily, such as "Get Ready".
# Make a one-shot timer and wait for it to finish.
yield(get_tree().create_timer(1), "timeout")
$StartButton.show()
.. code-tab:: csharp
async public void ShowGameOver()
{
ShowMessage("Game Over");
var messageTimer = GetNode<Timer>("MessageTimer");
await ToSignal(messageTimer, "timeout");
var message = GetNode<Label>("Message");
message.Text = "Dodge the\nCreeps!";
message.Show();
await ToSignal(GetTree().CreateTimer(1), "timeout");
GetNode<Button>("StartButton").Show();
}
.. code-tab:: cpp
// This code goes in `hud.cpp`.
// There is no `yield` in GDNative, so we need to have every
// step be its own method that is called on timer timeout.
void HUD::show_get_ready() {
_message_label->set_text("Get Ready");
_message_label->show();
_get_ready_message_timer->start();
}
void HUD::show_game_over() {
_message_label->set_text("Game Over");
_message_label->show();
_start_message_timer->start();
}
```
This function is called when the player loses. It will show "Game Over" for 2
seconds, then return to the title screen and, after a brief pause, show the
@ -262,72 +144,28 @@ seconds, then return to the title screen and, after a brief pause, show the
can be very useful to add delays such as in the above code, where we
want to wait some time before showing the "Start" button.
.. tabs::
.. code-tab:: gdscript GDScript
gdscript GDScript
```
func update_score(score):
$ScoreLabel.text = str(score)
.. code-tab:: csharp
public void UpdateScore(int score)
{
GetNode<Label>("ScoreLabel").Text = score.ToString();
}
.. code-tab:: cpp
// This code goes in `hud.cpp`.
void HUD::update_score(const int p_score) {
_score_label->set_text(godot::Variant(p_score));
}
```
This function is called by ``Main`` whenever the score changes.
Connect the ``timeout()`` signal of ``MessageTimer`` and the ``pressed()``
signal of ``StartButton`` and add the following code to the new functions:
.. tabs::
.. code-tab:: gdscript GDScript
gdscript GDScript
```
func _on_StartButton_pressed():
$StartButton.hide()
emit_signal("start_game")
func _on_MessageTimer_timeout():
$Message.hide()
.. code-tab:: csharp
public void OnStartButtonPressed()
{
GetNode<Button>("StartButton").Hide();
EmitSignal("StartGame");
}
public void OnMessageTimerTimeout()
{
GetNode<Label>("Message").Hide();
}
.. code-tab:: cpp
// This code goes in `hud.cpp`.
void HUD::_on_StartButton_pressed() {
_start_button_timer->stop();
_start_button->hide();
emit_signal("start_game");
}
void HUD::_on_StartMessageTimer_timeout() {
_message_label->set_text("Dodge the\nCreeps");
_message_label->show();
_start_button_timer->start();
}
void HUD::_on_GetReadyMessageTimer_timeout() {
_message_label->hide();
}
```
Connecting HUD to Main
~~~~~~~~~~~~~~~~~~~~~~
@ -348,53 +186,29 @@ next to ``func new_game()`` in the script.
In ``new_game()``, update the score display and show the "Get Ready" message:
.. tabs::
.. code-tab:: gdscript GDScript
gdscript GDScript
```
$HUD.update_score(score)
$HUD.show_message("Get Ready")
.. code-tab:: csharp
var hud = GetNode<HUD>("HUD");
hud.UpdateScore(Score);
hud.ShowMessage("Get Ready!");
.. code-tab:: cpp
_hud->update_score(score);
_hud->show_get_ready();
```
In ``game_over()`` we need to call the corresponding ``HUD`` function:
.. tabs::
.. code-tab:: gdscript GDScript
gdscript GDScript
```
$HUD.show_game_over()
.. code-tab:: csharp
GetNode<HUD>("HUD").ShowGameOver();
.. code-tab:: cpp
_hud->show_game_over();
```
Finally, add this to ``_on_ScoreTimer_timeout()`` to keep the display in sync
with the changing score:
.. tabs::
.. code-tab:: gdscript GDScript
gdscript GDScript
```
$HUD.update_score(score)
.. code-tab:: csharp
GetNode<HUD>("HUD").UpdateScore(Score);
.. code-tab:: cpp
_hud->update_score(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``.
@ -416,20 +230,11 @@ click "Groups" and you can type a new group name and click "Add".
Now all mobs will be in the "mobs" group. We can then add the following line to
the ``new_game()`` function in ``Main``:
.. tabs::
.. code-tab:: gdscript GDScript
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");
.. code-tab:: cpp
get_tree()->call_group("mobs", "queue_free");
```
The ``call_group()`` function calls the named function on every node in a
group - in this case we are telling every mob to delete itself.

View File

@ -16,9 +16,9 @@ Let's start with the class's properties. We're going to define a movement speed,
a fall acceleration representing gravity, and a velocity we'll use to move the
character.
.. tabs::
.. code-tab:: gdscript GDScript
gdscript GDScript
```
extends KinematicBody
# How fast the player moves in meters per second.
@ -27,22 +27,7 @@ character.
export var fall_acceleration = 75
var velocity = Vector3.ZERO
.. code-tab:: csharp
public class Player : KinematicBody
{
// Don't forget to rebuild the project so the editor knows about the new export variable.
// How fast the player moves in meters per second.
[Export]
public int Speed = 14;
// The downward acceleration when in the air, in meters per second squared.
[Export]
public int FallAcceleration = 75;
private Vector3 _velocity = Vector3.Zero;
}
```
These are common properties for a moving body. The ``velocity`` is a 3D vector
@ -58,9 +43,9 @@ we want to update and reuse its value across frames.
Let's code the movement now. We start by calculating the input direction vector
using the global ``Input`` object, in ``_physics_process()``.
.. tabs::
.. code-tab:: gdscript GDScript
gdscript GDScript
```
func _physics_process(delta):
# We create a local variable to store the input direction.
var direction = Vector3.ZERO
@ -76,34 +61,7 @@ using the global ``Input`` object, in ``_physics_process()``.
direction.z += 1
if Input.is_action_pressed("move_forward"):
direction.z -= 1
.. code-tab:: csharp
public override void _PhysicsProcess(float 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;
}
if (Input.IsActionPressed("move_left"))
{
direction.x -= 1f;
}
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;
}
if (Input.IsActionPressed("move_forward"))
{
direction.z -= 1f;
}
}
```
Here, we're going to make all calculations using the ``_physics_process()``
virtual function. Like ``_process()``, it allows you to update the node every
@ -127,28 +85,16 @@ have a length of about ``1.4``. But if they press a single key, it will have a
length of ``1``. We want the vector's length to be consistent. To do so, we can
call its ``normalize()`` method.
.. tabs::
.. code-tab:: gdscript GDScript
gdscript GDScript
```
#func _physics_process(delta):
#...
if direction != Vector3.ZERO:
direction = direction.normalized()
$Pivot.look_at(translation + direction, Vector3.UP)
.. code-tab:: csharp
public override void _PhysicsProcess(float delta)
{
// ...
if (direction != Vector3.Zero)
{
direction = direction.Normalized();
GetNode<Spatial>("Pivot").LookAt(Translation + direction, Vector3.Up);
}
}
```
Here, we only normalize the vector if the direction has a length greater than
zero, which means the player is pressing a direction key.
@ -171,9 +117,9 @@ Then, we update the velocity. We have to calculate the ground velocity and the
fall speed separately. Be sure to go back one tab so the lines are inside the
``_physics_process()`` function but outside the condition we just wrote.
.. tabs::
.. code-tab:: gdscript GDScript
gdscript GDScript
```
func _physics_process(delta):
#...
if direction != Vector3.ZERO:
@ -186,21 +132,7 @@ fall speed separately. Be sure to go back one tab so the lines are inside the
velocity.y -= fall_acceleration * delta
# Moving the character
velocity = move_and_slide(velocity, Vector3.UP)
.. code-tab:: csharp
public override void _PhysicsProcess(float delta)
{
// ...
// Ground velocity
_velocity.x = direction.x * Speed;
_velocity.z = direction.z * Speed;
// Vertical velocity
_velocity.y -= FallAcceleration * delta;
// Moving the character
_velocity = MoveAndSlide(_velocity, Vector3.Up);
}
```
For the vertical velocity, we subtract the fall acceleration multiplied by the
delta time every frame. Notice the use of the ``-=`` operator, which is a
@ -230,9 +162,9 @@ And that's all the code you need to move the character on the floor.
Here is the complete ``Player.gd`` code for reference.
.. tabs::
.. code-tab:: gdscript GDScript
gdscript GDScript
```
extends KinematicBody
# How fast the player moves in meters per second.
@ -263,60 +195,7 @@ Here is the complete ``Player.gd`` code for reference.
velocity.z = direction.z * speed
velocity.y -= fall_acceleration * delta
velocity = move_and_slide(velocity, Vector3.UP)
.. code-tab:: csharp
public class Player : KinematicBody
{
// How fast the player moves in meters per second.
[Export]
public int Speed = 14;
// The downward acceleration when in the air, in meters per second squared.
[Export]
public int FallAcceleration = 75;
private Vector3 _velocity = Vector3.Zero;
public override void _PhysicsProcess(float 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;
}
if (Input.IsActionPressed("move_left"))
{
direction.x -= 1f;
}
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;
}
if (Input.IsActionPressed("move_forward"))
{
direction.z -= 1f;
}
if (direction != Vector3.Zero)
{
direction = direction.Normalized();
GetNode<Spatial>("Pivot").LookAt(Translation + direction, Vector3.Up);
}
// Ground velocity
_velocity.x = direction.x * Speed;
_velocity.z = direction.z * Speed;
// Vertical velocity
_velocity.y -= FallAcceleration * delta;
// Moving the character
_velocity = MoveAndSlide(_velocity, Vector3.Up);
}
}
```
Testing our player's movement
-----------------------------

View File

@ -97,9 +97,9 @@ Here's the movement code to start with. We define two properties, ``min_speed``
and ``max_speed``, to define a random speed range. We then define and initialize
the ``velocity``.
.. tabs::
.. code-tab:: gdscript GDScript
gdscript GDScript
```
extends KinematicBody
# Minimum speed of the mob in meters per second.
@ -112,27 +112,7 @@ the ``velocity``.
func _physics_process(_delta):
move_and_slide(velocity)
.. code-tab:: csharp
public class Mob : KinematicBody
{
// Don't forget to rebuild the project so the editor knows about the new export variable.
// Minimum speed of the mob in meters per second
[Export]
public int MinSpeed = 10;
// Maximum speed of the mob in meters per second
[Export]
public int MaxSpeed = 18;
private Vector3 _velocity = Vector3.Zero;
public override void _PhysicsProcess(float delta)
{
MoveAndSlide(_velocity);
}
}
```
Similarly to the player, we move the mob every frame by calling
``KinematicBody``\ 's ``move_and_slide()`` method. This time, we don't update
@ -158,26 +138,16 @@ the ``look_at_from_position()`` method, and randomize the angle by rotating a
random amount around the Y axis. Below, ``rand_range()`` outputs a random value
between ``-PI / 4`` radians and ``PI / 4`` radians.
.. tabs::
.. code-tab:: gdscript GDScript
gdscript GDScript
```
# We will call this function from the Main scene.
func initialize(start_position, player_position):
# We position the mob and turn it so that it looks at the player.
look_at_from_position(start_position, player_position, Vector3.UP)
# And rotate it randomly so it doesn't move exactly toward the player.
rotate_y(rand_range(-PI / 4, PI / 4))
.. code-tab:: csharp
// We will call this function 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.
LookAtFromPosition(startPosition, playerPosition, Vector3.Up);
// And rotate it randomly so it doesn't move exactly toward the player.
RotateY((float)GD.RandRange(-Mathf.Pi / 4.0, Mathf.Pi / 4.0));
}
```
We then calculate a random speed using ``rand_range()`` once again and we use it
to calculate the velocity.
@ -186,9 +156,9 @@ We start by creating a 3D vector pointing forward, multiply it by our
``random_speed``, and finally rotate it using the ``Vector3`` class's
``rotated()`` method.
.. tabs::
.. code-tab:: gdscript GDScript
gdscript GDScript
```
func initialize(start_position, player_position):
# ...
@ -198,20 +168,7 @@ We start by creating a 3D vector pointing forward, multiply it by our
velocity = Vector3.FORWARD * random_speed
# 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)
.. code-tab:: csharp
public void Initialize(Vector3 startPosition, Vector3 playerPosition)
{
// ...
// We calculate a random speed.
float randomSpeed = (float)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);
}
```
Leaving the screen
------------------
@ -238,29 +195,21 @@ This will take you back to the script editor and add a new function for you,
method. This will destroy the mob instance when the *VisibilityNotifier* \'s box
leaves the screen.
.. tabs::
.. code-tab:: gdscript GDScript
gdscript GDScript
```
func _on_VisibilityNotifier_screen_exited():
queue_free()
.. code-tab:: csharp
// We also specified this function name in PascalCase in the editor's connection window
public void OnVisibilityNotifierScreenExited()
{
QueueFree();
}
```
Our monster is ready to enter the game! In the next part, you will spawn
monsters in the game level.
Here is the complete ``Mob.gd`` script for reference.
.. tabs::
.. code-tab:: gdscript GDScript
gdscript GDScript
```
extends KinematicBody
# Minimum speed of the mob in meters per second.
@ -285,42 +234,7 @@ Here is the complete ``Mob.gd`` script for reference.
func _on_VisibilityNotifier_screen_exited():
queue_free()
.. code-tab:: csharp
public class Mob : KinematicBody
{
// Minimum speed of the mob in meters per second
[Export]
public int MinSpeed = 10;
// Maximum speed of the mob in meters per second
[Export]
public int MaxSpeed = 18;
private Vector3 _velocity = Vector3.Zero;
public override void _PhysicsProcess(float delta)
{
MoveAndSlide(_velocity);
}
// We will call this function from the Main scene
public void Initialize(Vector3 startPosition, Vector3 playerPosition)
{
LookAtFromPosition(startPosition, playerPosition, Vector3.Up);
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 also specified this function name in PascalCase in the editor's connection window
public void OnVisibilityNotifierScreenExited()
{
QueueFree();
}
}
```
.. |image0| image:: img/04.mob_scene/01.initial_three_nodes.png
.. |image1| image:: img/04.mob_scene/02.add_child_node.png

View File

@ -158,9 +158,9 @@ 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
gdscript GDScript
```
extends Node
export (PackedScene) var mob_scene
@ -168,24 +168,7 @@ always spawn following the same sequence.
func _ready():
randomize()
.. code-tab:: csharp
public class Main : Node
{
// 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();
}
}
```
We want to spawn mobs at regular time intervals. To do this, we need to go back
to the scene and add a timer. Before that, though, we need to assign the
@ -231,9 +214,8 @@ Let's code the mob spawning logic. We're going to:
the player's position.
5. Add the mob as a child of the *Main* node.
.. tabs::
.. code-tab:: gdscript GDScript
gdscript GDScript
```
func _on_MobTimer_timeout():
# Create a new instance of the Mob scene.
var mob = mob_scene.instance()
@ -248,36 +230,16 @@ Let's code the mob spawning logic. We're going to:
mob.initialize(mob_spawn_location.translation, player_position)
add_child(mob)
.. code-tab:: csharp
// We also specified this function name in PascalCase in the editor's connection window
public void OnMobTimerTimeout()
{
// Create a new instance of the Mob scene.
Mob mob = (Mob)MobScene.Instance();
// 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();
Vector3 playerPosition = GetNode<Player>("Player").Transform.origin;
mob.Initialize(mobSpawnLocation.Translation, playerPosition);
AddChild(mob);
}
```
Above, ``randf()`` produces a random value between ``0`` and ``1``, which is
what the *PathFollow* node's ``unit_offset`` expects.
Here is the complete ``Main.gd`` script so far, for reference.
.. tabs::
.. code-tab:: gdscript GDScript
gdscript GDScript
```
extends Node
export (PackedScene) var mob_scene
@ -296,34 +258,7 @@ Here is the complete ``Main.gd`` script so far, for reference.
mob.initialize(mob_spawn_location.translation, player_position)
add_child(mob)
.. code-tab:: csharp
public class Main : Node
{
#pragma warning disable 649
[Export]
public PackedScene MobScene;
#pragma warning restore 649
public override void _Ready()
{
GD.Randomize();
}
public void OnMobTimerTimeout()
{
Mob mob = (Mob)MobScene.Instance();
var mobSpawnLocation = GetNode<PathFollow>("SpawnPath/SpawnLocation");
mobSpawnLocation.UnitOffset = GD.Randf();
Vector3 playerPosition = GetNode<Player>("Player").Transform.origin;
mob.Initialize(mobSpawnLocation.Translation, playerPosition);
AddChild(mob);
}
}
```
You can test the scene by pressing :kbd:`F6`. You should see the monsters spawn and
move in a straight line.

View File

@ -109,28 +109,20 @@ script. We need a value to control the jump's strength and update
After the line that defines ``fall_acceleration``, at the top of the script, add
the ``jump_impulse``.
.. tabs::
.. code-tab:: gdscript GDScript
gdscript GDScript
```
#...
# Vertical impulse applied to the character upon jumping in meters per second.
export var jump_impulse = 20
.. code-tab:: csharp
// Don't forget to rebuild the project so the editor knows about the new export variable.
// ...
// Vertical impulse applied to the character upon jumping in meters per second.
[Export]
public int JumpImpulse = 20;
```
Inside ``_physics_process()``, add the following code before the line where we
called ``move_and_slide()``.
.. tabs::
.. code-tab:: gdscript GDScript
gdscript GDScript
```
func _physics_process(delta):
#...
@ -139,21 +131,7 @@ called ``move_and_slide()``.
velocity.y += jump_impulse
#...
.. code-tab:: csharp
public override void _PhysicsProcess(float delta)
{
// ...
// Jumping.
if (IsOnFloor() && Input.IsActionJustPressed("jump"))
{
_velocity.y += JumpImpulse;
}
// ...
}
```
That's all you need to jump!
@ -207,20 +185,13 @@ At the top of the script, we need another property, ``bounce_impulse``. When
squashing an enemy, we don't necessarily want the character to go as high up as
when jumping.
.. tabs::
.. code-tab:: gdscript GDScript
gdscript GDScript
```
# Vertical impulse applied to the character upon bouncing over a mob in
# meters per second.
export var bounce_impulse = 16
.. code-tab:: csharp
// Don't forget to rebuild the project so the editor knows about the new export variable.
// Vertical impulse applied to the character upon bouncing over a mob in meters per second.
[Export]
public int BounceImpulse = 16;
```
Then, at the bottom of ``_physics_process()``, add the following loop. With
``move_and_slide()``, Godot makes the body move sometimes multiple times in a
@ -232,9 +203,9 @@ it and bounce.
With this code, if no collisions occurred on a given frame, the loop won't run.
.. tabs::
.. code-tab:: gdscript GDScript
gdscript GDScript
```
func _physics_process(delta):
#...
for index in range(get_slide_count()):
@ -248,30 +219,7 @@ With this code, if no collisions occurred on a given frame, the loop won't run.
# If so, we squash it and bounce.
mob.squash()
velocity.y = bounce_impulse
.. code-tab:: csharp
public override void _PhysicsProcess(float delta)
{
// ...
for (int index = 0; index < GetSlideCount(); index++)
{
// We check every collision that occurred this frame.
KinematicCollision collision = GetSlideCollision(index);
// If we collide with a monster...
if (collision.Collider is Mob mob && mob.IsInGroup("mob"))
{
// ...we check that we are hitting it from above.
if (Vector3.Up.Dot(collision.Normal) > 0.1f)
{
// If so, we squash it and bounce.
mob.Squash();
_velocity.y = BounceImpulse;
}
}
}
}
```
That's a lot of new functions. Here's some more information about them.
@ -306,9 +254,9 @@ the top of the script, we want to define a new signal named ``squashed``. And at
the bottom, you can add the squash function, where we emit the signal and
destroy the mob.
.. tabs::
.. code-tab:: gdscript GDScript
gdscript GDScript
```
# Emitted when the player jumped on the mob.
signal squashed
@ -318,22 +266,7 @@ destroy the mob.
func squash():
emit_signal("squashed")
queue_free()
.. code-tab:: csharp
// Don't forget to rebuild the project so the editor knows about the new signal.
// Emitted when the played jumped on the mob.
[Signal]
public delegate void Squashed();
// ...
public void Squash()
{
EmitSignal(nameof(Squashed));
QueueFree();
}
```
We will use the signal to add points to the score in the next lesson.

View File

@ -60,9 +60,9 @@ Code-wise, we're going to do two things: emit a signal we'll later use
to end the game and destroy the player. We can wrap these operations in
a ``die()`` function that helps us put a descriptive label on the code.
.. tabs::
.. code-tab:: gdscript GDScript
gdscript GDScript
```
# Emitted when the player was hit by a mob.
# Put this at the top of the script.
signal hit
@ -76,28 +76,7 @@ a ``die()`` function that helps us put a descriptive label on the code.
func _on_MobDetector_body_entered(_body):
die()
.. code-tab:: csharp
// Don't forget to rebuild the project so the editor knows about the new signal.
// Emitted when the player was hit by a mob.
[Signal]
public delegate void Hit();
// ...
private void Die()
{
EmitSignal(nameof(Hit));
QueueFree();
}
// We also specified this function name in PascalCase in the editor's connection window
public void OnMobDetectorBodyEntered(Node body)
{
Die();
}
```
Try the game again by pressing :kbd:`F5`. If everything is set up correctly,
the character should die when an enemy runs into it.
@ -120,19 +99,12 @@ connect its ``hit`` signal to the *Main* node.
Get and stop the timer in the ``_on_Player_hit()`` function.
.. tabs::
.. code-tab:: gdscript GDScript
gdscript GDScript
```
func _on_Player_hit():
$MobTimer.stop()
.. code-tab:: csharp
// We also specified this function name in PascalCase in the editor's connection window
public void OnPlayerHit()
{
GetNode<Timer>("MobTimer").Stop();
}
```
If you try the game now, the monsters will stop spawning when you die,
and the remaining ones will leave the screen.
@ -152,9 +124,9 @@ for reference. You can use them to compare and check your code.
Starting with ``Main.gd``.
.. tabs::
.. code-tab:: gdscript GDScript
gdscript GDScript
```
extends Node
export(PackedScene) var mob_scene
@ -183,51 +155,13 @@ Starting with ``Main.gd``.
func _on_Player_hit():
$MobTimer.stop()
.. code-tab:: csharp
public class Main : Node
{
#pragma warning disable 649
[Export]
public PackedScene MobScene;
#pragma warning restore 649
public override void _Ready()
{
GD.Randomize();
}
public void OnMobTimerTimeout()
{
// Create a new instance of the Mob scene.
var mob = (Mob)MobScene.Instance();
// 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();
// Communicate the spawn location and the player's location to the mob.
Vector3 playerPosition = GetNode<Player>("Player").Transform.origin;
mob.Initialize(mobSpawnLocation.Translation, playerPosition);
// Spawn the mob by adding it to the Main scene.
AddChild(mob);
}
public void OnPlayerHit()
{
GetNode<Timer>("MobTimer").Stop();
}
}
```
Next is ``Mob.gd``.
.. tabs::
.. code-tab:: gdscript GDScript
gdscript GDScript
```
extends KinematicBody
# Emitted when the player jumped on the mob.
@ -261,56 +195,13 @@ Next is ``Mob.gd``.
func _on_VisibilityNotifier_screen_exited():
queue_free()
.. code-tab:: csharp
public class Mob : KinematicBody
{
// Emitted when the played jumped on the mob.
[Signal]
public delegate void Squashed();
// Minimum speed of the mob in meters per second
[Export]
public int MinSpeed = 10;
// Maximum speed of the mob in meters per second
[Export]
public int MaxSpeed = 18;
private Vector3 _velocity = Vector3.Zero;
public override void _PhysicsProcess(float delta)
{
MoveAndSlide(_velocity);
}
public void Initialize(Vector3 startPosition, Vector3 playerPosition)
{
LookAtFromPosition(startPosition, playerPosition, Vector3.Up);
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);
}
public void Squash()
{
EmitSignal(nameof(Squashed));
QueueFree();
}
public void OnVisibilityNotifierScreenExited()
{
QueueFree();
}
}
```
Finally, the longest script, ``Player.gd``.
.. tabs::
.. code-tab:: gdscript GDScript
gdscript GDScript
```
extends KinematicBody
# Emitted when a mob hit the player.
@ -370,94 +261,7 @@ Finally, the longest script, ``Player.gd``.
func _on_MobDetector_body_entered(_body):
die()
.. code-tab:: csharp
public class Player : KinematicBody
{
// Emitted when the player was hit by a mob.
[Signal]
public delegate void Hit();
// How fast the player moves in meters per second.
[Export]
public int Speed = 14;
// The downward acceleration when in the air, in meters per second squared.
[Export]
public int FallAcceleration = 75;
// Vertical impulse applied to the character upon jumping in meters per second.
[Export]
public int JumpImpulse = 20;
// Vertical impulse applied to the character upon bouncing over a mob in meters per second.
[Export]
public int BounceImpulse = 16;
private Vector3 _velocity = Vector3.Zero;
public override void _PhysicsProcess(float delta)
{
var direction = Vector3.Zero;
if (Input.IsActionPressed("move_right"))
{
direction.x += 1f;
}
if (Input.IsActionPressed("move_left"))
{
direction.x -= 1f;
}
if (Input.IsActionPressed("move_back"))
{
direction.z += 1f;
}
if (Input.IsActionPressed("move_forward"))
{
direction.z -= 1f;
}
if (direction != Vector3.Zero)
{
direction = direction.Normalized();
GetNode<Spatial>("Pivot").LookAt(Translation + direction, Vector3.Up);
}
_velocity.x = direction.x * Speed;
_velocity.z = direction.z * Speed;
// Jumping.
if (IsOnFloor() && Input.IsActionJustPressed("jump"))
{
_velocity.y += JumpImpulse;
}
_velocity.y -= FallAcceleration * delta;
_velocity = MoveAndSlide(_velocity, Vector3.Up);
for (int index = 0; index < GetSlideCount(); index++)
{
KinematicCollision collision = GetSlideCollision(index);
if (collision.Collider is Mob mob && mob.IsInGroup("mob"))
{
if (Vector3.Up.Dot(collision.Normal) > 0.1f)
{
mob.Squash();
_velocity.y = BounceImpulse;
}
}
}
}
private void Die()
{
EmitSignal(nameof(Hit));
QueueFree();
}
public void OnMobDetectorBodyEntered(Node body)
{
Die();
}
}
```
See you in the next lesson to add the score and the retry option.

View File

@ -95,19 +95,13 @@ Keeping track of the score
Let's work on the score next. Attach a new script to the *ScoreLabel* and define
the ``score`` variable.
.. tabs::
.. code-tab:: gdscript GDScript
gdscript GDScript
```
extends Label
var score = 0
.. code-tab:: csharp
public class ScoreLabel : Label
{
private int _score = 0;
}
```
The score should increase by ``1`` every time we squash a monster. We can use
their ``squashed`` signal to know when that happens. However, as we instantiate
@ -127,22 +121,14 @@ dock.
At the bottom of the ``_on_MobTimer_timeout()`` function, add the following
line.
.. tabs::
.. code-tab:: gdscript GDScript
gdscript GDScript
```
func _on_MobTimer_timeout():
#...
# We connect the mob to the score label to update the score upon squashing one.
mob.connect("squashed", $UserInterface/ScoreLabel, "_on_Mob_squashed")
.. code-tab:: csharp
public void OnMobTimerTimeout()
{
// ...
// We connect the mob to the score label to update the score upon squashing one.
mob.Connect(nameof(Mob.Squashed), GetNode<ScoreLabel>("UserInterface/ScoreLabel"), nameof(ScoreLabel.OnMobSquashed));
}
```
This line means that when the mob emits the ``squashed`` signal, the
*ScoreLabel* node will receive it and call the function ``_on_Mob_squashed()``.
@ -152,20 +138,13 @@ callback function.
There, we increment the score and update the displayed text.
.. tabs::
.. code-tab:: gdscript GDScript
gdscript GDScript
```
func _on_Mob_squashed():
score += 1
text = "Score: %s" % score
.. code-tab:: csharp
public void OnMobSquashed()
{
_score += 1;
Text = string.Format("Score: {0}", _score);
}
```
The second line uses the value of the ``score`` variable to replace the
placeholder ``%s``. When using this feature, Godot automatically converts values
@ -247,37 +226,24 @@ dies and plays again.
Open the script ``Main.gd``. First, we want to hide the overlay at the start of
the game. Add this line to the ``_ready()`` function.
.. tabs::
.. code-tab:: gdscript GDScript
gdscript GDScript
```
func _ready():
#...
$UserInterface/Retry.hide()
.. code-tab:: csharp
public override void _Ready()
{
// ...
GetNode<Control>("UserInterface/Retry").Hide();
}
```
Then, when the player gets hit, we show the overlay.
.. tabs::
.. code-tab:: gdscript GDScript
gdscript GDScript
```
func _on_Player_hit():
#...
$UserInterface/Retry.show()
.. code-tab:: csharp
public void OnPlayerHit()
{
//...
GetNode<Control>("UserInterface/Retry").Show();
}
```
Finally, when the *Retry* node is visible, we need to listen to the player's
input and restart the game if they press enter. To do this, we use the built-in
@ -286,24 +252,14 @@ input and restart the game if they press enter. To do this, we use the built-in
If the player pressed the predefined ``ui_accept`` input action and *Retry* is
visible, we reload the current scene.
.. tabs::
.. code-tab:: gdscript GDScript
gdscript GDScript
```
func _unhandled_input(event):
if event.is_action_pressed("ui_accept") and $UserInterface/Retry.visible:
# This restarts the current scene.
get_tree().reload_current_scene()
.. code-tab:: csharp
public override void _UnhandledInput(InputEvent @event)
{
if (@event.IsActionPressed("ui_accept") && GetNode<Control>("UserInterface/Retry").Visible)
{
// This restarts the current scene.
GetTree().ReloadCurrentScene();
}
}
```
The function ``get_tree()`` gives us access to the global :ref:`SceneTree
<class_SceneTree>` object, which allows us to reload and restart the current
@ -374,9 +330,9 @@ make the game both look and feel much nicer.
Here is the complete ``Main.gd`` script for reference.
.. tabs::
.. code-tab:: gdscript GDScript
gdscript GDScript
```
extends Node
export (PackedScene) var mob_scene
@ -408,51 +364,7 @@ Here is the complete ``Main.gd`` script for reference.
func _on_Player_hit():
$MobTimer.stop()
$UserInterface/Retry.show()
.. code-tab:: csharp
public class Main : Node
{
#pragma warning disable 649
[Export]
public PackedScene MobScene;
#pragma warning restore 649
public override void _Ready()
{
GD.Randomize();
GetNode<Control>("UserInterface/Retry").Hide();
}
public override void _UnhandledInput(InputEvent @event)
{
if (@event.IsActionPressed("ui_accept") && GetNode<Control>("UserInterface/Retry").Visible)
{
GetTree().ReloadCurrentScene();
}
}
public void OnMobTimerTimeout()
{
Mob mob = (Mob)MobScene.Instance();
var mobSpawnLocation = GetNode<PathFollow>("SpawnPath/SpawnLocation");
mobSpawnLocation.UnitOffset = GD.Randf();
Vector3 playerPosition = GetNode<Player>("Player").Transform.origin;
mob.Initialize(mobSpawnLocation.Translation, playerPosition);
AddChild(mob);
mob.Connect(nameof(Mob.Squashed), GetNode<ScoreLabel>("UserInterface/ScoreLabel"), nameof(ScoreLabel.OnMobSquashed));
}
public void OnPlayerHit()
{
GetNode<Timer>("MobTimer").Stop();
GetNode<Control>("UserInterface/Retry").Show();
}
}
```
.. |image0| image:: img/08.score_and_replay/01.label_node.png
.. |image1| image:: img/08.score_and_replay/02.score_placeholder.png

View File

@ -186,9 +186,9 @@ Open the *Player*'s script by clicking the script icon next to it.
In ``_physics_process()``, after the line where we check the ``direction``
vector, add the following code.
.. tabs::
.. code-tab:: gdscript GDScript
gdscript GDScript
```
func _physics_process(delta):
#...
#if direction != Vector3.ZERO:
@ -196,22 +196,7 @@ vector, add the following code.
$AnimationPlayer.playback_speed = 4
else:
$AnimationPlayer.playback_speed = 1
.. code-tab:: csharp
public override void _PhysicsProcess(float delta)
{
// ...
if (direction != Vector3.Zero)
{
// ...
GetNode<AnimationPlayer>("AnimationPlayer").PlaybackSpeed = 4;
}
else
{
GetNode<AnimationPlayer>("AnimationPlayer").PlaybackSpeed = 1;
}
}
```
This code makes it so when the player moves, we multiply the playback speed by
``4``. When they stop, we reset it to normal.
@ -220,21 +205,13 @@ We mentioned that the pivot could layer transforms on top of the animation. We
can make the character arc when jumping using the following line of code. Add it
at the end of ``_physics_process()``.
.. tabs::
.. code-tab:: gdscript GDScript
gdscript GDScript
```
func _physics_process(delta):
#...
$Pivot.rotation.x = PI / 6 * velocity.y / jump_impulse
.. code-tab:: csharp
public override void _PhysicsProcess(float delta)
{
// ...
var pivot = GetNode<Spatial>("Pivot");
pivot.Rotation = new Vector3(Mathf.Pi / 6f * _velocity.y / JumpImpulse, pivot.Rotation.y, pivot.Rotation.z);
}
```
Animating the mobs
------------------
@ -254,20 +231,13 @@ We can change the playback speed based on the creature's ``random_speed``. Open
the *Mob*'s script and at the end of the ``initialize()`` function, add the
following line.
.. tabs::
.. code-tab:: gdscript GDScript
gdscript GDScript
```
func initialize(start_position, player_position):
#...
$AnimationPlayer.playback_speed = random_speed / min_speed
.. code-tab:: csharp
public void Initialize(Vector3 startPosition, Vector3 playerPosition)
{
// ...
GetNode<AnimationPlayer>("AnimationPlayer").PlaybackSpeed = randomSpeed / MinSpeed;
}
```
And with that, you finished coding your first complete 3D game.
@ -279,9 +249,9 @@ to keep learning more. But for now, here are the complete ``Player.gd`` and
Here's the *Player* script.
.. tabs::
.. code-tab:: gdscript GDScript
gdscript GDScript
```
extends KinematicBody
# Emitted when the player was hit by a mob.
@ -346,109 +316,13 @@ Here's the *Player* script.
func _on_MobDetector_body_entered(_body):
die()
.. code-tab:: csharp
public class Player : KinematicBody
{
// Emitted when the player was hit by a mob.
[Signal]
public delegate void Hit();
// How fast the player moves in meters per second.
[Export]
public int Speed = 14;
// The downward acceleration when in the air, in meters per second squared.
[Export]
public int FallAcceleration = 75;
// Vertical impulse applied to the character upon jumping in meters per second.
[Export]
public int JumpImpulse = 20;
// Vertical impulse applied to the character upon bouncing over a mob in meters per second.
[Export]
public int BounceImpulse = 16;
private Vector3 _velocity = Vector3.Zero;
public override void _PhysicsProcess(float delta)
{
var direction = Vector3.Zero;
if (Input.IsActionPressed("move_right"))
{
direction.x += 1f;
}
if (Input.IsActionPressed("move_left"))
{
direction.x -= 1f;
}
if (Input.IsActionPressed("move_back"))
{
direction.z += 1f;
}
if (Input.IsActionPressed("move_forward"))
{
direction.z -= 1f;
}
if (direction != Vector3.Zero)
{
direction = direction.Normalized();
GetNode<Spatial>("Pivot").LookAt(Translation + direction, Vector3.Up);
GetNode<AnimationPlayer>("AnimationPlayer").PlaybackSpeed = 4;
}
else
{
GetNode<AnimationPlayer>("AnimationPlayer").PlaybackSpeed = 1;
}
_velocity.x = direction.x * Speed;
_velocity.z = direction.z * Speed;
// Jumping.
if (IsOnFloor() && Input.IsActionJustPressed("jump"))
{
_velocity.y += JumpImpulse;
}
_velocity.y -= FallAcceleration * delta;
_velocity = MoveAndSlide(_velocity, Vector3.Up);
for (int index = 0; index < GetSlideCount(); index++)
{
KinematicCollision collision = GetSlideCollision(index);
if (collision.Collider is Mob mob && mob.IsInGroup("mob"))
{
if (Vector3.Up.Dot(collision.Normal) > 0.1f)
{
mob.Squash();
_velocity.y = BounceImpulse;
}
}
}
var pivot = GetNode<Spatial>("Pivot");
pivot.Rotation = new Vector3(Mathf.Pi / 6f * _velocity.y / JumpImpulse, pivot.Rotation.y, pivot.Rotation.z);
}
private void Die()
{
EmitSignal(nameof(Hit));
QueueFree();
}
public void OnMobDetectorBodyEntered(Node body)
{
Die();
}
}
```
And the *Mob*'s script.
.. tabs::
.. code-tab:: gdscript GDScript
gdscript GDScript
```
extends KinematicBody
# Emitted when the player jumped on the mob.
@ -484,52 +358,7 @@ And the *Mob*'s script.
func _on_VisibilityNotifier_screen_exited():
queue_free()
.. code-tab:: csharp
public class Mob : KinematicBody
{
// Emitted when the played jumped on the mob.
[Signal]
public delegate void Squashed();
// Minimum speed of the mob in meters per second
[Export]
public int MinSpeed = 10;
// Maximum speed of the mob in meters per second
[Export]
public int MaxSpeed = 18;
private Vector3 _velocity = Vector3.Zero;
public override void _PhysicsProcess(float delta)
{
MoveAndSlide(_velocity);
}
public void Initialize(Vector3 startPosition, Vector3 playerPosition)
{
LookAtFromPosition(startPosition, playerPosition, Vector3.Up);
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);
GetNode<AnimationPlayer>("AnimationPlayer").PlaybackSpeed = randomSpeed / MinSpeed;
}
public void Squash()
{
EmitSignal(nameof(Squashed));
QueueFree();
}
public void OnVisibilityNotifierScreenExited()
{
QueueFree();
}
}
```
.. |image0| image:: img/squash-the-creeps-final.gif
.. |image1| image:: img/09.adding_animations/01.animation_player_dock.png

View File

@ -89,29 +89,11 @@ other options by default and click the Create button to create the script.
The Script workspace should appear with your new ``Sprite.gd`` file open and the
following line of code:
.. tabs::
.. code-tab:: gdscript GDScript
gdscript GDScript
```
extends Sprite
.. code-tab:: csharp C#
public class Sprite : Godot.Sprite
// Declare member variables here. Examples:
// private int a = 2;
// private string b = "text";
// Called when the node enters the scene tree for the first time.
public override void _Ready()
{
}
// // Called every frame. 'delta' is the elapsed time since the previous frame.
// public override void _Process(float delta)
// {
//
// }
```
Every GDScript file is implicitly a class. The ``extends`` keyword defines the
class this script inherits or extends. In this case, it's ``Sprite``, meaning
@ -143,18 +125,12 @@ world!" to the Output bottom panel to get started.
Add the following code to your script:
.. tabs::
.. code-tab:: gdscript GDScript
gdscript GDScript
```
func _init():
print("Hello, world!")
.. code-tab:: csharp C#
public Sprite()
{
GD.Print("Hello, world!");
}
```
Let's break it down. The ``func`` keyword defines a new function named
@ -183,16 +159,12 @@ It's time to make our node move and rotate. To do so, we're going to add two
member variables to our script: the movement speed in pixels per second and the
angular speed in radians per second.
.. tabs::
.. code-tab:: gdscript GDScript
gdscript GDScript
```
var speed = 400
var angular_speed = PI
.. code-tab:: csharp C#
private int Speed = 400;
private float AngularSpeed = Mathf.Pi;
```
Member variables sit near the top of the script, after any "extends" lines,
but before functions. Every node
@ -224,18 +196,12 @@ time elapsed since the last frame.
At the bottom of the script, define the function:
.. tabs::
.. code-tab:: gdscript GDScript
gdscript GDScript
```
func _process(delta):
rotation += angular_speed * delta
.. code-tab:: csharp C#
public override void _Process(float delta)
{
Rotation += AngularSpeed * delta;
}
```
The ``func`` keyword defines a new function. After it, we have to write the
function's name and arguments it takes in parentheses. A colon ends the
@ -267,18 +233,13 @@ Let's now make the node move. Add the following two lines to the ``_process()``
function, ensuring the new lines are indented the same way as the one before
them.
.. tabs::
.. code-tab:: gdscript GDScript
gdscript GDScript
```
var velocity = Vector2.UP.rotated(rotation) * speed
position += velocity * delta
.. code-tab:: csharp C#
var velocity = Vector2.Up.Rotated(Rotation) * Speed;
Position += velocity * delta;
```
As we already saw, the ``var`` keyword defines a new variable. If you put it at
the top of the script, it defines a property of the class. Inside a function, it
@ -311,9 +272,9 @@ Complete script
Here is the complete ``Sprite.gd`` file for reference.
.. tabs::
.. code-tab:: gdscript GDScript
gdscript GDScript
```
extends Sprite
var speed = 400
@ -326,22 +287,4 @@ Here is the complete ``Sprite.gd`` file for reference.
var velocity = Vector2.UP.rotated(rotation) * speed
position += velocity * delta
.. code-tab:: csharp C#
using Godot;
public class Sprite : Godot.Sprite
{
private int Speed = 400;
private float AngularSpeed = Mathf.Pi;
public override void _Process(float delta)
{
Rotation += AngularSpeed * delta;
var velocity = Vector2.Up.Rotated(Rotation) * Speed;
Position += velocity * delta;
}
}
```

View File

@ -31,9 +31,9 @@ For turning, we should use a new variable: ``direction``. In our ``_process()``
function, replace the ``rotation += angular_speed * delta`` line with the
code below.
.. tabs::
.. code-tab:: gdscript GDScript
gdscript GDScript
```
var direction = 0
if Input.is_action_pressed("ui_left"):
direction = -1
@ -41,20 +41,7 @@ code below.
direction = 1
rotation += angular_speed * direction * delta
.. code-tab:: csharp C#
var direction = 0;
if (Input.IsActionPressed("ui_left"))
{
direction = -1;
}
if (Input.IsActionPressed("ui_right"))
{
direction = 1;
}
Rotation += AngularSpeed * direction * delta;
```
Our ``direction`` local variable is a multiplier representing the direction in
which the player wants to turn. A value of ``0`` means the player isn't pressing
@ -88,20 +75,13 @@ Moving when pressing "up"
To only move when pressing a key, we need to modify the code that calculates the
velocity. Replace the line starting with ``var velocity`` with the code below.
.. tabs::
.. code-tab:: gdscript GDScript
gdscript GDScript
```
var velocity = Vector2.ZERO
if Input.is_action_pressed("ui_up"):
velocity = Vector2.UP.rotated(rotation) * speed
.. code-tab:: csharp C#
var velocity = Vector2.Zero;
if (Input.IsActionPressed("ui_up"))
{
velocity = Vector2.Up.Rotated(Rotation) * Speed;
}
```
We initialize the ``velocity`` with a value of ``Vector2.ZERO``, another
constant of the built-in ``Vector`` type representing a 2D vector of length 0.
@ -114,9 +94,9 @@ Complete script
Here is the complete ``Sprite.gd`` file for reference.
.. tabs::
.. code-tab:: gdscript GDScript
gdscript GDScript
```
extends Sprite
var speed = 400
@ -137,39 +117,7 @@ Here is the complete ``Sprite.gd`` file for reference.
velocity = Vector2.UP.rotated(rotation) * speed
position += velocity * delta
.. code-tab:: csharp C#
using Godot;
public class Sprite : Godot.Sprite
{
private float Speed = 400;
private float AngularSpeed = Mathf.Pi;
public override void _Process(float delta)
{
var direction = 0;
if (Input.IsActionPressed("ui_left"))
{
direction = -1;
}
if (Input.IsActionPressed("ui_right"))
{
direction = 1;
}
Rotation += AngularSpeed * direction * delta;
var velocity = Vector2.Zero;
if (Input.IsActionPressed("ui_up"))
{
velocity = Vector2.Up.Rotated(Rotation) * Speed;
}
Position += velocity * delta;
}
}
```
If you run the scene, you should now be able to rotate with the left and right
arrow keys and move forward by pressing :kbd:`Up`.

View File

@ -146,11 +146,12 @@ method to toggle processing on and off: :ref:`Node.set_process()
``is_processing()``, returns ``true`` if idle processing is active. We can use
the ``not`` keyword to invert the value.
.. tabs::
.. code-tab:: gdscript GDScript
gdscript GDScript
```
func _on_Button_pressed():
set_process(not is_processing())
```
This function will toggle processing and, in turn, the icon's motion on and off
upon pressing the button.
@ -159,19 +160,20 @@ Before trying the game, we need to simplify our ``_process()`` function to move
the node automatically and not wait for user input. Replace it with the
following code, which we saw two lessons ago:
.. tabs::
.. code-tab:: gdscript GDScript
gdscript GDScript
```
func _process(delta):
rotation += angular_speed * delta
var velocity = Vector2.UP.rotated(rotation) * speed
position += velocity * delta
```
Your complete ``Sprite.gd`` code should look like the following.
.. tabs::
.. code-tab:: gdscript GDScript
gdscript GDScript
```
extends Sprite
var speed = 400
@ -186,6 +188,7 @@ Your complete ``Sprite.gd`` code should look like the following.
func _on_Button_pressed():
set_process(not is_processing())
```
Run the scene now and click the button to see the sprite start and stop.
@ -233,11 +236,12 @@ To get a reference to a node relative to the current one, we use the method
:ref:`Node.get_node() <class_Node_method_get_node>`. We can store the reference
in a variable.
.. tabs::
.. code-tab:: gdscript GDScript
gdscript GDScript
```
func _ready():
var timer = get_node("Timer")
```
The function ``get_node()`` looks at the Sprite's children and gets nodes by
their name. For example, if you renamed the Timer node to "BlinkingTimer" in the
@ -247,23 +251,25 @@ editor, you would have to change the call to ``get_node("BlinkingTimer")``.
We can now connect the Timer to the Sprite in the ``_ready()`` function.
.. tabs::
.. code-tab:: gdscript GDScript
gdscript GDScript
```
func _ready():
var timer = get_node("Timer")
timer.connect("timeout", self, "_on_Timer_timeout")
```
The line reads like so: we connect the Timer's "timeout" signal to the node to
which the script is attached (``self``). When the Timer emits "timeout", we want
to call the function "_on_Timer_timeout", that we need to define. Let's add it
at the bottom of our script and use it to toggle our sprite's visibility.
.. tabs::
.. code-tab:: gdscript GDScript
gdscript GDScript
```
func _on_Timer_timeout():
visible = not visible
```
The ``visible`` property is a boolean that controls the visibility of our node.
The line ``visible = not visible`` toggles the value. If ``visible`` is
@ -278,9 +284,9 @@ Complete script
That's it for our little moving and blinking Godot icon demo!
Here is the complete ``Sprite.gd`` file for reference.
.. tabs::
.. code-tab:: gdscript GDScript
gdscript GDScript
```
extends Sprite
var speed = 400
@ -304,6 +310,7 @@ Here is the complete ``Sprite.gd`` file for reference.
func _on_Timer_timeout():
visible = not visible
```
Custom signals
--------------
@ -316,14 +323,15 @@ show a game over screen when the player's health reaches zero. To do so, you
could define a signal named "died" or "health_depleted" when their health
reaches 0.
.. tabs::
.. code-tab:: gdscript GDScript
gdscript GDScript
```
extends Node2D
signal health_depleted
var health = 10
```
.. note:: As signals represent events that just occurred, we generally use an
action verb in the past tense in their names.
@ -335,23 +343,25 @@ you can connect to them like any other.
To emit a signal in your scripts, call ``emit_signal()``.
.. tabs::
.. code-tab:: gdscript GDScript
gdscript GDScript
```
func take_damage(amount):
health -= amount
if health <= 0:
emit_signal("health_depleted")
```
A signal can optionally declare one or more arguments. Specify the argument
names between parentheses:
.. tabs::
.. code-tab:: gdscript GDScript
gdscript GDScript
```
extends Node
signal health_changed(old_value, new_value)
```
.. note::
@ -363,13 +373,14 @@ names between parentheses:
To emit values along with the signal, add them as extra arguments to the
``emit_signal()`` function:
.. tabs::
.. code-tab:: gdscript GDScript
gdscript GDScript
```
func take_damage(amount):
var old_health = health
health -= amount
emit_signal("health_changed", old_health, health)
```
Summary
-------

View File

@ -38,9 +38,9 @@ fact that the player can move diagonally by pressing two keys at the same time.
Add a script to the kinematic body and add the following code:
.. tabs::
.. code-tab:: gdscript GDScript
gdscript GDScript
```
extends KinematicBody2D
export (int) var speed = 200
@ -62,43 +62,7 @@ Add a script to the kinematic body and add the following code:
func _physics_process(delta):
get_input()
velocity = move_and_slide(velocity)
.. code-tab:: csharp
using Godot;
using System;
public class Movement : KinematicBody2D
{
[Export] public int speed = 200;
public Vector2 velocity = new Vector2();
public void GetInput()
{
velocity = new Vector2();
if (Input.IsActionPressed("right"))
velocity.x += 1;
if (Input.IsActionPressed("left"))
velocity.x -= 1;
if (Input.IsActionPressed("down"))
velocity.y += 1;
if (Input.IsActionPressed("up"))
velocity.y -= 1;
velocity = velocity.Normalized() * speed;
}
public override void _PhysicsProcess(float delta)
{
GetInput();
velocity = MoveAndSlide(velocity);
}
}
```
In the ``get_input()`` function, we check for the four key events and sum them
up to get the velocity vector. This has the benefit of making two opposite keys
@ -126,9 +90,9 @@ while up/down moves it forward or backward in whatever direction it's facing.
.. image:: img/movement_rotate1.gif
.. tabs::
.. code-tab:: gdscript GDScript
gdscript GDScript
```
extends KinematicBody2D
export (int) var speed = 200
@ -153,47 +117,7 @@ while up/down moves it forward or backward in whatever direction it's facing.
get_input()
rotation += rotation_dir * rotation_speed * delta
velocity = move_and_slide(velocity)
.. code-tab:: csharp
using Godot;
using System;
public class Movement : KinematicBody2D
{
[Export] public int speed = 200;
[Export] public float rotationSpeed = 1.5f;
public Vector2 velocity = new Vector2();
public int rotationDir = 0;
public void GetInput()
{
rotationDir = 0;
velocity = new Vector2();
if (Input.IsActionPressed("right"))
rotationDir += 1;
if (Input.IsActionPressed("left"))
rotationDir -= 1;
if (Input.IsActionPressed("down"))
velocity = new Vector2(-speed, 0).Rotated(Rotation);
if (Input.IsActionPressed("up"))
velocity = new Vector2(speed, 0).Rotated(Rotation);
velocity = velocity.Normalized() * speed;
}
public override void _PhysicsProcess(float delta)
{
GetInput();
Rotation += rotationDir * rotationSpeed * delta;
velocity = MoveAndSlide(velocity);
}
}
```
Here we've added two new variables to track our rotation direction and speed.
Again, pressing both keys at once will cancel out and result in no rotation.
@ -213,9 +137,9 @@ is set by the mouse position instead of the keyboard. The character will always
.. image:: img/movement_rotate2.gif
.. tabs::
.. code-tab:: gdscript GDScript
gdscript GDScript
```
extends KinematicBody2D
export (int) var speed = 200
@ -233,51 +157,17 @@ is set by the mouse position instead of the keyboard. The character will always
func _physics_process(delta):
get_input()
velocity = move_and_slide(velocity)
.. code-tab:: csharp
using Godot;
using System;
public class Movement : KinematicBody2D
{
[Export] public int speed = 200;
public Vector2 velocity = new Vector2();
public void GetInput()
{
LookAt(GetGlobalMousePosition());
velocity = new Vector2();
if (Input.IsActionPressed("down"))
velocity = new Vector2(-speed, 0).Rotated(Rotation);
if (Input.IsActionPressed("up"))
velocity = new Vector2(speed, 0).Rotated(Rotation);
velocity = velocity.Normalized() * speed;
}
public override void _PhysicsProcess(float delta)
{
GetInput();
velocity = MoveAndSlide(velocity);
}
}
```
Here we're using the :ref:`Node2D <class_Node2D>` ``look_at()`` method to
point the player towards a given position. Without this function, you
could get the same effect by setting the angle like this:
.. tabs::
.. code-tab:: gdscript GDScript
gdscript GDScript
```
rotation = get_global_mouse_position().angle_to_point(position)
.. code-tab:: csharp
var rotation = GetGlobalMousePosition().AngleToPoint(Position);
```
Click-and-move
@ -288,9 +178,9 @@ on the screen will cause the player to move to the target location.
.. image:: img/movement_click.gif
.. tabs::
.. code-tab:: gdscript GDScript
gdscript GDScript
```
extends KinematicBody2D
export (int) var speed = 200
@ -307,42 +197,7 @@ on the screen will cause the player to move to the target location.
# look_at(target)
if position.distance_to(target) > 5:
velocity = move_and_slide(velocity)
.. code-tab:: csharp
using Godot;
using System;
public class Movement : KinematicBody2D
{
[Export] public int speed = 200;
public Vector2 target;
public Vector2 velocity = new Vector2();
public override void _Ready()
{
target = Position;
}
public override void _Input(InputEvent @event)
{
if (@event.IsActionPressed("click"))
{
target = GetGlobalMousePosition();
}
}
public override void _PhysicsProcess(float delta)
{
velocity = Position.DirectionTo(target) * speed;
// LookAt(target);
if (Position.DistanceTo(target) > 5)
{
velocity = MoveAndSlide(velocity);
}
}
}
```
Note the ``distance_to()`` check we make prior to movement. Without this test,

View File

@ -74,9 +74,9 @@ the ``play()`` and ``stop()`` methods. Here is a brief example to play the
animation while the right arrow key is held, and stop it when the key is
released.
.. tabs::
.. code-tab:: gdscript GDScript
gdscript GDScript
```
extends KinematicBody2D
onready var _animated_sprite = $AnimatedSprite
@ -86,30 +86,7 @@ released.
_animated_sprite.play("run")
else:
_animated_sprite.stop()
.. code-tab:: csharp
public class Character : KinematicBody2D
{
private AnimatedSprite _animatedSprite;
public override void _Ready()
{
_animatedSprite = GetNode<AnimatedSprite>("AnimatedSprite");
}
public override _Process(float _delta)
{
if (Input.IsActionPressed("ui_right"))
{
_animatedSprite.Play("run");
}
else
{
_animatedSprite.Stop();
}
}
}
```
Sprite sheet with AnimatedSprite
@ -213,9 +190,9 @@ the ``play()`` and ``stop()`` methods. Again, here is an example to play the
animation while the right arrow key is held, and stop it when the key is
released.
.. tabs::
.. code-tab:: gdscript GDScript
gdscript GDScript
```
extends KinematicBody2D
onready var _animation_player = $AnimationPlayer
@ -225,30 +202,7 @@ released.
_animation_player.play("walk")
else:
_animation_player.stop()
.. code-tab:: csharp
public class Character : KinematicBody2D
{
private AnimationPlayer _animationPlayer;
public override void _Ready()
{
_animationPlayer = GetNode<AnimationPlayer>("AnimationPlayer");
}
public override void _Process(float _delta)
{
if (Input.IsActionPressed("ui_right"))
{
_animationPlayer.Play("walk");
}
else
{
_animationPlayer.Stop();
}
}
}
```
.. note:: If updating both an animation and a separate property at once
(for example, a platformer may update the sprite's ``h_flip``/``v_flip``

View File

@ -72,14 +72,11 @@ Obtaining each transform can be achieved with the following functions:
Finally, then, to convert a CanvasItem local coordinates to screen
coordinates, just multiply in the following order:
.. tabs::
.. code-tab:: gdscript GDScript
gdscript GDScript
```
var screen_coord = get_viewport_transform() * (get_global_transform() * local_pos)
.. code-tab:: csharp
var screenCord = (GetViewportTransform() * GetGlobalTransform()).Xform(localPos);
```
Keep in mind, however, that it is generally not desired to work with
screen coordinates. The recommended approach is to simply work in Canvas
@ -93,19 +90,12 @@ It is often desired to feed custom input events to the scene tree. With
the above knowledge, to correctly do this, it must be done the following
way:
.. tabs::
.. code-tab:: gdscript GDScript
gdscript GDScript
```
var local_pos = Vector2(10, 20) # local to Control/Node2D
var ie = InputEventMouseButton.new()
ie.button_index = BUTTON_LEFT
ie.position = get_viewport_transform() * (get_global_transform() * local_pos)
get_tree().input_event(ie)
.. code-tab:: csharp
var localPos = new Vector2(10,20); // local to Control/Node2D
var ie = new InputEventMouseButton();
ie.ButtonIndex = (int)ButtonList.Left;
ie.Position = (GetViewportTransform() * GetGlobalTransform()).Xform(localPos);
GetTree().InputEvent(ie);
```

View File

@ -34,21 +34,15 @@ Add a script to any :ref:`CanvasItem <class_CanvasItem>`
derived node, like :ref:`Control <class_Control>` or
:ref:`Node2D <class_Node2D>`. Then override the ``_draw()`` function.
.. tabs::
.. code-tab:: gdscript GDScript
gdscript GDScript
```
extends Node2D
func _draw():
# Your draw commands here
pass
.. code-tab:: csharp
public override void _Draw()
{
// Your draw commands here
}
```
Draw commands are described in the :ref:`CanvasItem <class_CanvasItem>`
class reference. There are plenty of them.
@ -66,9 +60,9 @@ in that same node and a new ``_draw()`` call will happen.
Here is a little more complex example, a texture variable that will be
redrawn if modified:
.. tabs::
.. code-tab:: gdscript GDScript
gdscript GDScript
```
extends Node2D
export (Texture) var texture setget _set_texture
@ -81,38 +75,14 @@ redrawn if modified:
func _draw():
draw_texture(texture, Vector2())
.. code-tab:: csharp
public class CustomNode2D : Node2D
{
private Texture _texture;
public Texture Texture
{
get
{
return _texture;
}
set
{
_texture = value;
Update();
}
}
public override void _Draw()
{
DrawTexture(_texture, new Vector2());
}
}
```
In some cases, it may be desired to draw every frame. For this, just
call ``update()`` from the ``_process()`` callback, like this:
.. tabs::
.. code-tab:: gdscript GDScript
gdscript GDScript
```
extends Node2D
func _draw():
@ -121,21 +91,7 @@ call ``update()`` from the ``_process()`` callback, like this:
func _process(delta):
update()
.. code-tab:: csharp
public class CustomNode2D : Node2D
{
public override void _Draw()
{
// Your draw commands here
}
public override void _Process(float delta)
{
Update();
}
}
```
An example: drawing circular arcs
@ -162,9 +118,9 @@ it being angular-looking. On the contrary, if your shape is small (or in 3D, far
you may decrease its number of points to save processing costs; this is known as *Level of Detail (LOD)*.
In our example, we will simply use a fixed number of points, no matter the radius.
.. tabs::
.. code-tab:: gdscript GDScript
gdscript GDScript
```
func draw_circle_arc(center, radius, angle_from, angle_to, color):
var nb_points = 32
var points_arc = PoolVector2Array()
@ -175,23 +131,7 @@ In our example, we will simply use a fixed number of points, no matter the radiu
for index_point in range(nb_points):
draw_line(points_arc[index_point], points_arc[index_point + 1], color)
.. code-tab:: csharp
public void DrawCircleArc(Vector2 center, float radius, float angleFrom, float angleTo, Color color)
{
int nbPoints = 32;
var pointsArc = new Vector2[nbPoints];
for (int i = 0; i < nbPoints; ++i)
{
float anglePoint = Mathf.Deg2Rad(angleFrom + i * (angleTo - angleFrom) / nbPoints - 90f);
pointsArc[i] = center + new Vector2(Mathf.Cos(anglePoint), Mathf.Sin(anglePoint)) * radius;
}
for (int i = 0; i < nbPoints - 1; ++i)
DrawLine(pointsArc[i], pointsArc[i + 1], color);
}
```
Remember the number of points our shape has to be decomposed into? We fixed this
@ -232,10 +172,9 @@ Draw the arc on the screen
We now have a function that draws stuff on the screen;
it is time to call it inside the ``_draw()`` function:
.. tabs::
.. code-tab:: gdscript GDScript
gdscript GDScript
```
func _draw():
var center = Vector2(200, 200)
var radius = 80
@ -243,18 +182,7 @@ it is time to call it inside the ``_draw()`` function:
var angle_to = 195
var color = Color(1.0, 0.0, 0.0)
draw_circle_arc(center, radius, angle_from, angle_to, color)
.. code-tab:: csharp
public override void _Draw()
{
var center = new Vector2(200, 200);
float radius = 80;
float angleFrom = 75;
float angleTo = 195;
var color = new Color(1, 0, 0);
DrawCircleArc(center, radius, angleFrom, angleTo, color);
}
```
Result:
@ -267,9 +195,9 @@ We can take this a step further and not only write a function that draws the pla
portion of the disc defined by the arc, but also its shape. The method is exactly
the same as before, except that we draw a polygon instead of lines:
.. tabs::
.. code-tab:: gdscript GDScript
gdscript GDScript
```
func draw_circle_arc_poly(center, radius, angle_from, angle_to, color):
var nb_points = 32
var points_arc = PoolVector2Array()
@ -280,25 +208,7 @@ the same as before, except that we draw a polygon instead of lines:
var angle_point = deg2rad(angle_from + i * (angle_to - angle_from) / nb_points - 90)
points_arc.push_back(center + Vector2(cos(angle_point), sin(angle_point)) * radius)
draw_polygon(points_arc, colors)
.. code-tab:: csharp
public void DrawCircleArcPoly(Vector2 center, float radius, float angleFrom, float angleTo, Color color)
{
int nbPoints = 32;
var pointsArc = new Vector2[nbPoints + 1];
pointsArc[0] = center;
var colors = new Color[] { color };
for (int i = 0; i < nbPoints; ++i)
{
float anglePoint = Mathf.Deg2Rad(angleFrom + i * (angleTo - angleFrom) / nbPoints - 90);
pointsArc[i + 1] = center + new Vector2(Mathf.Cos(anglePoint), Mathf.Sin(anglePoint)) * radius;
}
DrawPolygon(pointsArc, colors);
}
```
.. image:: img/result_drawarc_poly.png
@ -315,23 +225,15 @@ First, we have to make both angle_from and angle_to variables global at the top
of our script. Also note that you can store them in other nodes and access them
using ``get_node()``.
.. tabs::
.. code-tab:: gdscript GDScript
gdscript GDScript
```
extends Node2D
var rotation_angle = 50
var angle_from = 75
var angle_to = 195
.. code-tab:: csharp
public class CustomNode2D : Node2D
{
private float _rotationAngle = 50;
private float _angleFrom = 75;
private float _angleTo = 195;
}
```
We make these values change in the _process(delta) function.
@ -345,9 +247,9 @@ When this happens, Godot may crash or produce unexpected behavior.
Finally, we must not forget to call the ``update()`` function, which automatically
calls ``_draw()``. This way, you can control when you want to refresh the frame.
.. tabs::
.. code-tab:: gdscript GDScript
gdscript GDScript
```
func _process(delta):
angle_from += rotation_angle
angle_to += rotation_angle
@ -357,47 +259,20 @@ calls ``_draw()``. This way, you can control when you want to refresh the frame.
angle_from = wrapf(angle_from, 0, 360)
angle_to = wrapf(angle_to, 0, 360)
update()
.. code-tab:: csharp
public override void _Process(float delta)
{
_angleFrom += _rotationAngle;
_angleTo += _rotationAngle;
// We only wrap angles when both of them are bigger than 360.
if (_angleFrom > 360 && _angleTo > 360)
{
_angleFrom = Mathf.Wrap(_angleFrom, 0, 360);
_angleTo = Mathf.Wrap(_angleTo, 0, 360);
}
Update();
}
```
Also, don't forget to modify the ``_draw()`` function to make use of these variables:
.. tabs::
.. code-tab:: gdscript GDScript
gdscript GDScript
```
func _draw():
var center = Vector2(200, 200)
var radius = 80
var color = Color(1.0, 0.0, 0.0)
draw_circle_arc( center, radius, angle_from, angle_to, color )
.. code-tab:: csharp
public override void _Draw()
{
var center = new Vector2(200, 200);
float radius = 80;
var color = new Color(1, 0, 0);
DrawCircleArc(center, radius, _angleFrom, _angleTo, color);
}
```
Let's run!
It works, but the arc is rotating insanely fast! What's wrong?
@ -414,9 +289,9 @@ In our case, we simply need to multiply our ``rotation_angle`` variable by ``del
in the ``_process()`` function. This way, our 2 angles will be increased by a much
smaller value, which directly depends on the rendering speed.
.. tabs::
.. code-tab:: gdscript GDScript
gdscript GDScript
```
func _process(delta):
angle_from += rotation_angle * delta
angle_to += rotation_angle * delta
@ -426,23 +301,7 @@ smaller value, which directly depends on the rendering speed.
angle_from = wrapf(angle_from, 0, 360)
angle_to = wrapf(angle_to, 0, 360)
update()
.. code-tab:: csharp
public override void _Process(float delta)
{
_angleFrom += _rotationAngle * delta;
_angleTo += _rotationAngle * delta;
// We only wrap angles when both of them are bigger than 360.
if (_angleFrom > 360 && _angleTo > 360)
{
_angleFrom = Wrap(_angleFrom, 0, 360);
_angleTo = Wrap(_angleTo, 0, 360);
}
Update();
}
```
Let's run again! This time, the rotation displays fine!

View File

@ -86,52 +86,56 @@ Next, add a script to the MeshInstance.
Under ``_ready()``, create a new Array.
.. tabs::
.. code-tab:: gdscript GDScript
gdscript GDScript
```
var surface_array = []
```
This will be the array that we keep our surface information in - it will hold
all the arrays of data that the surface needs. Godot will expect it to be of
size ``Mesh.ARRAY_MAX``, so resize it accordingly.
.. tabs::
.. code-tab:: gdscript GDScript
gdscript GDScript
```
var surface_array = []
surface_array.resize(Mesh.ARRAY_MAX)
```
Next create the arrays for each data type you will use.
.. tabs::
.. code-tab:: gdscript GDScript
gdscript GDScript
```
var verts = PoolVector3Array()
var uvs = PoolVector2Array()
var normals = PoolVector3Array()
var indices = PoolIntArray()
```
Once you have filled your data arrays with your geometry you can create a mesh
by adding each array to ``surface_array`` and then committing to the mesh.
.. tabs::
.. code-tab:: gdscript GDScript
gdscript GDScript
```
surface_array[Mesh.ARRAY_VERTEX] = verts
surface_array[Mesh.ARRAY_TEX_UV] = uvs
surface_array[Mesh.ARRAY_NORMAL] = normals
surface_array[Mesh.ARRAY_INDEX] = indices
mesh.add_surface_from_arrays(Mesh.PRIMITIVE_TRIANGLES, surface_array) # No blendshapes or compression used.
```
.. note:: In this example, we used ``Mesh.PRIMITIVE_TRIANGLES``, but you can use any primitive type
available from mesh.
Put together, the full code looks like:
.. tabs::
.. code-tab:: gdscript GDScript
gdscript GDScript
```
extends MeshInstance
func _ready():
@ -156,7 +160,7 @@ Put together, the full code looks like:
# Create mesh surface from mesh array.
mesh.add_surface_from_arrays(Mesh.PRIMITIVE_TRIANGLES, surface_array) # No blendshapes or compression used.
```
The code that goes in the middle can be whatever you want. Below we will present some example code
for generating a sphere.
@ -171,9 +175,9 @@ generic approach to generating a sphere. If you are having trouble understanding
or want to learn more about procedural geometry in general, you can use any tutorial
that you find online.
.. tabs::
.. code-tab:: gdscript GDScript
gdscript GDScript
```
extends MeshInstance
var rings = 50
@ -230,6 +234,7 @@ that you find online.
thisrow = point
# Insert committing to the ArrayMesh here.
```
Saving
------
@ -237,8 +242,9 @@ Saving
Finally, we can use the :ref:`ResourceSaver <class_resourcesaver>` class to save the ArrayMesh.
This is useful when you want to generate a mesh and then use it later without having to re-generate it.
.. tabs::
.. code-tab:: gdscript GDScript
gdscript GDScript
```
# Saves mesh to a .tres file with compression enabled.
ResourceSaver.save("res://sphere.tres", mesh, ResourceSaver.FLAG_COMPRESS)
```

View File

@ -23,13 +23,14 @@ Once you have called ``begin()`` you are ready to start adding vertices. You add
First you add vertex specific attributes such as normals or UVs using ``set_****()`` (e.g. ``set_normal()``).
Then you call ``add_vertex()`` to add a vertex with those attributes. For example:
.. tabs::
.. code-tab:: gdscript GDScript
gdscript GDScript
```
# Add a vertex with normal and uv.
set_normal(Vector3(0, 1, 0))
set_uv(Vector2(1, 1))
add_vertex(Vector3(0, 0, 1))
```
Only attributes added before the call to ``add_vertex()`` will be included in that vertex.
@ -37,9 +38,9 @@ Finally, once you have added all your vertices call ``end()`` to signal that you
The example code below draws a single triangle.
.. tabs::
.. code-tab:: gdscript GDScript
gdscript GDScript
```
extends ImmediateGeometry
func _process(_delta):
@ -65,3 +66,4 @@ The example code below draws a single triangle.
# End drawing.
end()
```

View File

@ -18,11 +18,12 @@ calling ``create_from_surface()`` will clear it for you. Alternatively, you can
In the examples below, assume an ArrayMesh called ``mesh`` has already been created. See :ref:`ArrayMesh tutorial <doc_arraymesh>` for an example of mesh generation.
.. tabs::
.. code-tab:: gdscript GDScript
gdscript GDScript
```
var mdt = MeshDataTool.new()
mdt.create_from_surface(mesh, 0)
```
``create_from_surface()`` uses the vertex arrays from the ArrayMesh to calculate two additional arrays,
one for edges and one for faces, for a total of three arrays.
@ -38,40 +39,43 @@ with each vertex.
To access information from these arrays you use a function of the form ``get_****()``:
.. tabs::
.. code-tab:: gdscript GDScript
gdscript GDScript
```
mdt.get_vertex_count() # Returns number of vertices in vertex array.
mdt.get_vertex_faces(0) # Returns array of faces that contain vertex[0].
mdt.get_face_normal(1) # Calculates and returns face normal of the second face.
mdt.get_edge_vertex(10, 1) # Returns the second vertex comprising the edge at index 10.
```
What you choose to do with these functions is up to you. A common use case is to iterate over all vertices
and transform them in some way:
.. tabs::
.. code-tab:: gdscript GDScript
gdscript GDScript
```
for i in range(get_vertex_count):
var vert = mdt.get_vertex(i)
vert *= 2.0 # Scales the vertex by doubling size.
mdt.set_vertex(i, vert)
```
These modifications are not done in place on the ArrayMesh. If you are dynamically updating an existing ArrayMesh,
first delete the existing surface before adding a new one using :ref:`commit_to_surface() <class_meshdatatool_method_commit_to_surface>`:
.. tabs::
.. code-tab:: gdscript GDScript
gdscript GDScript
```
mesh.surface_remove(0) # Deletes the first surface of the mesh.
mdt.commit_to_surface(mesh)
```
Below is a complete example that turns a spherical mesh called ``mesh`` into a randomly deformed blob complete with updated normals and vertex colors.
See :ref:`ArrayMesh tutorial <doc_arraymesh>` for how to generate the base mesh.
.. tabs::
.. code-tab:: gdscript GDScript
gdscript GDScript
```
extends MeshInstance
var sn = OpenSimplexNoise.new()
@ -115,3 +119,4 @@ See :ref:`ArrayMesh tutorial <doc_arraymesh>` for how to generate the base mesh.
mesh.surface_remove(0)
mdt.commit_to_surface(mesh)
```

View File

@ -12,32 +12,34 @@ The SurfaceTool also provides some useful helper functions like ``index()`` and
Attributes are added before each vertex is added:
.. tabs::
.. code-tab:: gdscript GDScript
gdscript GDScript
```
st.add_normal() # Overwritten by normal below.
st.add_normal() # Added to next vertex.
st.add_color() # Added to next vertex.
st.add_vertex() # Captures normal and color above.
st.add_normal() # Normal never added to a vertex.
```
When finished generating your geometry with the :ref:`SurfaceTool <class_surfacetool>`
call ``commit()`` to finish generating the mesh. If an :ref:`ArrayMesh <class_ArrayMesh>` is passed
to ``commit()`` then it appends a new surface to the end of the ArrayMesh. While if nothing is passed
in, ``commit()`` returns an ArrayMesh.
.. tabs::
.. code-tab:: gdscript GDScript
gdscript GDScript
```
st.commit(mesh)
# Or:
var mesh = st.commit()
```
Code creates a triangle with indices
.. tabs::
.. code-tab:: gdscript GDScript
gdscript GDScript
```
var st = SurfaceTool.new()
st.begin(Mesh.PRIMITIVE_TRIANGLES)
@ -58,14 +60,15 @@ Code creates a triangle with indices
# Commit to a mesh.
var mesh = st.commit()
```
You can optionally add an index array, either by calling ``add_index()`` and adding
vertices to the index array or by calling ``index()`` which shrinks the vertex array
to remove duplicate vertices.
.. tabs::
.. code-tab:: gdscript GDScript
gdscript GDScript
```
# Creates a quad from four corner vertices.
# Add_index does not need to be called before add_vertex.
st.add_index(0)
@ -78,14 +81,16 @@ to remove duplicate vertices.
# Alternatively:
st.index()
```
Similarly, if you have an index array, but you want each vertex to be unique (e.g. because
you want to use unique normals or colors per face instead of per-vertex), you can call ``deindex()``.
.. tabs::
.. code-tab:: gdscript GDScript
gdscript GDScript
```
st.deindex()
```
If you don't add custom normals yourself, you can add them using ``generate_normals()``, which should
be called after generating geometry and before committing the mesh using ``commit()`` or
@ -95,11 +100,12 @@ note, ``generate_normals()`` only works if the primitive type is set to ``Mesh.P
If you don't add custom tangents, they can be added with ``generate_tangents()``, but it requires
that each vertex have UVs and normals set already.
.. tabs::
.. code-tab:: gdscript GDScript
gdscript GDScript
```
st.generate_normals()
st.generate_tangents()
```
By default, when generating normals, they will be calculated on a per-face basis. If you want
smooth vertex normals, when adding vertices, call ``add_smooth_group()``. ``add_smooth_group()``

View File

@ -94,31 +94,15 @@ A transform has a :ref:`class_Basis` (transform.basis sub-property), which consi
A default basis (unmodified) is akin to:
.. tabs::
.. code-tab:: gdscript GDScript
gdscript GDScript
```
var basis = Basis()
# Contains the following default values:
basis.x = Vector3(1, 0, 0) # Vector pointing along the X axis
basis.y = Vector3(0, 1, 0) # Vector pointing along the Y axis
basis.z = Vector3(0, 0, 1) # Vector pointing along the Z axis
.. code-tab:: csharp
// Due to technical limitations on structs in C# the default
// constructor will contain zero values for all fields.
var defaultBasis = new Basis();
GD.Print(defaultBasis); // prints: ((0, 0, 0), (0, 0, 0), (0, 0, 0))
// Instead we can use the Identity property.
var identityBasis = Basis.Identity;
GD.Print(identityBasis.x); // prints: (1, 0, 0)
GD.Print(identityBasis.y); // prints: (0, 1, 0)
GD.Print(identityBasis.z); // prints: (0, 0, 1)
// The Identity basis is equivalent to:
var basis = new Basis(Vector3.Right, Vector3.Up, Vector3.Back);
GD.Print(basis); // prints: ((1, 0, 0), (0, 1, 0), (0, 0, 1))
```
This is also an analog of a 3x3 identity matrix.
@ -146,56 +130,38 @@ Of course, transforms are not as straightforward to manipulate as angles and hav
It is possible to rotate a transform, either by multiplying its basis by another (this is called accumulation), or by using the rotation methods.
.. tabs::
.. code-tab:: gdscript GDScript
gdscript GDScript
```
var axis = Vector3(1, 0, 0) # Or Vector3.RIGHT
var rotation_amount = 0.1
# Rotate the transform around the X axis by 0.1 radians.
transform.basis = Basis(axis, rotation_amount) * transform.basis
# shortened
transform.basis = transform.basis.rotated(axis, rotation_amount)
.. code-tab:: csharp
Vector3 axis = new Vector3(1, 0, 0); // Or Vector3.Right
float rotationAmount = 0.1f;
// Rotate the transform around the X axis by 0.1 radians.
transform.basis = new Basis(axis, rotationAmount) * transform.basis;
// shortened
transform.basis = transform.basis.Rotated(axis, rotationAmount);
```
A method in Spatial simplifies this:
.. tabs::
.. code-tab:: gdscript GDScript
gdscript GDScript
```
# Rotate the transform around the X axis by 0.1 radians.
rotate(Vector3(1, 0, 0), 0.1)
# shortened
rotate_x(0.1)
.. code-tab:: csharp
// Rotate the transform around the X axis by 0.1 radians.
Rotate(new Vector3(1, 0, 0), 0.1f);
// shortened
RotateX(0.1f);
```
This rotates the node relative to the parent node.
To rotate relative to object space (the node's own transform), use the following:
.. tabs::
.. code-tab:: gdscript GDScript
gdscript GDScript
```
# Rotate around the object's local X axis by 0.1 radians.
rotate_object_local(Vector3(1, 0, 0), 0.1)
.. code-tab:: csharp
// Rotate around the object's local X axis by 0.1 radians.
RotateObjectLocal(new Vector3(1, 0, 0), 0.1f);
```
Precision errors
================
@ -206,29 +172,22 @@ If a transform is rotated every frame, it will eventually start deforming over t
There are two different ways to handle this. The first is to *orthonormalize* the transform after some time (maybe once per frame if you modify it every frame):
.. tabs::
.. code-tab:: gdscript GDScript
gdscript GDScript
```
transform = transform.orthonormalized()
.. code-tab:: csharp
transform = transform.Orthonormalized();
```
This will make all axes have ``1.0`` length again and be ``90`` degrees from each other. However, any scale applied to the transform will be lost.
It is recommended you not scale nodes that are going to be manipulated; scale their children nodes instead (such as MeshInstance). If you absolutely must scale the node, then re-apply it at the end:
.. tabs::
.. code-tab:: gdscript GDScript
gdscript GDScript
```
transform = transform.orthonormalized()
transform = transform.scaled(scale)
.. code-tab:: csharp
transform = transform.Orthonormalized();
transform = transform.Scaled(scale);
```
Obtaining information
=====================
@ -237,71 +196,45 @@ You might be thinking at this point: **"Ok, but how do I get angles from a trans
Imagine you need to shoot a bullet in the direction your player is facing. Just use the forward axis (commonly ``Z`` or ``-Z``).
.. tabs::
.. code-tab:: gdscript GDScript
gdscript GDScript
```
bullet.transform = transform
bullet.speed = transform.basis.z * BULLET_SPEED
.. code-tab:: csharp
bullet.Transform = transform;
bullet.LinearVelocity = transform.basis.z * BulletSpeed;
```
Is the enemy looking at the player? Use the dot product for this (see the :ref:`doc_vector_math` tutorial for an explanation of the dot product):
.. tabs::
.. code-tab:: gdscript GDScript
gdscript GDScript
```
# Get the direction vector from player to enemy
var direction = enemy.transform.origin - player.transform.origin
if direction.dot(enemy.transform.basis.z) > 0:
enemy.im_watching_you(player)
.. code-tab:: csharp
// Get the direction vector from player to enemy
Vector3 direction = enemy.Transform.origin - player.Transform.origin;
if (direction.Dot(enemy.Transform.basis.z) > 0)
{
enemy.ImWatchingYou(player);
}
```
Strafe left:
.. tabs::
.. code-tab:: gdscript GDScript
gdscript GDScript
```
# Remember that +X is right
if Input.is_action_pressed("strafe_left"):
translate_object_local(-transform.basis.x)
.. code-tab:: csharp
// Remember that +X is right
if (Input.IsActionPressed("strafe_left"))
{
TranslateObjectLocal(-Transform.basis.x);
}
```
Jump:
.. tabs::
.. code-tab:: gdscript GDScript
gdscript GDScript
```
# Keep in mind Y is up-axis
if Input.is_action_just_pressed("jump"):
velocity.y = JUMP_SPEED
velocity = move_and_slide(velocity)
.. code-tab:: csharp
// Keep in mind Y is up-axis
if (Input.IsActionJustPressed("jump"))
velocity.y = JumpSpeed;
velocity = MoveAndSlide(velocity);
```
All common behaviors and logic can be done with just vectors.
@ -314,9 +247,9 @@ For such cases, keep the angles and rotations *outside* the transform and set th
Example of looking around, FPS style:
.. tabs::
.. code-tab:: gdscript GDScript
gdscript GDScript
```
# accumulators
var rot_x = 0
var rot_y = 0
@ -329,30 +262,7 @@ Example of looking around, FPS style:
transform.basis = Basis() # reset rotation
rotate_object_local(Vector3(0, 1, 0), rot_x) # first rotate in Y
rotate_object_local(Vector3(1, 0, 0), rot_y) # then rotate in X
.. code-tab:: csharp
// accumulators
private float _rotationX = 0f;
private float _rotationY = 0f;
public override void _Input(InputEvent @event)
{
if (@event is InputEventMouseMotion mouseMotion)
{
// modify accumulated mouse rotation
_rotationX += mouseMotion.Relative.x * LookAroundSpeed;
_rotationY += mouseMotion.Relative.y * LookAroundSpeed;
// reset rotation
Transform transform = Transform;
transform.basis = Basis.Identity;
Transform = transform;
RotateObjectLocal(Vector3.Up, _rotationX); // first rotate about Y
RotateObjectLocal(Vector3.Right, _rotationY); // then rotate about X
}
}
```
As you can see, in such cases it's even simpler to keep the rotation outside, then use the transform as the *final* orientation.
@ -363,9 +273,9 @@ Interpolating between two transforms can efficiently be done with quaternions. M
Converting a rotation to quaternion is straightforward.
.. tabs::
.. code-tab:: gdscript GDScript
gdscript GDScript
```
# Convert basis to quaternion, keep in mind scale is lost
var a = Quat(transform.basis)
var b = Quat(transform2.basis)
@ -373,16 +283,7 @@ Converting a rotation to quaternion is straightforward.
var c = a.slerp(b,0.5) # find halfway point between a and b
# Apply back
transform.basis = Basis(c)
.. code-tab:: csharp
// Convert basis to quaternion, keep in mind scale is lost
var a = transform.basis.Quat();
var b = transform2.basis.Quat();
// Interpolate using spherical-linear interpolation (SLERP).
var c = a.Slerp(b, 0.5f); // find halfway point between a and b
// Apply back
transform.basis = new Basis(c);
```
The :ref:`class_Quat` type reference has more information on the datatype (it
can also do transform accumulation, transform points, etc., though this is used

View File

@ -100,9 +100,9 @@ This node can be used to cause a seek command to happen to any sub-children of t
After setting the time and changing the animation playback, the seek node automatically goes into sleep mode on the next process frame by setting its ``seek_position`` value to ``-1.0``.
.. tabs::
.. code-tab:: gdscript GDScript
gdscript GDScript
```
# Play child animation from the start.
anim_tree.set("parameters/Seek/seek_position", 0.0)
# Alternative syntax (same result as above).
@ -112,14 +112,7 @@ After setting the time and changing the animation playback, the seek node automa
anim_tree.set("parameters/Seek/seek_position", 12.0)
# Alternative syntax (same result as above).
anim_tree["parameters/Seek/seek_position"] = 12.0
.. code-tab:: csharp
// Play child animation from the start.
animTree.Set("parameters/Seek/seek_position", 0.0);
// Play child animation from 12 second timestamp.
animTree.Set("parameters/Seek/seek_position", 12.0);
```
TimeScale
^^^^^^^^^
@ -202,14 +195,11 @@ transformation visually (the animation will stay in place).
Afterwards, the actual motion can be retrieved via the :ref:`AnimationTree <class_AnimationTree>` API as a transform:
.. tabs::
.. code-tab:: gdscript GDScript
gdscript GDScript
```
anim_tree.get_root_motion_transform()
.. code-tab:: csharp
animTree.GetRootMotionTransform();
```
This can be fed to functions such as :ref:`KinematicBody.move_and_slide <class_KinematicBody_method_move_and_slide>` to control the character movement.
@ -243,16 +233,13 @@ To modify these values from code, the property path must be obtained. This is do
Which allows setting them or reading them:
.. tabs::
.. code-tab:: gdscript GDScript
gdscript GDScript
```
anim_tree.set("parameters/eye_blend/blend_amount", 1.0)
# Simpler alternative form:
anim_tree["parameters/eye_blend/blend_amount"] = 1.0
.. code-tab:: csharp
animTree.Set("parameters/eye_blend/blend_amount", 1.0);
```
State machine travel
@ -267,25 +254,19 @@ To use the travel ability, you should first retrieve the :ref:`AnimationNodeStat
object from the ``AnimationTree`` node (it is exported as a property).
.. tabs::
.. code-tab:: gdscript GDScript
gdscript GDScript
```
var state_machine = anim_tree["parameters/playback"]
.. code-tab:: csharp
AnimationNodeStateMachinePlayback stateMachine = (AnimationNodeStateMachinePlayback)animTree.Get("parameters/playback");
```
Once retrieved, it can be used by calling one of the many functions it offers:
.. tabs::
.. code-tab:: gdscript GDScript
gdscript GDScript
```
state_machine.travel("SomeState")
.. code-tab:: csharp
stateMachine.Travel("SomeState");
```
The state machine must be running before you can travel. Make sure to either call ``start()`` or choose a node to **Autoplay on Load**.

View File

@ -26,9 +26,9 @@ An ``AudioStreamPlayer`` named ``AudioStreamRecord`` is used for recording.
.. image:: img/record_stream_player.png
.. tabs::
.. code-tab:: gdscript GDScript
gdscript GDScript
```
var effect
var recording
@ -39,20 +39,7 @@ An ``AudioStreamPlayer`` named ``AudioStreamRecord`` is used for recording.
# And use it to retrieve its first effect, which has been defined
# as an "AudioEffectRecord" resource.
effect = AudioServer.get_bus_effect(idx, 0)
.. code-tab:: csharp
private AudioEffectRecord _effect;
private AudioStreamSample _recording;
public override void _Ready()
{
// We get the index of the "Record" bus.
int idx = AudioServer.GetBusIndex("Record");
// And use it to retrieve its first effect, which has been defined
// as an "AudioEffectRecord" resource.
_effect = (AudioEffectRecord)AudioServer.GetBusEffect(idx, 0);
}
```
The audio recording is handled by the :ref:`class_AudioEffectRecord` resource
which has three methods:
@ -60,9 +47,9 @@ which has three methods:
:ref:`is_recording_active() <class_AudioEffectRecord_method_is_recording_active>`,
and :ref:`set_recording_active() <class_AudioEffectRecord_method_set_recording_active>`.
.. tabs::
.. code-tab:: gdscript GDScript
gdscript GDScript
```
func _on_RecordButton_pressed():
if effect.is_recording_active():
recording = effect.get_recording()
@ -77,29 +64,8 @@ and :ref:`set_recording_active() <class_AudioEffectRecord_method_set_recording_a
effect.set_recording_active(true)
$RecordButton.text = "Stop"
$Status.text = "Recording..."
```
.. code-tab:: csharp
public void OnRecordButtonPressed()
{
if (_effect.IsRecordingActive())
{
_recording = _effect.GetRecording();
GetNode<Button>("PlayButton").Disabled = false;
GetNode<Button>("SaveButton").Disabled = false;
_effect.SetRecordingActive(false);
GetNode<Button>("RecordButton").Text = "Record";
GetNode<Label>("Status").Text = "";
}
else
{
GetNode<Button>("PlayButton").Disabled = true;
GetNode<Button>("SaveButton").Disabled = true;
_effect.SetRecordingActive(true);
GetNode<Button>("RecordButton").Text = "Stop";
GetNode<Label>("Status").Text = "Recording...";
}
}
At the start of the demo, the recording effect is not active. When the user
presses the ``RecordButton``, the effect is enabled with
@ -109,9 +75,9 @@ On the next button press, as ``effect.is_recording_active()`` is ``true``,
the recorded stream can be stored into the ``recording`` variable by calling
``effect.get_recording()``.
.. tabs::
.. code-tab:: gdscript GDScript
gdscript GDScript
```
func _on_PlayButton_pressed():
print(recording)
print(recording.format)
@ -122,42 +88,20 @@ the recorded stream can be stored into the ``recording`` variable by calling
print(data.size())
$AudioStreamPlayer.stream = recording
$AudioStreamPlayer.play()
.. code-tab:: csharp
public void OnPlayButtonPressed()
{
GD.Print(_recording);
GD.Print(_recording.Format);
GD.Print(_recording.MixRate);
GD.Print(_recording.Stereo);
byte[] data = _recording.Data;
GD.Print(data);
GD.Print(data.Length);
var audioStreamPlayer = GetNode<AudioStreamPlayer>("AudioStreamPlayer");
audioStreamPlayer.Stream = _recording;
audioStreamPlayer.Play();
}
```
To playback the recording, you assign the recording as the stream of the
``AudioStreamPlayer`` and call ``play()``.
.. tabs::
.. code-tab:: gdscript GDScript
gdscript GDScript
```
func _on_SaveButton_pressed():
var save_path = $SaveButton/Filename.text
recording.save_to_wav(save_path)
$Status.text = "Saved WAV file to: %s\n(%s)" % [save_path, ProjectSettings.globalize_path(save_path)]
```
.. code-tab:: csharp
public void OnSavebuttonPressed()
{
string savePath = GetNode<LineEdit>("SaveButton/Filename").Text;
_recording.SaveToWav(savePath);
GetNode<Label>("Status").Text = string.Format("Saved WAV file to: {0}\n({1})", savePath, ProjectSettings.GlobalizePath(savePath));
}
To save the recording, you call ``save_to_wav()`` with the path to a file.
In this demo, the path is defined by the user via a ``LineEdit`` input box.

View File

@ -34,9 +34,9 @@ The output latency (what happens after the mix) can also be estimated by calling
Add these two and it's possible to guess almost exactly when sound or music will begin playing in the speakers during *_process()*:
.. tabs::
.. code-tab:: gdscript GDScript
gdscript GDScript
```
var time_begin
var time_delay
@ -55,25 +55,7 @@ Add these two and it's possible to guess almost exactly when sound or music will
# May be below 0 (did not begin yet).
time = max(0, time)
print("Time is: ", time)
.. code-tab:: csharp
private double _timeBegin;
private double _timeDelay;
public override void _Ready()
{
_timeBegin = OS.GetTicksUsec();
_timeDelay = AudioServer.GetTimeToNextMix() + AudioServer.GetOutputLatency();
GetNode<AudioStreamPlayer>("Player").Play();
}
public override void _Process(float _delta)
{
double time = (OS.GetTicksUsec() - _timeBegin) / 1000000.0d;
time = Math.Max(0.0d, time - _timeDelay);
GD.Print(string.Format("Time is: {0}", time));
}
```
In the long run, though, as the sound hardware clock is never exactly in sync with the system clock, the timing information will slowly drift away.
@ -90,35 +72,27 @@ To compensate for the "chunked" output, there is a function that can help: :ref:
Adding the return value from this function to *get_playback_position()* increases precision:
.. tabs::
.. code-tab:: gdscript GDScript
gdscript GDScript
```
var time = $Player.get_playback_position() + AudioServer.get_time_since_last_mix()
.. code-tab:: csharp
double time = GetNode<AudioStreamPlayer>("Player").GetPlaybackPosition() + AudioServer.GetTimeSinceLastMix();
```
To increase precision, subtract the latency information (how much it takes for the audio to be heard after it was mixed):
.. tabs::
.. code-tab:: gdscript GDScript
gdscript GDScript
```
var time = $Player.get_playback_position() + AudioServer.get_time_since_last_mix() - AudioServer.get_output_latency()
.. code-tab:: csharp
double time = GetNode<AudioStreamPlayer>("Player").GetPlaybackPosition() + AudioServer.GetTimeSinceLastMix() - AudioServer.GetOutputLatency();
```
The result may be a bit jittery due how multiple threads work. Just check that the value is not less than in the previous frame (discard it if so). This is also a less precise approach than the one before, but it will work for songs of any length, or synchronizing anything (sound effects, as an example) to music.
Here is the same code as before using this approach:
.. tabs::
.. code-tab:: gdscript GDScript
gdscript GDScript
```
func _ready():
$Player.play()
@ -128,18 +102,4 @@ Here is the same code as before using this approach:
# Compensate for output latency.
time -= AudioServer.get_output_latency()
print("Time is: ", time)
.. code-tab:: csharp
public override void _Ready()
{
GetNode<AudioStreamPlayer>("Player").Play();
}
public override void _Process(float _delta)
{
double time = GetNode<AudioStreamPlayer>("Player").GetPlaybackPosition() + AudioServer.GetTimeSinceLastMix();
// Compensate for output latency.
time -= AudioServer.GetOutputLatency();
GD.Print(string.Format("Time is: {0}", time));
}
```

View File

@ -230,9 +230,9 @@ class contains things that won't be relevant to one's custom data structure.
As such, it can be helpful to construct one's own node type when building
tree structures.
.. tabs::
.. code-tab:: gdscript GDScript
gdscript GDScript
```
extends Object
class_name TreeNode
@ -245,33 +245,7 @@ tree structures.
# Destructor.
for a_child in _children:
a_child.free()
.. code-tab:: csharp
// Can decide whether to expose getters/setters for properties later
public class TreeNode : Object
{
private TreeNode _parent = null;
private object[] _children = new object[0];
public override void Notification(int what)
{
switch (what)
{
case NotificationPredelete:
foreach (object child in _children)
{
TreeNode node = child as TreeNode;
if (node != null)
node.Free();
}
break;
default:
break;
}
}
}
```
From here, one can then create their own structures with specific features,
limited only by their imagination.

View File

@ -18,16 +18,12 @@ Acquiring object references
For all :ref:`Object <class_Object>`\s, the most basic way of referencing them
is to get a reference to an existing object from another acquired instance.
.. tabs::
.. code-tab:: gdscript GDScript
gdscript GDScript
```
var obj = node.object # Property access.
var obj = node.get_object() # Method access.
.. code-tab:: csharp
Object obj = node.Object; // Property access.
Object obj = node.GetObject(); // Method access.
```
The same principle applies for :ref:`Reference <class_Reference>` objects.
While users often access :ref:`Node <class_Node>` and
@ -36,9 +32,9 @@ While users often access :ref:`Node <class_Node>` and
Instead of property or method access, one can get Resources by load
access.
.. tabs::
.. code-tab:: gdscript GDScript
gdscript GDScript
```
var preres = preload(path) # Load resource during scene load
var res = load(path) # Load resource when program reaches statement
@ -65,52 +61,7 @@ access.
if not const_script:
return "Must initialize property 'const_script'."
return ""
.. code-tab:: csharp
// Tool script added for the sake of the "const [Export]" example.
[Tool]
public MyType
{
// Property initializations load during Script instancing, i.e. .new().
// No "preload" loads during scene load exists in C#.
// Initialize with a value. Editable at runtime.
public Script MyScript = GD.Load<Script>("MyScript.cs");
// Initialize with same value. Value cannot be changed.
public readonly Script MyConstScript = GD.Load<Script>("MyScript.cs");
// Like 'readonly' due to inaccessible setter.
// But, value can be set during constructor, i.e. MyType().
public Script Library { get; } = GD.Load<Script>("res://addons/plugin/library.gd");
// If need a "const [Export]" (which doesn't exist), use a
// conditional setter for a tool script that checks if it's executing
// in the editor.
private PackedScene _enemyScn;
[Export]
public PackedScene EnemyScn
{
get { return _enemyScn; }
set
{
if (Engine.IsEditorHint())
{
_enemyScn = value;
}
}
};
// Warn users if the value hasn't been set.
public String _GetConfigurationWarning()
{
if (EnemyScn == null)
return "Must initialize property 'EnemyScn'.";
return "";
}
}
```
Note the following:
@ -126,9 +77,9 @@ Note the following:
Nodes likewise have an alternative access point: the SceneTree.
.. tabs::
.. code-tab:: gdscript GDScript
gdscript GDScript
```
extends Node
# Slow.
@ -178,66 +129,7 @@ Nodes likewise have an alternative access point: the SceneTree.
print(globals)
print(globals.prop)
print(globals.my_getter())
.. code-tab:: csharp
public class MyNode
{
// Slow
public void DynamicLookupWithDynamicNodePath()
{
GD.Print(GetNode(NodePath("Child")));
}
// Fastest. Lookup node and cache for future access.
// Doesn't break if node moves later.
public Node Child;
public void _Ready()
{
Child = GetNode(NodePath("Child"));
}
public void LookupAndCacheForFutureAccess()
{
GD.Print(Child);
}
// Delegate reference assignment to an external source.
// Con: need to perform a validation check.
// Pro: node makes no requirements of its external structure.
// 'prop' can come from anywhere.
public object Prop;
public void CallMeAfterPropIsInitializedByParent()
{
// Validate prop in one of three ways.
// Fail with no notification.
if (prop == null)
{
return;
}
// Fail with an error message.
if (prop == null)
{
GD.PrintErr("'Prop' wasn't initialized");
return;
}
// Fail and terminate.
Debug.Assert(Prop, "'Prop' wasn't initialized");
}
// Use an autoload.
// Dangerous for typical nodes, but useful for true singleton nodes
// that manage their own data and don't interfere with other objects.
public void ReferenceAGlobalAutoloadedVariable()
{
Node globals = GetNode(NodePath("/root/Globals"));
GD.Print(globals);
GD.Print(globals.prop);
GD.Print(globals.my_getter());
}
};
```
.. _doc_accessing_data_or_logic_from_object:
@ -288,9 +180,9 @@ accesses:
- A duck-typed property access. These will property check (as described above).
If the operation isn't supported by the object, execution will halt.
.. tabs::
.. code-tab:: gdscript GDScript
gdscript GDScript
```
# All Objects have duck-typed get, set, and call wrapper methods.
get_parent().set("visible", false)
@ -304,22 +196,15 @@ accesses:
# existence, but the property isn't recognized in any _get_property_list
# method, then the set() and get() methods will work, but the symbol
# access will claim it can't find the property.
.. code-tab:: csharp
// All Objects have duck-typed Get, Set, and Call wrapper methods.
GetParent().Set("visible", false);
// C# is a static language, so it has no dynamic symbol access, e.g.
// `GetParent().Visible = false` won't work.
```
- A method check. In the case of
:ref:`CanvasItem.visible <class_CanvasItem_property_visible>`, one can
access the methods, ``set_visible`` and ``is_visible`` like any other method.
.. tabs::
.. code-tab:: gdscript GDScript
gdscript GDScript
```
var child = get_child(0)
# Dynamic lookup.
@ -375,89 +260,15 @@ accesses:
# defines (which means documentation! But maybe worth it?).
# Any script that conforms to the documented "interface" of the name or
# group can fill in for it.
.. code-tab:: csharp
Node child = GetChild(0);
// Dynamic lookup.
child.Call("SetVisible", false);
// Dynamic lookup, checks for method existence first.
if (child.HasMethod("SetVisible"))
{
child.Call("SetVisible", false);
}
// Use a group as if it were an "interface", i.e. assume it implements
// certain methods.
// Requires good documentation for the project to keep it reliable
// (unless you make editor tools to enforce it at editor time).
// Note, this is generally not as good as using an actual interface in
// C#, but you can't set C# interfaces from the editor since they are
// language-level features.
if (child.IsInGroup("Offer"))
{
child.Call("Accept");
child.Call("Reject");
}
// Cast check, followed by static lookup.
CanvasItem ci = GetParent() as CanvasItem;
if (ci != null)
{
ci.SetVisible(false);
// useful when you need to make multiple safe calls to the class
ci.ShowOnTop = true;
}
// If one does not wish to fail these checks without notifying users,
// one can use an assert instead. These will trigger runtime errors
// immediately if not true.
Debug.Assert(child.HasMethod("set_visible"));
Debug.Assert(child.IsInGroup("offer"));
Debug.Assert(CanvasItem.InstanceHas(child));
// Can also use object labels to imply an interface, i.e. assume it
// implements certain methods.
// There are two types, both of which only exist for Nodes: Names and
// Groups.
// Assuming...
// A "Quest" object exists and 1) that it can "Complete" or "Fail" and
// that it will have Text available before and after each state...
// 1. Use a name.
Node quest = GetNode("Quest");
GD.Print(quest.Get("Text"));
quest.Call("Complete"); // or "Fail".
GD.Print(quest.Get("Text")); // Implied new text content.
// 2. Use a group.
foreach (Node AChild in GetChildren())
{
if (AChild.IsInGroup("quest"))
{
GD.Print(quest.Get("Text"));
quest.Call("Complete"); // or "Fail".
GD.Print(quest.Get("Text")); // Implied new text content.
}
}
// Note that these interfaces are project-specific conventions the team
// defines (which means documentation! But maybe worth it?).
// Any script that conforms to the documented "interface" of the
// name or group can fill in for it. Also note that in C#, these methods
// will be slower than static accesses with traditional interfaces.
```
- Outsource the access to a :ref:`FuncRef <class_FuncRef>`. These may be useful
in cases where one needs the max level of freedom from dependencies. In
this case, one relies on an external context to setup the method.
.. tabs::
.. code-tab:: gdscript GDScript
gdscript GDScript
```
# child.gd
extends Node
var fn = null
@ -477,38 +288,8 @@ accesses:
func print_me():
print(name)
```
.. code-tab:: csharp
// Child.cs
public class Child : Node
{
public FuncRef FN = null;
public void MyMethod()
{
Debug.Assert(FN != null);
FN.CallFunc();
}
}
// Parent.cs
public class Parent : Node
{
public Node Child;
public void _Ready()
{
Child = GetNode("Child");
Child.Set("FN", GD.FuncRef(this, "PrintMe"));
Child.MyMethod();
}
public void PrintMe() {
{
GD.Print(GetClass());
}
}
These strategies contribute to Godot's flexible design. Between them, users
have a breadth of tools to meet their specific needs.

View File

@ -79,9 +79,9 @@ often execute here, but it comes down to the frequency at which one needs
the evaluations to update. If they don't need to execute every frame, then
implementing a Timer-yield-timeout loop is another option.
.. tabs::
.. code-tab:: gdscript GDScript
gdscript GDScript
```
# Infinitely loop, but only execute whenever the Timer fires.
# Allows for recurring operations that don't trigger script logic
# every frame (or even every fixed frame).
@ -89,6 +89,7 @@ implementing a Timer-yield-timeout loop is another option.
my_method()
$Timer.start()
yield($Timer, "timeout")
```
Use ``_physics_process`` when one needs a framerate-independent deltatime
between frames. If code needs consistent updates over time, regardless
@ -105,9 +106,9 @@ One can check for input actions within the input callbacks just the same.
If one wants to use delta time, one can fetch it from the related
deltatime methods as needed.
.. tabs::
.. code-tab:: gdscript GDScript
gdscript GDScript
```
# Called every frame, even when the engine detects no input.
func _process(delta):
if Input.is_action_just_pressed("ui_select"):
@ -119,34 +120,8 @@ deltatime methods as needed.
"InputEventKey":
if Input.is_action_just_pressed("ui_accept"):
print(get_process_delta_time())
```
.. code-tab:: csharp
public class MyNode : Node
{
// Called every frame, even when the engine detects no input.
public void _Process(float delta)
{
if (Input.IsActionJustPressed("ui_select"))
GD.Print(delta);
}
// Called during every input event. Equally true for _input().
public void _UnhandledInput(InputEvent event)
{
switch (event)
{
case InputEventKey keyEvent:
if (Input.IsActionJustPressed("ui_accept"))
GD.Print(GetProcessDeltaTime());
break;
default:
break;
}
}
}
_init vs. initialization vs. export
-----------------------------------
@ -159,9 +134,9 @@ initializations should also run here. This triggers before ``_ready`` or
Scripts have three types of property assignments that can occur during
instantiation:
.. tabs::
.. code-tab:: gdscript GDScript
gdscript GDScript
```
# "one" is an "initialized value". These DO NOT trigger the setter.
# If someone set the value as "two" from the Inspector, this would be an
# "exported value". These DO trigger the setter.
@ -177,31 +152,8 @@ instantiation:
func set_test(value):
test = value
print("Setting: ", test)
```
.. code-tab:: csharp
public class MyNode : Node
{
private string _test = "one";
// Changing the value from the inspector does trigger the setter in C#.
[Export]
public string Test
{
get { return _test; }
set
{
_test = value;
GD.Print("Setting: " + _test);
}
}
public MyNode()
{
// Triggers the setter as well
Test = "three";
}
}
When instantiating a scene, property values will set up according to the
following sequence:
@ -239,9 +191,9 @@ For example, here is a snippet that connects a node's method to
a custom signal on the parent node without failing. Useful on data-centric
nodes that one might create at runtime.
.. tabs::
.. code-tab:: gdscript GDScript
gdscript GDScript
```
extends Node
var parent_cache
@ -261,36 +213,4 @@ nodes that one might create at runtime.
func _on_parent_interacted_with():
print("I'm reacting to my parent's interaction!")
.. code-tab:: csharp
public class MyNode : Node
{
public Node ParentCache = null;
public void ConnectionCheck()
{
return ParentCache.HasUserSignal("InteractedWith");
}
public void _Notification(int what)
{
switch (what)
{
case NOTIFICATION_PARENTED:
ParentCache = GetParent();
if (ConnectionCheck())
ParentCache.Connect("InteractedWith", this, "OnParentInteractedWith");
break;
case NOTIFICATION_UNPARENTED:
if (ConnectionCheck())
ParentCache.Disconnect("InteractedWith", this, "OnParentInteractedWith");
break;
}
}
public void OnParentInteractedWith()
{
GD.Print("I'm reacting to my parent's interaction!");
}
}
```

View File

@ -24,9 +24,9 @@ accessible to *all* scripting languages.
So, when exactly does preloading occur versus loading, and when should one use
either? Let's see an example:
.. tabs::
.. code-tab:: gdscript GDScript
gdscript GDScript
```
# my_buildings.gd
extends Node
@ -64,26 +64,7 @@ either? Let's see an example:
# Successfully loads and only when one instantiates the script! Yay!
var office_scn = load("res://office.tscn")
.. code-tab:: csharp
using System;
using Godot;
// C# and other languages have no concept of "preloading".
public class MyBuildings : Node
{
//This is a read-only field, it can only be assigned when it's declared or during a constructor.
public readonly PackedScene Building = ResourceLoader.Load<PackedScene>("res://building.tscn");
public PackedScene ABuilding;
public override void _Ready()
{
// Can assign the value during initialization.
ABuilding = GD.Load<PackedScene>("res://office.tscn");
}
}
```
Preloading allows the script to handle all the loading the moment one loads the
script. Preloading is useful, but there are also times when one doesn't wish

View File

@ -53,99 +53,64 @@ initialize it:
behavior, not start it. Note that signal names are usually past-tense verbs
like "entered", "skill_activated", or "item_collected".
.. tabs::
.. code-tab:: gdscript GDScript
gdscript GDScript
```
# Parent
$Child.connect("signal_name", object_with_method, "method_on_the_object")
# Child
emit_signal("signal_name") # Triggers parent-defined behavior.
.. code-tab:: csharp
// Parent
GetNode("Child").Connect("SignalName", ObjectWithMethod, "MethodOnTheObject");
// Child
EmitSignal("SignalName"); // Triggers parent-defined behavior.
```
2. Call a method. Used to start behavior.
.. tabs::
.. code-tab:: gdscript GDScript
gdscript GDScript
```
# Parent
$Child.method_name = "do"
# Child, assuming it has String property 'method_name' and method 'do'.
call(method_name) # Call parent-defined method (which child must own).
.. code-tab:: csharp
// Parent
GetNode("Child").Set("MethodName", "Do");
// Child
Call(MethodName); // Call parent-defined method (which child must own).
```
3. Initialize a :ref:`FuncRef <class_FuncRef>` property. Safer than a method
as ownership of the method is unnecessary. Used to start behavior.
.. tabs::
.. code-tab:: gdscript GDScript
gdscript GDScript
```
# Parent
$Child.func_property = funcref(object_with_method, "method_on_the_object")
# Child
func_property.call_func() # Call parent-defined method (can come from anywhere).
.. code-tab:: csharp
// Parent
GetNode("Child").Set("FuncProperty", GD.FuncRef(ObjectWithMethod, "MethodOnTheObject"));
// Child
FuncProperty.CallFunc(); // Call parent-defined method (can come from anywhere).
```
4. Initialize a Node or other Object reference.
.. tabs::
.. code-tab:: gdscript GDScript
gdscript GDScript
```
# Parent
$Child.target = self
# Child
print(target) # Use parent-defined node.
.. code-tab:: csharp
// Parent
GetNode("Child").Set("Target", this);
// Child
GD.Print(Target); // Use parent-defined node.
```
5. Initialize a NodePath.
.. tabs::
.. code-tab:: gdscript GDScript
gdscript GDScript
```
# Parent
$Child.target_path = ".."
# Child
get_node(target_path) # Use parent-defined NodePath.
.. code-tab:: csharp
// Parent
GetNode("Child").Set("TargetPath", NodePath(".."));
// Child
GetNode(TargetPath); // Use parent-defined NodePath.
```
These options hide the points of access from the child node. This in turn
keeps the child **loosely coupled** to its environment. One can re-use it
@ -158,9 +123,9 @@ in another context without any extra changes to its API.
are siblings should only be aware of their hierarchies while an ancestor
mediates their communications and references.
.. tabs::
.. code-tab:: gdscript GDScript
gdscript GDScript
```
# Parent
$Left.target = $Right.get_node("Receiver")
@ -173,32 +138,7 @@ in another context without any extra changes to its API.
func _init():
var receiver = Receiver.new()
add_child(receiver)
.. code-tab:: csharp
// Parent
GetNode<Left>("Left").Target = GetNode("Right/Receiver");
public class Left : Node
{
public Node Target = null;
public void Execute()
{
// Do something with 'Target'.
}
}
public class Right : Node
{
public Node Receiver = null;
public Right()
{
Receiver = ResourceLoader.Load<Script>("Receiver.cs").New();
AddChild(Receiver);
}
}
```
The same principles also apply to non-Node objects that maintain dependencies
on other objects. Whichever object actually owns the objects should manage

View File

@ -23,38 +23,16 @@ But, choosing which one to use can be a dilemma. Creating script instances
is identical to creating in-engine classes whereas handling scenes requires
a change in API:
.. tabs::
.. code-tab:: gdscript GDScript
gdscript GDScript
```
const MyNode = preload("my_node.gd")
const MyScene = preload("my_scene.tscn")
var node = Node.new()
var my_node = MyNode.new() # Same method call
var my_scene = MyScene.instance() # Different method call
var my_inherited_scene = MyScene.instance(PackedScene.GEN_EDIT_STATE_MAIN) # Create scene inheriting from MyScene
.. code-tab:: csharp
using System;
using Godot;
public class Game : Node
{
public readonly Script MyNodeScr = (Script)ResourceLoader.Load("MyNode.cs");
public readonly PackedScene MySceneScn = (PackedScene)ResourceLoader.Load("MyScene.tscn");
public Node ANode;
public Node MyNode;
public Node MyScene;
public Node MyInheritedScene;
public Game()
{
ANode = new Node();
MyNode = new MyNode(); // Same syntax
MyScene = MySceneScn.Instance(); // Different. Instantiated from a PackedScene
MyInheritedScene = MySceneScn.Instance(PackedScene.GenEditState.Main); // Create scene inheriting from MyScene
}
}
```
Also, scripts will operate a little slower than scenes due to the
speed differences between engine and script code. The larger and more complex
@ -160,9 +138,9 @@ The code example below creates a new ``Node``, changes its name, assigns a
script to it, sets its future parent as its owner so it gets saved to disk along
with it, and finally adds it as a child of the ``Main`` node:
.. tabs::
.. code-tab:: gdscript GDScript
gdscript GDScript
```
# Main.gd
extends Node
@ -172,25 +150,7 @@ with it, and finally adds it as a child of the ``Main`` node:
child.script = preload("Child.gd")
child.owner = self
add_child(child)
.. code-tab:: csharp
using System;
using Godot;
public class Main : Resource
{
public Node Child { get; set; }
public Main()
{
Child = new Node();
Child.Name = "Child";
Child.Script = ResourceLoader.Load<Script>("child.gd");
Child.Owner = this;
AddChild(Child);
}
}
```
Script code like this is much slower than engine-side C++ code. Each instruction
makes a call to the scripting API which leads to many "lookups" on the back-end
@ -219,9 +179,9 @@ In the end, the best approach is to consider the following:
this in 3.1 by declaring a script class and giving it a scene as a constant.
The script becomes, in effect, a namespace:
.. tabs::
.. code-tab:: gdscript GDScript
gdscript GDScript
```
# game.gd
extends Reference
class_name Game # extends Reference, so it won't show up in the node creation dialog
@ -231,3 +191,4 @@ In the end, the best approach is to consider the following:
extends Node
func _ready():
add_child(Game.MyScene.instance())
```

View File

@ -54,9 +54,9 @@ set by the touch (or click) event.
Here is the full script for the player, with comments noting what we've
changed:
.. tabs::
.. code-tab:: gdscript GDScript
gdscript GDScript
```
extends Area2D
signal hit
@ -124,119 +124,7 @@ changed:
hide()
emit_signal("hit")
$CollisionShape2D.set_deferred("disabled", true)
.. code-tab:: csharp
using Godot;
using System;
public class Player : Area2D
{
[Signal]
public delegate void Hit();
[Export]
public int Speed = 400;
private Vector2 _screenSize;
// Add this variable to hold the clicked position.
private Vector2 _target;
public override void _Ready()
{
Hide();
_screenSize = GetViewport().Size;
}
public void Start(Vector2 pos)
{
Position = pos;
// Initial target us the start position.
_target = pos;
Show();
GetNode<CollisionShape2D>("CollisionShape2D").Disabled = false;
}
// Change the target whenever a touch event happens.
public override void _Input(InputEvent @event)
{
if (@event is InputEventScreenTouch eventMouseButton && eventMouseButton.Pressed)
{
_target = (@event as InputEventScreenTouch).Position;
}
}
public override void _Process(float delta)
{
var velocity = new Vector2();
// Move towards the target and stop when close.
if (Position.DistanceTo(_target) > 10)
{
velocity = _target - Position;
}
// Remove keyboard controls.
//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 = GetNode<AnimatedSprite>("AnimatedSprite");
if (velocity.Length() > 0)
{
velocity = velocity.Normalized() * Speed;
animatedSprite.Play();
}
else
{
animatedSprite.Stop();
}
Position += velocity * delta;
// We still need to clamp the player's position here because on devices that don't
// match your game's aspect ratio, Godot will try to maintain it as much as possible
// by creating black borders, if necessary.
// Without clamp(), the player would be able to move under those borders.
Position = new Vector2(
x: Mathf.Clamp(Position.x, 0, _screenSize.x),
y: Mathf.Clamp(Position.y, 0, _screenSize.y)
);
if (velocity.x != 0)
{
animatedSprite.Animation = "walk";
animatedSprite.FlipV = false;
animatedSprite.FlipH = velocity.x < 0;
}
else if(velocity.y != 0)
{
animatedSprite.Animation = "up";
animatedSprite.FlipV = velocity.y > 0;
}
}
public void OnPlayerBodyEntered(PhysicsBody2D body)
{
Hide(); // Player disappears after being hit.
EmitSignal("Hit");
GetNode<CollisionShape2D>("CollisionShape2D").SetDeferred("disabled", true);
}
}
```
Setting a main scene
--------------------

View File

@ -93,9 +93,9 @@ To import a PCK file, one uses the ProjectSettings singleton. The following
example expects a “mod.pck” file in the directory of the games executable.
The PCK file contains a “mod_scene.tscn” test scene in its root.
.. tabs::
.. code-tab:: gdscript GDScript
gdscript GDScript
```
func _your_function():
# This could fail if, for example, mod.pck cannot be found.
var success = ProjectSettings.load_resource_pack("res://mod.pck")
@ -103,20 +103,7 @@ The PCK file contains a “mod_scene.tscn” test scene in its root.
if success:
# Now one can use the assets as if they had them in the project from the start.
var imported_scene = load("res://mod_scene.tscn")
.. code-tab:: csharp
private void YourFunction()
{
// This could fail if, for example, mod.pck cannot be found.
var success = ProjectSettings.LoadResourcePack("res://mod.pck");
if (success)
{
// Now one can use the assets as if they had them in the project from the start.
var importedScene = (PackedScene)ResourceLoader.Load("res://mod_scene.tscn");
}
}
```
.. warning::

View File

@ -46,9 +46,9 @@ There are 3 ways to get input in an analog-aware way:
- When you have two axes (such as joystick or WASD movement) and want both
axes to behave as a single input, use ``Input.get_vector()``:
.. tabs::
.. code-tab:: gdscript GDScript
gdscript GDScript
```
# `velocity` will be a Vector2 between `Vector2(-1.0, -1.0)` and `Vector2(1.0, 1.0)`.
# This handles deadzone in a correct way for most use cases.
# The resulting deadzone will have a circular shape as it generally should.
@ -59,70 +59,42 @@ There are 3 ways to get input in an analog-aware way:
# a square-ish shape when it should ideally have a circular shape.
var velocity = Vector2(Input.get_action_strength("move_right") - Input.get_action_strength("move_left"),
Input.get_action_strength("move_back") - Input.get_action_strength("move_forward")).clamped(1)
.. code-tab:: csharp
// `velocity` will be a Vector2 between `Vector2(-1.0, -1.0)` and `Vector2(1.0, 1.0)`.
// This handles deadzone in a correct way for most use cases.
// The resulting deadzone will have a circular shape as it generally should.
Vector2 velocity = Input.GetVector("move_left", "move_right", "move_forward", "move_back");
// The line below is similar to `get_vector()`, except that it handles
// the deadzone in a less optimal way. The resulting deadzone will have
// a square-ish shape when it should ideally have a circular shape.
Vector2 velocity = new Vector2(Input.GetActionStrength("move_right") - Input.GetActionStrength("move_left"),
Input.GetActionStrength("move_back") - Input.GetActionStrength("move_forward")).Clamped(1);
```
- When you have one axis that can go both ways (such as a throttle on a
flight stick), or when you want to handle separate axes individually,
use ``Input.get_axis()``:
.. tabs::
.. code-tab:: gdscript GDScript
gdscript GDScript
```
# `walk` will be a floating-point number between `-1.0` and `1.0`.
var walk = Input.get_axis("move_left", "move_right")
# The line above is a shorter form of:
var walk = Input.get_action_strength("move_right") - Input.get_action_strength("move_left")
.. code-tab:: csharp
// `walk` will be a floating-point number between `-1.0` and `1.0`.
float walk = Input.GetAxis("move_left", "move_right");
// The line above is a shorter form of:
float walk = Input.GetActionStrength("move_right") - Input.GetActionStrength("move_left");
```
- For other types of analog input, such as handling a trigger or handling
one direction at a time, use ``Input.get_action_strength()``:
.. tabs::
.. code-tab:: gdscript GDScript
gdscript GDScript
```
# `strength` will be a floating-point number between `0.0` and `1.0`.
var strength = Input.get_action_strength("accelerate")
.. code-tab:: csharp
// `strength` will be a floating-point number between `0.0` and `1.0`.
float strength = Input.GetActionStrength("accelerate");
```
For non-analog digital/boolean input (only "pressed" or "not pressed" values),
such as controller buttons, mouse buttons or keyboard keys,
use ``Input.is_action_pressed()``:
.. tabs::
.. code-tab:: gdscript GDScript
gdscript GDScript
```
# `jumping` will be a boolean with a value of `true` or `false`.
var jumping = Input.is_action_pressed("jump")
.. tabs::
.. code-tab:: csharp
// `jumping` will be a boolean with a value of `true` or `false`.
bool jumping = Input.IsActionPressed("jump");
```
In Godot versions before 3.4, such as 3.3, ``Input.get_vector()`` and
``Input.get_axis()`` aren't available. Only ``Input.get_action_strength()``
@ -205,21 +177,26 @@ in the `official Joypads demo <https://godotengine.org/asset-library/asset/140>`
Once you have a working mapping for your controller, you can test it by defining
the ``SDL_GAMECONTROLLERCONFIG`` environment variable before running Godot:
.. tabs::
.. code-tab:: bash Linux/macOS
bash Linux/macOS
```
export SDL_GAMECONTROLLERCONFIG="your:mapping:here"
./path/to/godot.x86_64
```
.. code-tab:: bat Windows (cmd)
bat Windows (cmd)
```
set SDL_GAMECONTROLLERCONFIG=your:mapping:here
path\to\godot.exe
```
.. code-tab:: powershell Windows (powershell)
powershell Windows (powershell)
```
$env:SDL_GAMECONTROLLERCONFIG="your:mapping:here"
path\to\godot.exe
```
To test mappings on non-desktop platforms or to distribute your project with
additional controller mappings, you can add them by calling

View File

@ -38,9 +38,9 @@ Using a script
Create a Node and attach the following script.
.. tabs::
.. code-tab:: gdscript GDScript
gdscript GDScript
```
extends Node
@ -56,22 +56,7 @@ Create a Node and attach the following script.
# Changes a specific shape of the cursor (here, the I-beam shape).
Input.set_custom_mouse_cursor(beam, Input.CURSOR_IBEAM)
.. code-tab:: csharp
public override void _Ready()
{
// Load the custom images for the mouse cursor.
var arrow = ResourceLoader.Load("res://arrow.png");
var beam = ResourceLoader.Load("res://beam.png");
// Changes only the arrow shape of the cursor.
// This is similar to changing it in the project settings.
Input.SetCustomMouseCursor(arrow);
// Changes a specific shape of the cursor (here, the I-beam shape).
Input.SetCustomMouseCursor(beam, Input.CursorShape.Ibeam);
}
```
.. note::
Check :ref:`Input.set_custom_mouse_cursor() <class_Input_method_set_custom_mouse_cursor>`.

View File

@ -30,20 +30,13 @@ Pressing the Back button will exit the application if
Handling the notification is done as follows (on any node):
.. tabs::
.. code-tab:: gdscript GDScript
gdscript GDScript
```
func _notification(what):
if what == MainLoop.NOTIFICATION_WM_QUIT_REQUEST:
get_tree().quit() # default behavior
.. code-tab:: csharp
public override void _Notification(int what)
{
if (what == MainLoop.NotificationWmQuitRequest)
GetTree().Quit(); // default behavior
}
```
When developing mobile apps, quitting is not desired unless the user is
on the main screen, so the behavior can be changed.
@ -51,14 +44,11 @@ on the main screen, so the behavior can be changed.
It is important to note that by default, Godot apps have the built-in
behavior to quit when quit is requested, this can be changed:
.. tabs::
.. code-tab:: gdscript GDScript
gdscript GDScript
```
get_tree().set_auto_accept_quit(false)
.. code-tab:: csharp
GetTree().SetAutoAcceptQuit(false);
```
Sending your own quit notification
----------------------------------
@ -72,11 +62,8 @@ to delay the line that forces the quit.
Instead, you should send a quit request:
.. tabs::
.. code-tab:: gdscript GDScript
gdscript GDScript
```
get_tree().notification(MainLoop.NOTIFICATION_WM_QUIT_REQUEST)
.. code-tab:: csharp
GetTree().Notification(MainLoop.NotificationWmQuitRequest)
```

View File

@ -28,9 +28,9 @@ singleton, which you can use to query the state of an input.
Examples:
.. tabs::
.. code-tab:: gdscript GDScript
gdscript GDScript
```
func _input(event):
if event.is_action_pressed("jump"):
jump()
@ -40,25 +40,7 @@ Examples:
if Input.is_action_pressed("move_right"):
# Move as long as the key/button is pressed.
position.x += speed * delta
.. code-tab:: csharp
public override void _Input(InputEvent inputEvent)
{
if (inputEvent.IsActionPressed("jump"))
{
Jump();
}
}
public override void _PhysicsProcess(float delta)
{
if (Input.IsActionPressed("move_right"))
{
// Move as long as the key/button is pressed.
position.x += speed * delta;
}
}
```
This gives you the flexibility to mix-and-match the type of input processing
you do.
@ -74,27 +56,15 @@ Depending on the event type, the object will contain specific properties
related to that event. To see what events actually look like, add a Node and
attach the following script:
.. tabs::
.. code-tab:: gdscript GDScript
gdscript GDScript
```
extends Node
func _input(event):
print(event.as_text())
.. code-tab:: csharp
using Godot;
using System;
public class Node : Godot.Node
{
public override void _Input(InputEvent inputEvent)
{
GD.Print(inputEvent.AsText());
}
}
```
As you press keys, move the mouse, and perform other inputs, you'll see each
event scroll by in the output window. Here's an example of the output:
@ -128,22 +98,13 @@ You can encounter errors if you try to access a property on an input type that
doesn't contain it - calling ``position`` on ``InputEventKey`` for example. To
avoid this, make sure to test the event type first:
.. tabs::
.. code-tab:: gdscript GDScript
gdscript GDScript
```
func _input(event):
if event is InputEventMouseButton:
print("mouse button event at ", event.position)
.. code-tab:: csharp
public override void _Input(InputEvent inputEvent)
{
if (inputEvent is InputEventMouseButton mouseEvent)
{
GD.Print("mouse button event at ", mouseEvent.Position);
}
}
```
InputMap
--------
@ -164,22 +125,13 @@ Once you've defined your actions, you can process them in your scripts using
``is_action_pressed()`` and ``is_action_released()`` by passing the name of
the action you're looking for:
.. tabs::
.. code-tab:: gdscript GDScript
gdscript GDScript
```
func _input(event):
if event.is_action_pressed("my_action"):
print("my_action occurred!")
.. code-tab:: csharp
public override void _Input(InputEvent inputEvent)
{
if (inputEvent.IsActionPressed("my_action"))
{
GD.Print("my_action occurred!");
}
}
```
Keyboard events
---------------
@ -189,26 +141,14 @@ While it's recommended to use input actions instead, there may be cases where
you want to specifically look at key events. For this example, let's check for
the :kbd:`T`:
.. tabs::
.. code-tab:: gdscript GDScript
gdscript GDScript
```
func _input(event):
if event is InputEventKey and event.pressed:
if event.scancode == KEY_T:
print("T was pressed")
.. code-tab:: csharp
public override void _Input(InputEvent inputEvent)
{
if (inputEvent is InputEventKey keyEvent && keyEvent.Pressed)
{
if ((KeyList)keyEvent.Keycode == KeyList.T)
{
GD.Print("T was pressed");
}
}
}
```
.. tip:: See :ref:`@GlobalScope_KeyList <enum_@GlobalScope_KeyList>` for a list of scancode
constants.
@ -235,9 +175,9 @@ you to check for modifier combinations using boolean properties. Let's imagine
you want one thing to happen when the :kbd:`T` is pressed, but something
different when it's :kbd:`Shift + T`:
.. tabs::
.. code-tab:: gdscript GDScript
gdscript GDScript
```
func _input(event):
if event is InputEventKey and event.pressed:
if event.scancode == KEY_T:
@ -245,21 +185,7 @@ different when it's :kbd:`Shift + T`:
print("Shift+T was pressed")
else:
print("T was pressed")
.. code-tab:: csharp
public override void _Input(InputEvent inputEvent)
{
if (inputEvent is InputEventKey keyEvent && keyEvent.Pressed)
{
switch ((KeyList)keyEvent.Scancode)
{
case KeyList.T:
GD.Print(keyEvent.Shift ? "Shift+T was pressed" : "T was pressed");
break;
}
}
}
```
.. tip:: See :ref:`@GlobalScope_KeyList <enum_@GlobalScope_KeyList>` for a list of scancode
constants.
@ -281,33 +207,16 @@ be reported in the event's ``button_index`` property. Note that the scrollwheel
also counts as a button - two buttons, to be precise, with both
``BUTTON_WHEEL_UP`` and ``BUTTON_WHEEL_DOWN`` being separate events.
.. tabs::
.. code-tab:: gdscript GDScript
gdscript GDScript
```
func _input(event):
if event is InputEventMouseButton:
if event.button_index == BUTTON_LEFT and event.pressed:
print("Left button was clicked at ", event.position)
if event.button_index == BUTTON_WHEEL_UP and event.pressed:
print("Wheel up")
.. code-tab:: csharp
public override void _Input(InputEvent inputEvent)
{
if (inputEvent is InputEventMouseButton mouseEvent && mouseEvent.Pressed)
{
switch ((ButtonList)mouseEvent.ButtonIndex)
{
case ButtonList.Left:
GD.Print("Left button was clicked at ", {mouseEvent.Position});
break;
case ButtonList.WheelUp:
GD.Print("Wheel up");
break;
}
}
}
```
Mouse motion
~~~~~~~~~~~~
@ -319,9 +228,9 @@ property.
Here's an example using mouse events to drag-and-drop a :ref:`Sprite <class_Sprite>`
node:
.. tabs::
.. code-tab:: gdscript GDScript
gdscript GDScript
```
extends Node
@ -342,51 +251,7 @@ node:
if event is InputEventMouseMotion and dragging:
# While dragging, move the sprite with the mouse.
$Sprite.position = event.position
.. code-tab:: csharp
using Godot;
using System;
public class Node2D : Godot.Node2D
{
private bool dragging = false;
private int clickRadius = 32; // Size of the sprite.
public override void _Input(InputEvent inputEvent)
{
Sprite sprite = GetNodeOrNull<Sprite>("Sprite");
if (sprite == null)
{
return; // No suitable node was found.
}
if (inputEvent is InputEventMouseButton mouseEvent && (ButtonList)mouseEvent.ButtonIndex == ButtonList.Left)
{
if ((mouseEvent.Position - sprite.Position).Length() < clickRadius)
{
// Start dragging if the click is on the sprite.
if (!dragging && mouseEvent.Pressed)
{
dragging = true;
}
}
// Stop dragging if the button is released.
if (dragging && !mouseEvent.Pressed)
{
dragging = false;
}
}
else
{
if (inputEvent is InputEventMouseMotion motionEvent && dragging)
{
// While dragging, move the sprite with the mouse.
sprite.Position = motionEvent.Position;
}
}
}
}
```
Touch events
------------

View File

@ -14,22 +14,14 @@ multiple locations, depending on the purpose.
Here is a quick example, closing your game if the escape key is hit:
.. tabs::
.. code-tab:: gdscript GDScript
gdscript GDScript
```
func _unhandled_input(event):
if event is InputEventKey:
if event.pressed and event.scancode == KEY_ESCAPE:
get_tree().quit()
.. code-tab:: csharp
public override void _UnhandledInput(InputEvent @event)
{
if (@event is InputEventKey eventKey)
if (eventKey.Pressed && eventKey.Scancode == (int)KeyList.Escape)
GetTree().Quit();
}
```
However, it is cleaner and more flexible to use the provided :ref:`InputMap <class_InputMap>` feature,
which allows you to define input actions and assign them different keys. This way,
@ -39,22 +31,13 @@ and even build a key mapping feature on top of it to allow your game to change t
You can set up your InputMap under **Project > Project Settings > Input Map** and then use those actions like this:
.. tabs::
.. code-tab:: gdscript GDScript
gdscript GDScript
```
func _process(delta):
if Input.is_action_pressed("ui_right"):
# Move right.
.. code-tab:: csharp
public override void _Process(float delta)
{
if (Input.IsActionPressed("ui_right"))
{
// Move right.
}
}
```
How does it work?
-----------------
@ -180,24 +163,16 @@ from the game code (a good example of this is detecting gestures).
The Input singleton has a method for this:
:ref:`Input.parse_input_event() <class_input_method_parse_input_event>`. You would normally use it like this:
.. tabs::
.. code-tab:: gdscript GDScript
gdscript GDScript
```
var ev = InputEventAction.new()
# Set as move_left, pressed.
ev.action = "move_left"
ev.pressed = true
# Feedback.
Input.parse_input_event(ev)
.. code-tab:: csharp
var ev = new InputEventAction();
// Set as move_left, pressed.
ev.SetAction("move_left");
ev.SetPressed(true);
// Feedback.
Input.ParseInputEvent(ev);
`````
InputMap
--------

View File

@ -25,9 +25,9 @@ several options (see :ref:`doc_multiple_resolutions` tutorial). Use, then, the
functions in nodes to obtain the mouse coordinates and viewport size,
for example:
.. tabs::
.. code-tab:: gdscript GDScript
gdscript GDScript
```
func _input(event):
# Mouse in viewport coordinates.
if event is InputEventMouseButton:
@ -37,30 +37,15 @@ for example:
# Print the size of the viewport.
print("Viewport Resolution is: ", get_viewport_rect().size)
```
.. code-tab:: csharp
public override void _Input(InputEvent @event)
{
// Mouse in viewport coordinates.
if (@event is InputEventMouseButton eventMouseButton)
GD.Print("Mouse Click/Unclick at: ", eventMouseButton.Position);
else if (@event is InputEventMouseMotion eventMouseMotion)
GD.Print("Mouse Motion at: ", eventMouseMotion.Position);
// Print the size of the viewport.
GD.Print("Viewport Resolution is: ", GetViewportRect().Size);
}
Alternatively, it's possible to ask the viewport for the mouse position:
.. tabs::
.. code-tab:: gdscript GDScript
gdscript GDScript
```
get_viewport().get_mouse_position()
.. code-tab:: csharp
GetViewport().GetMousePosition();
```
.. note:: When the mouse mode is set to ``Input.MOUSE_MODE_CAPTURED``, the ``event.position`` value from ``InputEventMouseMotion`` is the center of the screen. Use ``event.relative`` instead of ``event.position`` and ``event.speed`` to process mouse movement and position changes.

View File

@ -34,21 +34,13 @@ GUI:
Once this is done, when we need to save the game, we can get all objects
to save them and then tell them all to save with this script:
.. tabs::
.. code-tab:: gdscript GDScript
gdscript GDScript
```
var save_nodes = get_tree().get_nodes_in_group("Persist")
for i in save_nodes:
# Now, we can call our save function on each node.
.. code-tab:: csharp
var saveNodes = GetTree().GetNodesInGroup("Persist");
foreach (Node saveNode in saveNodes)
{
// Now, we can call our save function on each node.
}
```
Serializing
-----------
@ -62,9 +54,9 @@ has helper functions for this, such as :ref:`to_json()
contain a save function that returns this data. The save function will look
like this:
.. tabs::
.. code-tab:: gdscript GDScript
gdscript GDScript
```
func save():
var save_dict = {
"filename" : get_filename(),
@ -87,33 +79,7 @@ like this:
"last_attack" : last_attack
}
return save_dict
.. code-tab:: csharp
public Godot.Collections.Dictionary<string, object> Save()
{
return new Godot.Collections.Dictionary<string, object>()
{
{ "Filename", GetFilename() },
{ "Parent", GetParent().GetPath() },
{ "PosX", Position.x }, // Vector2 is not supported by JSON
{ "PosY", Position.y },
{ "Attack", Attack },
{ "Defense", Defense },
{ "CurrentHealth", CurrentHealth },
{ "MaxHealth", MaxHealth },
{ "Damage", Damage },
{ "Regen", Regen },
{ "Experience", Experience },
{ "Tnl", Tnl },
{ "Level", Level },
{ "AttackGrowth", AttackGrowth },
{ "DefenseGrowth", DefenseGrowth },
{ "HealthGrowth", HealthGrowth },
{ "IsAlive", IsAlive },
{ "LastAttack", LastAttack }
};
}
```
This gives us a dictionary with the style
@ -131,9 +97,9 @@ convert it into an easily stored string and store them in a file. Doing
it this way ensures that each line is its own object, so we have an easy
way to pull the data out of the file as well.
.. tabs::
.. code-tab:: gdscript GDScript
gdscript GDScript
```
# Note: This can be called from anywhere inside the tree. This function is
# path independent.
# Go through everything in the persist category and ask them to return a
@ -159,44 +125,7 @@ way to pull the data out of the file as well.
# Store the save dictionary as a new line in the save file.
save_game.store_line(to_json(node_data))
save_game.close()
.. code-tab:: csharp
// Note: This can be called from anywhere inside the tree. This function is
// path independent.
// Go through everything in the persist category and ask them to return a
// dict of relevant variables.
public void SaveGame()
{
var saveGame = new File();
saveGame.Open("user://savegame.save", (int)File.ModeFlags.Write);
var saveNodes = GetTree().GetNodesInGroup("Persist");
foreach (Node saveNode in saveNodes)
{
// Check the node is an instanced scene so it can be instanced again during load.
if (saveNode.Filename.Empty())
{
GD.Print(String.Format("persistent node '{0}' is not an instanced scene, skipped", saveNode.Name));
continue;
}
// Check the node has a save function.
if (!saveNode.HasMethod("Save"))
{
GD.Print(String.Format("persistent node '{0}' is missing a Save() function, skipped", saveNode.Name));
continue;
}
// Call the node's save function.
var nodeData = saveNode.Call("Save");
// Store the save dictionary as a new line in the save file.
saveGame.StoreLine(JSON.Print(nodeData));
}
saveGame.Close();
}
```
Game saved! Loading is fairly simple as well. For that, we'll read each
@ -205,9 +134,9 @@ the dict to read our values. But we'll need to first create the object
and we can use the filename and parent values to achieve that. Here is our
load function:
.. tabs::
.. code-tab:: gdscript GDScript
gdscript GDScript
```
# Note: This can be called from anywhere inside the tree. This function
# is path independent.
func load_game():
@ -242,53 +171,7 @@ load function:
new_object.set(i, node_data[i])
save_game.close()
.. code-tab:: csharp
// Note: This can be called from anywhere inside the tree. This function is
// path independent.
public void LoadGame()
{
var saveGame = new File();
if (!saveGame.FileExists("user://savegame.save"))
return; // Error! We don't have a save to load.
// We need to revert the game state so we're not cloning objects during loading.
// This will vary wildly depending on the needs of a project, so take care with
// this step.
// For our example, we will accomplish this by deleting saveable objects.
var saveNodes = GetTree().GetNodesInGroup("Persist");
foreach (Node saveNode in saveNodes)
saveNode.QueueFree();
// Load the file line by line and process that dictionary to restore the object
// it represents.
saveGame.Open("user://savegame.save", (int)File.ModeFlags.Read);
while (saveGame.GetPosition() < saveGame.GetLen())
{
// Get the saved dictionary from the next line in the save file
var nodeData = new Godot.Collections.Dictionary<string, object>((Godot.Collections.Dictionary)JSON.Parse(saveGame.GetLine()).Result);
// Firstly, we need to create the object and add it to the tree and set its position.
var newObjectScene = (PackedScene)ResourceLoader.Load(nodeData["Filename"].ToString());
var newObject = (Node)newObjectScene.Instance();
GetNode(nodeData["Parent"].ToString()).AddChild(newObject);
newObject.Set("Position", new Vector2((float)nodeData["PosX"], (float)nodeData["PosY"]));
// Now we set the remaining variables.
foreach (KeyValuePair<string, object> entry in nodeData)
{
string key = entry.Key.ToString();
if (key == "Filename" || key == "Parent" || key == "PosX" || key == "PosY")
continue;
newObject.Set(key, entry.Value);
}
}
saveGame.Close();
}
```
Now we can save and load an arbitrary number of objects laid out
almost anywhere across the scene tree! Each object can store different

View File

@ -27,34 +27,23 @@ vertices of each of the two segments formed by the three points, using values
ranging from 0 to 1. This gives us two points that move along the segments as we
change the value of ``t`` from 0 to 1.
.. tabs::
.. code-tab:: gdscript GDScript
gdscript GDScript
```
func _quadratic_bezier(p0: Vector2, p1: Vector2, p2: Vector2, t: float):
var q0 = p0.linear_interpolate(p1, t)
var q1 = p1.linear_interpolate(p2, t)
.. code-tab:: csharp
private Vector2 QuadraticBezier(Vector2 p0, Vector2 p1, Vector2 p2, float t)
{
Vector2 q0 = p0.LinearInterpolate(p1, t);
Vector2 q1 = p1.LinearInterpolate(p2, t);
}
```
We then interpolate ``q0`` and ``q1`` to obtain a single point ``r`` that moves
along a curve.
.. tabs::
.. code-tab:: gdscript GDScript
gdscript GDScript
```
var r = q0.linear_interpolate(q1, t)
return r
.. code-tab:: csharp
Vector2 r = q0.LinearInterpolate(q1, t);
return r;
```
This type of curve is called a *Quadratic Bezier* curve.
@ -73,65 +62,46 @@ between four points.
We first use a function with four parameters to take four points as an input,
``p0``, ``p1``, ``p2`` and ``p3``:
.. tabs::
.. code-tab:: gdscript GDScript
gdscript GDScript
```
func _cubic_bezier(p0: Vector2, p1: Vector2, p2: Vector2, p3: Vector2, t: float):
.. code-tab:: csharp
public Vector2 CubicBezier(Vector2 p0, Vector2 p1, Vector2 p2, Vector2 p3, float t)
{
}
```
We apply a linear interpolation to each couple of points to reduce them to
three:
.. tabs::
.. code-tab:: gdscript GDScript
gdscript GDScript
```
var q0 = p0.linear_interpolate(p1, t)
var q1 = p1.linear_interpolate(p2, t)
var q2 = p2.linear_interpolate(p3, t)
.. code-tab:: csharp
Vector2 q0 = p0.LinearInterpolate(p1, t);
Vector2 q1 = p1.LinearInterpolate(p2, t);
Vector2 q2 = p2.LinearInterpolate(p3, t);
```
We then take our three points and reduce them to two:
.. tabs::
.. code-tab:: gdscript GDScript
gdscript GDScript
```
var r0 = q0.linear_interpolate(q1, t)
var r1 = q1.linear_interpolate(q2, t)
.. code-tab:: csharp
Vector2 r0 = q0.LinearInterpolate(q1, t);
Vector2 r1 = q1.LinearInterpolate(q2, t);
```
And to one:
.. tabs::
.. code-tab:: gdscript GDScript
gdscript GDScript
```
var s = r0.linear_interpolate(r1, t)
return s
.. code-tab:: csharp
Vector2 s = r0.LinearInterpolate(r1, t);
return s;
```
Here is the full function:
.. tabs::
.. code-tab:: gdscript GDScript
gdscript GDScript
```
func _cubic_bezier(p0: Vector2, p1: Vector2, p2: Vector2, p3: Vector2, t: float):
var q0 = p0.linear_interpolate(p1, t)
var q1 = p1.linear_interpolate(p2, t)
@ -142,21 +112,7 @@ Here is the full function:
var s = r0.linear_interpolate(r1, t)
return s
.. code-tab:: csharp
private Vector2 CubicBezier(Vector2 p0, Vector2 p1, Vector2 p2, Vector2 p3, float t)
{
Vector2 q0 = p0.LinearInterpolate(p1, t);
Vector2 q1 = p1.LinearInterpolate(p2, t);
Vector2 q2 = p2.LinearInterpolate(p3, t);
Vector2 r0 = q0.LinearInterpolate(q1, t);
Vector2 r1 = q1.LinearInterpolate(q2, t);
Vector2 s = r0.LinearInterpolate(r1, t);
return s;
}
```
The result will be a smooth curve interpolating between all four points:
@ -206,24 +162,15 @@ Just evaluating them may be an option, but in most cases it's not very useful. T
Let's do a simple example with the following pseudocode:
.. tabs::
.. code-tab:: gdscript GDScript
gdscript GDScript
```
var t = 0.0
func _process(delta):
t += delta
position = _cubic_bezier(p0, p1, p2, p3, t)
.. code-tab:: csharp
private float _t = 0.0f;
public override void _Process(float delta)
{
_t += delta;
Position = CubicBezier(p0, p1, p2, p3, _t);
}
```
.. image:: img/bezier_interpolation_speed.gif
@ -255,24 +202,15 @@ To make this easier, the curves need to be *baked* into equidistant points. This
Traversal at constant speed, then, can be done with the following pseudo-code:
.. tabs::
.. code-tab:: gdscript GDScript
gdscript GDScript
```
var t = 0.0
func _process(delta):
t += delta
position = curve.interpolate_baked(t * curve.get_baked_length(), true)
.. code-tab:: csharp
private float _t = 0.0f;
public override void _Process(float delta)
{
_t += delta;
Position = curve.InterpolateBaked(_t * curve.GetBakedLength(), true);
}
```
And the output will, then, move at constant speed:

View File

@ -35,30 +35,16 @@ For cubic interpolation, there are also :ref:`Vector2.cubic_interpolate() <class
Here is simple pseudo-code for going from point A to B using interpolation:
.. tabs::
.. code-tab:: gdscript GDScript
gdscript GDScript
```
var t = 0.0
func _physics_process(delta):
t += delta * 0.4
$Sprite.position = $A.position.linear_interpolate($B.position, t)
.. code-tab:: csharp
private float _t = 0.0f;
public override void _PhysicsProcess(float delta)
{
_t += delta * 0.4f;
Position2D a = GetNode<Position2D>("A");
Position2D b = GetNode<Position2D>("B");
Sprite sprite = GetNode<Sprite>("Sprite");
sprite.Position = a.Position.LinearInterpolate(b.Position, _t);
}
```
It will produce the following motion:
@ -76,30 +62,16 @@ Here is an example of transforming a monkey from Position1 to Position2:
Using the following pseudocode:
.. tabs::
.. code-tab:: gdscript GDScript
gdscript GDScript
```
var t = 0.0
func _physics_process(delta):
t += delta
$Monkey.transform = $Position1.transform.interpolate_with($Position2.transform, t)
.. code-tab:: csharp
private float _t = 0.0f;
public override void _PhysicsProcess(float delta)
{
_t += delta;
Position3D p1 = GetNode<Position3D>("Position1");
Position3D p2 = GetNode<Position3D>("Position2");
CSGMesh monkey = GetNode<CSGMesh>("Monkey");
monkey.Transform = p1.Transform.InterpolateWith(p2.Transform, _t);
}
```
And again, it will produce the following motion:
@ -111,28 +83,16 @@ Smoothing motion
Interpolation can be used to smooth movement, rotation, etc. Here is an example of a circle following the mouse using smoothed motion:
.. tabs::
.. code-tab:: gdscript GDScript
gdscript GDScript
```
const FOLLOW_SPEED = 4.0
func _physics_process(delta):
var mouse_pos = get_local_mouse_position()
$Sprite.position = $Sprite.position.linear_interpolate(mouse_pos, delta * FOLLOW_SPEED)
.. code-tab:: csharp
private const float FollowSpeed = 4.0f;
public override void _PhysicsProcess(float delta)
{
Vector2 mousePos = GetLocalMousePosition();
Sprite sprite = GetNode<Sprite>("Sprite");
sprite.Position = sprite.Position.LinearInterpolate(mousePos, delta * FollowSpeed);
}
```
Here is how it looks:

View File

@ -72,22 +72,15 @@ becomes 2, and 0 times 2 becomes 0, so we end up with this:
To do this in code, we can simply multiply each of the vectors:
.. tabs::
.. code-tab:: gdscript GDScript
gdscript GDScript
```
var t = Transform2D()
# Scale
t.x *= 2
t.y *= 2
transform = t # Change the node's transform to what we just calculated.
.. code-tab:: csharp
Transform2D t = Transform2D.Identity;
// Scale
t.x *= 2;
t.y *= 2;
Transform = t; // Change the node's transform to what we just calculated.
```
If we wanted to return it to its original scale, we can multiply
each component by 0.5. That's pretty much all there is to scaling
@ -155,9 +148,9 @@ to find what the actual values should be:
Here's how that would be done in code (place the script on a Node2D):
.. tabs::
.. code-tab:: gdscript GDScript
gdscript GDScript
```
var rot = 0.5 # The rotation to apply.
var t = Transform2D()
t.x.x = cos(rot)
@ -165,15 +158,7 @@ Here's how that would be done in code (place the script on a Node2D):
t.x.y = sin(rot)
t.y.x = -sin(rot)
transform = t # Change the node's transform to what we just calculated.
.. code-tab:: csharp
float rot = 0.5f; // The rotation to apply.
Transform2D t = Transform2D.Identity;
t.x.x = t.y.y = Mathf.Cos(rot);
t.x.y = t.y.x = Mathf.Sin(rot);
t.y.x *= -1;
Transform = t; // Change the node's transform to what we just calculated.
```
To calculate the object's rotation from an existing transformation
matrix, you can use `atan2(t.x.y, t.x.x)`, where t is the Transform2D.
@ -244,9 +229,9 @@ you to try and reproduce the screenshot without looking at the code!
.. image:: img/matrices_and_transforms/putting-all-together.png
.. tabs::
.. code-tab:: gdscript GDScript
gdscript GDScript
```
var t = Transform2D()
# Translation
t.origin = Vector2(350, 150)
@ -260,21 +245,7 @@ you to try and reproduce the screenshot without looking at the code!
t.x *= 3
t.y *= 3
transform = t # Change the node's transform to what we just calculated.
.. code-tab:: csharp
Transform2D t = Transform2D.Identity;
// Translation
t.origin = new Vector2(350, 150);
// Rotation
float rot = -0.5f; // The rotation to apply.
t.x.x = t.y.y = Mathf.Cos(rot);
t.x.y = t.y.x = Mathf.Sin(rot);
t.y.x *= -1;
// Scale
t.x *= 3;
t.y *= 3;
Transform = t; // Change the node's transform to what we just calculated.
```
Shearing the transformation matrix (advanced)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@ -310,20 +281,14 @@ As an example, let's set Y to (1, 1):
.. image:: img/matrices_and_transforms/shear.png
.. tabs::
.. code-tab:: gdscript GDScript
gdscript GDScript
```
var t = Transform2D()
# Shear by setting Y to (1, 1)
t.y = Vector2.ONE
transform = t # Change the node's transform to what we just calculated.
.. code-tab:: csharp
Transform2D t = Transform2D.Identity;
// Shear by setting Y to (1, 1)
t.y = Vector2.One;
Transform = t; // Change the node's transform to what we just calculated.
```
.. note:: You can't set the raw values of a Transform2D in the editor,
so you *must* use code if you want to shear the object.
@ -383,30 +348,22 @@ have a world position and want to know where it is relative to the player.
We can find what a vector relative to the player would be defined in
world space as using the "xform" method:
.. tabs::
.. code-tab:: gdscript GDScript
gdscript GDScript
```
# World space vector 100 units below the player.
print(transform.xform(Vector2(0, 100)))
.. code-tab:: csharp
// World space vector 100 units below the player.
GD.Print(Transform.Xform(new Vector2(0, 100)));
```
And we can use the "xform_inv" method to find a what world space position
would be if it was instead defined relative to the player:
.. tabs::
.. code-tab:: gdscript GDScript
gdscript GDScript
```
# Where is (0, 100) relative to the player?
print(transform.xform_inv(Vector2(0, 100)))
.. code-tab:: csharp
// Where is (0, 100) relative to the player?
GD.Print(Transform.XformInv(new Vector2(0, 100)));
```
.. note:: If you know in advance that the transform is positioned at
(0, 0), you can use the "basis_xform" or "basis_xform_inv"
@ -425,16 +382,11 @@ add multiples of the basis vectors to move an object relative to itself.
This code moves an object 100 units to its own right:
.. tabs::
.. code-tab:: gdscript GDScript
gdscript GDScript
```
transform.origin += transform.x * 100
.. code-tab:: csharp
Transform2D t = Transform;
t.origin += t.x * 100;
Transform = t;
```
For moving in 3D, you would need to replace "x" with "basis.x".
@ -472,9 +424,9 @@ basis vectors.
To calculate a child transform's world space transform manually, this is
the code we would use:
.. tabs::
.. code-tab:: gdscript GDScript
gdscript GDScript
```
# Set up transforms just like in the image, except make positions be 100 times bigger.
var parent = Transform2D(Vector2(2, 0), Vector2(0, 1), Vector2(100, 200))
var child = Transform2D(Vector2(0.5, 0), Vector2(0, 0.5), Vector2(100, 100))
@ -489,45 +441,21 @@ the code we would use:
# Change the node's transform to what we just calculated.
transform = Transform2D(basis_x, basis_y, origin)
.. code-tab:: csharp
// Set up transforms just like in the image, except make positions be 100 times bigger.
Transform2D parent = new Transform2D(2, 0, 0, 1, 100, 200);
Transform2D child = new Transform2D(0.5f, 0, 0, 0.5f, 100, 100);
// Calculate the child's world space transform
// origin = (2, 0) * 100 + (0, 1) * 100 + (100, 200)
Vector2 origin = parent.x * child.origin.x + parent.y * child.origin.y + parent.origin;
// basisX = (2, 0) * 0.5 + (0, 1) * 0 = (0.5, 0)
Vector2 basisX = parent.x * child.x.x + parent.y * child.x.y;
// basisY = (2, 0) * 0 + (0, 1) * 0.5 = (0.5, 0)
Vector2 basisY = parent.x * child.y.x + parent.y * child.y.y;
// Change the node's transform to what we just calculated.
Transform = new Transform2D(basisX, basisY, origin);
```
In actual projects, we can find the world transform of the child by
applying one transform onto another using the `*` operator:
.. tabs::
.. code-tab:: gdscript GDScript
gdscript GDScript
```
# Set up transforms just like in the image, except make positions be 100 times bigger.
var parent = Transform2D(Vector2(2, 0), Vector2(0, 1), Vector2(100, 200))
var child = Transform2D(Vector2(0.5, 0), Vector2(0, 0.5), Vector2(100, 100))
# Change the node's transform to what would be the child's world transform.
transform = parent * child
.. code-tab:: csharp
// Set up transforms just like in the image, except make positions be 100 times bigger.
Transform2D parent = new Transform2D(2, 0, 0, 1, 100, 200);
Transform2D child = new Transform2D(0.5f, 0, 0, 0.5f, 100, 100);
// Change the node's transform to what would be the child's world transform.
Transform = parent * child;
```
.. note:: When multiplying matrices, order matters! Don't mix them up.
@ -547,36 +475,25 @@ easier to just provide a few examples.
Multiplying an inverse transform by the normal transform undoes all
transformations:
.. tabs::
.. code-tab:: gdscript GDScript
gdscript GDScript
```
var ti = transform.affine_inverse()
var t = ti * transform
# The transform is the identity transform.
.. code-tab:: csharp
Transform2D ti = Transform.AffineInverse();
Transform2D t = ti * Transform;
// The transform is the identity transform.
```
Transforming a position by a transform and its inverse results in the
same position (same for "xform_inv"):
.. tabs::
.. code-tab:: gdscript GDScript
gdscript GDScript
```
var ti = transform.affine_inverse()
position = transform.xform(position)
position = ti.xform(position)
# The position is the same as before.
.. code-tab:: csharp
Transform2D ti = Transform.AffineInverse();
Position = Transform.Xform(Position);
Position = ti.Xform(Position);
// The position is the same as before.
```
How does it all work in 3D?
---------------------------

View File

@ -44,52 +44,36 @@ multiple times is unnecessary and may impact performance negatively.
Putting it in your main scene script's ``_ready()`` method is a good choice:
.. tabs::
.. code-tab:: gdscript GDScript
gdscript GDScript
```
func _ready():
randomize()
.. code-tab:: csharp
public override void _Ready()
{
GD.Randomize();
}
```
You can also set a fixed random seed instead using :ref:`seed()
<class_@GDScript_method_seed>`. Doing so will give you *deterministic* results
across runs:
.. tabs::
.. code-tab:: gdscript GDScript
gdscript GDScript
```
func _ready():
seed(12345)
# To use a string as a seed, you can hash it to a number.
seed("Hello world".hash())
.. code-tab:: csharp
public override void _Ready()
{
GD.Seed(12345);
GD.Seed("Hello world".Hash());
}
```
When using the RandomNumberGenerator class, you should call ``randomize()`` on
the instance since it has its own seed:
.. tabs::
.. code-tab:: gdscript GDScript
gdscript GDScript
```
var random = RandomNumberGenerator.new()
random.randomize()
```
.. code-tab:: csharp
var random = new RandomNumberGenerator();
random.Randomize();
Getting a random number
-----------------------
@ -102,22 +86,16 @@ number between 0 and 2^32-1. Since the maximum value is huge, you most likely
want to use the modulo operator (``%``) to bound the result between 0 and the
denominator:
.. tabs::
.. code-tab:: gdscript GDScript
gdscript GDScript
```
# Prints a random integer between 0 and 49.
print(randi() % 50)
# Prints a random integer between 10 and 60.
print(randi() % 51 + 10)
```
.. code-tab:: csharp
// Prints a random integer between 0 and 49.
GD.Print(GD.Randi() % 50);
// Prints a random integer between 10 and 60.
GD.Print(GD.Randi() % 51 + 10);
:ref:`randf() <class_@GDScript_method_randf>` returns a random floating-point
number between 0 and 1. This is useful to implement a
@ -130,62 +108,47 @@ floating-point number following a `normal distribution
value is more likely to be around the mean (0.0 by default),
varying by the deviation (1.0 by default):
.. tabs::
.. code-tab:: gdscript GDScript
gdscript GDScript
```
# Prints a random floating-point number from a normal distribution with a mean 0.0 and deviation 1.0.
var random = RandomNumberGenerator.new()
random.randomize()
print(random.randfn())
.. code-tab:: csharp
// Prints a normally distributed floating-point number between 0.0 and 1.0.
var random = new RandomNumberGenerator();
random.Randomize();
GD.Print(random.Randfn());
```
:ref:`rand_range() <class_@GDScript_method_rand_range>` takes two arguments
``from`` and ``to``, and returns a random floating-point number between ``from``
and ``to``:
.. tabs::
.. code-tab:: gdscript GDScript
gdscript GDScript
```
# Prints a random floating-point number between -4 and 6.5.
print(rand_range(-4, 6.5))
.. code-tab:: csharp
// Prints a random floating-point number between -4 and 6.5.
GD.Print(GD.RandRange(-4, 6.5));
```
:ref:`RandomNumberGenerator.randi_range()
<class_RandomNumberGenerator_method_randi_range>` takes two arguments ``from``
and ``to``, and returns a random integer between ``from`` and ``to``:
.. tabs::
.. code-tab:: gdscript GDScript
gdscript GDScript
```
# Prints a random integer between -10 and 10.
var random = RandomNumberGenerator.new()
random.randomize()
print(random.randi_range(-10, 10))
.. code-tab:: csharp
// Prints a random integer number between -10 and 10.
random.Randomize();
GD.Print(random.RandiRange(-10, 10));
```
Get a random array element
--------------------------
We can use random integer generation to get a random element from an array:
.. tabs::
.. code-tab:: gdscript GDScript
gdscript GDScript
```
var _fruits = ["apple", "orange", "pear", "banana"]
func _ready():
@ -201,36 +164,14 @@ We can use random integer generation to get a random element from an array:
# Returns "apple", "orange", "pear", or "banana" every time the code runs.
# We may get the same fruit multiple times in a row.
return random_fruit
.. code-tab:: csharp
private string[] _fruits = { "apple", "orange", "pear", "banana" };
public override void _Ready()
{
GD.Randomize();
for (int i = 0; i < 100; i++)
{
// Pick 100 fruits randomly.
GD.Print(GetFruit());
}
}
public string GetFruit()
{
string randomFruit = _fruits[GD.Randi() % _fruits.Length];
// Returns "apple", "orange", "pear", or "banana" every time the code runs.
// We may get the same fruit multiple times in a row.
return randomFruit;
}
```
To prevent the same fruit from being picked more than once in a row, we can add
more logic to this method:
.. tabs::
.. code-tab:: gdscript GDScript
gdscript GDScript
```
var _fruits = ["apple", "orange", "pear", "banana"]
var _last_fruit = ""
@ -257,38 +198,7 @@ more logic to this method:
# Returns "apple", "orange", "pear", or "banana" every time the code runs.
# The function will never return the same fruit more than once in a row.
return random_fruit
.. code-tab:: csharp
private string[] _fruits = { "apple", "orange", "pear", "banana" };
private string _lastFruit = "";
public override void _Ready()
{
GD.Randomize();
for (int i = 0; i < 100; i++)
{
// Pick 100 fruits randomly.
GD.Print(GetFruit());
}
}
public string GetFruit()
{
string randomFruit = _fruits[GD.Randi() % _fruits.Length];
while (randomFruit == _lastFruit)
{
// The last fruit was picked, try again until we get a different fruit.
randomFruit = _fruits[GD.Randi() % _fruits.Length];
}
_lastFruit = randomFruit;
// Returns "apple", "orange", "pear", or "banana" every time the code runs.
// The function will never return the same fruit more than once in a row.
return randomFruit;
}
```
This approach can be useful to make random number generation feel less
repetitive. Still, it doesn't prevent results from "ping-ponging" between a
@ -300,9 +210,9 @@ Get a random dictionary value
We can apply similar logic from arrays to dictionaries as well:
.. tabs::
.. code-tab:: gdscript GDScript
gdscript GDScript
```
var metals = {
"copper": {"quantity": 50, "price": 50},
"silver": {"quantity": 20, "price": 150},
@ -322,6 +232,7 @@ We can apply similar logic from arrays to dictionaries as well:
# Returns a random metal value dictionary every time the code runs.
# The same metal may be selected multiple times in succession.
return random_metal
```
.. _doc_random_number_generation_weighted_random_probability:
@ -332,9 +243,9 @@ The :ref:`randf() <class_@GDScript_method_randf>` method returns a
floating-point number between 0.0 and 1.0. We can use this to create a
"weighted" probability where different outcomes have different likelihoods:
.. tabs::
.. code-tab:: gdscript GDScript
gdscript GDScript
```
func _ready():
randomize()
@ -354,39 +265,7 @@ floating-point number between 0.0 and 1.0. We can use this to create a
else:
# 5% chance of being returned.
return "Rare"
.. code-tab:: csharp
public override void _Ready()
{
GD.Randomize();
for (int i = 0; i < 100; i++)
{
GD.Print(GetItemRarity());
}
}
public string GetItemRarity()
{
float randomFloat = GD.Randf();
if (randomFloat < 0.8f)
{
// 80% chance of being returned.
return "Common";
}
else if (randomFloat < 0.95f)
{
// 15% chance of being returned
return "Uncommon";
}
else
{
// 5% chance of being returned.
return "Rare";
}
}
```
.. _doc_random_number_generation_shuffle_bags:
@ -445,9 +324,9 @@ especially popular in procedural generation to generate realistic-looking
terrain. Godot provides :ref:`class_opensimplexnoise` for this, which supports
1D, 2D, 3D, and 4D noise. Here's an example with 1D noise:
.. tabs::
.. code-tab:: gdscript GDScript
gdscript GDScript
```
var _noise = OpenSimplexNoise.new()
func _ready():
@ -462,22 +341,4 @@ terrain. Godot provides :ref:`class_opensimplexnoise` for this, which supports
# Prints a slowly-changing series of floating-point numbers
# between -1.0 and 1.0.
print(_noise.get_noise_1d(i))
.. code-tab:: csharp
private OpenSimplexNoise _noise = new OpenSimplexNoise();
public override void _Ready()
{
GD.Randomize();
// Configure the OpenSimplexNoise instance.
_noise.Seed = (int)GD.Randi();
_noise.Octaves = 4;
_noise.Period = 20.0f;
_noise.Persistence = 0.8f;
for (int i = 0; i < 100; i++)
{
GD.Print(_noise.GetNoise1d(i));
}
}
```

View File

@ -63,15 +63,11 @@ coordinate notation. For example, in Godot, the origin is the top-left
corner of the screen, so to place a 2D node named ``Node2D`` 400 pixels to the right and
300 pixels down, use the following code:
.. tabs::
.. code-tab:: gdscript GDScript
gdscript GDScript
```
$Node2D.position = Vector2(400, 300)
.. code-tab:: csharp
var node2D = GetNode<Node2D>("Node2D");
node2D.Position = new Vector2(400, 300);
```
Godot supports both :ref:`Vector2 <class_Vector2>` and
:ref:`Vector3 <class_Vector3>` for 2D and 3D usage, respectively. The same
@ -82,38 +78,27 @@ Member access
The individual components of the vector can be accessed directly by name.
.. tabs::
.. code-tab:: gdscript GDScript
gdscript GDScript
```
# create a vector with coordinates (2, 5)
var a = Vector2(2, 5)
# create a vector and assign x and y manually
var b = Vector2()
b.x = 3
b.y = 1
.. code-tab:: csharp
// create a vector with coordinates (2, 5)
var a = new Vector2(2, 5);
// create a vector and assign x and y manually
var b = new Vector2();
b.x = 3;
b.y = 1;
```
Adding vectors
--------------
When adding or subtracting two vectors, the corresponding components are added:
.. tabs::
.. code-tab:: gdscript GDScript
gdscript GDScript
```
var c = a + b # (2, 5) + (3, 1) = (5, 6)
.. code-tab:: csharp
var c = a + b; // (2, 5) + (3, 1) = (5, 6)
```
We can also see this visually by adding the second vector at the end of
the first:
@ -130,16 +115,12 @@ Scalar multiplication
A vector can be multiplied by a **scalar**:
.. tabs::
.. code-tab:: gdscript GDScript
gdscript GDScript
```
var c = a * 2 # (2, 5) * 2 = (4, 10)
var d = b / 3 # (3, 6) / 3 = (1, 2)
.. code-tab:: csharp
var c = a * 2; // (2, 5) * 2 = (4, 10)
var d = b / 3; // (3, 6) / 3 = (1, 2)
```
.. image:: img/vector_mult1.png
@ -191,14 +172,11 @@ preserving its direction. This is done by dividing each of its components
by its magnitude. Because this is such a common operation,
``Vector2`` and ``Vector3`` provide a method for normalizing:
.. tabs::
.. code-tab:: gdscript GDScript
gdscript GDScript
```
a = a.normalized()
.. code-tab:: csharp
a = a.Normalized();
```
.. warning:: Because normalization involves dividing by the vector's length,
@ -226,26 +204,16 @@ to handle this. Here is a GDScript example of the diagram above using a
:ref:`KinematicBody2D <class_KinematicBody2D>`:
.. tabs::
.. code-tab:: gdscript GDScript
gdscript GDScript
```
# object "collision" contains information about the collision
var collision = move_and_collide(velocity * delta)
if collision:
var reflect = collision.remainder.bounce(collision.normal)
velocity = velocity.bounce(collision.normal)
move_and_collide(reflect)
.. code-tab:: csharp
// KinematicCollision2D contains information about the collision
KinematicCollision2D collision = MoveAndCollide(_velocity * delta);
if (collision != null)
{
var reflect = collision.Remainder.Bounce(collision.Normal);
_velocity = _velocity.Bounce(collision.Normal);
MoveAndCollide(reflect);
}
```
Dot product
~~~~~~~~~~~
@ -266,16 +234,12 @@ and
However, in most cases it is easiest to use the built-in method. Note that
the order of the two vectors does not matter:
.. tabs::
.. code-tab:: gdscript GDScript
gdscript GDScript
```
var c = a.dot(b)
var d = b.dot(a) # These are equivalent.
.. code-tab:: csharp
float c = a.Dot(b);
float d = b.Dot(a); // These are equivalent.
```
The dot product is most useful when used with unit vectors, making the
first formula reduce to just ``cosθ``. This means we can use the dot
@ -305,20 +269,13 @@ the player.
In code it would look like this:
.. tabs::
.. code-tab:: gdscript GDScript
gdscript GDScript
```
var AP = A.direction_to(P)
if AP.dot(fA) > 0:
print("A sees P!")
.. code-tab:: csharp
var AP = A.DirectionTo(P);
if (AP.Dot(fA) > 0)
{
GD.Print("A sees P!");
}
```
Cross product
~~~~~~~~~~~~~
@ -334,33 +291,22 @@ If two vectors are parallel, the result of their cross product will be a null ve
The cross product is calculated like this:
.. tabs::
.. code-tab:: gdscript GDScript
gdscript GDScript
```
var c = Vector3()
c.x = (a.y * b.z) - (a.z * b.y)
c.y = (a.z * b.x) - (a.x * b.z)
c.z = (a.x * b.y) - (a.y * b.x)
.. code-tab:: csharp
var c = new Vector3();
c.x = (a.y * b.z) - (a.z * b.y);
c.y = (a.z * b.x) - (a.x * b.z);
c.z = (a.x * b.y) - (a.y * b.x);
```
With Godot, you can use the built-in method:
.. tabs::
.. code-tab:: gdscript GDScript
gdscript GDScript
```
var c = a.cross(b)
.. code-tab:: csharp
var c = a.Cross(b);
```
.. note:: In the cross product, order matters. ``a.cross(b)`` does not
give the same result as ``b.cross(a)``. The resulting vectors
@ -376,26 +322,16 @@ subtraction to find two edges ``AB`` and ``AC``. Using the cross product,
Here is a function to calculate a triangle's normal:
.. tabs::
.. code-tab:: gdscript GDScript
gdscript GDScript
```
func get_triangle_normal(a, b, c):
# find the surface normal given 3 vertices
var side1 = b - a
var side2 = c - a
var normal = side1.cross(side2)
return normal
.. code-tab:: csharp
Vector3 GetTriangleNormal(Vector3 a, Vector3 b, Vector3 c)
{
// find the surface normal given 3 vertices
var side1 = b - a;
var side2 = c - a;
var normal = side1.Cross(side2);
return normal;
}
```
Pointing to a target
--------------------

View File

@ -37,14 +37,11 @@ The dot product between a **unit vector** and any **point in space**
(yes, this time we do dot product between vector and position), returns
the **distance from the point to the plane**:
.. tabs::
.. code-tab:: gdscript GDScript
gdscript GDScript
```
var distance = normal.dot(point)
.. code-tab:: csharp
var distance = normal.Dot(point);
```
But not just the absolute distance, if the point is in the negative half
space the distance will be negative, too:
@ -79,39 +76,30 @@ for both. It's the same as before, but D is the distance from the origin
to the plane, travelling in N direction. As an example, imagine you want
to reach a point in the plane, you will just do:
.. tabs::
.. code-tab:: gdscript GDScript
gdscript GDScript
```
var point_in_plane = N*D
.. code-tab:: csharp
var pointInPlane = N * D;
```
This will stretch (resize) the normal vector and make it touch the
plane. This math might seem confusing, but it's actually much simpler
than it seems. If we want to tell, again, the distance from the point to
the plane, we do the same but adjusting for distance:
.. tabs::
.. code-tab:: gdscript GDScript
gdscript GDScript
```
var distance = N.dot(point) - D
.. code-tab:: csharp
var distance = N.Dot(point) - D;
```
The same thing, using a built-in function:
.. tabs::
.. code-tab:: gdscript GDScript
gdscript GDScript
```
var distance = plane.distance_to(point)
.. code-tab:: csharp
var distance = plane.DistanceTo(point);
```
This will, again, return either a positive or negative distance.
@ -119,28 +107,21 @@ Flipping the polarity of the plane can be done by negating both
N and D. This will result in a plane in the same position, but with
inverted negative and positive half spaces:
.. tabs::
.. code-tab:: gdscript GDScript
gdscript GDScript
```
N = -N
D = -D
.. code-tab:: csharp
N = -N;
D = -D;
```
Of course, Godot also implements this operator in :ref:`Plane <class_Plane>`,
so doing:
.. tabs::
.. code-tab:: gdscript GDScript
gdscript GDScript
```
var inverted_plane = -plane
.. code-tab:: csharp
var invertedPlane = -plane;
```
Will work as expected.
@ -160,16 +141,12 @@ In the case of a normal and a point, most of the work is done, as the
normal is already computed, so just calculate D from the dot product of
the normal and the point.
.. tabs::
.. code-tab:: gdscript GDScript
gdscript GDScript
```
var N = normal
var D = normal.dot(point)
.. code-tab:: csharp
var N = normal;
var D = normal.Dot(point);
```
For two points in space, there are actually two planes that pass through
them, sharing the same space but with normal pointing to the opposite
@ -177,42 +154,28 @@ directions. To compute the normal from the two points, the direction
vector must be obtained first, and then it needs to be rotated 90°
degrees to either side:
.. tabs::
.. code-tab:: gdscript GDScript
gdscript GDScript
```
# Calculate vector from `a` to `b`.
var dvec = (point_b - point_a).normalized()
# Rotate 90 degrees.
var normal = Vector2(dvec.y, -dvec.x)
# Alternatively (depending the desired side of the normal):
# var normal = Vector2(-dvec.y, dvec.x)
.. code-tab:: csharp
// Calculate vector from `a` to `b`.
var dvec = (pointB - pointA).Normalized();
// Rotate 90 degrees.
var normal = new Vector2(dvec.y, -dvec.x);
// Alternatively (depending the desired side of the normal):
// var normal = new Vector2(-dvec.y, dvec.x);
```
The rest is the same as the previous example, either point_a or
point_b will work since they are in the same plane:
.. tabs::
.. code-tab:: gdscript GDScript
gdscript GDScript
```
var N = normal
var D = normal.dot(point_a)
# this works the same
# var D = normal.dot(point_b)
.. code-tab:: csharp
var N = normal;
var D = normal.Dot(pointA);
// this works the same
// var D = normal.Dot(pointB);
```
Doing the same in 3D is a little more complex and will be explained
further down.
@ -237,28 +200,16 @@ can't, then the point is inside.
Code should be something like this:
.. tabs::
.. code-tab:: gdscript GDScript
gdscript GDScript
```
var inside = true
for p in planes:
# check if distance to plane is positive
if (p.distance_to(point) > 0):
inside = false
break # with one that fails, it's enough
.. code-tab:: csharp
var inside = true;
foreach (var p in planes)
{
// check if distance to plane is positive
if (p.DistanceTo(point) > 0)
{
inside = false;
break; // with one that fails, it's enough
}
}
```
Pretty cool, huh? But this gets much better! With a little more effort,
similar logic will let us know when two convex polygons are overlapping
@ -276,9 +227,9 @@ the planes of B against the points of A:
Code should be something like this:
.. tabs::
.. code-tab:: gdscript GDScript
gdscript GDScript
```
var overlapping = true
for p in planes_of_A:
@ -310,60 +261,7 @@ Code should be something like this:
if (overlapping):
print("Polygons Collided!")
.. code-tab:: csharp
var overlapping = true;
foreach (Plane plane in planesOfA)
{
var allOut = true;
foreach (Vector3 point in pointsOfB)
{
if (plane.DistanceTo(point) < 0)
{
allOut = false;
break;
}
}
if (allOut)
{
// a separating plane was found
// do not continue testing
overlapping = false;
break;
}
}
if (overlapping)
{
// only do this check if no separating plane
// was found in planes of A
foreach (Plane plane in planesOfB)
{
var allOut = true;
foreach (Vector3 point in pointsOfA)
{
if (plane.DistanceTo(point) < 0)
{
allOut = false;
break;
}
}
if (allOut)
{
overlapping = false;
break;
}
}
}
if (overlapping)
{
GD.Print("Polygons Collided!");
}
```
As you can see, planes are quite useful, and this is the tip of the
iceberg. You might be wondering what happens with non convex polygons.
@ -407,9 +305,9 @@ edges of polygon B
So the final algorithm is something like:
.. tabs::
.. code-tab:: gdscript GDScript
gdscript GDScript
```
var overlapping = true
for p in planes_of_A:
@ -477,113 +375,7 @@ So the final algorithm is something like:
if (overlapping):
print("Polygons collided!")
.. code-tab:: csharp
var overlapping = true;
foreach (Plane plane in planesOfA)
{
var allOut = true;
foreach (Vector3 point in pointsOfB)
{
if (plane.DistanceTo(point) < 0)
{
allOut = false;
break;
}
}
if (allOut)
{
// a separating plane was found
// do not continue testing
overlapping = false;
break;
}
}
if (overlapping)
{
// only do this check if no separating plane
// was found in planes of A
foreach (Plane plane in planesOfB)
{
var allOut = true;
foreach (Vector3 point in pointsOfA)
{
if (plane.DistanceTo(point) < 0)
{
allOut = false;
break;
}
}
if (allOut)
{
overlapping = false;
break;
}
}
}
if (overlapping)
{
foreach (Vector3 edgeA in edgesOfA)
{
foreach (Vector3 edgeB in edgesOfB)
{
var normal = edgeA.Cross(edgeB);
if (normal.Length() == 0)
{
continue;
}
var maxA = float.MinValue; // tiny number
var minA = float.MaxValue; // huge number
// we are using the dot product directly
// so we can map a maximum and minimum range
// for each polygon, then check if they
// overlap.
foreach (Vector3 point in pointsOfA)
{
var distance = normal.Dot(point);
maxA = Mathf.Max(maxA, distance);
minA = Mathf.Min(minA, distance);
}
var maxB = float.MinValue; // tiny number
var minB = float.MaxValue; // huge number
foreach (Vector3 point in pointsOfB)
{
var distance = normal.Dot(point);
maxB = Mathf.Max(maxB, distance);
minB = Mathf.Min(minB, distance);
}
if (minA > maxB || minB > maxA)
{
// not overlapping!
overlapping = false;
break;
}
}
if (!overlapping)
{
break;
}
}
}
if (overlapping)
{
GD.Print("Polygons Collided!");
}
```
More information
~~~~~~~~~~~~~~~~

View File

@ -17,22 +17,18 @@ which has a tutorial available :ref:`here <doc_http_request_class>`.
Here's an example of using the :ref:`HTTPClient <class_HTTPClient>`
class. It's just a script, so it can be run by executing:
.. tabs::
.. code-tab:: console GDScript
console GDScript
```
c:\godot> godot -s http_test.gd
```
.. code-tab:: console C#
c:\godot> godot -s HTTPTest.cs
It will connect and fetch a website.
.. tabs::
.. code-tab:: gdscript GDScript
gdscript GDScript
```
extends SceneTree
# HTTPClient demo
@ -121,106 +117,4 @@ It will connect and fetch a website.
print("Text: ", text)
quit()
.. code-tab:: csharp
class HTTPTest : SceneTree
{
// HTTPClient demo.
// This simple class can make HTTP requests; it will not block, but it needs to be polled.
public override async void _Initialize()
{
Error err;
HTTPClient http = new HTTPClient(); // Create the client.
err = http.ConnectToHost("www.php.net", 80); // Connect to host/port.
Debug.Assert(err == Error.Ok); // Make sure the connection is OK.
// Wait until resolved and connected.
while (http.GetStatus() == HTTPClient.Status.Connecting || http.GetStatus() == HTTPClient.Status.Resolving)
{
http.Poll();
GD.Print("Connecting...");
OS.DelayMsec(500);
}
Debug.Assert(http.GetStatus() == HTTPClient.Status.Connected); // Check if the connection was made successfully.
// Some headers.
string[] headers = { "User-Agent: Pirulo/1.0 (Godot)", "Accept: */*" };
err = http.Request(HTTPClient.Method.Get, "/ChangeLog-5.php", headers); // Request a page from the site.
Debug.Assert(err == Error.Ok); // Make sure all is OK.
// Keep polling for as long as the request is being processed.
while (http.GetStatus() == HTTPClient.Status.Requesting)
{
http.Poll();
GD.Print("Requesting...");
if (OS.HasFeature("web"))
{
// Synchronous HTTP requests are not supported on the web,
// so wait for the next main loop iteration.
await ToSignal(Engine.GetMainLoop(), "idle_frame");
}
else
{
OS.DelayMsec(500);
}
}
Debug.Assert(http.GetStatus() == HTTPClient.Status.Body || http.GetStatus() == HTTPClient.Status.Connected); // Make sure the request finished well.
GD.Print("Response? ", http.HasResponse()); // The site might not have a response.
// If there is a response...
if (http.HasResponse())
{
headers = http.GetResponseHeaders(); // Get response headers.
GD.Print("Code: ", http.GetResponseCode()); // Show response code.
GD.Print("Headers:");
foreach (string header in headers)
{
// Show headers.
GD.Print(header);
}
if (http.IsResponseChunked())
{
// Does it use chunks?
GD.Print("Response is Chunked!");
}
else
{
// Or just Content-Length.
GD.Print("Response Length: ", http.GetResponseBodyLength());
}
// This method works for both anyways.
List<byte> rb = new List<byte>(); // List that will hold the data.
// While there is data left to be read...
while (http.GetStatus() == HTTPClient.Status.Body)
{
http.Poll();
byte[] chunk = http.ReadResponseBodyChunk(); // Read a chunk.
if (chunk.Length == 0)
{
// If nothing was read, wait for the buffer to fill.
OS.DelayMsec(500);
}
else
{
// Append the chunk to the read buffer.
rb.AddRange(chunk);
}
}
// Done!
GD.Print("Bytes Downloaded: ", rb.Count);
string text = Encoding.ASCII.GetString(rb.ToArray());
GD.Print(text);
}
Quit();
}
}
```

View File

@ -30,10 +30,9 @@ Scripting
Below is all the code we need to make it work. The URL points to an online API mocker; it returns a pre-defined JSON string, which we will then parse to get access to the data.
.. tabs::
.. code-tab:: gdscript GDScript
gdscript GDScript
```
extends CanvasLayer
func _ready():
@ -45,29 +44,7 @@ Below is all the code we need to make it work. The URL points to an online API m
func _on_request_completed(result, response_code, headers, body):
var json = JSON.parse(body.get_string_from_utf8())
print(json.result)
.. code-tab:: csharp
class HTTPRequestDemo : CanvasLayer
{
public override void _Ready()
{
GetNode("HTTPRequest").Connect("request_completed", this, "OnRequestCompleted");
GetNode("Button").Connect("pressed", this, "OnButtonPressed");
}
public void OnButtonPressed()
{
HTTPRequest httpRequest = GetNode<HTTPRequest>("HTTPRequest");
httpRequest.Request("http://www.mocky.io/v2/5185415ba171ea3a00704eed");
}
public void OnRequestCompleted(int result, int response_code, string[] headers, byte[] body)
{
JSONParseResult json = JSON.Parse(Encoding.UTF8.GetString(body));
GD.Print(json.Result);
}
}
```
With this, you should see ``(hello:world)`` printed on the console; hello being a key, and world being a value, both of them strings.
@ -78,16 +55,11 @@ Note that you may want to check whether the ``result`` equals ``RESULT_SUCCESS``
Of course, you can also set custom HTTP headers. These are given as a string array, with each string containing a header in the format ``"header: value"``.
For example, to set a custom user agent (the HTTP ``user-agent`` header) you could use the following:
.. tabs::
.. code-tab:: gdscript GDScript
gdscript GDScript
```
$HTTPRequest.request("http://www.mocky.io/v2/5185415ba171ea3a00704eed", ["user-agent: YourCustomUserAgent"])
.. code-tab:: csharp
HTTPRequest httpRequest = GetNode<HTTPRequest>("HTTPRequest");
httpRequest.Request("http://www.mocky.io/v2/5185415ba171ea3a00704eed", new string[] { "user-agent: YourCustomUserAgent" });
```
Please note that, for SSL/TLS encryption and thus HTTPS URLs to work, you may need to take some steps as described :ref:`here <doc_ssl_certificates>`.
@ -99,26 +71,16 @@ Sending data to server
Until now, we have limited ourselves to requesting data from a server. But what if you need to send data to the server? Here is a common way of doing it:
.. tabs::
.. code-tab:: gdscript GDScript
gdscript GDScript
```
func _make_post_request(url, data_to_send, use_ssl):
# Convert data to json string:
var query = JSON.print(data_to_send)
# Add 'Content-Type' header:
var headers = ["Content-Type: application/json"]
$HTTPRequest.request(url, headers, use_ssl, HTTPClient.METHOD_POST, query)
.. code-tab:: csharp
public void MakePostRequest(string url, object data_to_send, bool use_ssl)
{
string query = JSON.Print(data_to_send);
HTTPRequest httpRequest = GetNode<HTTPRequest>("HTTPRequest");
string[] headers = new string[] { "Content-Type: application/json" };
httpRequest.Request(url, headers, use_ssl, HTTPClient.Method.Post, query);
}
```
Keep in mind that you have to wait for a request to finish before sending another one. Making multiple request at once requires you to have one node per request.
A common strategy is to create and delete HTTPRequest nodes at runtime as necessary.

View File

@ -24,9 +24,9 @@ Creating a Thread
Creating a thread is very simple, just use the following code:
.. tabs::
.. code-tab:: gdscript GDScript
gdscript GDScript
```
var thread
# The thread will start here.
@ -47,6 +47,7 @@ Creating a thread is very simple, just use the following code:
# Thread must be disposed (or "joined"), for portability.
func _exit_tree():
thread.wait_to_finish()
```
Your function will, then, run in a separate thread until it returns.
Even if the function has returned already, the thread must collect it, so call
@ -75,9 +76,9 @@ allowed to proceed with the lock (but only one at a time).
Here is an example of using a Mutex:
.. tabs::
.. code-tab:: gdscript GDScript
gdscript GDScript
```
var counter = 0
var mutex
var thread
@ -106,6 +107,7 @@ Here is an example of using a Mutex:
func _exit_tree():
thread.wait_to_finish()
print("Counter is: ", counter) # Should be 2.
```
Semaphores
----------
@ -120,9 +122,9 @@ The main thread, instead, uses
:ref:`Semaphore.post()<class_Semaphore_method_post>` to signal that data is
ready to be processed:
.. tabs::
.. code-tab:: gdscript GDScript
gdscript GDScript
```
var counter = 0
var mutex
var semaphore
@ -183,3 +185,4 @@ ready to be processed:
# Print the counter.
print("Counter is: ", counter)
```

View File

@ -50,9 +50,9 @@ Multimesh example
Here is an example of using a MultiMesh from code. Languages other than GDScript may be more
efficient for millions of objects, but for a few thousands, GDScript should be fine.
.. tabs::
.. code-tab:: gdscript GDScript
gdscript GDScript
```
extends MultiMeshInstance
@ -71,31 +71,4 @@ efficient for millions of objects, but for a few thousands, GDScript should be f
# Set the transform of the instances.
for i in multimesh.visible_instance_count:
multimesh.set_instance_transform(i, Transform(Basis(), Vector3(i * 20, 0, 0)))
.. code-tab:: csharp C#
using Godot;
using System;
public class YourClassName : MultiMeshInstance
{
public override void _Ready()
{
// Create the multimesh.
Multimesh = new MultiMesh();
// Set the format first.
Multimesh.TransformFormat = MultiMesh.TransformFormatEnum.Transform3d;
Multimesh.ColorFormat = MultiMesh.ColorFormatEnum.None;
Multimesh.CustomDataFormat = MultiMesh.CustomDataFormatEnum.None;
// Then resize (otherwise, changing the format is not allowed)
Multimesh.InstanceCount = 1000;
// Maybe not all of them should be visible at first.
Multimesh.VisibleInstanceCount = 1000;
// Set the transform of the instances.
for (int i = 0; i < Multimesh.VisibleInstanceCount; i++)
{
Multimesh.SetInstanceTransform(i, new Transform(Basis.Identity, new Vector3(i * 20, 0, 0)));
}
}
}
```

View File

@ -88,9 +88,10 @@ Creating a sprite
This is a simple example of how to create a sprite from code and move it using the low-level
:ref:`CanvasItem <class_CanvasItem>` API.
.. tabs::
.. code-tab:: gdscript GDScript
gdscript GDScript
```
extends Node2D
@ -111,6 +112,7 @@ This is a simple example of how to create a sprite from code and move it using t
# Add the item, rotated 45 degrees and translated.
var xform = Transform2D().rotated(deg2rad(45)).translated(Vector2(20, 30))
VisualServer.canvas_item_set_transform(ci_rid, xform)
```
The Canvas Item API in the server allows you to add draw primitives to it. Once added, they can't be modified.
The Item needs to be cleared and the primitives re-added (this is not the case for setting the transform,
@ -118,20 +120,20 @@ which can be done as many times as desired).
Primitives are cleared this way:
.. tabs::
.. code-tab:: gdscript GDScript
gdscript GDScript
```
VisualServer.canvas_item_clear(ci_rid)
```
Instantiating a Mesh into 3D space
----------------------------------
The 3D APIs are different from the 2D ones, so the instantiation API must be used.
.. tabs::
.. code-tab:: gdscript GDScript
gdscript GDScript
```
extends Spatial
@ -153,6 +155,7 @@ The 3D APIs are different from the 2D ones, so the instantiation API must be use
# Move the mesh around.
var xform = Transform(Basis(), Vector3(20, 100, 0))
VisualServer.instance_set_transform(instance, xform)
```
Creating a 2D RigidBody and moving a sprite with it
---------------------------------------------------
@ -160,9 +163,9 @@ Creating a 2D RigidBody and moving a sprite with it
This creates a :ref:`RigidBody2D <class_RigidBody2D>` using the :ref:`Physics2DServer <class_Physics2DServer>` API,
and moves a :ref:`CanvasItem <class_CanvasItem>` when the body moves.
.. tabs::
.. code-tab:: gdscript GDScript
gdscript GDScript
```
# Physics2DServer expects references to be kept around.
var body
var shape
@ -191,6 +194,7 @@ and moves a :ref:`CanvasItem <class_CanvasItem>` when the body moves.
# The last parameter is optional, can be used as index
# if you have many bodies and a single callback.
Physics2DServer.body_set_force_integration_callback(body, self, "_body_moved", 0)
```
The 3D version should be very similar, as 2D and 3D physics servers are identical (using
:ref:`RigidBody <class_RigidBody>` and :ref:`PhysicsServer <class_PhysicsServer>` respectively).

View File

@ -54,25 +54,14 @@ per second, always. This makes physics and motion calculation work in a
more predictable way than using regular process, which might have spikes
or lose precision if the frame rate is too high or too low.
.. tabs::
.. code-tab:: gdscript GDScript
gdscript GDScript
```
extends KinematicBody2D
func _physics_process(delta):
pass
.. code-tab:: csharp
using Godot;
using System;
public class PhysicsScript : KinematicBody2D
{
public override void _PhysicsProcess(float delta)
{
}
}
```
Scene setup
@ -119,27 +108,14 @@ collision happens, it stops right at the moment of the collision.
So, let's move our sprite downwards until it hits the floor:
.. tabs::
.. code-tab:: gdscript GDScript
gdscript GDScript
```
extends KinematicBody2D
func _physics_process(delta):
move_and_collide(Vector2(0, 1)) # Move down 1 pixel per physics frame
.. code-tab:: csharp
using Godot;
using System;
public class PhysicsScript : KinematicBody2D
{
public override void _PhysicsProcess(float delta)
{
// Move down 1 pixel per physics frame
MoveAndCollide(new Vector2(0, 1));
}
}
```
The result is that the character will move, but stop right when
hitting the floor. Pretty cool, huh?
@ -147,9 +123,9 @@ hitting the floor. Pretty cool, huh?
The next step will be adding gravity to the mix, this way it behaves a
little more like a regular game character:
.. tabs::
.. code-tab:: gdscript GDScript
gdscript GDScript
```
extends KinematicBody2D
const GRAVITY = 200.0
@ -160,25 +136,7 @@ little more like a regular game character:
var motion = velocity * delta
move_and_collide(motion)
.. code-tab:: csharp
using Godot;
using System;
public class PhysicsScript : KinematicBody2D
{
const float gravity = 200.0f;
Vector2 velocity;
public override void _PhysicsProcess(float delta)
{
velocity.y += delta * gravity;
var motion = velocity * delta;
MoveAndCollide(motion);
}
}
```
Now the character falls smoothly. Let's make it walk to the sides, left
and right when touching the directional keys. Remember that the values
@ -186,9 +144,9 @@ being used (for speed at least) are pixels/second.
This adds simple walking support by pressing left and right:
.. tabs::
.. code-tab:: gdscript GDScript
gdscript GDScript
```
extends KinematicBody2D
const GRAVITY = 200.0
@ -211,43 +169,7 @@ This adds simple walking support by pressing left and right:
# The second parameter of "move_and_slide" is the normal pointing up.
# In the case of a 2D platformer, in Godot, upward is negative y, which translates to -1 as a normal.
move_and_slide(velocity, Vector2(0, -1))
.. code-tab:: csharp
using Godot;
using System;
public class PhysicsScript : KinematicBody2D
{
const float gravity = 200.0f;
const int walkSpeed = 200;
Vector2 velocity;
public override void _PhysicsProcess(float delta)
{
velocity.y += delta * gravity;
if (Input.IsActionPressed("ui_left"))
{
velocity.x = -walkSpeed;
}
else if (Input.IsActionPressed("ui_right"))
{
velocity.x = walkSpeed;
}
else
{
velocity.x = 0;
}
// We don't need to multiply velocity by delta because "MoveAndSlide" already takes delta time into account.
// The second parameter of "MoveAndSlide" is the normal pointing up.
// In the case of a 2D platformer, in Godot, upward is negative y, which translates to -1 as a normal.
MoveAndSlide(velocity, new Vector2(0, -1));
}
}
```
And give it a try.

View File

@ -263,9 +263,9 @@ the physics engine.
For example, here is the code for an "Asteroids" style spaceship:
.. tabs::
.. code-tab:: gdscript GDScript
gdscript GDScript
```
extends RigidBody2D
var thrust = Vector2(0, 250)
@ -282,29 +282,7 @@ For example, here is the code for an "Asteroids" style spaceship:
if Input.is_action_pressed("ui_left"):
rotation_dir -= 1
applied_torque = rotation_dir * torque
.. code-tab:: csharp
class Spaceship : RigidBody2D
{
private Vector2 _thrust = new Vector2(0, 250);
private float _torque = 20000;
public override void _IntegrateForces(Physics2DDirectBodyState state)
{
if (Input.IsActionPressed("ui_up"))
AppliedForce = _thrust.Rotated(Rotation);
else
AppliedForce = new Vector2();
var rotationDir = 0;
if (Input.IsActionPressed("ui_right"))
rotationDir += 1;
if (Input.IsActionPressed("ui_left"))
rotationDir -= 1;
AppliedTorque = rotationDir * _torque;
}
}
```
Note that we are not setting the ``linear_velocity`` or ``angular_velocity``
properties directly, but rather applying forces (``thrust`` and ``torque``) to
@ -362,9 +340,9 @@ information to determine the response.
For example, if you want to find the point in space where the collision
occurred:
.. tabs::
.. code-tab:: gdscript GDScript
gdscript GDScript
```
extends KinematicBody2D
var velocity = Vector2(250, 250)
@ -373,28 +351,13 @@ occurred:
var collision_info = move_and_collide(velocity * delta)
if collision_info:
var collision_point = collision_info.position
.. code-tab:: csharp
class Body : KinematicBody2D
{
private Vector2 _velocity = new Vector2(250, 250);
public override void _PhysicsProcess(float delta)
{
var collisionInfo = MoveAndCollide(_velocity * delta);
if (collisionInfo != null)
{
var collisionPoint = collisionInfo.GetPosition();
}
}
}
```
Or to bounce off of the colliding object:
.. tabs::
.. code-tab:: gdscript GDScript
gdscript GDScript
```
extends KinematicBody2D
var velocity = Vector2(250, 250)
@ -403,20 +366,7 @@ Or to bounce off of the colliding object:
var collision_info = move_and_collide(velocity * delta)
if collision_info:
velocity = velocity.bounce(collision_info.normal)
.. code-tab:: csharp
class Body : KinematicBody2D
{
private Vector2 _velocity = new Vector2(250, 250);
public override void _PhysicsProcess(float delta)
{
var collisionInfo = MoveAndCollide(_velocity * delta);
if (collisionInfo != null)
_velocity = _velocity.Bounce(collisionInfo.Normal);
}
}
```
:ref:`move_and_slide <class_KinematicBody2D_method_move_and_slide>`
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
@ -434,9 +384,9 @@ without writing much code.
For example, use the following code to make a character that can walk along
the ground (including slopes) and jump when standing on the ground:
.. tabs::
.. code-tab:: gdscript GDScript
gdscript GDScript
```
extends KinematicBody2D
var run_speed = 350
@ -462,40 +412,7 @@ the ground (including slopes) and jump when standing on the ground:
velocity.y += gravity * delta
get_input()
velocity = move_and_slide(velocity, Vector2(0, -1))
.. code-tab:: csharp
class Body : KinematicBody2D
{
private float _runSpeed = 350;
private float _jumpSpeed = -1000;
private float _gravity = 2500;
private Vector2 _velocity = new Vector2();
private void GetInput()
{
_velocity.x = 0;
var right = Input.IsActionPressed("ui_right");
var left = Input.IsActionPressed("ui_left");
var jump = Input.IsActionPressed("ui_select");
if (IsOnFloor() && jump)
_velocity.y = _jumpSpeed;
if (right)
_velocity.x += _runSpeed;
if (left)
_velocity.x -= _runSpeed;
}
public override void _PhysicsProcess(float delta)
{
_velocity.y += _gravity * delta;
GetInput();
_velocity = MoveAndSlide(_velocity, new Vector2(0,-1));
}
}
```
See :ref:`doc_kinematic_character_2d` for more details on using ``move_and_slide()``,

View File

@ -62,11 +62,10 @@ Simulating the ragdoll
The ragdoll is now ready to use. To start the simulation and play the ragdoll animation, you need to call the ``physical_bones_start_simulation`` method. Attach a script to the skeleton node and call the method in the ``_ready`` method:
.. tabs::
.. code-tab:: gdscript GDScript
```
func _ready():
physical_bones_start_simulation()
```
To stop the simulation, call the ``physical_bones_stop_simulation()`` method.

View File

@ -51,50 +51,31 @@ must be used.
Use the following code in 2D:
.. tabs::
.. code-tab:: gdscript GDscript
gdscript GDscript
```
func _physics_process(delta):
var space_rid = get_world_2d().space
var space_state = Physics2DServer.space_get_direct_state(space_rid)
.. code-tab:: csharp
public override void _PhysicsProcess(float delta)
{
var spaceRid = GetWorld2d().Space;
var spaceState = Physics2DServer.SpaceGetDirectState(spaceRid);
}
```
Or more directly:
.. tabs::
.. code-tab:: gdscript GDScript
gdscript GDScript
```
func _physics_process(delta):
var space_state = get_world_2d().direct_space_state
.. code-tab:: csharp
public override void _PhysicsProcess(float delta)
{
var spaceState = GetWorld2d().DirectSpaceState;
}
```
And in 3D:
.. tabs::
.. code-tab:: gdscript GDScript
gdscript GDScript
```
func _physics_process(delta):
var space_state = get_world().direct_space_state
.. code-tab:: csharp
public override void _PhysicsProcess(float delta)
{
var spaceState = GetWorld().DirectSpaceState;
}
```
Raycast query
-------------
@ -103,36 +84,24 @@ For performing a 2D raycast query, the method
:ref:`Physics2DDirectSpaceState.intersect_ray() <class_Physics2DDirectSpaceState_method_intersect_ray>`
may be used. For example:
.. tabs::
.. code-tab:: gdscript GDScript
gdscript GDScript
```
func _physics_process(delta):
var space_state = get_world_2d().direct_space_state
# use global coordinates, not local to node
var result = space_state.intersect_ray(Vector2(0, 0), Vector2(50, 100))
.. code-tab:: csharp
public override void _PhysicsProcess(float delta)
{
var spaceState = GetWorld2d().DirectSpaceState;
// use global coordinates, not local to node
var result = spaceState.IntersectRay(new Vector2(), new Vector2(50, 100));
}
```
The result is a dictionary. If the ray didn't hit anything, the dictionary will
be empty. If it did hit something, it will contain collision information:
.. tabs::
.. code-tab:: gdscript GDScript
gdscript GDScript
```
if result:
print("Hit at point: ", result.position)
.. code-tab:: csharp
if (result.Count > 0)
GD.Print("Hit at point: ", result["position"]);
```
The ``result`` dictionary when a collision occurs contains the following
data:
@ -166,25 +135,15 @@ optional third parameter which is an array of exceptions. This is an
example of how to use it from a KinematicBody2D or any other
collision object node:
.. tabs::
.. code-tab:: gdscript GDScript
gdscript GDScript
```
extends KinematicBody2D
func _physics_process(delta):
var space_state = get_world_2d().direct_space_state
var result = space_state.intersect_ray(global_position, enemy_position, [self])
.. code-tab:: csharp
class Body : KinematicBody2D
{
public override void _PhysicsProcess(float delta)
{
var spaceState = GetWorld2d().DirectSpaceState;
var result = spaceState.IntersectRay(globalPosition, enemyPosition, new Godot.Collections.Array { this });
}
}
```
The exceptions array can contain objects or RIDs.
@ -199,27 +158,16 @@ The optional fourth argument for ``intersect_ray()`` is a collision mask. For
example, to use the same mask as the parent body, use the ``collision_mask``
member variable:
.. tabs::
.. code-tab:: gdscript GDScript
gdscript GDScript
```
extends KinematicBody2D
func _physics_process(delta):
var space_state = get_world().direct_space_state
var result = space_state.intersect_ray(global_position, enemy_position,
[self], collision_mask)
.. code-tab:: csharp
class Body : KinematicBody2D
{
public override void _PhysicsProcess(float delta)
{
var spaceState = GetWorld2d().DirectSpaceState;
var result = spaceState.IntersectRay(globalPosition, enemyPosition,
new Godot.Collections.Array { this }, CollisionMask);
}
}
```
See :ref:`doc_physics_introduction_collision_layer_code_example` for details on how to set the collision mask.
@ -242,9 +190,9 @@ obtained. This is because ``origin`` changes in orthogonal mode, while
To obtain it using a camera, the following code can be used:
.. tabs::
.. code-tab:: gdscript GDScript
gdscript GDScript
```
const ray_length = 1000
func _input(event):
@ -252,20 +200,7 @@ To obtain it using a camera, the following code can be used:
var camera = $Camera
var from = camera.project_ray_origin(event.position)
var to = from + camera.project_ray_normal(event.position) * ray_length
.. code-tab:: csharp
private const float rayLength = 1000;
public override void _Input(InputEvent @event)
{
if (@event is InputEventMouseButton eventMouseButton && eventMouseButton.Pressed && eventMouseButton.ButtonIndex == 1)
{
var camera = GetNode<Camera>("Camera");
var from = camera.ProjectRayOrigin(eventMouseButton.Position);
var to = from + camera.ProjectRayNormal(eventMouseButton.Position) * rayLength;
}
}
```
Remember that during ``_input()``, the space may be locked, so in practice

View File

@ -31,9 +31,9 @@ The "look at" method
As described above, using the Spatial node's ``look_at()`` method can't be used each frame to follow a target.
Here is a custom ``look_at()`` method that will work reliably with rigid bodies:
.. tabs::
.. code-tab:: gdscript GDScript
gdscript GDScript
```
extends RigidBody
func look_follow(state, current_transform, target_position):
@ -47,27 +47,7 @@ Here is a custom ``look_at()`` method that will work reliably with rigid bodies:
func _integrate_forces(state):
var target_position = $my_target_spatial_node.get_global_transform().origin
look_follow(state, get_global_transform(), target_position)
.. code-tab:: csharp
class Body : RigidBody
{
private void LookFollow(PhysicsDirectBodyState state, Transform currentTransform, Vector3 targetPosition)
{
var upDir = new Vector3(0, 1, 0);
var curDir = currentTransform.basis.Xform(new Vector3(0, 0, 1));
var targetDir = (targetPosition - currentTransform.origin).Normalized();
var rotationAngle = Mathf.Acos(curDir.x) - Mathf.Acos(targetDir.x);
state.SetAngularVelocity(upDir * (rotationAngle / state.GetStep()));
}
public override void _IntegrateForces(PhysicsDirectBodyState state)
{
var targetPosition = GetNode<Spatial>("my_target_spatial_node").GetGlobalTransform().origin;
LookFollow(state, GetGlobalTransform(), targetPosition);
}
}
```
This method uses the rigid body's ``set_angular_velocity()`` method to rotate the body. It first calculates the difference between the current and desired angle and then adds the velocity needed to rotate by that amount in one frame's time.

View File

@ -68,24 +68,14 @@ use ``area_entered``. However, let's assume our player is a ``KinematicBody2D``
.. note:: If you're not familiar with using signals, see :ref:`doc_signals` for
an introduction.
.. tabs::
.. code-tab:: gdscript GDScript
gdscript GDScript
```
extends Area2D
func _on_Coin_body_entered(body):
queue_free()
.. code-tab:: csharp
public class Coin : Area2D
{
public void OnCoinBodyEntered(PhysicsBody2D body)
{
QueueFree();
}
}
```
Now our player can collect the coins!

View File

@ -116,9 +116,9 @@ When using ``move_and_slide()`` it's possible to have multiple collisions occur,
as the slide response is calculated. To process these collisions, use ``get_slide_count()``
and ``get_slide_collision()``:
.. tabs::
.. code-tab:: gdscript GDScript
gdscript GDScript
```
# Using move_and_collide.
var collision = move_and_collide(velocity * delta)
if collision:
@ -129,23 +129,7 @@ and ``get_slide_collision()``:
for i in get_slide_count():
var collision = get_slide_collision(i)
print("I collided with ", collision.collider.name)
.. code-tab:: csharp
// Using MoveAndCollide.
var collision = MoveAndCollide(velocity * delta);
if (collision != null)
{
GD.Print("I collided with ", ((Node)collision.Collider).Name);
}
// Using MoveAndSlide.
velocity = MoveAndSlide(velocity);
for (int i = 0; i < GetSlideCount(); i++)
{
var collision = GetSlideCollision(i);
GD.Print("I collided with ", ((Node)collision.Collider).Name);
}
```
.. note:: `get_slide_count()` only counts times the body has collided and changed direction.
@ -164,9 +148,9 @@ the same collision response:
.. image:: img/k2d_compare.gif
.. tabs::
.. code-tab:: gdscript GDScript
gdscript GDScript
```
# using move_and_collide
var collision = move_and_collide(velocity * delta)
if collision:
@ -174,17 +158,7 @@ the same collision response:
# using move_and_slide
velocity = move_and_slide(velocity)
.. code-tab:: csharp
// using MoveAndCollide
var collision = MoveAndCollide(velocity * delta);
if (collision != null)
{
velocity = velocity.Slide(collision.Normal);
}
// using MoveAndSlide
velocity = MoveAndSlide(velocity);
```
Anything you do with ``move_and_slide()`` can also be done with ``move_and_collide()``,
but it might take a little more code. However, as we'll see in the examples below,
@ -226,9 +200,9 @@ size the rectangle to fit over the sprite image.
Attach a script to the KinematicBody2D and add the following code:
.. tabs::
.. code-tab:: gdscript GDScript
gdscript GDScript
```
extends KinematicBody2D
var speed = 250
@ -250,42 +224,7 @@ Attach a script to the KinematicBody2D and add the following code:
func _physics_process(delta):
get_input()
move_and_collide(velocity * delta)
.. code-tab:: csharp
using Godot;
using System;
public class KBExample : KinematicBody2D
{
public int Speed = 250;
private Vector2 _velocity = new Vector2();
public void GetInput()
{
// Detect up/down/left/right keystate and only move when pressed
_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;
_velocity = _velocity.Normalized() * Speed;
}
public override void _PhysicsProcess(float delta)
{
GetInput();
MoveAndCollide(_velocity * delta);
}
}
```
Run this scene and you'll see that ``move_and_collide()`` works as expected, moving
@ -322,9 +261,9 @@ The Bullet and Wall are separate scenes so that they can be instanced.
The Player is controlled by the `w` and `s` keys for forward and back. Aiming
uses the mouse pointer. Here is the code for the Player, using ``move_and_slide()``:
.. tabs::
.. code-tab:: gdscript GDScript
gdscript GDScript
```
extends KinematicBody2D
var Bullet = preload("res://Bullet.tscn")
@ -354,63 +293,14 @@ uses the mouse pointer. Here is the code for the Player, using ``move_and_slide(
if dir.length() > 5:
rotation = dir.angle()
velocity = move_and_slide(velocity)
.. code-tab:: csharp
using Godot;
using System;
public class KBExample : KinematicBody2D
{
private PackedScene _bullet = (PackedScene)GD.Load("res://Bullet.tscn");
public int Speed = 200;
private Vector2 _velocity = new Vector2();
public void GetInput()
{
// add these actions in Project Settings -> Input Map
_velocity = new Vector2();
if (Input.IsActionPressed("backward"))
{
_velocity = new Vector2(-Speed/3, 0).Rotated(Rotation);
}
if (Input.IsActionPressed("forward"))
{
_velocity = new Vector2(Speed, 0).Rotated(Rotation);
}
if (Input.IsActionPressed("mouse_click"))
{
Shoot();
}
}
public void Shoot()
{
// "Muzzle" is a Position2D placed at the barrel of the gun
var b = (Bullet)_bullet.Instance();
b.Start(GetNode<Node2D>("Muzzle").GlobalPosition, Rotation);
GetParent().AddChild(b);
}
public override void _PhysicsProcess(float delta)
{
GetInput();
var dir = GetGlobalMousePosition() - GlobalPosition;
// Don't move if too close to the mouse pointer
if (dir.Length() > 5)
{
Rotation = dir.Angle();
_velocity = MoveAndSlide(_velocity);
}
}
}
```
And the code for the Bullet:
.. tabs::
.. code-tab:: gdscript GDScript
gdscript GDScript
```
extends KinematicBody2D
var speed = 750
@ -430,43 +320,7 @@ And the code for the Bullet:
func _on_VisibilityNotifier2D_screen_exited():
queue_free()
.. code-tab:: csharp
using Godot;
using System;
public class Bullet : KinematicBody2D
{
public int Speed = 750;
private Vector2 _velocity = new Vector2();
public void Start(Vector2 pos, float dir)
{
Rotation = dir;
Position = pos;
_velocity = new Vector2(speed, 0).Rotated(Rotation);
}
public override void _PhysicsProcess(float delta)
{
var collision = MoveAndCollide(_velocity * delta);
if (collision != null)
{
_velocity = _velocity.Bounce(collision.Normal);
if (collision.Collider.HasMethod("Hit"))
{
collision.Collider.Call("Hit");
}
}
}
public void OnVisibilityNotifier2DScreenExited()
{
QueueFree();
}
}
```
The action happens in ``_physics_process()``. After using ``move_and_collide()``, if a
collision occurs, a ``KinematicCollision2D`` object is returned (otherwise, the return
@ -495,9 +349,9 @@ They can be any shape and size. In the sample project, we're using
Here's the code for the player body:
.. tabs::
.. code-tab:: gdscript GDScript
gdscript GDScript
```
extends KinematicBody2D
export (int) var run_speed = 100
@ -527,49 +381,7 @@ Here's the code for the player body:
if jumping and is_on_floor():
jumping = false
velocity = move_and_slide(velocity, Vector2(0, -1))
.. code-tab:: csharp
using Godot;
using System;
public class KBExample : KinematicBody2D
{
[Export] public int RunSpeed = 100;
[Export] public int JumpSpeed = -400;
[Export] public int Gravity = 1200;
Vector2 velocity = new Vector2();
bool jumping = false;
public void GetInput()
{
velocity.x = 0;
bool right = Input.IsActionPressed("ui_right");
bool left = Input.IsActionPressed("ui_left");
bool jump = Input.IsActionPressed("ui_select");
if (jump && IsOnFloor())
{
jumping = true;
velocity.y = JumpSpeed;
}
if (right)
velocity.x += RunSpeed;
if (left)
velocity.x -= RunSpeed;
}
public override void _PhysicsProcess(float delta)
{
GetInput();
velocity.y += Gravity * delta;
if (jumping && IsOnFloor())
jumping = false;
velocity = MoveAndSlide(velocity, new Vector2(0, -1));
}
}
```
.. image:: img/k2d_platform.gif

View File

@ -41,9 +41,9 @@ you should remove the instance you have added by calling
.. note:: Here, you are loading a script and not a packed scene. Therefore you
should use ``new()`` instead of ``instance()``.
.. tabs::
.. code-tab:: gdscript GDScript
gdscript GDScript
```
# plugin.gd
tool
extends EditorPlugin
@ -58,30 +58,7 @@ you should remove the instance you have added by calling
func _exit_tree():
remove_inspector_plugin(plugin)
.. code-tab:: csharp
// Plugin.cs
#if TOOLS
using Godot;
[Tool]
public class Plugin : EditorPlugin
{
private MyInspectorPlugin _plugin;
public override void _EnterTree()
{
_plugin = new MyInspectorPlugin();
AddInspectorPlugin(_plugin);
}
public override void _ExitTree()
{
RemoveInspectorPlugin(_plugin);
}
}
#endif
```
Interacting with the inspector
@ -109,9 +86,9 @@ you can call both ``add_property_editor()`` and
``add_property_editor_for_multiple_properties()``. Use these last two methods to
specifically add :ref:`class_EditorProperty`-based controls.
.. tabs::
.. code-tab:: gdscript GDScript
gdscript GDScript
```
# MyInspectorPlugin.gd
extends EditorInspectorPlugin
@ -134,38 +111,8 @@ specifically add :ref:`class_EditorProperty`-based controls.
return true
else:
return false
```
.. code-tab:: csharp
// MyInspectorPlugin.cs
#if TOOLS
using Godot;
public class MyInspectorPlugin : EditorInspectorPlugin
{
public override bool CanHandle(Object @object)
{
// We support all objects in this example.
return true;
}
public override bool ParseProperty(Object @object, int type, string path, int hint, string hintText, int usage)
{
// We handle properties of type integer.
if (type == (int)Variant.Type.Int)
{
// Create an instance of the custom property editor and register
// it to a specific property path.
AddPropertyEditor(path, new RandomIntEditor());
// Inform the editor to remove the default property editor for
// this property type.
return true;
}
return false;
}
}
#endif
Adding an interface to edit properties
--------------------------------------
@ -190,9 +137,9 @@ You can display your custom widget in two ways. Use just the default ``add_child
method to display it to the right of the property name, and use ``add_child()``
followed by ``set_bottom_editor()`` to position it below the name.
.. tabs::
.. code-tab:: gdscript GDScript
gdscript GDScript
```
# RandomIntEditor.gd
extends EditorProperty
@ -240,69 +187,7 @@ followed by ``set_bottom_editor()`` to position it below the name.
func refresh_control_text():
property_control.text = "Value: " + str(current_value)
.. code-tab:: csharp
// RandomIntEditor.cs
#if TOOLS
using Godot;
public class RandomIntEditor : EditorProperty
{
// The main control for editing the property.
private Button _propertyControl = new Button();
// An internal value of the property.
private int _currentValue = 0;
// A guard against internal changes when the property is updated.
private bool _updating = false;
public RandomIntEditor()
{
// Add the control as a direct child of EditorProperty node.
AddChild(_propertyControl);
// Make sure the control is able to retain the focus.
AddFocusable(_propertyControl);
// Setup the initial state and connect to the signal to track changes.
RefreshControlText();
_propertyControl.Connect("pressed", this, nameof(OnButtonPressed));
}
private void OnButtonPressed()
{
// Ignore the signal if the property is currently being updated.
if (_updating)
{
return;
}
// Generate a new random integer between 0 and 99.
_currentValue = (int)GD.Randi() % 100;
RefreshControlText();
EmitChanged(GetEditedProperty(), _currentValue);
}
public override void UpdateProperty()
{
// Read the current value from the property.
var newValue = (int)GetEditedObject().Get(GetEditedProperty());
if (newValue == _currentValue)
{
return;
}
// Update the control with the new value.
_updating = true;
_currentValue = newValue;
RefreshControlText();
_updating = false;
}
private void RefreshControlText()
{
_propertyControl.Text = $"Value: {_currentValue}";
}
}
#endif
```
Using the example code above you should be able to make a custom widget that
replaces the default :ref:`class_SpinBox` control for integers with a

View File

@ -43,9 +43,9 @@ creation of the files and the config file's values.
To continue with the example, use the following values:
.. tabs::
.. code-tab:: ini GDScript
ini GDScript
```
Plugin Name: My Custom Node
Subfolder: my_custom_node
Description: A custom node made to extend the Godot Engine.
@ -54,17 +54,7 @@ To continue with the example, use the following values:
Language: GDScript
Script Name: custom_node.gd
Activate now: No
.. code-tab:: ini C#
Plugin Name: My Custom Node
Subfolder: my_custom_node
Description: A custom node made to extend the Godot Engine.
Author: Your Name Here
Version: 1.0.0
Language: C#
Script Name: CustomNode.cs
Activate now: No
```
.. warning::
@ -107,9 +97,10 @@ the dialog generates these callbacks for you. Your script should look something
like this:
.. _doc_making_plugins_template_code:
.. tabs::
.. code-tab:: gdscript GDScript
gdscript GDScript
```
tool
extends EditorPlugin
@ -122,27 +113,7 @@ like this:
func _exit_tree():
# Clean-up of the plugin goes here.
pass
.. code-tab:: csharp
#if TOOLS
using Godot;
using System;
[Tool]
public class CustomNode : EditorPlugin
{
public override void _EnterTree()
{
// Initialization of the plugin goes here.
}
public override void _ExitTree()
{
// Clean-up of the plugin goes here.
}
}
#endif
```
This is a good template to use when creating new plugins.
@ -174,9 +145,9 @@ clicked. For that, we'll need a simple script that extends from
:ref:`class_Button`. It could also extend
:ref:`class_BaseButton` if you prefer:
.. tabs::
.. code-tab:: gdscript GDScript
gdscript GDScript
```
tool
extends Button
@ -187,25 +158,7 @@ clicked. For that, we'll need a simple script that extends from
func clicked():
print("You clicked me!")
.. code-tab:: csharp
using Godot;
using System;
[Tool]
public class MyButton : Button
{
public override void _EnterTree()
{
Connect("pressed", this, "clicked");
}
public void clicked()
{
GD.Print("You clicked me!");
}
}
```
That's it for our basic button. You can save this as ``my_button.gd`` inside the
plugin folder. You'll also need a 16×16 icon to show in the scene tree. If you
@ -218,9 +171,9 @@ don't have one, you can grab the default one from the engine and save it in your
Now, we need to add it as a custom type so it shows on the **Create New Node**
dialog. For that, change the ``custom_node.gd`` script to the following:
.. tabs::
.. code-tab:: gdscript GDScript
gdscript GDScript
```
tool
extends EditorPlugin
@ -235,33 +188,7 @@ dialog. For that, change the ``custom_node.gd`` script to the following:
# Clean-up of the plugin goes here.
# Always remember to remove it from the engine when deactivated.
remove_custom_type("MyButton")
.. code-tab:: csharp
#if TOOLS
using Godot;
using System;
[Tool]
public class CustomNode : EditorPlugin
{
public override void _EnterTree()
{
// Initialization of the plugin goes here.
// Add the new type with a name, a parent type, a script and an icon.
var script = GD.Load<Script>("MyButton.cs");
var texture = GD.Load<Texture>("icon.png");
AddCustomType("MyButton", "Button", script, texture);
}
public override void _ExitTree()
{
// Clean-up of the plugin goes here.
// Always remember to remove it from the engine when deactivated.
RemoveCustomType("MyButton");
}
}
#endif
```
With that done, the plugin should already be available in the plugin list in the
**Project Settings**, so activate it as explained in `Checking the results`_.
@ -287,9 +214,9 @@ Creating a custom dock is done just like a custom node. Create a new
``plugin.cfg`` file in the ``addons/my_custom_dock`` folder, then
add the following content to it:
.. tabs::
.. code-tab:: gdscript GDScript
gdscript GDScript
```
[plugin]
name="My Custom Dock"
@ -297,16 +224,7 @@ add the following content to it:
author="Your Name Here"
version="1.0"
script="custom_dock.gd"
.. code-tab:: csharp
[plugin]
name="My Custom Dock"
description="A custom dock made so I can learn how to make plugins."
author="Your Name Here"
version="1.0"
script="CustomDock.cs"
```
Then create the script ``custom_dock.gd`` in the same folder. Fill it with the
:ref:`template we've seen before <doc_making_plugins_template_code>` to get a
@ -334,9 +252,9 @@ You need to select a dock position and define the control to add
**remove the dock** when the plugin is deactivated.
The script could look like this:
.. tabs::
.. code-tab:: gdscript GDScript
gdscript GDScript
```
tool
extends EditorPlugin
@ -361,34 +279,7 @@ The script could look like this:
remove_control_from_docks(dock)
# Erase the control from the memory.
dock.free()
.. code-tab:: csharp
#if TOOLS
using Godot;
using System;
[Tool]
public class CustomDock : EditorPlugin
{
Control dock;
public override void _EnterTree()
{
dock = (Control)GD.Load<PackedScene>("addons/my_custom_dock/my_dock.tscn").Instance();
AddControlToDock(DockSlot.LeftUl, dock);
}
public override void _ExitTree()
{
// Clean-up of the plugin goes here.
// Remove the dock.
RemoveControlFromDocks(dock);
// Erase the control from the memory.
dock.Free();
}
}
#endif
```
Note that, while the dock will initially appear at its specified position,
the user can freely change its position and save the resulting layout.

View File

@ -33,41 +33,29 @@ To check if you are currently in the editor, use: ``Engine.editor_hint``.
For example, if you want to execute some code only in the editor, use:
.. tabs::
.. code-tab:: gdscript GDScript
gdscript GDScript
```
if Engine.editor_hint:
# Code to execute when in editor.
.. code-tab:: csharp
if (Engine.EditorHint)
{
// Code to execute when in editor.
}
```
On the other hand, if you want to execute code only in game, simply negate the same statement:
.. tabs::
.. code-tab:: gdscript GDScript
gdscript GDScript
```
if not Engine.editor_hint:
# Code to execute when in game.
.. code-tab:: csharp
if (!Engine.EditorHint)
{
// Code to execute when in game.
}
```
Pieces of code do not have either of the 2 conditions above will run both in-editor and in-game.
Here is how a ``_process()`` function might look for you:
.. tabs::
.. code-tab:: gdscript GDScript
gdscript GDScript
```
func _process(delta):
if Engine.editor_hint:
# Code to execute in editor.
@ -76,23 +64,7 @@ Here is how a ``_process()`` function might look for you:
# Code to execute in game.
# Code to execute both in editor and in game.
.. code-tab:: csharp
public override void _Process(float delta)
{
if (Engine.EditorHint)
{
// Code to execute in editor.
}
if (!Engine.EditorHint)
{
// Code to execute in game.
}
// Code to execute both in editor and in game.
}
```
.. note:: Modifications in editor are permanent. For example, in the following case, when we remove the script, the node will keep its rotation. Be careful to avoid making unwanted modifications.
@ -101,28 +73,15 @@ Try it out
Add a ``Sprite`` node to your scene and set the texture to Godot icon. Attach and open a script, and change it to this:
.. tabs::
.. code-tab:: gdscript GDScript
gdscript GDScript
```
tool
extends Sprite
func _process(delta):
rotation_degrees += 180 * delta
.. code-tab:: csharp
using Godot;
using System;
[Tool]
public class MySprite : Sprite
{
public override void _Process(float delta)
{
RotationDegrees += 180 * delta;
}
}
```
Save the script and return to the editor. You should now see your object rotate. If you run the game, it will also rotate.
@ -132,28 +91,15 @@ Save the script and return to the editor. You should now see your object rotate.
Now let's choose which code runs when. Modify your ``_process()`` function to look like this:
.. tabs::
.. code-tab:: gdscript GDScript
gdscript GDScript
```
func _process(delta):
if Engine.editor_hint:
rotation_degrees += 180 * delta
else:
rotation_degrees -= 180 * delta
.. code-tab:: csharp
public override void _Process(float delta)
{
if (Engine.EditorHint)
{
RotationDegrees += 180 * delta;
}
else
{
RotationDegrees -= 180 * delta;
}
}
```
Save the script. Now the object will spin clockwise in the editor, but if you run the game, it will spin counter-clockwise.
@ -162,9 +108,9 @@ Editing variables
Add and export a variable speed to the script. The function set_speed after "setget" is executed with your input to change the variable.
Modify ``_process()`` to include the rotation speed.
.. tabs::
.. code-tab:: gdscript GDScript
gdscript GDScript
```
tool
extends Sprite
@ -180,35 +126,8 @@ Modify ``_process()`` to include the rotation speed.
func _process(delta):
rotation_degrees += 180 * delta * speed
```
.. code-tab:: csharp
using Godot;
using System;
[Tool]
public class MySprite : Sprite
{
private float speed = 1;
[Export]
public float Speed {
get => speed;
set => SetSpeed(value);
}
// Update speed and reset the rotation.
private void SetSpeed(float newSpeed)
{
speed = newSpeed;
RotationDegrees = 0;
}
public override void _Process(float delta)
{
RotationDegrees += 180 * delta * speed;
}
}
.. note:: Code from other nodes doesn't run in the editor. Your access to other nodes is limited. You can access the tree and nodes, and their default properties, but you can't access user variables. If you want to do so, other nodes have to run in the editor too. AutoLoad nodes cannot be accessed in the editor at all.
@ -225,9 +144,9 @@ property to the currently edited scene root.
If you are using ``tool``:
.. tabs::
.. code-tab:: gdscript GDScript
gdscript GDScript
```
func _ready():
var node = Spatial.new()
add_child(node) # Parent could be any node in the scene
@ -235,24 +154,13 @@ If you are using ``tool``:
# The line below is required to make the node visible in the Scene tree dock
# and persist changes made by the tool script to the saved scene file.
node.set_owner(get_tree().edited_scene_root)
.. code-tab:: csharp
public override void _Ready()
{
var node = new Spatial();
AddChild(node); // Parent could be any node in the scene
// The line below is required to make the node visible in the Scene tree dock
// and persist changes made by the tool script to the saved scene file.
node.Owner = GetTree().EditedSceneRoot;
}
```
If you are using :ref:`EditorScript<class_EditorScript>`:
.. tabs::
.. code-tab:: gdscript GDScript
gdscript GDScript
```
func _run():
var parent = get_scene().find_node("Parent") # Parent could be any node in the scene
var node = Spatial.new()
@ -261,18 +169,6 @@ If you are using :ref:`EditorScript<class_EditorScript>`:
# The line below is required to make the node visible in the Scene tree dock
# and persist changes made by the tool script to the saved scene file.
node.set_owner(get_scene())
.. code-tab:: csharp
public override void _Run()
{
var parent = GetScene().FindNode("Parent"); // Parent could be any node in the scene
var node = new Spatial();
parent.AddChild(node);
// The line below is required to make the node visible in the Scene tree dock
// and persist changes made by the tool script to the saved scene file.
node.Owner = GetScene();
}
```
.. warning:: Using ``tool`` improperly can yield many errors. It is advised to first write the code how you want it, and only then add the ``tool`` keyword to the top. Also, make sure to separate code that runs in-editor from code that runs in-game. This way, you can find bugs more easily.

View File

@ -9,31 +9,16 @@ will render to the image it generates. This holds true even for nodes outside
of the "current" scene. Autoloads fall into this category, but so do
scenes which one instances and adds to the tree at runtime:
.. tabs::
.. code-tab:: gdscript GDScript
gdscript GDScript
```
var simultaneous_scene = preload("res://levels/level2.tscn").instance()
func _add_a_scene_manually():
# This is like autoloading the scene, only
# it happens after already loading the main scene.
get_tree().get_root().add_child(simultaneous_scene)
.. code-tab:: csharp
public PackedScene simultaneousScene;
public MyClass()
{
simultaneousScene = (PackedScene)ResourceLoader.Load("res://levels/level2.tscn").instance();
}
public void _AddASceneManually()
{
// This is like autoloading the scene, only
// it happens after already loading the main scene.
GetTree().GetRoot().AddChild(simultaneousScene);
}
```
To complete the cycle and swap out the new scene with the old one,
developers have a choice to make. Many strategies exist for removing a scene
@ -121,14 +106,11 @@ There are also cases where one may wish to have many scenes present at the same
time. Perhaps one is adding their own singleton at runtime, or preserving a
a scene's data between scene changes (adding the scene to the root node).
.. tabs::
.. code-tab:: gdscript GDScript
gdscript GDScript
```
get_tree().get_root().add_child(scene)
.. code-tab:: csharp
GetTree().GetRoot().AddChild(scene);
```
Perhaps instead they wish to display multiple scenes at the same time using
:ref:`ViewportContainers <class_ViewportContainer>`. This is optimal in

View File

@ -66,10 +66,10 @@ The ``Default`` template is always generated dynamically per language and cannot
be configured nor overridden, but you can use these as the base for creating
other templates.
.. tabs::
.. code-tab:: gdscript GDScript
gdscript GDScript
```
extends %BASE%
@ -86,31 +86,7 @@ other templates.
# Called every frame. 'delta' is the elapsed time since the previous frame.
#func _process(delta%FLOAT_TYPE%)%VOID_RETURN%:
# pass
.. code-tab:: csharp
using Godot;
using System;
public class %CLASS% : %BASE%
{
// Declare member variables here. Examples:
// private int a = 2;
// private string b = "text";
// Called when the node enters the scene tree for the first time.
public override void _Ready()
{
}
// // Called every frame. 'delta' is the elapsed time since the previous frame.
// public override void _Process(float delta)
// {
//
// }
}
```
List of template placeholders
-----------------------------

View File

@ -10,10 +10,9 @@ in different languages.
The following two scripts will be used as references throughout this page.
.. tabs::
.. code-tab:: gdscript GDScript
gdscript GDScript
```
extends Node
var str1 : String = "foo"
@ -32,35 +31,7 @@ The following two scripts will be used as references throughout this page.
func print_n_times(msg : String, n : int) -> void:
for i in range(n):
print(msg)
.. code-tab:: csharp
public class MyCSharpNode : Node
{
public String str1 = "bar";
public String str2 { get { return "barbar"; } }
public void PrintNodeName(Node node)
{
GD.Print(node.GetName());
}
public void PrintArray(String[] arr)
{
foreach (String element in arr)
{
GD.Print(element);
}
}
public void PrintNTimes(String msg, int n)
{
for (int i = 0; i < n; ++i)
{
GD.Print(msg);
}
}
}
```
Instantiating nodes
-------------------

View File

@ -74,20 +74,12 @@ You can also manage groups from scripts. The following code adds the node to
which you attach the script to the ``guards`` group as soon as it enters the
scene tree.
.. tabs::
.. code-tab:: gdscript GDScript
gdscript GDScript
```
func _ready():
add_to_group("guards")
.. code-tab:: csharp
public override void _Ready()
{
base._Ready();
AddToGroup("guards");
}
```
Imagine you're creating an infiltration game. When an
enemy spots the player, you want all guards and robots to be on alert.
@ -95,18 +87,12 @@ enemy spots the player, you want all guards and robots to be on alert.
In the fictional example below, we use ``SceneTree.call_group()`` to alert all
enemies that the player was spotted.
.. tabs::
.. code-tab:: gdscript GDScript
gdscript GDScript
```
func _on_Player_spotted():
get_tree().call_group("guards", "enter_alert_mode")
.. code-tab:: csharp
public void _OnPlayerDiscovered()
{
GetTree().CallGroup("guards", "enter_alert_mode");
}
```
The above code calls the function ``enter_alert_mode`` on every member of the
group ``guards``.
@ -115,14 +101,11 @@ To get the full list of nodes in the ``guards`` group as an array, you can call
:ref:`SceneTree.get_nodes_in_group()
<class_SceneTree_method_get_nodes_in_group>`:
.. tabs::
.. code-tab:: gdscript GDScript
gdscript GDScript
```
var guards = get_tree().get_nodes_in_group("guards")
.. code-tab:: csharp
var guards = GetTree().GetNodesInGroup("guards");
```
The :ref:`SceneTree <class_SceneTree>` class provides many more useful methods
to interact with scenes, their node hierarchy, and groups. It allows you to

View File

@ -24,19 +24,13 @@ script. You can turn it off and back on by calling :ref:`Node.set_process()
The engine calls this method every time it draws a frame:
.. tabs::
.. code-tab:: gdscript GDScript
gdscript GDScript
```
func _process(delta):
# Do something...
pass
.. code-tab:: csharp
public override void _Process(float delta)
{
// Do something...
}
```
Keep in mind that the frequency at which the engine calls ``_process()`` depends
on your application's framerate, which varies over time and across devices.
@ -56,19 +50,13 @@ Physics Fps. By default, it's set to run 60 times per second.
The engine calls this method every time it draws a frame:
.. tabs::
.. code-tab:: gdscript GDScript
gdscript GDScript
```
func _physics_process(delta):
# Do something...
pass
.. code-tab:: csharp
public override void _PhysicsProcess(float delta)
{
// Do something...
}
```
The function ``_process()`` is not synchronized with physics. Its rate depends on
hardware and game optimization. It also runs after the physics step in
@ -77,9 +65,9 @@ single-threaded games.
You can see the ``_process()`` function at work by creating a scene with a
single Label node, with the following script attached to it:
.. tabs::
.. code-tab:: gdscript GDScript
gdscript GDScript
```
extends Label
var time = 0
@ -87,18 +75,6 @@ single Label node, with the following script attached to it:
func _process(delta):
time += delta
text = str(time) # 'text' is a built-in Label property.
.. code-tab:: csharp
public class CustomLabel : Label
{
private float _time;
public override void _Process(float delta)
{
_time += delta;
Text = _time.ToString(); // 'Text' is a built-in Label property.
}
}
```
When you run the scene, you should see a counter increasing each frame.

View File

@ -25,27 +25,16 @@ player's location. See :ref:`doc_instancing` for details.
We'll use an ``Area2D`` for the bullet, which moves in a straight line at a
given velocity:
.. tabs::
.. code-tab:: gdscript GDScript
gdscript GDScript
```
extends Area2D
var velocity = Vector2.ZERO
func _physics_process(delta):
position += velocity * delta
.. code-tab:: csharp
public class Bullet : Area2D
{
Vector2 Velocity = new Vector2();
public override void _PhysicsProcess(float delta)
{
Position += Velocity * delta;
}
}
```
However, if the bullets are added as children of the player, then they will
remain "attached" to the player as it rotates:
@ -60,16 +49,12 @@ scene, which may be the player's parent or even further up the tree.
You could do this by adding the bullet to the main scene directly:
.. tabs::
.. code-tab:: gdscript GDScript
gdscript GDScript
```
var bullet_instance = Bullet.instance()
get_parent().add_child(bullet_instance)
.. code-tab:: csharp
Node bulletInstance = Bullet.Instance();
GetParent().AddChild(bulletInstance);
```
However, this will lead to a different problem. Now if you try to test your
"Player" scene independently, it will crash on shooting, because there is no
@ -85,9 +70,9 @@ appropriate action to spawn them.
Here is the code for the player using signals to emit the bullet:
.. tabs::
.. code-tab:: gdscript GDScript
gdscript GDScript
```
extends Sprite
signal shoot(bullet, direction, location)
@ -101,56 +86,21 @@ Here is the code for the player using signals to emit the bullet:
func _process(delta):
look_at(get_global_mouse_position())
.. code-tab:: csharp
public class Player : Sprite
{
[Signal]
delegate void Shoot(PackedScene bullet, Vector2 direction, Vector2 location);
private PackedScene _bullet = GD.Load<PackedScene>("res://Bullet.tscn");
public override void _Input(InputEvent event)
{
if (input is InputEventMouseButton mouseButton)
{
if (mouseButton.ButtonIndex == (int)ButtonList.Left && mouseButton.Pressed)
{
EmitSignal(nameof(Shoot), _bullet, Rotation, Position);
}
}
}
public override _Process(float delta)
{
LookAt(GetGlobalMousePosition());
}
}
```
In the main scene, we then connect the player's signal (it will appear in the
"Node" tab).
.. tabs::
.. code-tab:: gdscript GDScript
gdscript GDScript
```
func _on_Player_shoot(Bullet, direction, location):
var b = Bullet.instance()
add_child(b)
b.rotation = direction
b.position = location
b.velocity = b.velocity.rotated(direction)
.. code-tab:: csharp
public void _on_Player_Shoot(PackedScene bullet, Vector2 direction, Vector2 location)
{
var bulletInstance = (Bullet)bullet.Instance();
AddChild(bulletInstance);
bulletInstance.Rotation = direction;
bulletInstance.Position = location;
bulletInstance.Velocity = bulletInstance.Velocity.Rotated(direction);
}
```
Now the bullets will maintain their own movement independent of the player's
rotation:

View File

@ -21,28 +21,16 @@ Sprite and Camera2D nodes to access them in your script.
To do so, you can use the following code.
.. tabs::
.. code-tab:: gdscript GDScript
gdscript GDScript
```
var sprite
var camera2d
func _ready():
sprite = get_node("Sprite")
camera2d = get_node("Camera2D")
.. code-tab:: csharp
private Sprite _sprite;
private Camera2D _camera2d;
public override void _Ready()
{
base._Ready();
_sprite = GetNode<Sprite>("Sprite");
_camera2d = GetNode<Camera2D>("Camera2D");
}
```
Note that you get nodes using their name, not their type. Above, "Sprite" and
"Camera2D" are the nodes' names in the scene.
@ -68,24 +56,14 @@ node.
To get the Tween node, you would use the following code.
.. tabs::
.. code-tab:: gdscript GDScript
gdscript GDScript
```
var tween
func _ready():
tween = get_node("ShieldBar/Tween")
.. code-tab:: csharp
private Tween _tween;
public override void _Ready()
{
base._Ready();
_tween = GetNode<Tween>("ShieldBar/Tween");
}
```
.. note:: As with file paths, you can use ".." to get a parent node. The best
practice is to avoid doing that though not to break encapsulation.
@ -122,40 +100,26 @@ You can store the newly created node's reference in a variable and call
``add_child()`` to add it as a child of the node to which you attached the
script.
.. tabs::
.. code-tab:: gdscript GDScript
gdscript GDScript
```
var sprite
func _ready():
var sprite = Sprite.new() # Create a new Sprite.
add_child(sprite) # Add it as a child of this node.
.. code-tab:: csharp
private Sprite _sprite;
public override void _Ready()
{
base._Ready();
_sprite = new Sprite(); // Create a new Sprite.
AddChild(_sprite); // Add it as a child of this node.
}
```
To delete a node and free it from memory, you can call its ``queue_free()``
method. Doing so queues the node for deletion at the end of the current frame
after it has finished processing. At that point, the engine removes the node from
the scene and frees the object in memory.
.. tabs::
.. code-tab:: gdscript GDScript
gdscript GDScript
```
sprite.queue_free()
.. code-tab:: csharp
_sprite.QueueFree();
```
Before calling ``sprite.queue_free()``, the remote scene tree looks like this.
@ -185,39 +149,33 @@ steps:
2. Creating an instance of the loaded :ref:`PackedScene <class_PackedScene>`
resource.
.. tabs::
.. code-tab:: gdscript GDScript
gdscript GDScript
```
var scene = load("res://MyScene.tscn")
.. code-tab:: csharp
var scene = GD.Load<PackedScene>("res://MyScene.tscn");
```
Preloading the scene can improve the user's experience as the load operation
happens when the compiler reads the script and not at runtime. This feature is
only available with GDScript.
.. tabs::
.. code-tab:: gdscript GDScript
gdscript GDScript
```
var scene = preload("res://MyScene.tscn")
```
At that point, ``scene`` is a packed scene resource, not a node. To create the
actual node, you need to call :ref:`PackedScene.instance()
<class_PackedScene_method_instance>`. It returns a tree of nodes that you can
as a child of your current node.
.. tabs::
.. code-tab:: gdscript GDScript
gdscript GDScript
```
var instance = scene.instance()
add_child(instance)
.. code-tab:: csharp
var instance = scene.Instance();
AddChild(instance);
```
The advantage of this two-step process is you can keep a packed scene loaded and
create new instances on the fly. For example, to quickly instance several

View File

@ -31,9 +31,9 @@ Another related callback is ``_exit_tree()``, which the engine calls every time
a node exits the scene tree. This can be when you call :ref:`Node.remove_child()
<class_Node_method_remove_child>` or when you free a node.
.. tabs::
.. code-tab:: gdscript GDScript
gdscript GDScript
```
# Called every time the node enters the scene tree.
func _enter_tree():
pass
@ -46,36 +46,16 @@ a node exits the scene tree. This can be when you call :ref:`Node.remove_child()
# children received the _exit_tree() callback.
func _exit_tree():
pass
.. code-tab:: csharp
// Called every time the node enters the scene tree.
public override void _EnterTree()
{
base._EnterTree();
}
// Called when both the node and its children have entered the scene tree.
public override void _Ready()
{
base._Ready();
}
// Called when the node is about to leave the scene tree, after all its
// children.
public override void _ExitTree()
{
base._ExitTree();
}
```
The two virtual methods ``_process()`` and ``_physics_process()`` allow you to
update the node, every frame and every physics frame respectively. For more
information, read the dedicated documentation:
:ref:`doc_idle_and_physics_processing`.
.. tabs::
.. code-tab:: gdscript GDScript
gdscript GDScript
```
# Called every frame, as often as possible.
func _process(delta):
pass
@ -83,20 +63,7 @@ information, read the dedicated documentation:
# Called every physics frame.
func _physics_process(delta):
pass
.. code-tab:: csharp
public override void _Process(float delta)
{
// Called every frame, as often as possible.
base._Process(delta);
}
public override void _PhysicsProcess(float delta)
{
// Called every physics frame.
base._PhysicsProcess(delta);
}
```
Two more essential built-in node callback functions are
:ref:`Node._unhandled_input() <class_Node_method__unhandled_input>` and
@ -109,9 +76,9 @@ process input events before ``_unhandled_input()`` gets them.
To learn more about inputs in Godot, see the :ref:`Input section <toc-learn-features-inputs>`.
.. tabs::
.. code-tab:: gdscript GDScript
gdscript GDScript
```
# Called once for every event.
func _unhandled_input(event):
pass
@ -120,21 +87,7 @@ To learn more about inputs in Godot, see the :ref:`Input section <toc-learn-feat
# consume some events.
func _input(event):
pass
.. code-tab:: csharp
// Called once for every event.
public override void _UnhandledInput(InputEvent @event)
{
base._UnhandledInput(event);
}
// Called once for every event, before _unhandled_input(), allowing you to
// consume some events.
public override void _Input(InputEvent @event)
{
base._Input(event);
}
```
There are some more overridable functions like
:ref:`Node._get_configuration_warning()

View File

@ -18,14 +18,11 @@ How pausing works
To pause the game the pause state must be set. This is done by assigning
``true`` to the :ref:`SceneTree.paused <class_SceneTree_property_paused>` property:
.. tabs::
.. code-tab:: gdscript GDScript
gdscript GDScript
```
get_tree().paused = true
.. code-tab:: csharp
GetTree().Paused = true;
```
Doing this will cause two things. First, 2D and 3D physics will be stopped
for all nodes. Second, the behavior of certain nodes will stop or start
@ -44,18 +41,12 @@ be found and changed under a node's :ref:`Node <class_Node>` properties in the i
You can also alter the property with code:
.. tabs::
.. code-tab:: gdscript GDScript
gdscript GDScript
```
func _ready():
pause_mode = Node.PAUSE_MODE_PROCESS
.. code-tab:: csharp
public override void _Ready()
{
PauseMode = Node.PauseModeEnum.Process;
}
```
This is what each mode tells a node to do:
@ -98,35 +89,21 @@ working when paused.
Finally, make it so when a pause button is pressed (any button will do),
enable the pause and show the pause screen.
.. tabs::
.. code-tab:: gdscript GDScript
gdscript GDScript
```
func _on_pause_button_pressed():
get_tree().paused = true
$pause_popup.show()
.. code-tab:: csharp
public void _on_pause_button_pressed()
{
GetTree().Paused = true;
GetNode<Control>("pause_popup").Show();
}
```
To unpause, do the opposite when the pause screen is
closed:
.. tabs::
.. code-tab:: gdscript GDScript
gdscript GDScript
```
func _on_pause_popup_close_pressed():
$pause_popup.hide()
get_tree().paused = false
.. code-tab:: csharp
public void _on_pause_popup_close_pressed()
{
GetNode<Control>("pause_popup").Hide();
GetTree().Paused = false;
}
```

View File

@ -71,36 +71,25 @@ Loading resources from code
There are two ways to load resources from code. First, you can use the ``load()`` function anytime:
.. tabs::
.. code-tab:: gdscript GDScript
gdscript GDScript
```
func _ready():
var res = load("res://robi.png") # Godot loads the Resource when it reads the line.
get_node("sprite").texture = res
.. code-tab:: csharp
public override void _Ready()
{
var texture = (Texture)GD.Load("res://robi.png"); // Godot loads the Resource when it reads the line.
var sprite = GetNode<Sprite>("sprite");
sprite.Texture = texture;
}
```
You can also ``preload`` resources. Unlike ``load``, this function will read the
file from disk and load it at compile-time. As a result, you cannot call preload
with a variable path: you need to use a constant string.
.. tabs::
.. code-tab:: gdscript GDScript
gdscript GDScript
```
func _ready():
var res = preload("res://robi.png") # Godot loads the resource at compile-time
get_node("sprite").texture = res
.. code-tab:: csharp
// 'preload()' is unavailable in C Sharp.
```
Loading scenes
--------------
@ -112,23 +101,13 @@ scene is packed inside a resource.
To get an instance of the scene, you have to use the
:ref:`PackedScene.instance() <class_PackedScene_method_instance>` method.
.. tabs::
.. code-tab:: gdscript GDScript
gdscript GDScript
```
func _on_shoot():
var bullet = preload("res://bullet.tscn").instance()
add_child(bullet)
.. code-tab:: csharp
private PackedScene _bulletScene = (PackedScene)GD.Load("res://bullet.tscn");
public void OnShoot()
{
Node bullet = _bulletScene.Instance();
AddChild(bullet);
}
```
This method creates the nodes in the scene's hierarchy, configures them, and
returns the root node of the scene. You can then add it as a child of any other
@ -197,9 +176,9 @@ object you create.
Let's see some examples.
.. tabs::
.. code-tab:: gdscript GDScript
gdscript GDScript
```
# bot_stats.gd
extends Resource
export(int) var health
@ -223,54 +202,7 @@ Let's see some examples.
# Uses an implicit, duck-typed interface for any 'health'-compatible resources.
if stats:
print(stats.health) # Prints '10'.
.. code-tab:: csharp
// BotStats.cs
using System;
using Godot;
namespace ExampleProject {
public class BotStats : Resource
{
[Export]
public int Health { get; set; }
[Export]
public Resource SubResource { get; set; }
[Export]
public String[] Strings { get; set; }
// Make sure that every parameter has a default value.
// Otherwise, there will be problems with creating and editing
// your resource via the inspector.
public BotStats(int health = 0, Resource subResource = null, String[] strings = null)
{
Health = health;
SubResource = subResource;
Strings = strings ?? new String[0];
}
}
}
// Bot.cs
using System;
using Godot;
namespace ExampleProject {
public class Bot : KinematicBody
{
[Export]
public Resource Stats;
public override void _Ready()
{
if (Stats != null && Stats is BotStats botStats) {
GD.Print(botStats.Health); // Prints '10'.
}
}
}
}
```
.. note::
@ -284,9 +216,9 @@ Let's see some examples.
Resource scripts. DataTables are a String mapped to a custom struct, similar
to a Dictionary mapping a String to a secondary custom Resource script.
.. tabs::
.. code-tab:: gdscript GDScript
gdscript GDScript
```
# bot_stats_table.gd
extends Resource
@ -315,6 +247,7 @@ Let's see some examples.
GD.Print(_stats);
}
}
```
Instead of just inlining the Dictionary values, one could also, alternatively...
@ -338,9 +271,9 @@ Let's see some examples.
extend ``Resource``, and then determine that the script failed to load for the
Resource object since the types are incompatible.
.. tabs::
.. code-tab:: gdscript GDScript
gdscript GDScript
```
extends Node
class MyResource:
@ -352,24 +285,4 @@ Let's see some examples.
# This will NOT serialize the 'value' property.
ResourceSaver.save("res://my_res.tres", my_res)
.. code-tab:: csharp
using System;
using Godot;
public class MyNode : Node
{
public class MyResource : Resource
{
[Export]
public int Value { get; set; } = 5;
}
public override void _Ready()
{
var res = new MyResource();
// This will NOT serialize the 'Value' property.
ResourceSaver.Save("res://MyRes.tres", res);
}
}
```

View File

@ -66,16 +66,12 @@ The root :ref:`Viewport <class_Viewport>`
is always at the top of the scene. From a node, it can be obtained in
two different ways:
.. tabs::
.. code-tab:: gdscript GDScript
gdscript GDScript
```
get_tree().get_root() # Access via scene main loop.
get_node("/root") # Access via absolute path.
.. code-tab:: csharp
GetTree().GetRoot(); // Access via scene main loop.
GetNode("/root"); // Access via absolute path.
```
This node contains the main viewport. Anything that is a child of a
:ref:`Viewport <class_Viewport>`
@ -137,39 +133,26 @@ another one. The simple way to do this is to use the
:ref:`SceneTree.change_scene() <class_SceneTree_method_change_scene>`
function:
.. tabs::
.. code-tab:: gdscript GDScript
gdscript GDScript
```
func _my_level_was_completed():
get_tree().change_scene("res://levels/level2.tscn")
.. code-tab:: csharp
public void _MyLevelWasCompleted()
{
GetTree().ChangeScene("res://levels/level2.tscn");
}
```
Rather than using file paths, one can also use ready-made
:ref:`PackedScene <class_PackedScene>` resources using the equivalent
function
:ref:`SceneTree.change_scene_to(PackedScene scene) <class_SceneTree_method_change_scene_to>`:
.. tabs::
.. code-tab:: gdscript GDScript
gdscript GDScript
```
var next_scene = preload("res://levels/level2.tscn")
func _my_level_was_completed():
get_tree().change_scene_to(next_scene)
.. code-tab:: csharp
public void _MyLevelWasCompleted()
{
var nextScene = (PackedScene)ResourceLoader.Load("res://levels/level2.tscn");
GetTree().ChangeSceneTo(nextScene);
}
```
These are quick and useful ways to switch scenes but have the drawback
that the game will stall until the new scene is loaded and running. At

View File

@ -31,7 +31,8 @@ to its name in the scene tree:
To use a unique node in a script, use the ``%`` symbol and the node's
name in the path for ``get_node()``. For example:
.. tabs::
.. code-tab:: gdscript GDScript
gdscript GDScript
```
get_node("%RedButton").text = "Hello"
```

View File

@ -75,29 +75,21 @@ in top-to-bottom order.
This means that any node can access a singleton named "PlayerVariables" with:
.. tabs::
.. code-tab:: gdscript GDScript
gdscript GDScript
```
var player_vars = get_node("/root/PlayerVariables")
player_vars.health -= 10
.. code-tab:: csharp
var playerVariables = GetNode<PlayerVariables>("/root/PlayerVariables");
playerVariables.Health -= 10; // Instance field.
```
If the **Enable** column is checked (which is the default), then the singleton can
be accessed directly without requiring ``get_node()``:
.. tabs::
.. code-tab:: gdscript GDScript
gdscript GDScript
```
PlayerVariables.health -= 10
.. code-tab:: csharp
// Static members can be accessed by using the class name.
PlayerVariables.Health -= 10;
```
Note that autoload objects (scripts and/or scenes) are accessed just like any
other node in the scene tree. In fact, if you look at the running scene tree,
@ -149,9 +141,9 @@ Returning to the script, it needs to fetch the current scene in the
``Global.gd`` are children of root, but autoloaded nodes are always first. This
means that the last child of root is always the loaded scene.
.. tabs::
.. code-tab:: gdscript GDScript
gdscript GDScript
```
extends Node
var current_scene = null
@ -159,29 +151,14 @@ means that the last child of root is always the loaded scene.
func _ready():
var root = get_tree().root
current_scene = root.get_child(root.get_child_count() - 1)
.. code-tab:: csharp
using Godot;
using System;
public class Global : Godot.Node
{
public Node CurrentScene { get; set; }
public override void _Ready()
{
Viewport root = GetTree().Root;
CurrentScene = root.GetChild(root.GetChildCount() - 1);
}
}
```
Now we need a function for changing the scene. This function needs to free the
current scene and replace it with the requested one.
.. tabs::
.. code-tab:: gdscript GDScript
gdscript GDScript
```
func goto_scene(path):
# This function will usually be called from a signal callback,
# or some other function in the current scene.
@ -210,40 +187,7 @@ current scene and replace it with the requested one.
# Optionally, to make it compatible with the SceneTree.change_scene() API.
get_tree().current_scene = current_scene
.. code-tab:: csharp
public void GotoScene(string path)
{
// This function will usually be called from a signal callback,
// or some other function from the current scene.
// Deleting the current scene at this point is
// a bad idea, because it may still be executing code.
// This will result in a crash or unexpected behavior.
// The solution is to defer the load to a later time, when
// we can be sure that no code from the current scene is running:
CallDeferred(nameof(DeferredGotoScene), path);
}
public void DeferredGotoScene(string path)
{
// It is now safe to remove the current scene
CurrentScene.Free();
// Load a new scene.
var nextScene = (PackedScene)GD.Load(path);
// Instance the new scene.
CurrentScene = nextScene.Instance();
// Add it to the active scene, as child of root.
GetTree().Root.AddChild(CurrentScene);
// Optionally, to make it compatible with the SceneTree.change_scene() API.
GetTree().CurrentScene = CurrentScene;
}
```
Using :ref:`Object.call_deferred() <class_Object_method_call_deferred>`,
the second function will only run once all code from the current scene has
@ -252,43 +196,25 @@ still being used (i.e. its code is still running).
Finally, we need to fill the empty callback functions in the two scenes:
.. tabs::
.. code-tab:: gdscript GDScript
gdscript GDScript
```
# Add to 'Scene1.gd'.
func _on_Button_pressed():
Global.goto_scene("res://Scene2.tscn")
.. code-tab:: csharp
// Add to 'Scene1.cs'.
public void OnButtonPressed()
{
var global = GetNode<Global>("/root/Global");
global.GotoScene("res://Scene2.tscn");
}
```
and
.. tabs::
.. code-tab:: gdscript GDScript
gdscript GDScript
```
# Add to 'Scene2.gd'.
func _on_Button_pressed():
Global.goto_scene("res://Scene1.tscn")
.. code-tab:: csharp
// Add to 'Scene2.cs'.
public void OnButtonPressed()
{
var global = GetNode<Global>("/root/Global");
global.GotoScene("res://Scene1.tscn");
}
```
Run the project and test that you can switch between scenes by pressing
the button.

View File

@ -42,28 +42,15 @@ indicate that this is the currently focused control. To check for this
status, the :ref:`Control.has_focus() <class_Control_method_has_focus>` method
exists. Example
.. tabs::
.. code-tab:: gdscript GDScript
gdscript GDScript
```
func _draw():
if has_focus():
draw_selected()
else:
draw_normal()
.. code-tab:: csharp
public override void _Draw()
{
if (HasFocus())
{
DrawSelected()
}
else
{
DrawNormal();
}
}
```
Sizing
------
@ -80,33 +67,21 @@ To provide this callback, just override
:ref:`Control.get_minimum_size() <class_Control_method_get_minimum_size>`,
for example:
.. tabs::
.. code-tab:: gdscript GDScript
gdscript GDScript
```
func get_minimum_size():
return Vector2(30, 30)
.. code-tab:: csharp
public override Vector2 _GetMinimumSize()
{
return new Vector2(20, 20);
}
```
Alternatively, set it using a function:
.. tabs::
.. code-tab:: gdscript GDScript
gdscript GDScript
```
func _ready():
set_custom_minimum_size(Vector2(30, 30))
.. code-tab:: csharp
public override void _Ready()
{
SetCustomMinimumSize(new Vector2(20, 20));
}
```
Input
-----
@ -131,24 +106,15 @@ This function is
:ref:`Control._gui_input() <class_Control_method__gui_input>`.
Simply override it in your control. No processing needs to be set.
.. tabs::
.. code-tab:: gdscript GDScript
gdscript GDScript
```
extends Control
func _gui_input(event):
if event is InputEventMouseButton and event.button_index == BUTTON_LEFT and event.pressed:
print("Left mouse button was pressed!")
.. code-tab:: csharp
public override void _GuiInput(InputEvent @event)
{
if (@event is InputEventMouseButton mbe && mbe.ButtonIndex == (int)ButtonList.Left && mbe.Pressed)
{
GD.Print("Left mouse button was pressed!");
}
}
```
For more information about events themselves, check the :ref:`doc_inputevent`
tutorial.
@ -159,9 +125,9 @@ Notifications
Controls also have many useful notifications for which no dedicated callback
exists, but which can be checked with the _notification callback:
.. tabs::
.. code-tab:: gdscript GDScript
gdscript GDScript
```
func _notification(what):
match what:
NOTIFICATION_MOUSE_ENTER:
@ -184,45 +150,4 @@ exists, but which can be checked with the _notification callback:
NOTIFICATION_MODAL_CLOSE:
pass # For modal pop-ups, notification
# that the pop-up was closed.
.. code-tab:: csharp
public override void _Notification(int what)
{
switch (what)
{
case NotificationMouseEnter:
// Mouse entered the area of this control.
break;
case NotificationMouseExit:
// Mouse exited the area of this control.
break;
case NotificationFocusEnter:
// Control gained focus.
break;
case NotificationFocusExit:
// Control lost focus.
break;
case NotificationThemeChanged:
// Theme used to draw the control changed;
// update and redraw is recommended if using a theme.
break;
case NotificationVisibilityChanged:
// Control became visible/invisible;
// check new status with is_visible().
break;
case NotificationResized:
// Control changed size; check new size with get_size().
break;
case NotificationModalClose:
// For modal pop-ups, notification that the pop-up was closed.
break;
}
}
```

View File

@ -164,9 +164,9 @@ Creating custom Containers
It is possible to easily create a custom container using script. Here is an example of a simple container that fits children
to its rect size:
.. tabs::
.. code-tab:: gdscript GDScript
gdscript GDScript
```
extends Container
func _notification(what):
@ -179,3 +179,4 @@ to its rect size:
func set_some_setting():
# Some setting changed, ask for children re-sort
queue_sort()
```

View File

@ -62,18 +62,12 @@ For keyboard and controller navigation to work correctly, any node must be focus
using code when the scene starts. Without doing this, pressing buttons or keys won't
do anything. Here is a basic example of setting initial focus with code:
.. tabs::
.. code-tab:: gdscript GDScript
gdscript GDScript
```
func _ready():
$StartButton.grab_focus()
.. code-tab:: csharp
public override void _Ready()
{
GetNode<Button>("StartButton").GrabFocus();
}
```
Now when the scene starts the "Start Button" node will be focused, and the keyboard
or a controller can be used to navigate between it and other UI elements.

View File

@ -116,16 +116,12 @@ your custom theme types, you must utilize scripts to access those items. All con
nodes have several methods that allow to fetch theme items from the theme that
is applied to them. Those methods accept the theme type as one of the arguments.
.. tabs::
.. code-tab:: gdscript
gdscript
```
var accent_color = get_color("accent_color", "MyType")
label.add_color_override("font_color", accent_color)
.. code-tab:: csharp
Color accentColor = GetColor("accent_color", "MyType");
label.AddColorOverride("font_color", accentColor);
```
To give more customization opportunities types can also be linked together as
type variations. This is another use-case for custom theme types. For example,