From b4b74565a35d6f110e185f2217514fc1742f1534 Mon Sep 17 00:00:00 2001 From: Relintai Date: Fri, 26 Apr 2024 19:46:06 +0200 Subject: [PATCH] Cleanups. --- .../03_first_2d_game/02_player_scene.md | 21 +-- .../03_first_2d_game/03_coding_the_player.md | 137 ++++++++---------- .../03_first_2d_game/04_creating_the_enemy.md | 16 +- .../05_the_main_game_scene.md | 12 +- .../03_first_2d_game/06_heads_up_display.md | 18 +-- .../03_first_2d_game/07_finishing-up.md | 12 +- 02_tutorials/04_first_3d_game/00_intro.md | 6 +- .../04_first_3d_game/01_game_setup.md | 58 +++----- .../04_first_3d_game/02_player_input.md | 64 +++----- .../03_player_movement_code.md | 46 ++---- 02_tutorials/04_first_3d_game/04_mob_scene.md | 46 ++---- .../04_first_3d_game/05_spawning_mobs.md | 90 ++++-------- .../04_first_3d_game/06_jump_and_squash.md | 49 ++----- .../04_first_3d_game/07_killing_player.md | 31 ++-- .../04_first_3d_game/08_score_and_replay.md | 92 ++++-------- .../04_first_3d_game/09_adding_animations.md | 84 ++++------- .../04_first_3d_game/10_going_further.md | 6 +- 03_usage/01_math/01_vector_math.md | 59 +++----- 03_usage/01_math/02_interpolation.md | 12 +- .../01_math/03_random_number_generation.md | 32 ++-- .../01_math/04_matrices_and_transforms.md | 48 ++---- 03_usage/01_math/05_beziers_and_curves.md | 25 +--- 03_usage/01_math/06_vectors_advanced.md | 24 +-- 23 files changed, 344 insertions(+), 644 deletions(-) diff --git a/02_tutorials/03_first_2d_game/02_player_scene.md b/02_tutorials/03_first_2d_game/02_player_scene.md index 47e44ad..7cfb104 100644 --- a/02_tutorials/03_first_2d_game/02_player_scene.md +++ b/02_tutorials/03_first_2d_game/02_player_scene.md @@ -1,7 +1,6 @@ -Creating the player scene -========================= +# Creating the player scene With the project settings in place, we can start working on the player-controlled character. @@ -10,8 +9,7 @@ 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 -~~~~~~~~~~~~~~ +## 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 - @@ -39,20 +37,15 @@ 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 Pandemonium naming conventions. - - **GDScript**: Classes (nodes) use PascalCase, variables and - functions use snake_case, and constants use ALL_CAPS (See - `doc_gdscript_styleguide`). +For this project, we will be following the Pandemonium naming conventions. - - **C#**: Classes, export variables and methods use PascalCase, - private fields use _camelCase, local variables and parameters use - camelCase (See `doc_c_sharp_styleguide`). Be careful to type - the method names precisely when connecting signals. + - **GDScript**: Classes (nodes) use PascalCase, variables and + functions use snake_case, and constants use ALL_CAPS (See + `doc_gdscript_styleguide`). -Sprite animation -~~~~~~~~~~~~~~~~ +## Sprite animation Click on the `Player` node and add an `AnimatedSprite ( AnimatedSprite )` node as a child. The `AnimatedSprite` will handle the diff --git a/02_tutorials/03_first_2d_game/03_coding_the_player.md b/02_tutorials/03_first_2d_game/03_coding_the_player.md index 6c51b7c..ddf5f47 100644 --- a/02_tutorials/03_first_2d_game/03_coding_the_player.md +++ b/02_tutorials/03_first_2d_game/03_coding_the_player.md @@ -1,7 +1,6 @@ -Coding the player -================= +# Coding the player In this lesson, we'll add player movement, animation, and set it up to detect collisions. @@ -15,22 +14,19 @@ Script" button: 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. - ![](img/attach_node_window.png) Note: - If this is your first time encountering GDScript, please read - `doc_scripting` before continuing. + + - If this is your first time encountering GDScript, please read + `doc_scripting` before continuing. Start by declaring the member variables this object will need: gdscript GDScript ``` - extends Area2D +extends Area2D export var speed = 400 # How fast the player will move (pixels/sec). var screen_size # Size of the game window. @@ -49,8 +45,8 @@ 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: ``` - func _ready(): - screen_size = get_viewport_rect().size +func _ready(): + screen_size = get_viewport_rect().size ``` Now we can use the `process()` function to define what the player will do. @@ -94,31 +90,30 @@ Click the "Close" button to close the project settings. Note: - - We only mapped one key to each input action, but you can map multiple keys, - joystick buttons, or mouse buttons to the same input action. + - We only mapped one key to each input action, but you can map multiple keys, + joystick buttons, or mouse buttons to the same input action. 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. gdscript GDScript ``` - func _process(delta): - var velocity = Vector2.ZERO # The player's movement vector. - if Input.is_action_pressed("move_right"): - velocity.x += 1 - if Input.is_action_pressed("move_left"): - velocity.x -= 1 - if Input.is_action_pressed("move_down"): - velocity.y += 1 - if Input.is_action_pressed("move_up"): - velocity.y -= 1 +func _process(delta): + var velocity = Vector2.ZERO # The player's movement vector. + if Input.is_action_pressed("move_right"): + velocity.x += 1 + if Input.is_action_pressed("move_left"): + velocity.x -= 1 + if Input.is_action_pressed("move_down"): + velocity.y += 1 + if Input.is_action_pressed("move_up"): + velocity.y -= 1 - if velocity.length() > 0: - velocity = velocity.normalized() * speed - $AnimatedSprite.play() - else: - $AnimatedSprite.stop() + if velocity.length() > 0: + velocity = velocity.normalized() * speed + $AnimatedSprite.play() + else: + $AnimatedSprite.stop() ``` We start by setting the `velocity` to `(0, 0)` - by default, the player @@ -133,22 +128,21 @@ We can prevent that if we *normalize* the velocity, which means we set its diagonal movement. Tip: - If you've never used vector math before, or need a refresher, you can - see an explanation of vector usage in Pandemonium at `doc_vector_math`. - It's good to know but won't be necessary for the rest of this tutorial. + + - If you've never used vector math before, or need a refresher, you can + see an explanation of vector usage in Pandemonium at `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 AnimatedSprite. Tip: - `$` is shorthand for `get_node()`. So in the code above, - `$AnimatedSprite.play()` is the same as - `get_node("AnimatedSprite").play()`. - In GDScript, `$` returns the node at the relative path from the - current node, or returns `null` if the node is not found. Since - AnimatedSprite is a child of the current node, we can use - `$AnimatedSprite`. + - `$` is shorthand for `get_node()`. So in the code above, `$AnimatedSprite.play()` is the same as + `get_node("AnimatedSprite").play()`. + + - In GDScript, `$` returns the node at the relative path from the current node, or returns `null` if the node is not found. Since + AnimatedSprite is a child of the current node, we can use`$AnimatedSprite`. 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 @@ -158,32 +152,29 @@ the `process` function (make sure it's not indented under the `else`): gdscript GDScript ``` - position += velocity * delta - position.x = clamp(position.x, 0, screen_size.x) - position.y = clamp(position.y, 0, screen_size.y) + position += velocity * delta + position.x = clamp(position.x, 0, screen_size.x) + position.y = clamp(position.y, 0, screen_size.y) ``` 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. + + - 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 evenif 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` +If you get an error in the "Debugger" panel that says - this likely means you spelled the name of the AnimatedSprite node - wrong. Node names are case-sensitive and `$NodeName` must match - the name you see in the scene tree. + - `Attempt to call function 'play' in base 'null instance' on a null instance` -Choosing animations -~~~~~~~~~~~~~~~~~~~ + - this likely means you spelled the name of the AnimatedSprite 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 AnimatedSprite is playing based on its direction. We have the "walk" animation, @@ -195,30 +186,27 @@ movement. Let's place this code at the end of the `process()` function: gdscript GDScript ``` - if velocity.x != 0: - $AnimatedSprite.animation = "walk" - $AnimatedSprite.flip_v = false - # See the note below about boolean assignment. - $AnimatedSprite.flip_h = velocity.x < 0 - elif velocity.y != 0: - $AnimatedSprite.animation = "up" - $AnimatedSprite.flip_v = velocity.y > 0 + if velocity.x != 0: + $AnimatedSprite.animation = "walk" + $AnimatedSprite.flip_v = false + # See the note below about boolean assignment. + $AnimatedSprite.flip_h = velocity.x < 0 + elif velocity.y != 0: + $AnimatedSprite.animation = "up" + $AnimatedSprite.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: - ``` + - 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: - if velocity.x < 0: - $AnimatedSprite.flip_h = true - else: - $AnimatedSprite.flip_h = false - - ``` +``` + if velocity.x < 0: + $AnimatedSprite.flip_h = true + else: + $AnimatedSprite.flip_h = false +``` Play the scene again and check that the animations are correct in each of the directions. @@ -238,8 +226,7 @@ gdscript GDScript hide() ``` -Preparing for collisions -~~~~~~~~~~~~~~~~~~~~~~~~ +## 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 Pandemonium's *signal* diff --git a/02_tutorials/03_first_2d_game/04_creating_the_enemy.md b/02_tutorials/03_first_2d_game/04_creating_the_enemy.md index 22bbf0e..86da7fd 100644 --- a/02_tutorials/03_first_2d_game/04_creating_the_enemy.md +++ b/02_tutorials/03_first_2d_game/04_creating_the_enemy.md @@ -1,7 +1,6 @@ -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, @@ -10,16 +9,14 @@ 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 -~~~~~~~~~~ +## Node setup Click Scene -> New Scene and add the following nodes: - `RigidBody2D` (named `Mob`) - - - `AnimatedSprite` - - `CollisionShape2D` - - `VisibilityNotifier2D` +- `AnimatedSprite` +- `CollisionShape2D` +- `VisibilityNotifier2D` Don't forget to set the children so they can't be selected, like you did with the Player scene. @@ -53,8 +50,7 @@ to `90` (under "Transform" in the Inspector). Save the scene. -Enemy script -~~~~~~~~~~~~ +## Enemy script Add a script to the `Mob` like this: diff --git a/02_tutorials/03_first_2d_game/05_the_main_game_scene.md b/02_tutorials/03_first_2d_game/05_the_main_game_scene.md index 4dc6ef5..4bd3ac5 100644 --- a/02_tutorials/03_first_2d_game/05_the_main_game_scene.md +++ b/02_tutorials/03_first_2d_game/05_the_main_game_scene.md @@ -1,7 +1,6 @@ -The main game scene -=================== +# The main game scene Now it's time to bring everything we did together into a playable game scene. @@ -35,8 +34,7 @@ Set the `Wait Time` property of each of the `Timer` nodes as follows: In addition, set the `One Shot` property of `StartTimer` to "On" and set `Position` of the `StartPosition` node to `(240, 450)`. -Spawning mobs -~~~~~~~~~~~~~ +## 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 `Path2D` node @@ -70,8 +68,7 @@ Your scene should look like this: ![](img/main_scene_nodes.png) -Main script -~~~~~~~~~~~ +## 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. @@ -190,8 +187,7 @@ gdscript GDScript use the `deg2rad()` and `rad2deg()` functions to convert between the two. -Testing the scene -~~~~~~~~~~~~~~~~~ +## Testing the scene Let's test the scene to make sure everything is working. Add this `new_game` call to `ready()`: diff --git a/02_tutorials/03_first_2d_game/06_heads_up_display.md b/02_tutorials/03_first_2d_game/06_heads_up_display.md index 43b699a..04ff43c 100644 --- a/02_tutorials/03_first_2d_game/06_heads_up_display.md +++ b/02_tutorials/03_first_2d_game/06_heads_up_display.md @@ -1,7 +1,6 @@ -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. @@ -68,23 +67,20 @@ node's layout: You can drag the nodes to place them manually, or for more precise placement, use the following settings: -ScoreLabel -~~~~~~~~~~ +## ScoreLabel - *Layout* : "Top Wide" - *Text* : `0` - *Align* : "Center" -Message -~~~~~~~~~~~~ +## Message - *Layout* : "HCenter Wide" - *Text* : `Dodge the Creeps!` - *Align* : "Center" - *Autowrap* : "On" -StartButton -~~~~~~~~~~~ +## StartButton - *Text* : `Start` - *Layout* : "Center Bottom" @@ -169,8 +165,7 @@ gdscript GDScript $Message.hide() ``` -Connecting HUD to Main -~~~~~~~~~~~~~~~~~~~~~~ +## Connecting HUD to Main Now that we're done creating the `HUD` scene, go back to `Main`. Instance the `HUD` scene in `Main` like you did the `Player` scene. The scene tree @@ -215,8 +210,7 @@ gdscript GDScript 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`. -Removing old creeps -~~~~~~~~~~~~~~~~~~~ +## Removing old creeps If you play until "Game Over" and then start a new game right away, the creeps from the previous game may still be on the screen. It would be better if they diff --git a/02_tutorials/03_first_2d_game/07_finishing-up.md b/02_tutorials/03_first_2d_game/07_finishing-up.md index 0afa035..a511040 100644 --- a/02_tutorials/03_first_2d_game/07_finishing-up.md +++ b/02_tutorials/03_first_2d_game/07_finishing-up.md @@ -1,15 +1,13 @@ -Finishing up -============ +# Finishing up We have now completed all the functionality for our game. Below are some remaining steps to add a bit more "juice" to improve the game experience. Feel free to expand the gameplay with your own ideas. -Background -~~~~~~~~~~ +## Background The default gray background is not very appealing, so let's change its color. One way to do this is to use a `ColorRect` node. Make it @@ -20,8 +18,7 @@ select "Layout" -> "Full Rect" so that it covers the screen. You could also add a background image, if you have one, by using a `TextureRect` node instead. -Sound effects -~~~~~~~~~~~~~ +## Sound effects Sound and music can be the single most effective way to add appeal to the game experience. In your game assets folder, you have two sound files: "House In a @@ -38,8 +35,7 @@ To play the music, add `$Music.play()` in the `new_game()` function and Finally, add `$DeathSound.play()` in the `game_over()` function. -Keyboard shortcut -~~~~~~~~~~~~~~~~~ +## Keyboard shortcut Since the game is played with keyboard controls, it would be convenient if we could also start the game by pressing a key on the keyboard. We can do this with diff --git a/02_tutorials/04_first_3d_game/00_intro.md b/02_tutorials/04_first_3d_game/00_intro.md index c46e2f7..2bb53f4 100644 --- a/02_tutorials/04_first_3d_game/00_intro.md +++ b/02_tutorials/04_first_3d_game/00_intro.md @@ -1,7 +1,6 @@ -Your first 3D game -================== +# Your first 3D game In this step-by-step tutorial series, you will create your first complete 3D game with Pandemonium. By the end of the series, you will have a simple yet finished @@ -50,8 +49,7 @@ add the monsters that we'll spawn randomly around the screen. After that, we'll implement the jump and squashing mechanic before refining the game with some nice animation. We'll wrap up with the score and the retry screen. -Contents --------- +## Contents .. toctree:: :maxdepth: 1 diff --git a/02_tutorials/04_first_3d_game/01_game_setup.md b/02_tutorials/04_first_3d_game/01_game_setup.md index 0a6dc8b..e85f417 100644 --- a/02_tutorials/04_first_3d_game/01_game_setup.md +++ b/02_tutorials/04_first_3d_game/01_game_setup.md @@ -1,7 +1,6 @@ -Setting up the game area -======================== +# Setting up the game area In this first part, we're going to set up the game area. Let's get started by importing the start assets and setting up the game scene. @@ -14,36 +13,35 @@ the archive here: `Squash the Creeps assets Once you downloaded it, extract the .zip archive on your computer. Open the Pandemonium project manager and click the *Import* button. -|image1| +![](img/01.game_setup/01.import_button.png) In the import popup, enter the full path to the freshly created directory `squash_the_creeps_start/`. You can click the *Browse* button on the right to open a file browser and navigate to the `project.pandemonium` file the folder contains. -|image2| +![](img/01.game_setup/02.browse_to_project_folder.png) Click *Import & Edit* to open the project in the editor. -|image3| +![](img/01.game_setup/03.import_and_edit.png) The start project contains an icon and two folders: `art/` and `fonts/`. There, you will find the art assets and music we'll use in the game. -|image4| +![](img/01.game_setup/04.start_assets.png) There are two 3D models, `player.glb` and `mob.glb`, some materials that belong to these models, and a music track. -Setting up the playable area ----------------------------- +## Setting up the playable area We're going to create our main scene with a plain *Node* as its root. In the *Scene* dock, click the *Add Node* button represented by a "+" icon in the top-left and double-click on *Node*. Name the node "Main". Alternatively, to add a node to the scene, you can press :kbd:`Ctrl + a` (or :kbd:`Cmd + a` on macOS). -|image5| +![](img/01.game_setup/05.main_node.png) Save the scene as `Main.tscn` by pressing :kbd:`Ctrl + s` (:kbd:`Cmd + s` on macOS). @@ -53,18 +51,18 @@ create static colliders like the floor, walls, or ceilings, you can use define the collision area. With the *Main* node selected, add a *StaticBody* node, then a *CollisionShape*. Rename the *StaticBody* as *Ground*. -|image6| +![](img/01.game_setup/06.staticbody_node.png) A warning sign next to the *CollisionShape* appears because we haven't defined its shape. If you click the icon, a popup appears to give you more information. -|image7| +![](img/01.game_setup/07.collision_shape_warning.png) To create a shape, with the *CollisionShape* selected, head to the *Inspector* and click the *[empty]* field next to the *Shape* property. Create a new *Box Shape*. -|image8| +![](img/01.game_setup/08.create_box_shape.png) The box shape is perfect for flat ground and walls. Its thickness makes it reliable to block even fast-moving objects. @@ -75,7 +73,7 @@ set the size in the inspector. Click on the *BoxShape* to expand the resource. Set its *Extents* to `30` on the X axis, `1` for the Y axis, and `30` for the Z axis. -|image9| +![](img/01.game_setup/09.box_extents.png) Note: @@ -88,19 +86,19 @@ Note: Collision shapes are invisible. We need to add a visual floor that goes along with it. Select the *Ground* node and add a *MeshInstance* as its child. -|image10| +![](img/01.game_setup/10.mesh_instance.png) In the *Inspector*, click on the field next to *Mesh* and create a *CubeMesh* resource to create a visible cube. -|image11| +![](img/01.game_setup/11.cube_mesh.png) Once again, it's too small by default. Click the cube icon to expand the resource and set its *Size* to `60`, `2`, and `60`. As the cube resource works with a size rather than extents, we need to use these values so it matches our collision shape. -|image12| +![](img/01.game_setup/12.cube_resized.png) You should see a wide grey slab that covers the grid and blue and red axes in the viewport. @@ -109,7 +107,7 @@ We're going to move the ground down so we can see the floor grid. Select the *Ground* node, hold the :kbd:`Ctrl` key down to turn on grid snapping (:kbd:`Cmd` on macOS), and click and drag down on the Y axis. It's the green arrow in the move gizmo. -|image13| +![](img/01.game_setup/13.move_gizmo_y_axis.png) Note: @@ -117,12 +115,12 @@ Note: If you can't see the 3D object manipulator like on the image above, ensure the *Select Mode* is active in the toolbar above the view. -|image14| +![](img/01.game_setup/14.select_mode_icon.png) Move the ground down `1` meter. A label in the bottom-left corner of the viewport tells you how much you're translating the node. -|image15| +![](img/01.game_setup/15.translation_amount.png) Note: @@ -139,29 +137,13 @@ ground is lit. In the *Inspector*, turn on *Shadow -> Enabled* by clicking the checkbox. -|image16| +![](img/01.game_setup/16.turn_on_shadows.png) At this point, your project should look like this. -|image17| +![](img/01.game_setup/17.project_with_light.png) That's our starting point. In the next part, we will work on the player scene and base movement. -.. |image1| image:: img/01.game_setup/01.import_button.png) -.. |image2| image:: img/01.game_setup/02.browse_to_project_folder.png) -.. |image3| image:: img/01.game_setup/03.import_and_edit.png) -.. |image4| image:: img/01.game_setup/04.start_assets.png) -.. |image5| image:: img/01.game_setup/05.main_node.png) -.. |image6| image:: img/01.game_setup/06.staticbody_node.png) -.. |image7| image:: img/01.game_setup/07.collision_shape_warning.png) -.. |image8| image:: img/01.game_setup/08.create_box_shape.png) -.. |image9| image:: img/01.game_setup/09.box_extents.png) -.. |image10| image:: img/01.game_setup/10.mesh_instance.png) -.. |image11| image:: img/01.game_setup/11.cube_mesh.png) -.. |image12| image:: img/01.game_setup/12.cube_resized.png) -.. |image13| image:: img/01.game_setup/13.move_gizmo_y_axis.png) -.. |image14| image:: img/01.game_setup/14.select_mode_icon.png) -.. |image15| image:: img/01.game_setup/15.translation_amount.png) -.. |image16| image:: img/01.game_setup/16.turn_on_shadows.png) -.. |image17| image:: img/01.game_setup/17.project_with_light.png) + diff --git a/02_tutorials/04_first_3d_game/02_player_input.md b/02_tutorials/04_first_3d_game/02_player_input.md index 3cda507..9eb421b 100644 --- a/02_tutorials/04_first_3d_game/02_player_input.md +++ b/02_tutorials/04_first_3d_game/02_player_input.md @@ -1,7 +1,6 @@ -Player scene and input actions -============================== +# Player scene and input actions In the next two lessons, we will design the player scene, register custom input actions, and code player movement. By the end, you'll have a playable character @@ -13,7 +12,7 @@ that moves in eight directions. Create a new scene by going to the Scene menu in the top-left and clicking *New Scene*. Create a *KinematicBody* node as the root and name it *Player*. -|image0| +![](img/02.player_input/01.new_scene.png) Kinematic bodies are complementary to the area and rigid bodies used in the 2D game tutorial. Like rigid bodies, they can move and collide with the @@ -34,16 +33,15 @@ Add a *Spatial* node as a child of *Player* and name it *Pivot*. Then, in the FileSystem dock, expand the `art/` folder by double-clicking it and drag and drop `player.glb` onto the *Pivot* node. -|image1| +![](img/02.player_input/02.instantiating_the_model.png) This should instantiate the model as a child of *Pivot*. You can rename it to *Character*. -|image2| +![](img/02.player_input/03.scene_structure.png) Note: - The `.glb` files contain 3D scene data based on the open-source GLTF 2.0 specification. They're a modern and powerful alternative to a proprietary format like FBX, which Pandemonium also supports. To produce these files, we designed the @@ -54,7 +52,7 @@ to collide with the environment. Select the *Player* node again and add a *CollisionShape*. In the *Inspector*, assign a *SphereShape* to the *Shape* property. The sphere's wireframe appears below the character. -|image3| +![](img/02.player_input/04.sphere_shape.png) It will be the shape the physics engine uses to collide with the environment, so we want it to better fit the 3D model. Shrink it a bit by dragging the orange @@ -62,20 +60,19 @@ dot in the viewport. My sphere has a radius of about `0.8` meters. Then, move the shape up so its bottom roughly aligns with the grid's plane. -|image4| +![](img/02.player_input/05.moving_the_sphere_up.png) You can toggle the model's visibility by clicking the eye icon next to the *Character* or the *Pivot* nodes. -|image5| +![](img/02.player_input/06.toggling_visibility.png) Save the scene as `Player.tscn`. With the nodes ready, we can almost get coding. But first, we need to define some input actions. -Creating input actions ----------------------- +## Creating input actions To move the character, we will listen to the player's input, like pressing the arrow keys. In Pandemonium, while we could write all the key bindings in code, there's @@ -85,13 +82,13 @@ buttons. This simplifies our scripts and makes them more readable. This system is the Input Map. To access its editor, head to the *Project* menu and select *Project Settings…*. -|image6| +![](img/02.player_input/07.project_settings.png) At the top, there are multiple tabs. Click on *Input Map*. This window allows you to add new actions at the top; they are your labels. In the bottom part, you can bind keys to these actions. -|image7| +![](img/02.player_input/07.input_map_tab.png) Pandemonium projects come with some predefined actions designed for user interface design, which we could use here. But we're defining our own to support gamepads. @@ -101,30 +98,30 @@ We're going to name our actions `move_left`, `move_right`, `move_forward`, To add an action, write its name in the bar at the top and press Enter. -|image8| +![](img/02.player_input/07.adding_action.png) Create the five actions. Your window should have them all listed at the bottom. -|image9| +![](img/02.player_input/08.actions_list_empty.png) To bind a key or button to an action, click the "+" button to its right. Do this for `move_left` and in the drop-down menu, click *Key*. -|image10| +![](img/02.player_input/08.create_key_action.png) This option allows you to add a keyboard input. A popup appears and waits for you to press a key. Press the left arrow key and click *OK*. -|image11| +![](img/02.player_input/09.keyboard_key_popup.png) Do the same for the A key. -|image12| +![](img/02.player_input/09.keyboard_keys.png) Let's now add support for a gamepad's left joystick. Click the "+" button again but this time, select *Joy Axis*. -|image13| +![](img/02.player_input/10.joy_axis_option.png) The popup gives you two drop-down menus. On the left, you can select a gamepad by index. *Device 0* corresponds to the first plugged gamepad, *Device 1* @@ -132,48 +129,29 @@ corresponds to the second, and so on. You can select the joystick and direction you want to bind to the input action on the right. Leave the default values and press the *Add* button. -|image14| +![](img/02.player_input/11.joy_axis_popup.png) Do the same for the other input actions. For example, bind the right arrow, D, and the left joystick's right axis to `move_right`. After binding all keys, your interface should look like this. -|image15| +![](img/02.player_input/12.move_inputs_mapped.png) We have the `jump` action left to set up. Bind the Space key and the gamepad's A button. To bind a gamepad's button, select the *Joy Button* option in the menu. -|image16| +![](img/02.player_input/13.joy_button_option.png) Leave the default values and click the *Add* button. -|image17| +![](img/02.player_input/14.add_jump_button.png) Your jump input action should look like this. -|image18| +![](img/02.player_input/14.jump_input_action.png) That's all the actions we need for this game. You can use this menu to label any groups of keys and buttons in your projects. In the next part, we'll code and test the player's movement. -.. |image0| image:: img/02.player_input/01.new_scene.png) -.. |image1| image:: img/02.player_input/02.instantiating_the_model.png) -.. |image2| image:: img/02.player_input/03.scene_structure.png) -.. |image3| image:: img/02.player_input/04.sphere_shape.png) -.. |image4| image:: img/02.player_input/05.moving_the_sphere_up.png) -.. |image5| image:: img/02.player_input/06.toggling_visibility.png) -.. |image6| image:: img/02.player_input/07.project_settings.png) -.. |image7| image:: img/02.player_input/07.input_map_tab.png) -.. |image8| image:: img/02.player_input/07.adding_action.png) -.. |image9| image:: img/02.player_input/08.actions_list_empty.png) -.. |image10| image:: img/02.player_input/08.create_key_action.png) -.. |image11| image:: img/02.player_input/09.keyboard_key_popup.png) -.. |image12| image:: img/02.player_input/09.keyboard_keys.png) -.. |image13| image:: img/02.player_input/10.joy_axis_option.png) -.. |image14| image:: img/02.player_input/11.joy_axis_popup.png) -.. |image15| image:: img/02.player_input/12.move_inputs_mapped.png) -.. |image16| image:: img/02.player_input/13.joy_button_option.png) -.. |image17| image:: img/02.player_input/14.add_jump_button.png) -.. |image18| image:: img/02.player_input/14.jump_input_action.png) diff --git a/02_tutorials/04_first_3d_game/03_player_movement_code.md b/02_tutorials/04_first_3d_game/03_player_movement_code.md index 4ff97a8..7cf0197 100644 --- a/02_tutorials/04_first_3d_game/03_player_movement_code.md +++ b/02_tutorials/04_first_3d_game/03_player_movement_code.md @@ -1,7 +1,6 @@ -Moving the player with code -=========================== +# Moving the player with code It's time to code! We're going to use the input actions we created in the last part to move the character. @@ -10,7 +9,7 @@ Right-click the *Player* node and select *Attach Script* to add a new script to it. In the popup, set the *Template* to *Empty* before pressing the *Create* button. -|image0| +![](img/03.player_movement_code/01.attach_script_to_player.png) 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 @@ -19,7 +18,7 @@ character. gdscript GDScript ``` - extends KinematicBody + extends KinematicBody # How fast the player moves in meters per second. export var speed = 14 @@ -36,7 +35,6 @@ we want to update and reuse its value across frames. Note: - The values are quite different from 2D code because distances are in meters. While in 2D, a thousand units (pixels) may only correspond to half of your screen's width, in 3D, it's a kilometer. @@ -200,8 +198,7 @@ gdscript GDScript velocity = move_and_slide(velocity, Vector3.UP) ``` -Testing our player's movement ------------------------------ +## Testing our player's movement We're going to put our player in the *Main* scene to test it. To do so, we need to instantiate the player and then add a camera. Unlike in 2D, in 3D, you won't @@ -210,7 +207,7 @@ see anything if your viewport doesn't have a camera pointing at something. Save your *Player* scene and open the *Main* scene. You can click on the *Main* tab at the top of the editor to do so. -|image1| +![](img/03.player_movement_code/02.clicking_main_tab.png) If you closed the scene before, head to the *FileSystem* dock and double-click `Main.tscn` to re-open it. @@ -218,25 +215,24 @@ If you closed the scene before, head to the *FileSystem* dock and double-click To instantiate the *Player*, right-click on the *Main* node and select *Instance Child Scene*. -|image2| +![](img/03.player_movement_code/03.instance_child_scene.png) In the popup, double-click *Player.tscn*. The character should appear in the center of the viewport. -Adding a camera -~~~~~~~~~~~~~~~ +## Adding a camera Let's add the camera next. Like we did with our *Player*\ 's *Pivot*, we're going to create a basic rig. Right-click on the *Main* node again and select *Add Child Node* this time. Create a new *Position3D*, name it *CameraPivot*, and add a *Camera* node as a child of it. Your scene tree should look like this. -|image3| +![](img/03.player_movement_code/04.scene_tree_with_camera.png) Notice the *Preview* checkbox that appears in the top-left when you have the *Camera* selected. You can click it to preview the in-game camera projection. -|image4| +![](img/03.player_movement_code/05.camera_preview_checkbox.png) We're going to use the *Pivot* to rotate the camera as if it was on a crane. Let's first split the 3D view to be able to freely navigate the scene and see @@ -245,28 +241,28 @@ what the camera sees. In the toolbar right above the viewport, click on *View*, then *2 Viewports*. You can also press :kbd:`Ctrl + 2` (:kbd:`Cmd + 2` on macOS). -|image5| +![](img/03.player_movement_code/06.two_viewports.png) On the bottom view, select the *Camera* and turn on camera preview by clicking the checkbox. -|image6| +![](img/03.player_movement_code/07.camera_preview_checkbox.png) In the top view, move the camera about `19` units on the Z axis (the blue one). -|image7| +![](img/03.player_movement_code/08.camera_moved.png) Here's where the magic happens. Select the *CameraPivot* and rotate it `45` degrees around the X axis (using the red circle). You'll see the camera move as if it was attached to a crane. -|image8| +![](img/03.player_movement_code/09.camera_rotated.png) You can run the scene by pressing :kbd:`F6` and press the arrow keys to move the character. -|image9| +![](img/03.player_movement_code/10.camera_perspective.png) We can see some empty space around the character due to the perspective projection. In this game, we're going to use an orthographic projection instead @@ -277,19 +273,7 @@ Select the *Camera* again and in the *Inspector*, set the *Projection* to *Orthogonal* and the *Size* to `19`. The character should now look flatter and the ground should fill the background. -|image10| +![](img/03.player_movement_code/11.camera_orthographic.png) With that, we have both player movement and the view in place. Next, we will work on the monsters. - -.. |image0| image:: img/03.player_movement_code/01.attach_script_to_player.png) -.. |image1| image:: img/03.player_movement_code/02.clicking_main_tab.png) -.. |image2| image:: img/03.player_movement_code/03.instance_child_scene.png) -.. |image3| image:: img/03.player_movement_code/04.scene_tree_with_camera.png) -.. |image4| image:: img/03.player_movement_code/05.camera_preview_checkbox.png) -.. |image5| image:: img/03.player_movement_code/06.two_viewports.png) -.. |image6| image:: img/03.player_movement_code/07.camera_preview_checkbox.png) -.. |image7| image:: img/03.player_movement_code/08.camera_moved.png) -.. |image8| image:: img/03.player_movement_code/09.camera_rotated.png) -.. |image9| image:: img/03.player_movement_code/10.camera_perspective.png) -.. |image10| image:: img/03.player_movement_code/11.camera_orthographic.png) diff --git a/02_tutorials/04_first_3d_game/04_mob_scene.md b/02_tutorials/04_first_3d_game/04_mob_scene.md index f6f2faf..69d9fa9 100644 --- a/02_tutorials/04_first_3d_game/04_mob_scene.md +++ b/02_tutorials/04_first_3d_game/04_mob_scene.md @@ -1,7 +1,6 @@ -Designing the mob scene -======================= +# Designing the mob scene In this part, you're going to code the monsters, which we'll call mobs. In the next lesson, we'll spawn them randomly around the playable area. @@ -15,20 +14,20 @@ the file `mob.glb` from the *FileSystem* dock onto the *Pivot* to add the monster's 3D model to the scene. You can rename the newly created *mob* node into *Character*. -|image0| +![](img/04.mob_scene/01.initial_three_nodes.png) We need a collision shape for our body to work. Right-click on the *Mob* node, the scene's root, and click *Add Child Node*. -|image1| +![](img/04.mob_scene/02.add_child_node.png) Add a *CollisionShape*. -|image2| +![](img/04.mob_scene/03.scene_with_collision_shape.png) In the *Inspector*, assign a *BoxShape* to the *Shape* property. -|image3| +![](img/04.mob_scene/04.create_box_shape.png) We should change its size to fit the 3D model better. You can do so interactively by clicking and dragging on the orange dots. @@ -39,15 +38,14 @@ corner, a collision will occur. If the box is a little too big compared to the 3D model, you may die at a distance from the monster, and the game will feel unfair to the players. -|image4| +![](img/04.mob_scene/05.box_final_size.png) Notice that my box is taller than the monster. It is okay in this game because we're looking at the scene from above and using a fixed perspective. Collision shapes don't have to match the model exactly. It's the way the game feels when you test it that should dictate their form and size. -Removing monsters off-screen -~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +## Removing monsters off-screen We're going to spawn monsters at regular time intervals in the game level. If we're not careful, their count could increase to infinity, and we don't want @@ -76,14 +74,13 @@ Select the *Mob* node and add a *VisibilityNotifier* as a child of it. Another box, pink this time, appears. When this box completely leaves the screen, the node will emit a signal. -|image5| +![](img/04.mob_scene/06.visibility_notifier.png) Resize it using the orange dots until it covers the entire 3D model. -|image6| +![](img/04.mob_scene/07.visibility_notifier_bbox_resized.png) -Coding the mob's movement -------------------------- +## Coding the mob's movement Let's implement the monster's motion. We're going to do this in two steps. First, we'll write a script on the *Mob* that defines a function to initialize @@ -92,7 +89,7 @@ and call the function from there. Attach a script to the *Mob*. -|image7| +![](img/04.mob_scene/08.mob_attach_script.png) 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 @@ -171,8 +168,7 @@ gdscript GDScript velocity = velocity.rotated(Vector3.UP, rotation.y) ``` -Leaving the screen ------------------- +## Leaving the screen We still have to destroy the mobs when they leave the screen. To do so, we'll connect our *VisibilityNotifier* node's `screen_exited` signal to the *Mob*. @@ -180,16 +176,16 @@ connect our *VisibilityNotifier* node's `screen_exited` signal to the *Mob*. Head back to the 3D viewport by clicking on the *3D* label at the top of the editor. You can also press :kbd:`Ctrl + F2` (:kbd:`Alt + 2` on macOS). -|image8| +![](img/04.mob_scene/09.switch_to_3d_workspace.png) Select the *VisibilityNotifier* node and on the right side of the interface, navigate to the *Node* dock. Double-click the *screen_exited()* signal. -|image9| +![](img/04.mob_scene/10.node_dock.png) Connect the signal to the *Mob*. -|image10| +![](img/04.mob_scene/11.connect_signal.png) This will take you back to the script editor and add a new function for you, `on_VisibilityNotifier_screen_exited()`. From it, call the `queue_free()` @@ -236,15 +232,3 @@ gdscript GDScript func _on_VisibilityNotifier_screen_exited(): queue_free() ``` - -.. |image0| image:: img/04.mob_scene/01.initial_three_nodes.png) -.. |image1| image:: img/04.mob_scene/02.add_child_node.png) -.. |image2| image:: img/04.mob_scene/03.scene_with_collision_shape.png) -.. |image3| image:: img/04.mob_scene/04.create_box_shape.png) -.. |image4| image:: img/04.mob_scene/05.box_final_size.png) -.. |image5| image:: img/04.mob_scene/06.visibility_notifier.png) -.. |image6| image:: img/04.mob_scene/07.visibility_notifier_bbox_resized.png) -.. |image7| image:: img/04.mob_scene/08.mob_attach_script.png) -.. |image8| image:: img/04.mob_scene/09.switch_to_3d_workspace.png) -.. |image9| image:: img/04.mob_scene/10.node_dock.png) -.. |image10| image:: img/04.mob_scene/11.connect_signal.png) diff --git a/02_tutorials/04_first_3d_game/05_spawning_mobs.md b/02_tutorials/04_first_3d_game/05_spawning_mobs.md index 473be75..094b6a7 100644 --- a/02_tutorials/04_first_3d_game/05_spawning_mobs.md +++ b/02_tutorials/04_first_3d_game/05_spawning_mobs.md @@ -1,12 +1,11 @@ -Spawning monsters -================= +# Spawning monsters In this part, we're going to spawn monsters along a path randomly. By the end, you will have monsters roaming the game board. -|image0| +![](img/05.spawning_mobs/01.monsters_path_preview.png) Double-click on `Main.tscn` in the *FileSystem* dock to open the *Main* scene. @@ -16,15 +15,14 @@ nice little box. Go to *Project -> Project Settings*. -|image1| +![](img/05.spawning_mobs/02.project_settings.png) In the left menu, navigate down to *Display -> Window*. On the right, set the *Width* to `720` and the *Height* to `540`. -|image2| +![](img/05.spawning_mobs/03.window_settings.png) -Creating the spawn path ------------------------ +## Creating the spawn path Like you did in the 2D game tutorial, you're going to design a path and use a *PathFollow* node to sample random locations on it. @@ -39,41 +37,40 @@ that isn't the case, press :kbd:`Ctrl + 2` (:kbd:`Cmd + 2` on macOS) to split th Select the *Camera* node and click the *Preview* checkbox in the bottom viewport. -|image3| +![](img/05.spawning_mobs/04.camera_preview.png) -Adding placeholder cylinders -~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +## Adding placeholder cylinders Let's add the placeholder meshes. Add a new *Spatial* node as a child of the *Main* node and name it *Cylinders*. We'll use it to group the cylinders. As a child of it, add a *MeshInstance* node. -|image4| +![](img/05.spawning_mobs/05.cylinders_node.png) In the *Inspector*, assign a *CylinderMesh* to the *Mesh* property. -|image5| +![](img/05.spawning_mobs/06.cylinder_mesh.png) Set the top viewport to the top orthogonal view using the menu in the viewport's top-left corner. Alternatively, you can press the keypad's 7 key. -|image6| +![](img/05.spawning_mobs/07.top_view.png) The grid is a bit distracting for me. You can toggle it by going to the *View* menu in the toolbar and clicking *View Grid*. -|image7| +![](img/05.spawning_mobs/08.toggle_view_grid.png) You now want to move the cylinder along the ground plane, looking at the camera preview in the bottom viewport. I recommend using grid snap to do so. You can toggle it by clicking the magnet icon in the toolbar or pressing Y. -|image8| +![](img/05.spawning_mobs/09.toggle_grid_snap.png) Place the cylinder so it's right outside the camera's view in the top-left corner. -|image9| +![](img/05.spawning_mobs/10.place_first_cylinder.png) We're going to create copies of the mesh and place them around the game area. Press :kbd:`Ctrl + D` (:kbd:`Cmd + D` on macOS) to duplicate the node. You can also right-click @@ -83,11 +80,11 @@ the blue Z axis until it's right outside the camera's preview. Select both cylinders by pressing the :kbd:`Shift` key and clicking on the unselected one and duplicate them. -|image10| +![](img/05.spawning_mobs/11.both_cylinders_selected.png) Move them to the right by dragging the red X axis. -|image11| +![](img/05.spawning_mobs/12.four_cylinders.png) They're a bit hard to see in white, aren't they? Let's make them stand out by giving them a new material. @@ -99,12 +96,12 @@ We can update all four cylinders at once. Select all the mesh instances in the *Scene* dock. To do so, you can click on the first one and Shift click on the last one. -|image12| +![](img/05.spawning_mobs/13.selecting_all_cylinders.png) In the *Inspector*, expand the *Material* section and assign a *SpatialMaterial* to slot *0*. -|image13| +![](img/05.spawning_mobs/14.spatial_material.png) Click the sphere icon to open the material resource. You get a preview of the material and a long list of sections filled with properties. You can use these @@ -113,18 +110,18 @@ to create all sorts of surfaces, from metal to rock or water. Expand the *Albedo* section and set the color to something that contrasts with the background, like a bright orange. -|image14| +![](img/05.spawning_mobs/15.bright-cylinders.png) We can now use the cylinders as guides. Fold them in the *Scene* dock by clicking the grey arrow next to them. Moving forward, you can also toggle their visibility by clicking the eye icon next to *Cylinders*. -|image15| +![](img/05.spawning_mobs/16.cylinders_fold.png) Add a *Path* node as a child of *Main*. In the toolbar, four icons appear. Click the *Add Point* tool, the icon with the green "+" sign. -|image16| +![](img/05.spawning_mobs/17.points_options.png) Note: You can hover any icon to see a tooltip describing the tool. @@ -133,22 +130,21 @@ Click in the center of each cylinder to create a point. Then, click the *Close Curve* icon in the toolbar to close the path. If any point is a bit off, you can click and drag on it to reposition it. -|image17| +![](img/05.spawning_mobs/18.close_path.png) Your path should look like this. -|image18| +![](img/05.spawning_mobs/19.path_result.png) To sample random positions on it, we need a *PathFollow* node. Add a *PathFollow* as a child of the *Path*. Rename the two nodes to *SpawnPath* and *SpawnLocation*, respectively. It's more descriptive of what we'll use them for. -|image19| +![](img/05.spawning_mobs/20.spawn_nodes.png) With that, we're ready to code the spawn mechanism. -Spawning monsters randomly --------------------------- +## Spawning monsters randomly Right-click on the *Main* node and attach a new script to it. @@ -178,16 +174,16 @@ to the scene and add a timer. Before that, though, we need to assign the Head back to the 3D screen and select the *Main* node. Drag `Mob.tscn` from the *FileSystem* dock to the *Mob Scene* slot in the *Inspector*. -|image20| +![](img/05.spawning_mobs/20.mob_scene_property.png) Add a new *Timer* node as a child of *Main*. Name it *MobTimer*. -|image21| +![](img/05.spawning_mobs/21.mob_timer.png) In the *Inspector*, set its *Wait Time* to `0.5` seconds and turn on *Autostart* so it automatically starts when we run the game. -|image22| +![](img/05.spawning_mobs/22.mob_timer_properties.png) Timers emit a `timeout` signal every time they reach the end of their *Wait Time*. By default, they restart automatically, emitting the signal in a cycle. @@ -197,11 +193,11 @@ We can connect to this signal from the *Main* node to spawn monsters every With the *MobTimer* still selected, head to the *Node* dock on the right and double-click the `timeout` signal. -|image23| +![](img/05.spawning_mobs/23.timeout_signal.png) Connect it to the *Main* node. -|image24| +![](img/05.spawning_mobs/24.connect_timer_to_main.png) This will take you back to the script, with a new empty `on_MobTimer_timeout()` function. @@ -264,34 +260,8 @@ gdscript GDScript You can test the scene by pressing :kbd:`F6`. You should see the monsters spawn and move in a straight line. -|image25| +![](img/05.spawning_mobs/25.spawn_result.png) For now, they bump and slide against one another when their paths cross. We'll address this in the next part. -.. |image0| image:: img/05.spawning_mobs/01.monsters_path_preview.png) -.. |image1| image:: img/05.spawning_mobs/02.project_settings.png) -.. |image2| image:: img/05.spawning_mobs/03.window_settings.png) -.. |image3| image:: img/05.spawning_mobs/04.camera_preview.png) -.. |image4| image:: img/05.spawning_mobs/05.cylinders_node.png) -.. |image5| image:: img/05.spawning_mobs/06.cylinder_mesh.png) -.. |image6| image:: img/05.spawning_mobs/07.top_view.png) -.. |image7| image:: img/05.spawning_mobs/08.toggle_view_grid.png) -.. |image8| image:: img/05.spawning_mobs/09.toggle_grid_snap.png) -.. |image9| image:: img/05.spawning_mobs/10.place_first_cylinder.png) -.. |image10| image:: img/05.spawning_mobs/11.both_cylinders_selected.png) -.. |image11| image:: img/05.spawning_mobs/12.four_cylinders.png) -.. |image12| image:: img/05.spawning_mobs/13.selecting_all_cylinders.png) -.. |image13| image:: img/05.spawning_mobs/14.spatial_material.png) -.. |image14| image:: img/05.spawning_mobs/15.bright-cylinders.png) -.. |image15| image:: img/05.spawning_mobs/16.cylinders_fold.png) -.. |image16| image:: img/05.spawning_mobs/17.points_options.png) -.. |image17| image:: img/05.spawning_mobs/18.close_path.png) -.. |image18| image:: img/05.spawning_mobs/19.path_result.png) -.. |image19| image:: img/05.spawning_mobs/20.spawn_nodes.png) -.. |image20| image:: img/05.spawning_mobs/20.mob_scene_property.png) -.. |image21| image:: img/05.spawning_mobs/21.mob_timer.png) -.. |image22| image:: img/05.spawning_mobs/22.mob_timer_properties.png) -.. |image23| image:: img/05.spawning_mobs/23.timeout_signal.png) -.. |image24| image:: img/05.spawning_mobs/24.connect_timer_to_main.png) -.. |image25| image:: img/05.spawning_mobs/25.spawn_result.png) diff --git a/02_tutorials/04_first_3d_game/06_jump_and_squash.md b/02_tutorials/04_first_3d_game/06_jump_and_squash.md index 08d40f5..1ebc267 100644 --- a/02_tutorials/04_first_3d_game/06_jump_and_squash.md +++ b/02_tutorials/04_first_3d_game/06_jump_and_squash.md @@ -1,7 +1,6 @@ -Jumping and squashing monsters -============================== +# Jumping and squashing monsters In this part, we'll add the ability to jump, to squash the monsters. In the next lesson, we'll make the player die when a monster hits them on the ground. @@ -10,8 +9,7 @@ First, we have to change a few settings related to physics interactions. Enter the world of `physics layers ( doc_physics_introduction_collision_layers_and_masks )`. -Controlling physics interactions --------------------------------- +## Controlling physics interactions Physics bodies have access to two complementary properties: layers and masks. Layers define on which physics layer(s) an object is. @@ -32,36 +30,34 @@ This means they all collide with each other. Physics layers are represented by numbers, but we can give them names to keep track of what's what. -Setting layer names -~~~~~~~~~~~~~~~~~~~ +### Setting layer names Let's give our physics layers a name. Go to *Project -> Project Settings*. -|image0| +![](img/06.jump_and_squash/02.project_settings.png) In the left menu, navigate down to *Layer Names -> 3D Physics*. You can see a list of layers with a field next to each of them on the right. You can set their names there. Name the first three layers *player*, *enemies*, and *world*, respectively. -|image1| +![](img/06.jump_and_squash/03.physics_layers.png) Now, we can assign them to our physics nodes. -Assigning layers and masks -~~~~~~~~~~~~~~~~~~~~~~~~~~ +### Assigning layers and masks In the *Main* scene, select the *Ground* node. In the *Inspector*, expand the *Collision* section. There, you can see the node's layers and masks as a grid of buttons. -|image2| +![](img/06.jump_and_squash/04.default_physics_properties.png) The ground is part of the world, so we want it to be part of the third layer. Click the lit button to toggle off the first *Layer* and toggle on the third one. Then, toggle off the *Mask* by clicking on it. -|image3| +![](img/06.jump_and_squash/05.toggle_layer_and_mask.png) As I mentioned above, the *Mask* property allows a node to listen to interaction with other physics objects, but we don't need it to have collisions. The @@ -71,7 +67,7 @@ creatures from falling. Note that you can click the "..." button on the right side of the properties to see a list of named checkboxes. -|image4| +![](img/06.jump_and_squash/06.named_checkboxes.png) Next up are the *Player* and the *Mob*. Open `Player.tscn` by double-clicking the file in the *FileSystem* dock. @@ -80,7 +76,7 @@ Select the *Player* node and set its *Collision -> Mask* to both "enemies" and "world". You can leave the default *Layer* property as the first layer is the "player" one. -|image5| +![](img/06.jump_and_squash/07.player_physics_mask.png) Then, open the *Mob* scene by double-clicking on `Mob.tscn` and select the *Mob* node. @@ -88,7 +84,7 @@ Then, open the *Mob* scene by double-clicking on `Mob.tscn` and select the Set its *Collision -> Layer* to "enemies" and unset its *Collision -> Mask*, leaving the mask empty. -|image6| +![](img/06.jump_and_squash/08.mob_physics_mask.png) These settings mean the monsters will move through one another. If you want the monsters to collide with and slide against each other, turn on the "enemies" @@ -96,12 +92,10 @@ mask. Note: - The mobs don't need to mask the "world" layer because they only move on the XZ plane. We don't apply any gravity to them by design. -Jumping -------- +## Jumping The jumping mechanic itself requires only two lines of code. Open the *Player* script. We need a value to control the jump's strength and update @@ -149,8 +143,7 @@ great. Notice that the Y axis is positive upwards. That's unlike 2D, where the Y axis is positive downward. -Squashing monsters ------------------- +## Squashing monsters Let's add the squash mechanic next. We're going to make the character bounce over monsters and kill them at the same time. @@ -167,18 +160,17 @@ tags to nodes. Click on it to reveal a field where you can write a tag name. Enter "mob" in the field and click the *Add* button. -|image7| +![](img/06.jump_and_squash/09.groups_tab.png) An icon appears in the *Scene* dock to indicate the node is part of at least one group. -|image8| +![](img/06.jump_and_squash/10.group_scene_icon.png) We can now use the group from the code to distinguish collisions with monsters from collisions with the floor. -Coding the squash mechanic -~~~~~~~~~~~~~~~~~~~~~~~~~~ +### Coding the squash mechanic Head back to the *Player* script to code the squash and bounce. @@ -277,12 +269,3 @@ With that, you should be able to kill monsters by jumping on them. You can press However, the player won't die yet. We'll work on that in the next part. -.. |image0| image:: img/06.jump_and_squash/02.project_settings.png) -.. |image1| image:: img/06.jump_and_squash/03.physics_layers.png) -.. |image2| image:: img/06.jump_and_squash/04.default_physics_properties.png) -.. |image3| image:: img/06.jump_and_squash/05.toggle_layer_and_mask.png) -.. |image4| image:: img/06.jump_and_squash/06.named_checkboxes.png) -.. |image5| image:: img/06.jump_and_squash/07.player_physics_mask.png) -.. |image6| image:: img/06.jump_and_squash/08.mob_physics_mask.png) -.. |image7| image:: img/06.jump_and_squash/09.groups_tab.png) -.. |image8| image:: img/06.jump_and_squash/10.group_scene_icon.png) diff --git a/02_tutorials/04_first_3d_game/07_killing_player.md b/02_tutorials/04_first_3d_game/07_killing_player.md index dbd13df..10a52ac 100644 --- a/02_tutorials/04_first_3d_game/07_killing_player.md +++ b/02_tutorials/04_first_3d_game/07_killing_player.md @@ -1,7 +1,6 @@ -Killing the player -================== +# Killing the player We can kill enemies by jumping on them, but the player still can't die. Let's fix this. @@ -12,17 +11,16 @@ they're in the air. We could use vector math to distinguish the two kinds of collisions. Instead, though, we will use an *Area* node, which works well for hitboxes. -Hitbox with the Area node -------------------------- +## Hitbox with the Area node Head back to the *Player* scene and add a new *Area* node. Name it *MobDetector*. Add a *CollisionShape* node as a child of it. -|image0| +![](img/07.killing_player/01.adding_area_node.png) In the *Inspector*, assign a cylinder shape to it. -|image1| +![](img/07.killing_player/02.cylinder_shape.png) Here is a trick you can use to make the collisions only happen when the player is on the ground or close to it. You can reduce the cylinder's @@ -30,7 +28,7 @@ height and move it up to the top of the character. This way, when the player jumps, the shape will be too high up for the enemies to collide with it. -|image2| +![](img/07.killing_player/03.cylinder_in_editor.png) You also want the cylinder to be wider than the sphere. This way, the player gets hit before colliding and being pushed on top of the @@ -44,13 +42,13 @@ cannot detect the area. The complementary *Monitoring* property allows it to detect collisions. Then, remove the *Collision -> Layer* and set the mask to the "enemies" layer. -|image3| +![](img/07.killing_player/04.mob_detector_properties.png) When areas detect a collision, they emit signals. We're going to connect one to the *Player* node. In the *Node* tab, double-click the `body_entered` signal and connect it to the *Player*. -|image4| +![](img/07.killing_player/05.body_entered_signal.png) The *MobDetector* will emit `body_entered` when a *KinematicBody* or a *RigidBody* node enters it. As it only masks the "enemies" physics @@ -85,8 +83,7 @@ However, note that this depends entirely on the size and position of the *Player* and the *Mob*\ 's collision shapes. You may need to move them and resize them to achieve a tight game feel. -Ending the game ---------------- +## Ending the game We can use the *Player*\ 's `hit` signal to end the game. All we need to do is connect it to the *Main* node and stop the *MobTimer* in @@ -95,7 +92,7 @@ reaction. Open `Main.tscn`, select the *Player* node, and in the *Node* dock, connect its `hit` signal to the *Main* node. -|image5| +![](img/07.killing_player/06.player_hit_signal.png) Get and stop the timer in the `on_Player_hit()` function. @@ -116,8 +113,7 @@ From there, we'll add a score, the option to retry the game, and you'll see how you can make the game feel much more alive with minimalistic animations. -Code checkpoint ---------------- +## Code checkpoint Here are the complete scripts for the *Main*, *Mob*, and *Player* nodes, for reference. You can use them to compare and check your code. @@ -265,9 +261,4 @@ gdscript GDScript See you in the next lesson to add the score and the retry option. -.. |image0| image:: img/07.killing_player/01.adding_area_node.png) -.. |image1| image:: img/07.killing_player/02.cylinder_shape.png) -.. |image2| image:: img/07.killing_player/03.cylinder_in_editor.png) -.. |image3| image:: img/07.killing_player/04.mob_detector_properties.png) -.. |image4| image:: img/07.killing_player/05.body_entered_signal.png) -.. |image5| image:: img/07.killing_player/06.player_hit_signal.png) + diff --git a/02_tutorials/04_first_3d_game/08_score_and_replay.md b/02_tutorials/04_first_3d_game/08_score_and_replay.md index 1d1f882..5c33704 100644 --- a/02_tutorials/04_first_3d_game/08_score_and_replay.md +++ b/02_tutorials/04_first_3d_game/08_score_and_replay.md @@ -1,7 +1,6 @@ -Score and replay -================ +# Score and replay In this part, we'll add the score, music playback, and the ability to restart the game. @@ -15,11 +14,11 @@ edit your User Interface (UI). Add a *Label* node and rename it to *ScoreLabel*. -|image0| +![](img/08.score_and_replay/01.label_node.png) In the *Inspector*, set the *Label*'s *Text* to a placeholder like "Score: 0". -|image1| +![](img/08.score_and_replay/02.score_placeholder.png) Also, the text is white by default, like our game's background. We need to change its color to see it at runtime. @@ -27,51 +26,49 @@ change its color to see it at runtime. Scroll down to *Theme Overrides*, and expand *Colors* and click the black box next to *Font Color* to tint the text. -|image2| +![](img/08.score_and_replay/02.score_custom_color.png) Pick a dark tone so it contrasts well with the 3D scene. -|image3| +![](img/08.score_and_replay/02.score_color_picker.png) Finally, click and drag on the text in the viewport to move it away from the top-left corner. -|image4| +![](img/08.score_and_replay/02.score_label_moved.png) The *UserInterface* node allows us to group our UI in a branch of the scene tree and use a theme resource that will propagate to all its children. We'll use it to set our game's font. -Creating a UI theme -------------------- +## Creating a UI theme Once again, select the *UserInterface* node. In the *Inspector*, create a new theme resource in *Theme -> Theme*. -|image5| +![](img/08.score_and_replay/03.creating_theme.png) Click on it to open the theme editor In the bottom panel. It gives you a preview of how all the built-in UI widgets will look with your theme resource. -|image6| +![](img/08.score_and_replay/04.theme_preview.png) By default, a theme only has one property, the *Default Font*. See also: - You can add more properties to the theme resource to design complex user interfaces, but that is beyond the scope of this series. To learn more about creating and editing themes, see `doc_gui_skinning`. Click the *Default Font* property and create a new *DynamicFont*. -|image7| +![](img/08.score_and_replay/05.dynamic_font.png) Expand the *DynamicFont* by clicking on it and expand its *Font* section. There, you will see an empty *Font Data* field. -|image8| +![](img/08.score_and_replay/06.font_data.png) This one expects a font file like the ones you have on your computer. DynamicFont supports the following formats: @@ -88,10 +85,9 @@ The text will reappear in the theme preview. The text is a bit small. Set the *Settings -> Size* to `22` pixels to increase the text's size. -|image9| +![](img/08.score_and_replay/07.font_size.png) -Keeping track of the score --------------------------- +## Keeping track of the score Let's work on the score next. Attach a new script to the *ScoreLabel* and define the `score` variable. @@ -114,7 +110,7 @@ monster. Open the script `Main.gd`. If it's still open, you can click on its name in the script editor's left column. -|image10| +![](img/08.score_and_replay/08.open_main_script.png) Alternatively, you can double-click the `Main.gd` file in the *FileSystem* dock. @@ -154,13 +150,12 @@ function. See also: - You can learn more about string formatting here: `doc_gdscript_printf`. You can now play the game and squash a few enemies to see the score increase. -|image11| +![](img/08.score_and_replay/09.score_in_game.png) Note: @@ -171,8 +166,7 @@ Note: object. But when prototyping or when your project is simple, it is fine to keep your code simple. Programming is always a balancing act. -Retrying the game ------------------ +## Retrying the game We'll now add the ability to play again after dying. When the player dies, we'll display a message on the screen and wait for input. @@ -185,16 +179,16 @@ screen. To make it span over the whole viewport, you can use the *Layout* menu in the toolbar. -|image12| +![](img/08.score_and_replay/10.layout_icon.png) Open it and apply the *Full Rect* command. -|image13| +![](img/08.score_and_replay/11.full_rect_option.png) Nothing happens. Well, almost nothing: only the four green pins move to the corners of the selection box. -|image14| +![](img/08.score_and_replay/12.anchors_updated.png) This is because UI nodes (all the ones with a green icon) work with anchors and margins relative to their parent's bounding box. Here, the *UserInterface* node @@ -208,20 +202,19 @@ Let's change its color so it darkens the game area. Select *Retry* and in the in the color picker, drag the *A* slider to the left. It controls the color's alpha channel, that is to say, its opacity. -|image15| +![](img/08.score_and_replay/13.retry_color_picker.png) Next, add a *Label* as a child of *Retry* and give it the *Text* "Press Enter to retry." -|image16| +![](img/08.score_and_replay/14.retry_node.png) To move it and anchor it in the center of the screen, apply *Layout -> Center* to it. -|image17| +![](img/08.score_and_replay/15.layout_center.png) -Coding the retry option -~~~~~~~~~~~~~~~~~~~~~~~ +### Coding the retry option We can now head to the code to show and hide the *Retry* node when the player dies and plays again. @@ -268,8 +261,7 @@ The function `get_tree()` gives us access to the global `SceneTree ( SceneTree )` object, which allows us to reload and restart the current scene. -Adding music ------------- +## Adding music To add music that plays continuously in the background, we're going to use another feature in Pandemonium: `autoloads ( doc_singletons_autoload )`. @@ -285,19 +277,19 @@ use it to create globally accessible objects. Create a new scene by going to the *Scene* menu and clicking *New Scene*. -|image18| +![](img/08.score_and_replay/16.new_scene.png) Click the *Other Node* button to create an *AudioStreamPlayer* and rename it to *MusicPlayer*. -|image19| +![](img/08.score_and_replay/17.music_player_node.png) We included a music soundtrack in the `art/` directory, `House In a Forest Loop.ogg`. Click and drag it onto the *Stream* property in the *Inspector*. Also, turn on *Autoplay* so the music plays automatically at the start of the game. -|image20| +![](img/08.score_and_replay/18.music_node_properties.png) Save the scene as `MusicPlayer.tscn`. @@ -308,7 +300,7 @@ In the *Path* field, you want to enter the path to your scene. Click the folder icon to open the file browser and double-click on `MusicPlayer.tscn`. Then, click the *Add* button on the right to register the node. -|image21| +![](img/08.score_and_replay/19.register_autoload.png) If you run the game now, the music will play automatically. And even when you lose and retry, it keeps going. @@ -317,13 +309,13 @@ Before we wrap up this lesson, here's a quick look at how it works under the hood. When you run the game, your *Scene* dock changes to give you two tabs: *Remote* and *Local*. -|image22| +![](img/08.score_and_replay/20.scene_dock_tabs.png) The *Remote* tab allows you to visualize the node tree of your running game. There, you will see the *Main* node and everything the scene contains and the instantiated mobs at the bottom. -|image23| +![](img/08.score_and_replay/21.remote_scene_tree.png) At the top are the autoloaded *MusicPlayer* and a *root* node, which is your game's viewport. @@ -369,27 +361,3 @@ gdscript GDScript $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) -.. |image2| image:: img/08.score_and_replay/02.score_custom_color.png) -.. |image3| image:: img/08.score_and_replay/02.score_color_picker.png) -.. |image4| image:: img/08.score_and_replay/02.score_label_moved.png) -.. |image5| image:: img/08.score_and_replay/03.creating_theme.png) -.. |image6| image:: img/08.score_and_replay/04.theme_preview.png) -.. |image7| image:: img/08.score_and_replay/05.dynamic_font.png) -.. |image8| image:: img/08.score_and_replay/06.font_data.png) -.. |image9| image:: img/08.score_and_replay/07.font_size.png) -.. |image10| image:: img/08.score_and_replay/08.open_main_script.png) -.. |image11| image:: img/08.score_and_replay/09.score_in_game.png) -.. |image12| image:: img/08.score_and_replay/10.layout_icon.png) -.. |image13| image:: img/08.score_and_replay/11.full_rect_option.png) -.. |image14| image:: img/08.score_and_replay/12.anchors_updated.png) -.. |image15| image:: img/08.score_and_replay/13.retry_color_picker.png) -.. |image16| image:: img/08.score_and_replay/14.retry_node.png) -.. |image17| image:: img/08.score_and_replay/15.layout_center.png) -.. |image18| image:: img/08.score_and_replay/16.new_scene.png) -.. |image19| image:: img/08.score_and_replay/17.music_player_node.png) -.. |image20| image:: img/08.score_and_replay/18.music_node_properties.png) -.. |image21| image:: img/08.score_and_replay/19.register_autoload.png) -.. |image22| image:: img/08.score_and_replay/20.scene_dock_tabs.png) -.. |image23| image:: img/08.score_and_replay/21.remote_scene_tree.png) diff --git a/02_tutorials/04_first_3d_game/09_adding_animations.md b/02_tutorials/04_first_3d_game/09_adding_animations.md index 280bf0d..35bbf4e 100644 --- a/02_tutorials/04_first_3d_game/09_adding_animations.md +++ b/02_tutorials/04_first_3d_game/09_adding_animations.md @@ -1,18 +1,16 @@ -Character animation -=================== +# Character animation In this final lesson, we'll use Pandemonium's built-in animation tools to make our characters float and flap. You'll learn to design animations in the editor and use code to make your game feel alive. -|image0| +![](img/squash-the-creeps-final.gif) We'll start with an introduction to using the animation editor. -Using the animation editor --------------------------- +## Using the animation editor The engine comes with tools to author animations in the editor. You can then use the code to play and control them at runtime. @@ -21,7 +19,7 @@ Open the player scene, select the player node, and add an *AnimationPlayer* node The *Animation* dock appears in the bottom panel. -|image1| +![](img/09.adding_animations/01.animation_player_dock.png) It features a toolbar and the animation drop-down menu at the top, a track editor in the middle that's currently empty, and filter, snap, and zoom options @@ -29,16 +27,16 @@ at the bottom. Let's create an animation. Click on *Animation -> New*. -|image2| +![](img/09.adding_animations/02.new_animation.png) Name the animation "float". -|image3| +![](img/09.adding_animations/03.float_name.png) Once you created the animation, the timeline appears with numbers representing time in seconds. -|image4| +![](img/09.adding_animations/03.timeline.png) We want the animation to start playback automatically at the start of the game. Also, it should loop. @@ -46,30 +44,29 @@ Also, it should loop. To do so, you can click the button with an "A+" icon in the animation toolbar and the looping arrows, respectively. -|image5| +![](img/09.adding_animations/04.autoplay_and_loop.png) You can also pin the animation editor by clicking the pin icon in the top-right. This prevents it from folding when you click on the viewport and deselect the nodes. -|image6| +![](img/09.adding_animations/05.pin_icon.png) Set the animation duration to `1.2` seconds in the top-right of the dock. -|image7| +![](img/09.adding_animations/06.animation_duration.png) You should see the gray ribbon widen a bit. It shows you the start and end of your animation and the vertical blue line is your time cursor. -|image8| +![](img/09.adding_animations/07.editable_timeline.png) You can click and drag the slider in the bottom-right to zoom in and out of the timeline. -|image9| +![](img/09.adding_animations/08.zoom_slider.png) -The float animation -------------------- +## The float animation With the animation player node, you can animate most properties on as many nodes as you need. Notice the key icon next to properties in the *Inspector*. You can @@ -83,27 +80,27 @@ rotation of the *Character* node. Select the *Character* and click the key icon next to *Translation* in the *Inspector*. Do the same for *Rotation Degrees*. -|image10| +![](img/09.adding_animations/09.creating_first_keyframe.png) Two tracks appear in the editor with a diamond icon representing each keyframe. -|image11| +![](img/09.adding_animations/10.initial_keys.png) You can click and drag on the diamonds to move them in time. Move the translation key to `0.2` seconds and the rotation key to `0.1` seconds. -|image12| +![](img/09.adding_animations/11.moving_keys.png) Move the time cursor to `0.5` seconds by clicking and dragging on the gray timeline. In the *Inspector*, set the *Translation*'s *Y* axis to about `0.65` meters and the *Rotation Degrees*' *X* axis to `8`. -|image13| +![](img/09.adding_animations/12.second_keys_values.png) Create a keyframe for both properties and shift the translation key to `0.7` seconds by dragging it on the timeline. -|image14| +![](img/09.adding_animations/13.second_keys.png) Note: @@ -121,7 +118,7 @@ create a key for both properties. You can preview the result by clicking the play button or pressing :kbd:`Shift + D`. Click the stop button or press :kbd:`S` to stop playback. -|image15| +![](img/09.adding_animations/14.play_button.png) You can see that the engine interpolates between your keyframes to produce a continuous animation. At the moment, though, the motion feels very robotic. This @@ -132,33 +129,33 @@ We can control the transition between keyframes using easing curves. Click and drag around the first two keys in the timeline to box select them. -|image16| +![](img/09.adding_animations/15.box_select.png) You can edit the properties of both keys simultaneously in the *Inspector*, where you can see an *Easing* property. -|image17| +![](img/09.adding_animations/16.easing_property.png) Click and drag on the curve, pulling it towards the left. This will make it ease-out, that is to say, transition fast initially and slow down as the time cursor reaches the next keyframe. -|image18| +![](img/09.adding_animations/17.ease_out.png) Play the animation again to see the difference. The first half should already feel a bit bouncier. Apply an ease-out to the second keyframe in the rotation track. -|image19| +![](img/09.adding_animations/18.ease_out_second_rotation_key.png) Do the opposite for the second translation keyframe, dragging it to the right. -|image20| +![](img/09.adding_animations/19.ease_in_second_translation_key.png) Your animation should look something like this. -|image21| +![](img/09.adding_animations/20.float_animation.gif) Note: @@ -175,15 +172,14 @@ If you play the game, the player's creature will now float! If the creature is a little too close to the floor, you can move the *Pivot* up to offset it. -Controlling the animation in code -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +### Controlling the animation in code We can use code to control the animation playback based on the player's input. Let's change the animation speed when the character is moving. Open the *Player*'s script by clicking the script icon next to it. -|image22| +![](img/09.adding_animations/21.script_icon.png) In `physics_process()`, after the line where we check the `direction` vector, add the following code. @@ -215,8 +211,7 @@ gdscript GDScript $Pivot.rotation.x = PI / 6 * velocity.y / jump_impulse ``` -Animating the mobs ------------------- +## Animating the mobs Here's another nice trick with animations in Pandemonium: as long as you use a similar node structure, you can copy them to different scenes. @@ -362,26 +357,3 @@ gdscript GDScript queue_free() ``` -.. |image0| image:: img/squash-the-creeps-final.gif) -.. |image1| image:: img/09.adding_animations/01.animation_player_dock.png) -.. |image2| image:: img/09.adding_animations/02.new_animation.png) -.. |image3| image:: img/09.adding_animations/03.float_name.png) -.. |image4| image:: img/09.adding_animations/03.timeline.png) -.. |image5| image:: img/09.adding_animations/04.autoplay_and_loop.png) -.. |image6| image:: img/09.adding_animations/05.pin_icon.png) -.. |image7| image:: img/09.adding_animations/06.animation_duration.png) -.. |image8| image:: img/09.adding_animations/07.editable_timeline.png) -.. |image9| image:: img/09.adding_animations/08.zoom_slider.png) -.. |image10| image:: img/09.adding_animations/09.creating_first_keyframe.png) -.. |image11| image:: img/09.adding_animations/10.initial_keys.png) -.. |image12| image:: img/09.adding_animations/11.moving_keys.png) -.. |image13| image:: img/09.adding_animations/12.second_keys_values.png) -.. |image14| image:: img/09.adding_animations/13.second_keys.png) -.. |image15| image:: img/09.adding_animations/14.play_button.png) -.. |image16| image:: img/09.adding_animations/15.box_select.png) -.. |image17| image:: img/09.adding_animations/16.easing_property.png) -.. |image18| image:: img/09.adding_animations/17.ease_out.png) -.. |image19| image:: img/09.adding_animations/18.ease_out_second_rotation_key.png) -.. |image20| image:: img/09.adding_animations/19.ease_in_second_translation_key.png) -.. |image21| image:: img/09.adding_animations/20.float_animation.gif) -.. |image22| image:: img/09.adding_animations/21.script_icon.png) diff --git a/02_tutorials/04_first_3d_game/10_going_further.md b/02_tutorials/04_first_3d_game/10_going_further.md index 78399b2..3dfd363 100644 --- a/02_tutorials/04_first_3d_game/10_going_further.md +++ b/02_tutorials/04_first_3d_game/10_going_further.md @@ -1,7 +1,6 @@ -Going further -============= +# Going further You can pat yourself on the back for having completed your first 3D game with Pandemonium. @@ -19,8 +18,7 @@ build upon what you’ve learned so far. But before that, here’s a link to download a completed version of the project: `( https://github.com/GDQuest/pandemonium-3d-dodge-the-creeps )`. -Exploring the manual --------------------- +## Exploring the manual The manual is your ally whenever you have a doubt or you’re curious about a feature. It does not contain tutorials about specific game genres or mechanics. diff --git a/03_usage/01_math/01_vector_math.md b/03_usage/01_math/01_vector_math.md index 509f42d..010bf6d 100644 --- a/03_usage/01_math/01_vector_math.md +++ b/03_usage/01_math/01_vector_math.md @@ -1,10 +1,8 @@ -Vector math -=========== +# Vector math -Introduction -~~~~~~~~~~~~ +### Introduction This tutorial is a short and practical introduction to linear algebra as it applies to game development. Linear algebra is the study of vectors and @@ -18,8 +16,7 @@ Note: For a broader look at the mathematics, see https://www.khanacademy.org/math/linear-algebra -Coordinate systems (2D) -~~~~~~~~~~~~~~~~~~~~~~~ +### Coordinate systems (2D) In 2D space, coordinates are defined using a horizontal axis (`x`) and a vertical axis (`y`). A particular position in 2D space is written @@ -56,8 +53,7 @@ Both vectors represent a point 4 units to the right and 3 units below some starting point. It does not matter where on the plane you draw the vector, it always represents a relative direction and magnitude. -Vector operations -~~~~~~~~~~~~~~~~~ +### Vector operations You can use either method (x and y coordinates or angle and magnitude) to refer to a vector, but for convenience, programmers typically use the @@ -75,8 +71,7 @@ Pandemonium supports both `Vector2` and `Vector3` for 2D and 3D usage, respectively. The same mathematical rules discussed in this article apply to both types. -Member access -------------- +## Member access The individual components of the vector can be accessed directly by name. @@ -91,8 +86,7 @@ gdscript GDScript b.y = 1 ``` -Adding vectors --------------- +## Adding vectors When adding or subtracting two vectors, the corresponding components are added: @@ -109,8 +103,7 @@ the first: Note that adding `a + b` gives the same result as `b + a`. -Scalar multiplication ---------------------- +## Scalar multiplication Note: Vectors represent both direction and magnitude. A value @@ -131,13 +124,11 @@ Note: Multiplying a vector by a scalar does not change its direction, only its magnitude. This is how you **scale** a vector. -Practical applications -~~~~~~~~~~~~~~~~~~~~~~ +### Practical applications Let's look at two common uses for vector addition and subtraction. -Movement --------- +## Movement A vector can represent **any** quantity with a magnitude and direction. Typical examples are: position, velocity, acceleration, and force. In this image, the spaceship at step 1 has a position vector of `(1,3)` and @@ -151,8 +142,7 @@ Tip: Velocity measures the **change** in position per unit of time. The new position is found by adding velocity to the previous position. -Pointing toward a target ------------------------- +## Pointing toward a target In this scenario, you have a tank that wishes to point its turret at a robot. Subtracting the tank's position from the robot's position gives the @@ -163,15 +153,13 @@ vector pointing from the tank to the robot. Tip: To find a vector pointing from `A` to `B` use `B - A`. -Unit vectors -~~~~~~~~~~~~ +### Unit vectors A vector with **magnitude** of `1` is called a **unit vector**. They are also sometimes referred to as **direction vectors** or **normals**. Unit vectors are helpful when you need to keep track of a direction. -Normalization -------------- +## Normalization **Normalizing** a vector means reducing its length to `1` while preserving its direction. This is done by dividing each of its components @@ -184,14 +172,13 @@ gdscript GDScript a = a.normalized() ``` - Warning: + Because normalization involves dividing by the vector's length, you cannot normalize a vector of length `0`. Attempting to do so will result in an error. -Reflection ----------- +## Reflection A common use of unit vectors is to indicate **normals**. Normal vectors are unit vectors aligned perpendicularly to a surface, defining @@ -222,8 +209,7 @@ gdscript GDScript move_and_collide(reflect) ``` -Dot product -~~~~~~~~~~~ +### Dot product The **dot product** is one of the most important concepts in vector math, but is often misunderstood. Dot product is an operation on two vectors that @@ -257,8 +243,7 @@ product to tell us something about the angle between two vectors: When using unit vectors, the result will always be between `-1` (180°) and `1` (0°). -Facing ------- +## Facing We can use this fact to detect whether an object is facing toward another object. In the diagram below, the player `P` is trying to avoid the @@ -284,8 +269,7 @@ gdscript GDScript print("A sees P!") ``` -Cross product -~~~~~~~~~~~~~ +### Cross product Like the dot product, the **cross product** is an operation on two vectors. However, the result of the cross product is a vector with a direction @@ -320,8 +304,7 @@ Note: give the same result as `b.cross(a)`. The resulting vectors point in **opposite** directions. -Calculating normals -------------------- +## Calculating normals One common use of cross products is to find the surface normal of a plane or surface in 3D space. If we have the triangle `ABC` we can use vector @@ -341,8 +324,7 @@ gdscript GDScript return normal ``` -Pointing to a target --------------------- +## Pointing to a target In the dot product section above, we saw how it could be used to find the angle between two vectors. However, in 3D, this is not enough information. @@ -351,8 +333,7 @@ calculating the cross product of the current facing direction and the target direction. The resulting perpendicular vector is the axis of rotation. -More information -~~~~~~~~~~~~~~~~ +### More information For more information on using vector math in Pandemonium, see the following articles: diff --git a/03_usage/01_math/02_interpolation.md b/03_usage/01_math/02_interpolation.md index 52b42e6..db4a176 100644 --- a/03_usage/01_math/02_interpolation.md +++ b/03_usage/01_math/02_interpolation.md @@ -1,7 +1,6 @@ -Interpolation -============= +# Interpolation Interpolation is a very basic operation in graphics programming. It's good to become familiar with it in order to expand your horizons as a graphics developer. @@ -25,8 +24,7 @@ The name of this type of interpolation, which transforms a value into another at There are other types of interpolations, which will not be covered here. A recommended read afterwards is the `Bezier ( doc_beziers_and_curves )` page. -Vector interpolation --------------------- +## Vector interpolation Vector types (`Vector2`) can also be interpolated, they come with handy functions to do it `Vector2.linear_interpolate()`. @@ -50,8 +48,7 @@ It will produce the following motion: ![](img/interpolation_vector.gif) -Transform interpolation ------------------------ +## Transform interpolation It is also possible to interpolate whole transforms (make sure they have either uniform scale or, at least, the same non-uniform scale). For this, the function `Transform.interpolate_with()` can be used. @@ -78,8 +75,7 @@ And again, it will produce the following motion: ![](img/interpolation_monkey.gif) -Smoothing motion ----------------- +## Smoothing motion Interpolation can be used to smooth movement, rotation, etc. Here is an example of a circle following the mouse using smoothed motion: diff --git a/03_usage/01_math/03_random_number_generation.md b/03_usage/01_math/03_random_number_generation.md index 83842dd..e94e497 100644 --- a/03_usage/01_math/03_random_number_generation.md +++ b/03_usage/01_math/03_random_number_generation.md @@ -1,7 +1,6 @@ -Random number generation -======================== +# Random number generation Many games rely on randomness to implement core game mechanics. This page guides you through common types of randomness and how to implement them in @@ -13,13 +12,11 @@ and how to use a noise generator in GDScript. Note: - Computers cannot generate "true" random numbers. Instead, they rely on `pseudorandom number generators ( https://en.wikipedia.org/wiki/Pseudorandom_number_generator )` (PRNGs). -Global scope versus RandomNumberGenerator class ------------------------------------------------ +## Global scope versus RandomNumberGenerator class Pandemonium exposes two ways to generate random numbers: via *global scope* methods or using the `RandomNumberGenerator` class. @@ -35,8 +32,7 @@ multiple instances each with their own seed. This tutorial uses global scope methods, except when the method only exists in the RandomNumberGenerator class. -The randomize() method ----------------------- +## The randomize() method In global scope, you can find a `randomize() ( @GDScript_method_randomize )` method. **This method should be called only @@ -76,8 +72,7 @@ gdscript GDScript ``` -Getting a random number ------------------------ +## Getting a random number Let's look at some of the most commonly used functions and methods to generate random numbers in Pandemonium. @@ -142,8 +137,7 @@ gdscript GDScript print(random.randi_range(-10, 10)) ``` -Get a random array element --------------------------- +## Get a random array element We can use random integer generation to get a random element from an array: @@ -206,8 +200,7 @@ repetitive. Still, it doesn't prevent results from "ping-ponging" between a limited set of values. To prevent this, use the `shuffle bag ( doc_random_number_generation_shuffle_bags )` pattern instead. -Get a random dictionary value ------------------------------ +## Get a random dictionary value We can apply similar logic from arrays to dictionaries as well: @@ -235,10 +228,7 @@ gdscript GDScript return random_metal ``` - - -Weighted random probability ---------------------------- +## Weighted random probability The `randf()` method returns a floating-point number between 0.0 and 1.0. We can use this to create a @@ -268,10 +258,7 @@ gdscript GDScript return "Rare" ``` - - -"Better" randomness using shuffle bags --------------------------------------- +## "Better" randomness using shuffle bags Taking the same example as above, we would like to pick fruits at random. However, relying on random number generation every time a fruit is selected can @@ -315,8 +302,7 @@ row. Once we picked a fruit, it will no longer be a possible return value unless the array is now empty. When the array is empty, we reset it back to its default value, making it possible to have the same fruit again, but only once. -Random noise ------------- +## Random noise The random number generation shown above can show its limits when you need a value that *slowly* changes depending on the input. The input can be a position, diff --git a/03_usage/01_math/04_matrices_and_transforms.md b/03_usage/01_math/04_matrices_and_transforms.md index 755e1fc..c658fc1 100644 --- a/03_usage/01_math/04_matrices_and_transforms.md +++ b/03_usage/01_math/04_matrices_and_transforms.md @@ -1,10 +1,8 @@ -Matrices and transforms -======================= +# Matrices and transforms -Introduction ------------- +## Introduction Before reading this tutorial, we recommend that you thoroughly read and understand the `doc_vector_math` tutorial, as this tutorial @@ -30,8 +28,7 @@ Note: to match these conventions, but we will also represent the origin vector with a blue color. -Matrix components and the Identity matrix -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +### Matrix components and the Identity matrix The identity matrix represents a transform with no translation, no rotation, and no scale. Let's start by looking at the identity @@ -57,8 +54,7 @@ the X column vector. In other words, the bottom-left of the matrix. Similarly, `t.x.x` is top-left, `t.y.x` is top-right, and `t.y.y` is bottom-right, where `t` is the Transform2D. -Scaling the transformation matrix -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +### Scaling the transformation matrix Applying a scale is one of the easiest operations to understand. Let's start by placing the Pandemonium logo underneath our vectors @@ -95,8 +91,7 @@ Note: In actual projects, you can use the `scaled()` method to perform scaling. -Rotating the transformation matrix -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +### Rotating the transformation matrix We'll start the same way as earlier, with the Pandemonium logo underneath the identity matrix: @@ -172,8 +167,7 @@ Note: In actual projects, you can use the `rotated()` method to perform rotations. -Basis of the transformation matrix -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +### Basis of the transformation matrix So far we have only been working with the `x` and `y`, vectors, which are in charge of representing rotation, scale, and/or shearing @@ -195,8 +189,7 @@ since the code can get complex and it makes sense to separate it from `Transform` (which is composed of one `Basis` and one extra `Vector3` for the origin). -Translating the transformation matrix -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +### Translating the transformation matrix Changing the `origin` vector is called a *translating* the transformation matrix. Translating is basically a technical term for "moving" the @@ -223,8 +216,7 @@ Note: Pandemonium's 2D uses coordinates based on pixels, so in actual projects you will want to translate by hundreds of units. -Putting it all together -~~~~~~~~~~~~~~~~~~~~~~~ +### Putting it all together We're going to apply everything we mentioned so far onto one transform. To follow along, create a simple project with a Sprite node and use the @@ -254,8 +246,7 @@ gdscript GDScript transform = t # Change the node's transform to what we just calculated. ``` -Shearing the transformation matrix (advanced) -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +### Shearing the transformation matrix (advanced) Note: If you are only looking for how to *use* transformation matrices, @@ -336,8 +327,7 @@ If you would like additional explanation, you should check out 3Blue1Brown's excellent video about linear transformations: https://www.youtube.com/watch?v=kYB8IZa5AuE -Practical applications of transforms ------------------------------------- +## Practical applications of transforms In actual projects, you will usually be working with transforms inside transforms by having multiple `Node2D` or `Spatial` @@ -347,8 +337,7 @@ However, sometimes it's very useful to manually calculate the values we need. We will go over how you could use `Transform2D` or `Transform` to manually calculate transforms of nodes. -Converting positions between transforms -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +### Converting positions between transforms There are many cases where you'd want to convert a position in and out of a transform. For example, if you have a position relative to the player @@ -380,8 +369,7 @@ Note: (0, 0), you can use the "basis_xform" or "basis_xform_inv" methods instead, which skip dealing with translation. -Moving an object relative to itself -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +### Moving an object relative to itself A common operation, especially in 3D games, is to move an object relative to itself. For example, in first-person shooter games, you would want the @@ -405,8 +393,7 @@ Note: In actual projects, you can use `translate_object_local` in 3D or `move_local_x` and `move_local_y` in 2D to do this. -Applying transforms onto transforms -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +### Applying transforms onto transforms One of the most important things to know about transforms is how you can use several of them together. A parent node's transform affects @@ -478,8 +465,7 @@ If you would like additional explanation, you should check out 3Blue1Brown's excellent video about matrix composition: https://www.youtube.com/watch?v=XkY2DOUCWMU -Inverting a transformation matrix -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +### Inverting a transformation matrix The "affine_inverse" function returns a transform that "undoes" the previous transform. This can be useful in some situations, but it's @@ -508,8 +494,7 @@ gdscript GDScript # The position is the same as before. ``` -How does it all work in 3D? ---------------------------- +## How does it all work in 3D? One of the great things about transformation matrices is that they work very similarly between 2D and 3D transformations. @@ -548,8 +533,7 @@ If you would like additional explanation, you should check out 3Blue1Brown's excellent video about 3D linear transformations: https://www.youtube.com/watch?v=rHLEWRxRGiM -Representing rotation in 3D (advanced) -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +### Representing rotation in 3D (advanced) The biggest difference between 2D and 3D transformation matrices is how you represent rotation by itself without the basis vectors. diff --git a/03_usage/01_math/05_beziers_and_curves.md b/03_usage/01_math/05_beziers_and_curves.md index 492ccd2..579a500 100644 --- a/03_usage/01_math/05_beziers_and_curves.md +++ b/03_usage/01_math/05_beziers_and_curves.md @@ -1,7 +1,5 @@ - -Beziers, curves and paths -========================= +# Beziers, curves and paths Bezier curves are a mathematical approximation of natural geometric shapes. We use them to represent a curve with as little information as possible and with a @@ -15,8 +13,7 @@ previous article, combining multiple steps to create smooth curves. To better understand how Bezier curves work, let's start from its simplest form: Quadratic Bezier. -Quadratic Bezier ----------------- +## Quadratic Bezier Take three points, the minimum required for Quadratic Bezier to work: @@ -51,8 +48,7 @@ This type of curve is called a *Quadratic Bezier* curve. *(Image credit: Wikipedia)* -Cubic Bezier ------------- +## Cubic Bezier Building upon the previous example, we can get more control by interpolating between four points. @@ -124,8 +120,7 @@ Note: Cubic Bezier interpolation works the same in 3D, just use `Vector3` instead of `Vector2`. -Adding control points ---------------------- +## Adding control points Building upon Cubic Bezier, we can change the way two of the points work to control the shape of our curve freely. Instead of having `p0`, `p1`, `p2` @@ -145,8 +140,7 @@ this might look familiar: This is how graphics software presents Bezier curves to the users, and how they work and look in Pandemonium. -Curve2D, Curve3D, Path and Path2D ---------------------------------- +## Curve2D, Curve3D, Path and Path2D There are two objects that contain curves: `Curve3D` (for 3D and 2D respectively). @@ -156,8 +150,7 @@ They can contain several points, allowing for longer paths. It is also possible Using them, however, may not be completely obvious, so following is a description of the most common use cases for Bezier curves. -Evaluating ----------- +## Evaluating Just evaluating them may be an option, but in most cases it's not very useful. The big drawback with Bezier curves is that if you traverse them at constant speed, from `t = 0` to `t = 1`, the actual interpolation will *not* move at constant speed. The speed is also an interpolation between the distances between points `p0`, `p1`, `p2` and `p3` and there is not a mathematically simple way to traverse the curve at constant speed. @@ -177,8 +170,7 @@ gdscript GDScript As you can see, the speed (in pixels per second) of the circle varies, even though `t` is increased at constant speed. This makes beziers difficult to use for anything practical out of the box. -Drawing -------- +## Drawing Drawing beziers (or objects based on the curve) is a very common use case, but it's also not easy. For pretty much any case, Bezier curves need to be converted to some sort of segments. This is normally difficult, however, without creating a very high amount of them. @@ -193,8 +185,7 @@ Before drawing Bezier curves, *tessellation* is required. This is often done wit The *Curve* classes provide this via the `Curve2D.tessellate()` function (which receives optional `stages` of recursion and angle `tolerance` arguments). This way, drawing something based on a curve is easier. -Traversal ---------- +## Traversal The last common use case for the curves is to traverse them. Because of what was mentioned before regarding constant speed, this is also difficult. diff --git a/03_usage/01_math/06_vectors_advanced.md b/03_usage/01_math/06_vectors_advanced.md index 05c6f29..2e58473 100644 --- a/03_usage/01_math/06_vectors_advanced.md +++ b/03_usage/01_math/06_vectors_advanced.md @@ -1,10 +1,8 @@ -Advanced vector math -==================== +# Advanced vector math -Planes -~~~~~~ +### Planes The dot product has another interesting property with unit vectors. Imagine that perpendicular to that vector (and through the origin) @@ -29,8 +27,7 @@ except that the plane is an infinite surface (imagine an infinite, flat sheet of paper that you can orient and is pinned to the origin) instead of a line. -Distance to plane ------------------ +## Distance to plane Now that it's clear what a plane is, let's go back to the dot product. The dot product between a **unit vector** and any **point in space** @@ -50,8 +47,7 @@ space the distance will be negative, too: This allows us to tell which side of the plane a point is. -Away from the origin --------------------- +## Away from the origin I know what you are thinking! So far this is nice, but *real* planes are everywhere in space, not only passing through the origin. You want real @@ -130,8 +126,7 @@ calculating the distance to it. So, why is it useful to calculate the distance from a point to a plane? It's extremely useful! Let's see some simple examples.. -Constructing a plane in 2D --------------------------- +## Constructing a plane in 2D Planes clearly don't come out of nowhere, so they must be built. Constructing them in 2D is easy, this can be done from either a normal @@ -180,8 +175,7 @@ gdscript GDScript Doing the same in 3D is a little more complex and will be explained further down. -Some examples of planes ------------------------ +## Some examples of planes Here is a simple example of what planes are useful for. Imagine you have a `convex ( https://www.mathsisfun.com/definitions/convex.html )` @@ -269,8 +263,7 @@ This is usually just handled by splitting the concave polygon into smaller convex polygons, or using a technique such as BSP (which is not used much nowadays). -Collision detection in 3D -~~~~~~~~~~~~~~~~~~~~~~~~~ +### Collision detection in 3D This is another bonus bit, a reward for being patient and keeping up with this long tutorial. Here is another piece of wisdom. This might @@ -377,8 +370,7 @@ gdscript GDScript print("Polygons collided!") ``` -More information -~~~~~~~~~~~~~~~~ +### More information For more information on using vector math in Pandemonium, see the following article: