Cleanups.

This commit is contained in:
Relintai 2024-04-26 19:46:06 +02:00
parent 9a17898d09
commit b4b74565a3
23 changed files with 344 additions and 644 deletions

View File

@ -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`).
- **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.
Sprite animation
~~~~~~~~~~~~~~~~
## Sprite animation
Click on the `Player` node and add an `AnimatedSprite
( AnimatedSprite )` node as a child. The `AnimatedSprite` will handle the

View File

@ -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,14 +14,11 @@ 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
- If this is your first time encountering GDScript, please read
`doc_scripting` before continuing.
Start by declaring the member variables this object will need:
@ -94,8 +90,7 @@ 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,
- 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()`,
@ -133,7 +128,8 @@ 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
- 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.
@ -141,14 +137,12 @@ 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
- `$` 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`.
- 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
@ -164,26 +158,23 @@ gdscript GDScript
```
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`
- `Attempt to call function 'play' in base 'null instance' on a null instance`
this likely means you spelled the name of the AnimatedSprite node
wrong. Node names are case-sensitive and `$NodeName` must match
- 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
~~~~~~~~~~~~~~~~~~~
## 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,
@ -206,18 +197,15 @@ gdscript GDScript
```
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
```
Play the scene again and check that the animations are correct in each of the
@ -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*

View File

@ -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,13 +9,11 @@ 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`
@ -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:

View File

@ -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()`:

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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)

View File

@ -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)

View File

@ -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
@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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 youve learned so far.
But before that, heres 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 youre curious about a
feature. It does not contain tutorials about specific game genres or mechanics.

View File

@ -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:

View File

@ -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:

View File

@ -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,

View File

@ -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.

View File

@ -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.

View File

@ -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: