diff --git a/getting_started/editor/img/editable-children.png b/getting_started/editor/img/editable-children.png deleted file mode 100644 index 6505ed4bc..000000000 Binary files a/getting_started/editor/img/editable-children.png and /dev/null differ diff --git a/getting_started/editor/img/godot-gui-overlay.png b/getting_started/editor/img/godot-gui-overlay.png deleted file mode 100644 index 9d98d5bb0..000000000 Binary files a/getting_started/editor/img/godot-gui-overlay.png and /dev/null differ diff --git a/getting_started/editor/img/save-branch-as-scene.png b/getting_started/editor/img/save-branch-as-scene.png deleted file mode 100644 index 6c3fdd4ee..000000000 Binary files a/getting_started/editor/img/save-branch-as-scene.png and /dev/null differ diff --git a/getting_started/editor/img/unity-gui-overlay.png b/getting_started/editor/img/unity-gui-overlay.png deleted file mode 100644 index e035448bc..000000000 Binary files a/getting_started/editor/img/unity-gui-overlay.png and /dev/null differ diff --git a/getting_started/editor/img/unity-project-organization-example.png b/getting_started/editor/img/unity-project-organization-example.png deleted file mode 100644 index cc97154d9..000000000 Binary files a/getting_started/editor/img/unity-project-organization-example.png and /dev/null differ diff --git a/getting_started/editor/unity_to_godot.rst b/getting_started/editor/unity_to_godot.rst deleted file mode 100644 index f0dc90ee7..000000000 --- a/getting_started/editor/unity_to_godot.rst +++ /dev/null @@ -1,253 +0,0 @@ -.. _unity_to_godot: - -.. references : -.. https://wiki.unrealengine.com/Unity3D_Developer's_Guide_to_Unreal_Engine_4 -.. https://docs.unrealengine.com/latest/INT/GettingStarted/FromUnity/ - -From Unity to Godot Engine -========================== - -This guide provides an overview of Godot Engine from the viewpoint of a Unity user, -and aims to help you migrate your existing Unity experience into the world of Godot. - -.. note:: - - This article talks about older versions of Unity. Nestable prefabs ('Nested prefabs') were added to Unity 2018.3. Nestable prefabs are analogous to Godot's scenes, and allow a more Godot-like approach to scene organization. - -Differences ------------ - -+-------------------+------------------------------------------------------------------------------------+----------------------------------------------------------------------------------------------------------------+ -| | Unity | Godot | -+===================+====================================================================================+================================================================================================================+ -| License | Proprietary, closed, free license with revenue caps and usage restrictions | MIT license, free and fully open source without any restriction | -+-------------------+------------------------------------------------------------------------------------+----------------------------------------------------------------------------------------------------------------+ -| OS (editor) | Windows, macOS, Linux | Windows, macOS, X11 (Linux, \*BSD) | -+-------------------+------------------------------------------------------------------------------------+----------------------------------------------------------------------------------------------------------------+ -| OS (export) | * **Desktop:** Windows, macOS, Linux | * **Desktop:** Windows, macOS, X11 | -| | * **Mobile:** Android, iOS, Windows Phone, Tizen | * **Mobile:** Android, iOS | -| | * **Web:** WebAssembly or asm.js | * **Web:** WebAssembly | -| | * **Consoles:** PS4, PS Vita, Xbox One, Xbox 360, Wii U, Nintendo 3DS | * **Console:** See :ref:`doc_consoles` | -| | * **VR:** Oculus Rift, SteamVR, Google Cardboard, PlayStation VR, Gear VR, HoloLens| * **VR:** Oculus Rift, SteamVR | -| | * **TV:** Android TV, Samsung SMART TV, tvOS | | -+-------------------+------------------------------------------------------------------------------------+----------------------------------------------------------------------------------------------------------------+ -| Scene system | * Component/Scene (GameObject > Component) | :ref:`Scene tree and nodes `, allowing scenes to be nested and/or inherit other scenes | -| | * Prefabs | | -+-------------------+------------------------------------------------------------------------------------+----------------------------------------------------------------------------------------------------------------+ -| Third-party tools | Visual Studio or VS Code | * :ref:`External editors are possible ` | -| | | * :ref:`Android SDK for Android export ` | -+-------------------+------------------------------------------------------------------------------------+----------------------------------------------------------------------------------------------------------------+ -| Notable advantages| * Huge community | * Scene System | -| | * Large assets store | * :ref:`Animation Pipeline ` | -| | | * :ref:`Easy to write Shaders ` | -| | | * Debug on Device | -| | | | -| | | | -+-------------------+------------------------------------------------------------------------------------+----------------------------------------------------------------------------------------------------------------+ - - -The editor ----------- - -Godot Engine provides a rich-featured editor that allows you to build your games. -The pictures below display the default layouts of both editors with colored blocks to indicate common functionalities. - -.. image:: img/unity-gui-overlay.png -.. image:: img/godot-gui-overlay.png - -While both editors may seem similar, there are many differences below the surface. -Both let you organize the project using the filesystem, -but Godot's approach is simpler with a single configuration file, minimalist text format, -and no metadata. This makes Godot more friendly to VCS systems, such as Git, Subversion, or Mercurial. - -Godot's Scene panel is similar to Unity's Hierarchy panel but, as each node has a specific function, -the approach used by Godot is more visually descriptive. It's easier to understand -what a scene does at a glance. - -The Inspector in Godot is more minimal, it shows only properties. -Thanks to this, objects can expose more useful parameters to the user -without having to hide functionality in language APIs. As a plus, Godot allows animating any of those properties visually. -Changing colors, textures, enumerations, or even links to resources in real-time is possible without needing to write code. - -The Toolbar at the top of the screen is similar in both editors, offering control over project playback. -Projects in Godot run in a separate window, rather than inside the editor -(but the tree and objects can still be explored in the debugger window). - -This approach has several advantages: - -- Running the project and closing it is fast (Unity has to save, run the project, close the project, and then reload the previous state). -- Live editing is a lot more useful because changes done to the editor take effect immediately in the game and are not lost (nor have to be synced) when the game is closed. This allows fantastic workflows, like creating levels while you play them. -- The editor is more stable because the game runs in a separate process. -- The running game can be explored from different angles by toggling the "Camera Override" button in the editor viewport, which will switch between using the editor view and the game camera view. - -Finally, Godot's top toolbar includes a menu for remote debugging. -These options allow deployment to a device (connected phone, tablet, or browser via HTML5), -and debugging/live editing on it after the game is exported. - -The scene system ----------------- - -This is the most important difference between Unity and Godot and the favorite feature of most Godot users. - -Working on a 'level' in Unity usually means embedding all the required assets in a scene -and linking them together with components and scripts. - -Godot's scene system is superficially similar to Unity. A 'level' consists of a collection of nodes, each with its own purpose: Sprite, Mesh, Light, etc. However, in Godot the nodes are arranged in a tree. Each node can have multiple children, which makes each a subscene of the main scene. -This means you can compose a whole scene with different scenes stored in different files. - -For example, think of a platformer level. You would compose it with multiple elements: - -- Bricks -- Coins -- The player -- The enemies - -In Unity, you would put all the GameObjects in the scene: the player, multiple instances of enemies, -bricks everywhere to form the ground of the level and then multiple instances of coins all over the level. -You would then add various components to each element to link them and add logic in the level: For example, -you'd add a BoxCollider2D to all the elements of the scene so that they can collide. This principle is different in Godot. - -In Godot, you would split your whole scene into three separate, smaller scenes, and instance them in the main scene. - -1. **A scene for the Player alone.** - -Consider the player as an element we'd like to use in different parent scenes (for instance 'level' scenes). In our case, the player element needs at least an AnimatedSprite node. This node contains the sprite textures necessary for various animations (for example, a walking animation). - -2. **A scene for the Enemy.** - -An enemy is also an element we'd like to use in several scenes. It's almost the same -as the Player node. The only differences are the script (it needs 'AI' routines to generate the enemy's behavior) -and the sprite textures used by the AnimatedSprite node. - -3. **A Level scene.** - -A Level scene is composed of Bricks (for platforms), Coins (for the player to collect) and a -number of instances of the Enemy scene. Each instance is a node in the Level scene tree. These instances are separate enemies, -which initially have shared behavior and appearance as defined in the Enemy scene. You can set different properties for each Enemy node (to change its color, for example). - -4. **A Main scene.** -The Main scene would be composed of one root node with 2 children: a Player instance node, and a Level instance node. -The root node can be anything, generally a "root" type such as "Node" which is the most global type, -or "Node2D" (root type of all 2D-related nodes), "Spatial" (root type of all 3D-related nodes) or -"Control" (root type of all GUI-related nodes). - -As you can see, every scene is organized as a tree. The same goes for nodes' properties: you don't *add* a -collision component to a node to make it collidable like Unity does. Instead, you make this node a *child* of a -new specific node that has collision properties. Godot features various collision types nodes, depending on the usage -(see the :ref:`Physics introduction `). - -- What are the advantages of this system? Wouldn't this system potentially increase the depth of the scene tree? And doesn't Unity already allow you to organize GameObjects by putting them inside empty GameObjects? - - - Godot's system is closer to the well-known object-oriented paradigm: Godot provides a number of nodes which are not clearly "Game Objects", but they provide their children with their own capabilities: this is inheritance. - - Godot allows the extraction of a subtree of a scene to make it a scene of its own. So if a scene tree gets too deep, it can be split into smaller subtrees. This is better for reusability, as you can include any subtree as a child of any node. Putting multiple GameObjects in an empty GameObject in Unity does not provide the same functionality. - -Project organization --------------------- - -.. image:: img/unity-project-organization-example.png - -There is no perfect project architecture. -Any architecture can be made to work in either Unity and Godot. - -However, a common architecture for Unity projects is to have one Assets folder in the root directory -that contains various folders, one per type of asset: Audio, Graphics, Models, Materials, Scripts, Scenes, and so on. - -Since Godot allows splitting scenes into smaller scenes, each scene and subscene existing as a file in the project, we recommend organizing your project a bit differently. -This wiki provides a page for this: :ref:`doc_project_organization`. - - -Where are my prefabs? ---------------------- - -A prefab as provided by Unity is a 'template' element of the scene. -It is reusable, and each instance of the prefab that exists in the scene has an existence of its own, -but all of them have the same properties as defined by the prefab. - -Godot does not provide prefabs as such, but the same functionality is provided by its scene system: -The scene system is organized as a tree. Godot allows you to save any subtree of a scene as a scene file. This new scene can then be instanced as many times as you want, as a child of any node. -Any change you make to this new, separate scene will be applied to its instances. -However, any change you make to the instance will not have any impact on the 'template' scene. - -.. image:: img/save-branch-as-scene.png - -To be precise, you can modify the parameters of an instance in the Inspector panel. -The nodes that compose this instance are initially locked. You can unlock them if you need to by -right-clicking the instance in the Scene tree and selecting "Editable children" in the menu. -You don't need to do this to add *new* child nodes to this node. -Remember that any new children will belong to the instance, not to the 'template' scene on disk. -If you want to add new children to every instance of your 'template' scene, then you should add them in the 'template' scene. - -.. image:: img/editable-children.png - -Glossary correspondence ------------------------ - -- GameObject -> Node -- Add a component -> Inheriting -- Prefab -> Reusable Scene file - - -Scripting: GDScript, C# and Visual Script ------------------------------------------ - -Design -^^^^^^ - -Unity supports C#. C# benefits from its integration with Visual Studio and has desirable features such as static typing. - -Godot provides its own scripting language, :ref:`GDScript ` as well as support -for :ref:`Visual Script ` and :ref:`C# `. -GDScript borrows its syntax from Python, but is not related to it. If you wonder about the reasoning for a custom scripting language, -please read the :ref:`doc_gdscript` and :ref:`doc_faq` pages. GDScript is strongly attached to the Godot API -and doesn't take long to learn: Between one evening for an experienced programmer and a week for a complete beginner. - -Unity allows you to attach as many scripts as you want to a GameObject. -Each script adds a behavior to the GameObject: For example, you can attach a script so that it reacts to the player's controls, -and another that controls its specific game logic. - -In Godot, you can only attach one script per node. You can use either an external GDScript file -or include the script directly in the node. If you need to attach more scripts to one node, then you may consider two solutions, -depending on your scene and on what you want to achieve: - -- either add a new node between your target node and its current parent, then add a script to this new node. -- or, you can split your target node into multiple children and attach one script to each of them. - -As you can see, it can be easy to turn a scene tree to a mess. Consider splitting any complicated scene into multiple, smaller branches. - -Connections: groups and signals -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -You can control nodes by accessing them via script and calling built-in -or user-defined functions on them. You can also place nodes in a group -and call functions on all nodes in this group. See more in the -:ref:`scripting documentation `. - -Nodes can send a signal when a specified action occurs. A signal can -be set to call any function. You can define custom signals and specify -when they are triggered. See more in the :ref:`signals documentation `. - -Script serialization -^^^^^^^^^^^^^^^^^^^^ - -Unity can handle script serialization in two ways: - -- Implicit: All public fields in a class are automatically serialized if the type is a serializable type (``Dictionary`` is not serializable). -- Explicit: Non-public fields can be serialized using the ``[SerializeField]`` attribute. - -Godot also has a built-in script serialization system, but it works only explicitly. -You can serialize any serializable type (:ref:`built-in and various engine types `, -including :ref:`class_Array` and :ref:`class_Dictionary`) using the ``export`` keyword. -See the :ref:`exports documentation ` for details. - -Unity also has a data type called ``ScriptableObject`` used to serialize custom asset objects. -Its equivalent in Godot is the base class for all resources: :ref:`class_Resource`. -Creating a script that inherits :ref:`class_Resource` will allow you to create custom serializable objects. More information about resources can be found :ref:`here `. - -Using Godot in C++ ------------------- - -Godot allows you to develop your project directly in C++ by using its API, which is not possible with Unity at the moment. -As an example, you can consider Godot Engine's editor as a "game" written in C++ using the Godot API. - -If you are interested in using Godot in C++, you may want to start reading the :ref:`Developing in -C++ ` page. diff --git a/getting_started/editor/using_the_web_editor.rst b/getting_started/editor/using_the_web_editor.rst deleted file mode 100644 index dd690143a..000000000 --- a/getting_started/editor/using_the_web_editor.rst +++ /dev/null @@ -1,124 +0,0 @@ -.. _doc_using_the_web_editor: - -Using the Web editor -==================== - -Since Godot 3.3, there is a `Web editor `__ -you can use to work on new or existing projects. - -.. note:: - - The web editor is in a preliminary stage. While its feature set may be - sufficient for educational purposes, it is currently **not recommended for - production work**. See :ref:`doc_using_the_web_editor_limitations` below. - -Browser support ---------------- - -The Web editor requires support for WebAssembly's SharedArrayBuffer. This -is in turn required to support threading in the browser. The following desktop -browsers support WebAssembly threading and can therefore run the web editor: - -- Chrome 68 or later -- Firefox 79 or later -- Edge 79 or later - -Opera and Safari are not supported yet. Safari may work in the future once -proper threading support is added. - -**Mobile browsers are currently not supported.** - -The web editor supports both the GLES3 and GLES2 renderers, although GLES2 is -recommended for better performance and compatibility with old/low-end hardware. - -.. note:: - - If you use Linux, due to - `poor Firefox WebGL performance `__, - it's recommended to use a Chromium-based browser instead of Firefox. - -.. _doc_using_the_web_editor_limitations: - -Limitations ------------ - -Due to limitations on the Godot or Web platform side, the following features -are currently missing: - -- No C#/Mono support. -- No GDNative support. -- No debugging support. This means GDScript debugging/profiling, live scene - editing, the Remote Scene tree dock and other features that rely on the debugger - protocol will not work. -- No project exporting. As a workaround, you can download the project source - using **Project > Tools > Download Project Source** and export it using a - `native version of the Godot editor `__. -- The editor won't warn you when closing the tab with unsaved changes. -- No lightmap baking support. You can still use existing lightmaps if they were - baked with a native version of the Godot editor - (e.g. by importing an existing project). - -The following features are unlikely to be supported due to inherent limitations -of the Web platform: - -- No support for external script editors. -- No support for Android one-click deploy. - -.. seealso:: - - See the - `list of open issues on GitHub related to the web editor `__ for a list of known bugs. - -Importing a project -------------------- - -To import an existing project, the current process is as follows: - -- Specify a ZIP file to preload on the HTML5 filesystem using the - **Preload project ZIP** input. -- Run the editor by clicking **Start Godot editor**. - The Godot project manager should appear after 10-20 seconds. - On slower machines or connections, loading may take up to a minute. -- In the dialog that appears at the middle of the window, specify a name for - the folder to create then click the **Create Folder** button - (it doesn't have to match the ZIP archive's name). -- Click **Install & Edit** and the project will open in the editor. - -.. attention:: - - It's important to place the project folder somewhere in ``/home/web_user/``. - If your project folder is placed outside ``/home/web_user/``, you will - lose your project when closing the editor! - - When you follow the steps described above, the project folder will always be - located in ``/home/web_user/projects``, keeping it safe. - -Editing and running a project ------------------------------ - -Unlike the native version of Godot, the web editor is constrained to a single -window. Therefore, it cannot open a new window when running the project. -Instead, when you run the project by clicking the Run button or pressing -:kbd:`F5`, it will appear to "replace" the editor window. - -The web editor offers an alternative way to deal with the editor and game -windows (which are now "tabs"). You can switch between the **Editor** and -**Game** tabs using the buttons on the top. You can also close the running game -or editor by clicking the **×** button next to those tabs. - -Where are my project files? ---------------------------- - -Due to browser security limitations, the editor will save the project files to -the browser's IndexedDB storage. This storage isn't accessible as a regular folder -on your machine, but is abstracted away in a database. - -You can download the project files as a ZIP archive by using -**Project > Tools > Download Project Source**. This can be used to export the -project using a `native Godot editor `__, -since exporting from the web editor isn't supported yet. - -In the future, it may be possible to use the -`HTML5 FileSystem API `__ -to store the project files on the user's filesystem as the native editor would do. -However, this isn't implemented yet. diff --git a/getting_started/first_2d_game/01.project_setup.rst b/getting_started/first_2d_game/01.project_setup.rst new file mode 100644 index 000000000..84a88bf6c --- /dev/null +++ b/getting_started/first_2d_game/01.project_setup.rst @@ -0,0 +1,77 @@ +.. _doc_your_first_2d_game_project_setup: + +Setting up the project +====================== + +In this short first part, we'll set up and organize the project. + +Launch Godot and create a new project. + +.. image:: img/new-project-button.png + +.. tabs:: + .. tab:: GDScript + + Download :download:`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 `. + 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 + `. + 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 `_ + and place them in your project. + +Your project folder should look like this. + +.. image:: img/folder-content.png + +This game is designed for portrait mode, so we need to adjust the size of the +game window. Click on *Project -> Project Settings* to open the project settings +window and in the left column, open the *Display -> Window* tab. There, set +"Width" to ``480`` and "Height" to ``720``. + +.. image:: img/setting-project-width-and-height.png + +Also, scroll down to the bottom of the section and, under the "Stretch" options, +set ``Mode`` to "2d" and ``Aspect`` to "keep". This ensures that the game scales +consistently on different sized screens. + +.. image:: img/setting-stretch-mode.png + +Organizing the project +~~~~~~~~~~~~~~~~~~~~~~ + +In this project, we will make 3 independent scenes: ``Player``, ``Mob``, and +``HUD``, which we will combine into the game's ``Main`` scene. + +In a larger project, it might be useful to create folders to hold the various +scenes and their scripts, but for this relatively small game, you can save your +scenes and scripts in the project's root folder, identified by ``res://``. You +can see your project folders in the FileSystem dock in the lower left corner: + +.. image:: img/filesystem_dock.png + +With the project in place, we're ready to design the player scene in the next lesson. diff --git a/getting_started/first_2d_game/02.player_scene.rst b/getting_started/first_2d_game/02.player_scene.rst new file mode 100644 index 000000000..d965bbb58 --- /dev/null +++ b/getting_started/first_2d_game/02.player_scene.rst @@ -0,0 +1,100 @@ +.. _doc_your_first_2d_game_player_scene: + +Creating the player scene +========================= + +With the project settings in place, we can start working on the +player-controlled character. + +The first scene will define the ``Player`` object. One of the benefits of +creating a separate Player scene is that we can test it separately, even before +we've created other parts of the game. + +Node structure +~~~~~~~~~~~~~~ + +To begin, we need to choose a root node for the player object. As a general +rule, a scene's root node should reflect the object's desired functionality - +what the object *is*. Click the "Other Node" button and add an :ref:`Area2D +` node to the scene. + +.. image:: img/add_node.png + +Godot will display a warning icon next to the node in the scene tree. You can +ignore it for now. We will address it later. + +With ``Area2D`` we can detect objects that overlap or run into the player. +Change the node's name to ``Player`` by double-clicking on it. Now that we've +set the scene's root node, we can add additional nodes to give it more +functionality. + +Before we add any children to the ``Player`` node, we want to make sure we don't +accidentally move or resize them by clicking on them. Select the node and click +the icon to the right of the lock; its tooltip says "Makes sure the object's +children are not selectable." + +.. image:: img/lock_children.png + +Save the scene. Click Scene -> Save, or press :kbd:`Ctrl + S` on Windows/Linux +or :kbd:`Cmd + S` on macOS. + +.. note:: For this project, we will be following the Godot naming conventions. + + - **GDScript**: Classes (nodes) use PascalCase, variables and + functions use snake_case, and constants use ALL_CAPS (See + :ref:`doc_gdscript_styleguide`). + + - **C#**: Classes, export variables and methods use PascalCase, + private fields use _camelCase, local variables and parameters use + camelCase (See :ref:`doc_c_sharp_styleguide`). Be careful to type + the method names precisely when connecting signals. + + +Sprite animation +~~~~~~~~~~~~~~~~ + +Click on the ``Player`` node and add an :ref:`AnimatedSprite2D +` node as a child. The ``AnimatedSprite2D`` will handle the +appearance and animations for our player. Notice that there is a warning symbol +next to the node. An ``AnimatedSprite2D`` requires a :ref:`SpriteFrames +` resource, which is a list of the animations it can +display. To create one, find the ``Frames`` property in the Inspector and click +"[empty]" -> "New SpriteFrames". Click again to open the "SpriteFrames" panel: + +.. image:: img/spriteframes_panel.png + + +On the left is a list of animations. Click the "default" one and rename it to +"walk". Then click the "New Animation" button to create a second animation named +"up". Find the player images in the "FileSystem" tab - they're in the ``art`` +folder you unzipped earlier. Drag the two images for each animation, named +``playerGrey_up[1/2]`` and ``playerGrey_walk[1/2]``, into the "Animation Frames" +side of the panel for the corresponding animation: + +.. image:: img/spriteframes_panel2.png + +The player images are a bit too large for the game window, so we need to scale +them down. Click on the ``AnimatedSprite2D`` node and set the ``Scale`` property +to ``(0.5, 0.5)``. You can find it in the Inspector under the ``Node2D`` +heading. + +.. image:: img/player_scale.png + +Finally, add a :ref:`CollisionShape2D ` as a child of +``Player``. This will determine the player's "hitbox", or the bounds of its +collision area. For this character, a ``CapsuleShape2D`` node gives the best +fit, so next to "Shape" in the Inspector, click "[empty]"" -> "New +CapsuleShape2D". Using the two size handles, resize the shape to cover the +sprite: + +.. image:: img/player_coll_shape.png + +When you're finished, your ``Player`` scene should look like this: + +.. image:: img/player_scene_nodes.png + +Make sure to save the scene again after these changes. + +In the next part, we'll add a script to the player node to move and animate it. +Then, we'll set up collision detection to know when the player got hit by +something. diff --git a/getting_started/first_2d_game/03.coding_the_player.rst b/getting_started/first_2d_game/03.coding_the_player.rst new file mode 100644 index 000000000..0d97de5a1 --- /dev/null +++ b/getting_started/first_2d_game/03.coding_the_player.rst @@ -0,0 +1,515 @@ +.. _doc_your_first_2d_game_coding_the_player: + +Coding the player +================= + +In this lesson, we'll add player movement, animation, and set it up to detect +collisions. + +To do so, we need to add some functionality that we can't get from a built-in +node, so we'll add a script. Click the ``Player`` node and click the "Attach +Script" button: + +.. image:: img/add_script_button.png + +In the script settings window, you can leave the default settings alone. Just +click "Create": + +.. note:: If you're creating a C# script or other languages, select the language + from the `language` drop down menu before hitting create. + +.. image:: img/attach_node_window.png + +.. note:: If this is your first time encountering GDScript, please read + :ref:`doc_scripting` before continuing. + +Start by declaring the member variables this object will need: + +.. tabs:: + .. code-tab:: 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 + #include + #include + #include + #include + + class Player : public godot::Area2D { + GODOT_CLASS(Player, godot::Area2D) + + godot::AnimatedSprite2D *_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 +able to adjust just like a node's built-in properties. Click on the ``Player`` +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("AnimatedSprite2D"); + _collision_shape = get_node("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 +game, which we expect will change often. For the player, we need to do the +following: + +- Check for input. +- Move in the given direction. +- Play the appropriate animation. + +First, we need to check for input - is the player pressing a key? For this game, +we have 4 direction inputs to check. Input actions are defined in the Project +Settings under "Input Map". Here, you can define custom events and assign +different keys, mouse events, or other inputs to them. For this game, we will +just use the default events called "ui_right" etc that are assigned to the arrow +keys on the keyboard. + +You can detect whether a key is pressed using ``Input.is_action_pressed()``, +which returns ``true`` if it's pressed or ``false`` if it isn't. + +.. tabs:: + .. code-tab:: gdscript GDScript + + func _process(delta): + var velocity = Vector2.ZERO # The player's movement vector. + if Input.is_action_pressed("ui_right"): + velocity.x += 1 + if Input.is_action_pressed("ui_left"): + velocity.x -= 1 + if Input.is_action_pressed("ui_down"): + velocity.y += 1 + if Input.is_action_pressed("ui_up"): + velocity.y -= 1 + + if velocity.length() > 0: + velocity = velocity.normalized() * speed + $AnimatedSprite2D.play() + else: + $AnimatedSprite2D.stop() + + .. code-tab:: csharp + + public override void _Process(float delta) + { + var velocity = Vector2.Zero; // The player's movement vector. + + 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("AnimatedSprite2D"); + + 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 +``velocity`` to obtain a total direction. For example, if you hold ``right`` and +``down`` at the same time, the resulting ``velocity`` vector will be ``(1, 1)``. +In this case, since we're adding a horizontal and a vertical movement, the +player would move *faster* diagonally than if it just moved horizontally. + +We can prevent that if we *normalize* the velocity, which means we set its +*length* to ``1``, then multiply by the desired speed. This means no more fast +diagonal movement. + +.. tip:: If you've never used vector math before, or need a refresher, you can + see an explanation of vector usage in Godot at :ref:`doc_vector_math`. + It's good to know but won't be necessary for the rest of this tutorial. + +We also check whether the player is moving so we can call ``play()`` or +``stop()`` on the AnimatedSprite2D. + + ``$`` is shorthand for ``get_node()``. So in the code above, + ``$AnimatedSprite2D.play()`` is the same as + ``get_node("AnimatedSprite2D").play()``. + +.. tip:: In GDScript, ``$`` returns the node at the relative path from the + current node, or returns ``null`` if the node is not found. Since + AnimatedSprite2D is a child of the current node, we can use + ``$AnimatedSprite2D``. + +Now that we have a movement direction, we can update the player's position. We +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 + + 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. + Using this value ensures that your movement will remain consistent even + if the frame rate changes. + +Click "Play Scene" (:kbd:`F6`, :kbd:`Cmd + R` on macOS) and confirm you can move +the player around the screen in all directions. + +.. warning:: If you get an error in the "Debugger" panel that says + + ``Attempt to call function 'play' in base 'null instance' on a null + instance`` + + this likely means you spelled the name of the AnimatedSprite2D node + wrong. Node names are case-sensitive and ``$NodeName`` must match + the name you see in the scene tree. + +Choosing animations +~~~~~~~~~~~~~~~~~~~ + +Now that the player can move, we need to change which animation the +AnimatedSprite2D is playing based on its direction. We have the "walk" animation, +which shows the player walking to the right. This animation should be flipped +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 + + if velocity.x != 0: + $AnimatedSprite2D.animation = "walk" + $AnimatedSprite2D.flip_v = false + # See the note below about boolean assignment. + $AnimatedSprite2D.flip_h = velocity.x < 0 + elif velocity.y != 0: + $AnimatedSprite2D.animation = "up" + $AnimatedSprite2D.flip_v = velocity.y > 0 + + .. code-tab:: csharp + + if (velocity.x != 0) + { + animatedSprite2D.Animation = "walk"; + animatedSprite2D.FlipV = false; + // See the note below about boolean assignment. + animatedSprite2D.FlipH = velocity.x < 0; + } + else if (velocity.y != 0) + { + animatedSprite2D.Animation = "up"; + animatedSprite2D.FlipV = velocity.y > 0; + } + + .. code-tab:: cpp + + if (velocity.x != 0) { + _animated_sprite->set_animation("right"); + _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: + $AnimatedSprite2D.flip_h = true + else: + $AnimatedSprite2D.flip_h = false + + .. code-tab:: csharp + + if (velocity.x < 0) + { + animatedSprite2D.FlipH = true; + } + else + { + animatedSprite2D.FlipH = false; + } + +Play the scene again and check that the animations are correct in each of the +directions. + +.. tip:: A common mistake here is to type the names of the animations wrong. The + animation names in the SpriteFrames panel must match what you type in + the code. If you named the animation ``"Walk"``, you must also use a + capital "W" in the code. + +When you're sure the movement is working correctly, add this line to +``_ready()``, so the player will be hidden when the game starts: + +.. tabs:: + .. code-tab:: gdscript GDScript + + hide() + + .. code-tab:: csharp + + Hide(); + + .. code-tab:: cpp + + hide(); + +Preparing for collisions +~~~~~~~~~~~~~~~~~~~~~~~~ + +We want ``Player`` to detect when it's hit by an enemy, but we haven't made any +enemies yet! That's OK, because we're going to use Godot's *signal* +functionality to make it work. + +Add the following at the top of the script, after ``extends Area2D``: + +.. tabs:: + .. code-tab:: 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("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 +collision. Select the ``Player`` node and click the "Node" tab next to the +Inspector tab to see the list of signals the player can emit: + +.. image:: img/player_signals.png + +Notice our custom "hit" signal is there as well! Since our enemies are going to +be ``RigidBody2D`` nodes, we want the ``body_entered(body: Node)`` signal. This +signal will be emitted when a body contacts the player. Click "Connect.." and +the "Connect a Signal" window appears. We don't need to change any of these +settings so click "Connect" again. Godot will automatically create a function in +your player's script. + +.. image:: img/player_signal_connection.png + +Note the green icon indicating that a signal is connected to this function. Add +this code to the function: + +.. tabs:: + .. code-tab:: gdscript GDScript + + func _on_Player_body_entered(body): + hide() # Player disappears after being hit. + emit_signal("hit") + # 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").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 +more than once. + +.. Note:: Disabling the area's collision shape can cause an error if it happens + in the middle of the engine's collision processing. Using + ``set_deferred()`` tells Godot to wait to disable the shape until it's + safe to do so. + +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 + + func start(pos): + position = pos + show() + $CollisionShape2D.disabled = false + + .. code-tab:: csharp + + public void Start(Vector2 pos) + { + Position = pos; + Show(); + GetNode("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. diff --git a/getting_started/first_2d_game/04.creating_the_enemy.rst b/getting_started/first_2d_game/04.creating_the_enemy.rst new file mode 100644 index 000000000..21e13279c --- /dev/null +++ b/getting_started/first_2d_game/04.creating_the_enemy.rst @@ -0,0 +1,180 @@ +.. _doc_your_first_2d_game_creating_the_enemy: + +Creating the enemy +================== + +Now it's time to make the enemies our player will have to dodge. Their behavior +will not be very complex: mobs will spawn randomly at the edges of the screen, +choose a random direction, and move in a straight line. + +We'll create a ``Mob`` scene, which we can then *instance* to create any number +of independent mobs in the game. + +Node setup +~~~~~~~~~~ + +Click Scene -> New Scene and add the following nodes: + +- :ref:`RigidBody2D ` (named ``Mob``) + + - :ref:`AnimatedSprite2D ` + - :ref:`CollisionShape2D ` + - :ref:`VisibilityNotifier2D ` + +Don't forget to set the children so they can't be selected, like you did with +the Player scene. + +In the :ref:`RigidBody2D ` properties, set ``Gravity Scale`` +to ``0``, so the mob will not fall downward. In addition, under the +``PhysicsBody2D`` section, click the ``Mask`` property and uncheck the first +box. This will ensure the mobs do not collide with each other. + +.. image:: img/set_collision_mask.png + +Set up the :ref:`AnimatedSprite2D ` like you did for the +player. This time, we have 3 animations: ``fly``, ``swim``, and ``walk``. There +are two images for each animation in the art folder. + +Adjust the "Speed (FPS)" to ``3`` for all animations. + +.. image:: img/mob_animations.gif + +Set the ``Playing`` property in the Inspector to "On". + +We'll select one of these animations randomly so that the mobs will have some +variety. + +Like the player images, these mob images need to be scaled down. Set the +``AnimatedSprite2D``'s ``Scale`` property to ``(0.75, 0.75)``. + +As in the ``Player`` scene, add a ``CapsuleShape2D`` for the collision. To align +the shape with the image, you'll need to set the ``Rotation Degrees`` property +to ``90`` (under "Transform" in the Inspector). + +Save the scene. + +Enemy script +~~~~~~~~~~~~ + +Add a script to the ``Mob`` like this: + +.. tabs:: + .. code-tab:: 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 + #include + #include + + class Mob : public godot::RigidBody2D { + GODOT_CLASS(Mob, godot::RigidBody2D) + + godot::AnimatedSprite2D *_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 + + func _ready(): + $AnimatedSprite2D.playing = true + var mob_types = $AnimatedSprite2D.frames.get_animation_names() + $AnimatedSprite2D.animation = mob_types[randi() % mob_types.size()] + + .. code-tab:: csharp + + public override void _Ready() + { + var animSprite2D = GetNode("AnimatedSprite2D"); + animSprite2D.Playing = true; + string[] mobTypes = animSprite2D.Frames.GetAnimationNames(); + animSprite2D.Animation = mobTypes[GD.Randi() % mobTypes.Length]; + } + + .. code-tab:: cpp + + // This code goes in `mob.cpp`. + #include "mob.hpp" + + #include + #include + + void Mob::_ready() { + godot::Ref random = godot::RandomNumberGenerator::_new(); + random->randomize(); + _animated_sprite = get_node("AnimatedSprite2D"); + _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 AnimatedSprite2D's ``frames`` +property. This returns an Array containing all three animation names: ``["walk", +"swim", "fly"]``. + +We then need to pick a random number between ``0`` and ``2`` to select one of +these names from the list (array indices start at ``0``). ``randi() % n`` +selects a random integer between ``0`` and ``n-1``. + +.. note:: You must use ``randomize()`` if you want your sequence of "random" + numbers to be different every time you run the scene. We're going to + use ``randomize()`` in our ``Main`` scene, so we won't need it here. + +The last piece is to make the mobs delete themselves when they leave the screen. +Connect the ``screen_exited()`` signal of the ``VisibilityNotifier2D`` node and +add this code: + +.. tabs:: + .. code-tab:: 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. + +With the player and enemies ready, in the next part, we'll bring them together +in a new scene. We'll make enemies spawn randomly around the game board and move +forward, turning our project into a playable game. diff --git a/getting_started/first_2d_game/05.the_main_game_scene.rst b/getting_started/first_2d_game/05.the_main_game_scene.rst new file mode 100644 index 000000000..605b40425 --- /dev/null +++ b/getting_started/first_2d_game/05.the_main_game_scene.rst @@ -0,0 +1,446 @@ +.. _doc_your_first_2d_game_the_main_game_scene: + +The main game scene +=================== + +Now it's time to bring everything we did together into a playable game scene. + +Create a new scene and add a :ref:`Node ` named ``Main``. Ensure you +create a Node, **not** a Node2D. Click the "Instance" button and select your +saved ``Player.tscn``. + +.. image:: img/instance_scene.png + +Now, add the following nodes as children of ``Main``, and name them as shown +(values are in seconds): + +- :ref:`Timer ` (named ``MobTimer``) - to control how often mobs + spawn +- :ref:`Timer ` (named ``ScoreTimer``) - to increment the score + every second +- :ref:`Timer ` (named ``StartTimer``) - to give a delay before + starting +- :ref:`Position2D ` (named ``StartPosition``) - to indicate + the player's start position + +Set the ``Wait Time`` property of each of the ``Timer`` nodes as follows: + +- ``MobTimer``: ``0.5`` +- ``ScoreTimer``: ``1`` +- ``StartTimer``: ``2`` + +In addition, set the ``One Shot`` property of ``StartTimer`` to "On" and set +``Position`` of the ``StartPosition`` node to ``(240, 450)``. + +Spawning mobs +~~~~~~~~~~~~~ + +The Main node will be spawning new mobs, and we want them to appear at a random +location on the edge of the screen. Add a :ref:`Path2D ` node +named ``MobPath`` as a child of ``Main``. When you select ``Path2D``, you will +see some new buttons at the top of the editor: + +.. image:: img/path2d_buttons.png + +Select the middle one ("Add Point") and draw the path by clicking to add the +points at the corners shown. To have the points snap to the grid, make sure "Use +Grid Snap" and "Use Snap" are both selected. These options can be found to the +left of the "Lock" button, appearing as a magnet next to some dots and +intersecting lines, respectively. + +.. image:: img/grid_snap_button.png + +.. important:: Draw the path in *clockwise* order, or your mobs will spawn + pointing *outwards* instead of *inwards*! + +.. image:: img/draw_path2d.gif + +After placing point ``4`` in the image, click the "Close Curve" button and your +curve will be complete. + +Now that the path is defined, add a :ref:`PathFollow2D ` +node as a child of ``MobPath`` and name it ``MobSpawnLocation``. This node will +automatically rotate and follow the path as it moves, so we can use it to select +a random position and direction along the path. + +Your scene should look like this: + +.. image:: img/main_scene_nodes.png + +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 + + 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 + #include + #include + #include + #include + #include + #include + #include + + #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 _random; + + public: + godot::Ref 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 + + 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 + + #include "mob.hpp" + + void Main::_ready() { + _hud = get_node("HUD"); + _player = get_node("Player"); + _start_position = get_node("StartPosition"); + _mob_spawn_location = get_node("MobPath/MobSpawnLocation"); + _mob_timer = get_node("MobTimer"); + _score_timer = get_node("ScoreTimer"); + _start_timer = get_node("StartTimer"); + // Uncomment these after adding the nodes in the "Sound effects" section of "Finishing up". + //_music = get_node("Music"); + //_death_sound = get_node("DeathSound"); + _random = (godot::Ref)godot::RandomNumberGenerator::_new(); + _random->randomize(); + } + +Click the ``Main`` node and you will see the ``Mob Scene`` property in the Inspector +under "Script Variables". + +You can assign this property's value in two ways: + +- Drag ``Mob.tscn`` from the "FileSystem" panel and drop it in the ``Mob`` + property . +- Click the down arrow next to "[empty]" and choose "Load". Select ``Mob.tscn``. + +Next, select the ``Player`` node in the Scene dock, and access the Node dock on +the sidebar. Make sure to have the Signals tab selected in the Node dock. + +You should see a list of the signals for the ``Player`` node. Find and +double-click the ``hit`` signal in the list (or right-click it and select +"Connect..."). This will open the signal connection dialog. We want to make a +new function named ``game_over``, which will handle what needs to happen when a +game ends. Type "game_over" in the "Receiver Method" box at the bottom of the +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 + + func game_over(): + $ScoreTimer.stop() + $MobTimer.stop() + + func new_game(): + score = 0 + $Player.start($StartPosition.position) + $StartTimer.start() + + .. code-tab:: csharp + + public void GameOver() + { + GetNode("MobTimer").Stop(); + GetNode("ScoreTimer").Stop(); + } + + public void NewGame() + { + Score = 0; + + var player = GetNode("Player"); + var startPosition = GetNode("StartPosition"); + player.Start(startPosition.Position); + + GetNode("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 + + 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("MobTimer").Start(); + GetNode("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)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 +``PathFollow2D`` node will automatically rotate as it follows the path, so we +will use that to select the mob's direction as well as its position. +When we spawn a mob, we'll pick a random value between ``150.0`` and +``250.0`` for how fast each mob will move (it would be boring if they were +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 + + func _on_MobTimer_timeout(): + # Choose a random location on Path2D. + var mob_spawn_location = get_node("MobPath/MobSpawnLocation"); + mob_spawn_location.offset = randi() + + # Create a Mob instance and add it to the scene. + var mob = mob_scene.instance() + add_child(mob) + + # Set the mob's direction perpendicular to the path direction. + var direction = mob_spawn_location.rotation + PI / 2 + + # Set the mob's position to a random location. + mob.position = mob_spawn_location.position + + # Add some randomness to the direction. + direction += rand_range(-PI / 4, PI / 4) + mob.rotation = direction + + # Choose the velocity. + var velocity = Vector2(rand_range(150.0, 250.0), 0.0) + mob.linear_velocity = velocity.rotated(direction) + + .. code-tab:: csharp + + public void OnMobTimerTimeout() + { + // Note: Normally it is best to use explicit types rather than the `var` + // keyword. However, var is acceptable to use here because the types are + // obviously PathFollow2D and Mob, since they appear later on the line. + + // Choose a random location on Path2D. + var mobSpawnLocation = GetNode("MobPath/MobSpawnLocation"); + mobSpawnLocation.Offset = GD.Randi(); + + // Create a Mob instance and add it to the scene. + var mob = (Mob)MobScene.Instance(); + AddChild(mob); + + // Set the mob's direction perpendicular to the path direction. + float direction = mobSpawnLocation.Rotation + Mathf.Pi / 2; + + // Set the mob's position to a random location. + 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); + } + + .. code-tab:: cpp + + // This code goes in `main.cpp`. + void Main::_on_MobTimer_timeout() { + // Choose a random location on Path2D. + _mob_spawn_location->set_offset((real_t)_random->randi()); + + // Create a Mob instance and add it to the scene. + godot::Node *mob = mob_scene->instance(); + add_child(mob); + + // 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)); + } + +.. important:: Why ``PI``? In functions requiring angles, Godot uses *radians*, + not degrees. Pi represents a half turn in radians, about + ``3.1415`` (there is also ``TAU`` which is equal to ``2 * PI``). + If you're more comfortable working with degrees, you'll need to + use the ``deg2rad()`` and ``rad2deg()`` functions to convert + between the two. + +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 + + 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 +prompted. + +You should be able to move the player around, see mobs spawning, and see the +player disappear when hit by a mob. + +When you're sure everything is working, remove the call to ``new_game()`` from +``_ready()``. + +What's our game lacking? Some user interface. In the next lesson, we'll add a +title screen and display the player's score. diff --git a/getting_started/first_2d_game/06.heads_up_display.rst b/getting_started/first_2d_game/06.heads_up_display.rst new file mode 100644 index 000000000..87f850320 --- /dev/null +++ b/getting_started/first_2d_game/06.heads_up_display.rst @@ -0,0 +1,437 @@ +.. _doc_your_first_2d_game_heads_up_display: + +Heads up display +================ + +The final piece our game needs is a User Interface (UI) to display things like +score, a "game over" message, and a restart button. + +Create a new scene, and add a :ref:`CanvasLayer ` node named +``HUD``. "HUD" stands for "heads-up display", an informational display that +appears as an overlay on top of the game view. + +The :ref:`CanvasLayer ` node lets us draw our UI elements on +a layer above the rest of the game, so that the information it displays isn't +covered up by any game elements like the player or mobs. + +The HUD needs to display the following information: + +- Score, changed by ``ScoreTimer``. +- A message, such as "Game Over" or "Get Ready!" +- A "Start" button to begin the game. + +The basic node for UI elements is :ref:`Control `. To create our +UI, we'll use two types of :ref:`Control ` nodes: :ref:`Label +` and :ref:`Button `. + +Create the following as children of the ``HUD`` node: + +- :ref:`Label ` named ``ScoreLabel``. +- :ref:`Label ` named ``Message``. +- :ref:`Button ` named ``StartButton``. +- :ref:`Timer ` named ``MessageTimer``. + +Click on the ``ScoreLabel`` and type a number into the ``Text`` field in the +Inspector. The default font for ``Control`` nodes is small and doesn't scale +well. There is a font file included in the game assets called +"Xolonium-Regular.ttf". To use this font, do the following: + +1. Under "Custom Fonts", choose "New Font" + +.. image:: img/custom_font1.png + +2. Click on the "Font" you added, and under "Font/Data/0", + choose "Load" and select the "Xolonium-Regular.ttf" file. + +.. image:: img/custom_font2.png + +Once you've done this on the ``ScoreLabel``, you can click the down arrow next +to the Font property and choose "Copy", then "Paste" it in the same place +on the other two Control nodes. +Set "Custom Font Size" property of the ``ScoreLabel``. A setting of ``64`` works well. + +.. image:: img/custom_font3.png + +.. note:: **Anchors and Margins:** ``Control`` nodes have a position and size, + but they also have anchors and margins. Anchors define the origin - + the reference point for the edges of the node. Margins update + automatically when you move or resize a control node. They represent + the distance from the control node's edges to its anchor. + +Arrange the nodes as shown below. Click the "Layout" button to set a Control +node's layout: + +.. image:: img/ui_anchor.png + +You can drag the nodes to place them manually, or for more precise placement, +use the following settings: + +ScoreLabel +~~~~~~~~~~ + +- *Layout* : "Top Wide" +- *Text* : ``0`` +- *Align* : "Center" + +Message +~~~~~~~~~~~~ + +- *Layout* : "HCenter Wide" +- *Text* : ``Dodge the Creeps!`` +- *Align* : "Center" +- *Autowrap* : "On" + +StartButton +~~~~~~~~~~~ + +- *Text* : ``Start`` +- *Layout* : "Center Bottom" +- *Margin* : + + - Top: ``-200`` + - Bottom: ``-100`` + +On the ``MessageTimer``, set the ``Wait Time`` to ``2`` and set the ``One Shot`` +property to "On". + +Now add this script to ``HUD``: + +.. tabs:: + .. code-tab:: 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 + #include + #include + #include + #include + + 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 + + func show_message(text): + $Message.text = text + $Message.show() + $MessageTimer.start() + + .. code-tab:: csharp + + public void ShowMessage(string text) + { + var message = GetNode