mirror of
https://github.com/Relintai/pandemonium_engine_docs.git
synced 2025-01-19 14:57:21 +01:00
Cleanups.
This commit is contained in:
parent
9a17898d09
commit
b4b74565a3
@ -1,7 +1,6 @@
|
|||||||
|
|
||||||
|
|
||||||
Creating the player scene
|
# Creating the player scene
|
||||||
=========================
|
|
||||||
|
|
||||||
With the project settings in place, we can start working on the
|
With the project settings in place, we can start working on the
|
||||||
player-controlled character.
|
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
|
creating a separate Player scene is that we can test it separately, even before
|
||||||
we've created other parts of the game.
|
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
|
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 -
|
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.
|
or :kbd:`Cmd + S` on macOS.
|
||||||
|
|
||||||
Note:
|
Note:
|
||||||
For this project, we will be following the Pandemonium naming conventions.
|
|
||||||
|
|
||||||
- **GDScript**: Classes (nodes) use PascalCase, variables and
|
For this project, we will be following the Pandemonium naming conventions.
|
||||||
functions use snake_case, and constants use ALL_CAPS (See
|
|
||||||
`doc_gdscript_styleguide`).
|
|
||||||
|
|
||||||
- **C#**: Classes, export variables and methods use PascalCase,
|
- **GDScript**: Classes (nodes) use PascalCase, variables and
|
||||||
private fields use _camelCase, local variables and parameters use
|
functions use snake_case, and constants use ALL_CAPS (See
|
||||||
camelCase (See `doc_c_sharp_styleguide`). Be careful to type
|
`doc_gdscript_styleguide`).
|
||||||
the method names precisely when connecting signals.
|
|
||||||
|
|
||||||
|
|
||||||
Sprite animation
|
## Sprite animation
|
||||||
~~~~~~~~~~~~~~~~
|
|
||||||
|
|
||||||
Click on the `Player` node and add an `AnimatedSprite
|
Click on the `Player` node and add an `AnimatedSprite
|
||||||
( AnimatedSprite )` node as a child. The `AnimatedSprite` will handle the
|
( AnimatedSprite )` node as a child. The `AnimatedSprite` will handle the
|
||||||
|
@ -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
|
In this lesson, we'll add player movement, animation, and set it up to detect
|
||||||
collisions.
|
collisions.
|
||||||
@ -15,22 +14,19 @@ Script" button:
|
|||||||
In the script settings window, you can leave the default settings alone. Just
|
In the script settings window, you can leave the default settings alone. Just
|
||||||
click "Create":
|
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)
|
![](img/attach_node_window.png)
|
||||||
|
|
||||||
Note:
|
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:
|
Start by declaring the member variables this object will need:
|
||||||
|
|
||||||
gdscript GDScript
|
gdscript GDScript
|
||||||
|
|
||||||
```
|
```
|
||||||
extends Area2D
|
extends Area2D
|
||||||
|
|
||||||
export var speed = 400 # How fast the player will move (pixels/sec).
|
export var speed = 400 # How fast the player will move (pixels/sec).
|
||||||
var screen_size # Size of the game window.
|
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:
|
a good time to find the size of the game window:
|
||||||
|
|
||||||
```
|
```
|
||||||
func _ready():
|
func _ready():
|
||||||
screen_size = get_viewport_rect().size
|
screen_size = get_viewport_rect().size
|
||||||
```
|
```
|
||||||
|
|
||||||
Now we can use the `process()` function to define what the player will do.
|
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:
|
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.
|
||||||
joystick buttons, or mouse buttons to the same input action.
|
|
||||||
|
|
||||||
You can detect whether a key is pressed using `Input.is_action_pressed()`,
|
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.
|
which returns `true` if it's pressed or `false` if it isn't.
|
||||||
|
|
||||||
gdscript GDScript
|
gdscript GDScript
|
||||||
```
|
```
|
||||||
func _process(delta):
|
func _process(delta):
|
||||||
var velocity = Vector2.ZERO # The player's movement vector.
|
var velocity = Vector2.ZERO # The player's movement vector.
|
||||||
if Input.is_action_pressed("move_right"):
|
if Input.is_action_pressed("move_right"):
|
||||||
velocity.x += 1
|
velocity.x += 1
|
||||||
if Input.is_action_pressed("move_left"):
|
if Input.is_action_pressed("move_left"):
|
||||||
velocity.x -= 1
|
velocity.x -= 1
|
||||||
if Input.is_action_pressed("move_down"):
|
if Input.is_action_pressed("move_down"):
|
||||||
velocity.y += 1
|
velocity.y += 1
|
||||||
if Input.is_action_pressed("move_up"):
|
if Input.is_action_pressed("move_up"):
|
||||||
velocity.y -= 1
|
velocity.y -= 1
|
||||||
|
|
||||||
if velocity.length() > 0:
|
if velocity.length() > 0:
|
||||||
velocity = velocity.normalized() * speed
|
velocity = velocity.normalized() * speed
|
||||||
$AnimatedSprite.play()
|
$AnimatedSprite.play()
|
||||||
else:
|
else:
|
||||||
$AnimatedSprite.stop()
|
$AnimatedSprite.stop()
|
||||||
```
|
```
|
||||||
|
|
||||||
We start by setting the `velocity` to `(0, 0)` - by default, the player
|
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.
|
diagonal movement.
|
||||||
|
|
||||||
Tip:
|
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`.
|
- If you've never used vector math before, or need a refresher, you can
|
||||||
It's good to know but won't be necessary for the rest of this tutorial.
|
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
|
We also check whether the player is moving so we can call `play()` or
|
||||||
`stop()` on the AnimatedSprite.
|
`stop()` on the AnimatedSprite.
|
||||||
|
|
||||||
Tip:
|
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
|
- `$` is shorthand for `get_node()`. So in the code above, `$AnimatedSprite.play()` is the same as
|
||||||
current node, or returns `null` if the node is not found. Since
|
`get_node("AnimatedSprite").play()`.
|
||||||
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
|
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
|
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
|
gdscript GDScript
|
||||||
|
|
||||||
```
|
```
|
||||||
position += velocity * delta
|
position += velocity * delta
|
||||||
position.x = clamp(position.x, 0, screen_size.x)
|
position.x = clamp(position.x, 0, screen_size.x)
|
||||||
position.y = clamp(position.y, 0, screen_size.y)
|
position.y = clamp(position.y, 0, screen_size.y)
|
||||||
```
|
```
|
||||||
|
|
||||||
Tip:
|
Tip:
|
||||||
The `delta` parameter in the `process()` function refers to the *frame
|
|
||||||
length* - the amount of time that the previous frame took to complete.
|
- 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
|
Using this value ensures that your movement will remain consistent evenif the frame rate changes.
|
||||||
if the frame rate changes.
|
|
||||||
|
|
||||||
Click "Play Scene" (:kbd:`F6`, :kbd:`Cmd + R` on macOS) and confirm you can move
|
Click "Play Scene" (:kbd:`F6`, :kbd:`Cmd + R` on macOS) and confirm you can move
|
||||||
the player around the screen in all directions.
|
the player around the screen in all directions.
|
||||||
|
|
||||||
Warning:
|
Warning:
|
||||||
If you get an error in the "Debugger" panel that says
|
|
||||||
|
|
||||||
`Attempt to call function 'play' in base 'null instance' on a null
|
If you get an error in the "Debugger" panel that says
|
||||||
instance`
|
|
||||||
|
|
||||||
this likely means you spelled the name of the AnimatedSprite node
|
- `Attempt to call function 'play' in base 'null instance' on a null instance`
|
||||||
wrong. Node names are case-sensitive and `$NodeName` must match
|
|
||||||
the name you see in the scene tree.
|
|
||||||
|
|
||||||
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
|
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,
|
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
|
gdscript GDScript
|
||||||
|
|
||||||
```
|
```
|
||||||
if velocity.x != 0:
|
if velocity.x != 0:
|
||||||
$AnimatedSprite.animation = "walk"
|
$AnimatedSprite.animation = "walk"
|
||||||
$AnimatedSprite.flip_v = false
|
$AnimatedSprite.flip_v = false
|
||||||
# See the note below about boolean assignment.
|
# See the note below about boolean assignment.
|
||||||
$AnimatedSprite.flip_h = velocity.x < 0
|
$AnimatedSprite.flip_h = velocity.x < 0
|
||||||
elif velocity.y != 0:
|
elif velocity.y != 0:
|
||||||
$AnimatedSprite.animation = "up"
|
$AnimatedSprite.animation = "up"
|
||||||
$AnimatedSprite.flip_v = velocity.y > 0
|
$AnimatedSprite.flip_v = velocity.y > 0
|
||||||
```
|
```
|
||||||
|
|
||||||
Note:
|
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
|
if velocity.x < 0:
|
||||||
else:
|
$AnimatedSprite.flip_h = true
|
||||||
$AnimatedSprite.flip_h = false
|
else:
|
||||||
|
$AnimatedSprite.flip_h = false
|
||||||
```
|
```
|
||||||
|
|
||||||
Play the scene again and check that the animations are correct in each of the
|
Play the scene again and check that the animations are correct in each of the
|
||||||
directions.
|
directions.
|
||||||
@ -238,8 +226,7 @@ gdscript GDScript
|
|||||||
hide()
|
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
|
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*
|
enemies yet! That's OK, because we're going to use Pandemonium's *signal*
|
||||||
|
@ -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
|
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,
|
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
|
We'll create a `Mob` scene, which we can then *instance* to create any number
|
||||||
of independent mobs in the game.
|
of independent mobs in the game.
|
||||||
|
|
||||||
Node setup
|
## Node setup
|
||||||
~~~~~~~~~~
|
|
||||||
|
|
||||||
Click Scene -> New Scene and add the following nodes:
|
Click Scene -> New Scene and add the following nodes:
|
||||||
|
|
||||||
- `RigidBody2D` (named `Mob`)
|
- `RigidBody2D` (named `Mob`)
|
||||||
|
- `AnimatedSprite`
|
||||||
- `AnimatedSprite`
|
- `CollisionShape2D`
|
||||||
- `CollisionShape2D`
|
- `VisibilityNotifier2D`
|
||||||
- `VisibilityNotifier2D`
|
|
||||||
|
|
||||||
Don't forget to set the children so they can't be selected, like you did with
|
Don't forget to set the children so they can't be selected, like you did with
|
||||||
the Player scene.
|
the Player scene.
|
||||||
@ -53,8 +50,7 @@ to `90` (under "Transform" in the Inspector).
|
|||||||
|
|
||||||
Save the scene.
|
Save the scene.
|
||||||
|
|
||||||
Enemy script
|
## Enemy script
|
||||||
~~~~~~~~~~~~
|
|
||||||
|
|
||||||
Add a script to the `Mob` like this:
|
Add a script to the `Mob` like this:
|
||||||
|
|
||||||
|
@ -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.
|
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
|
In addition, set the `One Shot` property of `StartTimer` to "On" and set
|
||||||
`Position` of the `StartPosition` node to `(240, 450)`.
|
`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
|
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
|
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)
|
![](img/main_scene_nodes.png)
|
||||||
|
|
||||||
Main script
|
## Main script
|
||||||
~~~~~~~~~~~
|
|
||||||
|
|
||||||
Add a script to `Main`. At the top of the script, we use `export
|
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.
|
(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
|
use the `deg2rad()` and `rad2deg()` functions to convert
|
||||||
between the two.
|
between the two.
|
||||||
|
|
||||||
Testing the scene
|
## Testing the scene
|
||||||
~~~~~~~~~~~~~~~~~
|
|
||||||
|
|
||||||
Let's test the scene to make sure everything is working. Add this `new_game`
|
Let's test the scene to make sure everything is working. Add this `new_game`
|
||||||
call to `ready()`:
|
call to `ready()`:
|
||||||
|
@ -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
|
The final piece our game needs is a User Interface (UI) to display things like
|
||||||
score, a "game over" message, and a restart button.
|
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,
|
You can drag the nodes to place them manually, or for more precise placement,
|
||||||
use the following settings:
|
use the following settings:
|
||||||
|
|
||||||
ScoreLabel
|
## ScoreLabel
|
||||||
~~~~~~~~~~
|
|
||||||
|
|
||||||
- *Layout* : "Top Wide"
|
- *Layout* : "Top Wide"
|
||||||
- *Text* : `0`
|
- *Text* : `0`
|
||||||
- *Align* : "Center"
|
- *Align* : "Center"
|
||||||
|
|
||||||
Message
|
## Message
|
||||||
~~~~~~~~~~~~
|
|
||||||
|
|
||||||
- *Layout* : "HCenter Wide"
|
- *Layout* : "HCenter Wide"
|
||||||
- *Text* : `Dodge the Creeps!`
|
- *Text* : `Dodge the Creeps!`
|
||||||
- *Align* : "Center"
|
- *Align* : "Center"
|
||||||
- *Autowrap* : "On"
|
- *Autowrap* : "On"
|
||||||
|
|
||||||
StartButton
|
## StartButton
|
||||||
~~~~~~~~~~~
|
|
||||||
|
|
||||||
- *Text* : `Start`
|
- *Text* : `Start`
|
||||||
- *Layout* : "Center Bottom"
|
- *Layout* : "Center Bottom"
|
||||||
@ -169,8 +165,7 @@ gdscript GDScript
|
|||||||
$Message.hide()
|
$Message.hide()
|
||||||
```
|
```
|
||||||
|
|
||||||
Connecting HUD to Main
|
## Connecting HUD to Main
|
||||||
~~~~~~~~~~~~~~~~~~~~~~
|
|
||||||
|
|
||||||
Now that we're done creating the `HUD` scene, go back to `Main`. Instance
|
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
|
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
|
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`.
|
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
|
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
|
from the previous game may still be on the screen. It would be better if they
|
||||||
|
@ -1,15 +1,13 @@
|
|||||||
|
|
||||||
|
|
||||||
Finishing up
|
# Finishing up
|
||||||
============
|
|
||||||
|
|
||||||
We have now completed all the functionality for our game. Below are some
|
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.
|
remaining steps to add a bit more "juice" to improve the game experience.
|
||||||
|
|
||||||
Feel free to expand the gameplay with your own ideas.
|
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.
|
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
|
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
|
You could also add a background image, if you have one, by using a
|
||||||
`TextureRect` node instead.
|
`TextureRect` node instead.
|
||||||
|
|
||||||
Sound effects
|
## Sound effects
|
||||||
~~~~~~~~~~~~~
|
|
||||||
|
|
||||||
Sound and music can be the single most effective way to add appeal to the game
|
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
|
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.
|
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
|
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
|
could also start the game by pressing a key on the keyboard. We can do this with
|
||||||
|
@ -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
|
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
|
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
|
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.
|
nice animation. We'll wrap up with the score and the retry screen.
|
||||||
|
|
||||||
Contents
|
## Contents
|
||||||
--------
|
|
||||||
|
|
||||||
.. toctree::
|
.. toctree::
|
||||||
:maxdepth: 1
|
:maxdepth: 1
|
||||||
|
@ -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
|
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.
|
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
|
Once you downloaded it, extract the .zip archive on your computer. Open the
|
||||||
Pandemonium project manager and click the *Import* button.
|
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
|
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
|
`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
|
open a file browser and navigate to the `project.pandemonium` file the folder
|
||||||
contains.
|
contains.
|
||||||
|
|
||||||
|image2|
|
![](img/01.game_setup/02.browse_to_project_folder.png)
|
||||||
|
|
||||||
Click *Import & Edit* to open the project in the editor.
|
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/`.
|
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.
|
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
|
There are two 3D models, `player.glb` and `mob.glb`, some materials that
|
||||||
belong to these models, and a music track.
|
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
|
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
|
*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
|
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).
|
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).
|
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*
|
define the collision area. With the *Main* node selected, add a *StaticBody*
|
||||||
node, then a *CollisionShape*. Rename the *StaticBody* as *Ground*.
|
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
|
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.
|
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*
|
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
|
and click the *[empty]* field next to the *Shape* property. Create a new *Box
|
||||||
Shape*.
|
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
|
The box shape is perfect for flat ground and walls. Its thickness makes it
|
||||||
reliable to block even fast-moving objects.
|
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
|
Set its *Extents* to `30` on the X axis, `1` for the Y axis, and `30` for
|
||||||
the Z axis.
|
the Z axis.
|
||||||
|
|
||||||
|image9|
|
![](img/01.game_setup/09.box_extents.png)
|
||||||
|
|
||||||
Note:
|
Note:
|
||||||
|
|
||||||
@ -88,19 +86,19 @@ Note:
|
|||||||
Collision shapes are invisible. We need to add a visual floor that goes along
|
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.
|
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*
|
In the *Inspector*, click on the field next to *Mesh* and create a *CubeMesh*
|
||||||
resource to create a visible cube.
|
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
|
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 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
|
resource works with a size rather than extents, we need to use these values so
|
||||||
it matches our collision shape.
|
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
|
You should see a wide grey slab that covers the grid and blue and red axes in
|
||||||
the viewport.
|
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),
|
*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.
|
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:
|
Note:
|
||||||
|
|
||||||
@ -117,12 +115,12 @@ Note:
|
|||||||
If you can't see the 3D object manipulator like on the image above, ensure
|
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.
|
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
|
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.
|
viewport tells you how much you're translating the node.
|
||||||
|
|
||||||
|image15|
|
![](img/01.game_setup/15.translation_amount.png)
|
||||||
|
|
||||||
Note:
|
Note:
|
||||||
|
|
||||||
@ -139,29 +137,13 @@ ground is lit.
|
|||||||
|
|
||||||
In the *Inspector*, turn on *Shadow -> Enabled* by clicking the checkbox.
|
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.
|
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
|
That's our starting point. In the next part, we will work on the player scene
|
||||||
and base movement.
|
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)
|
|
||||||
|
@ -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
|
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
|
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
|
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*.
|
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
|
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
|
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
|
FileSystem dock, expand the `art/` folder by double-clicking it and drag and
|
||||||
drop `player.glb` onto the *Pivot* node.
|
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
|
This should instantiate the model as a child of *Pivot*. You can rename it to
|
||||||
*Character*.
|
*Character*.
|
||||||
|
|
||||||
|image2|
|
![](img/02.player_input/03.scene_structure.png)
|
||||||
|
|
||||||
Note:
|
Note:
|
||||||
|
|
||||||
|
|
||||||
The `.glb` files contain 3D scene data based on the open-source GLTF 2.0
|
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
|
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
|
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*
|
*CollisionShape*. In the *Inspector*, assign a *SphereShape* to the *Shape*
|
||||||
property. The sphere's wireframe appears below the character.
|
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
|
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
|
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.
|
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
|
You can toggle the model's visibility by clicking the eye icon next to the
|
||||||
*Character* or the *Pivot* nodes.
|
*Character* or the *Pivot* nodes.
|
||||||
|
|
||||||
|image5|
|
![](img/02.player_input/06.toggling_visibility.png)
|
||||||
|
|
||||||
Save the scene as `Player.tscn`.
|
Save the scene as `Player.tscn`.
|
||||||
|
|
||||||
With the nodes ready, we can almost get coding. But first, we need to define
|
With the nodes ready, we can almost get coding. But first, we need to define
|
||||||
some input actions.
|
some input actions.
|
||||||
|
|
||||||
Creating input actions
|
## Creating input actions
|
||||||
----------------------
|
|
||||||
|
|
||||||
To move the character, we will listen to the player's input, like pressing the
|
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
|
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
|
This system is the Input Map. To access its editor, head to the *Project* menu
|
||||||
and select *Project Settings…*.
|
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
|
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
|
you to add new actions at the top; they are your labels. In the bottom part, you
|
||||||
can bind keys to these actions.
|
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
|
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.
|
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.
|
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.
|
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
|
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*.
|
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
|
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*.
|
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.
|
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
|
Let's now add support for a gamepad's left joystick. Click the "+" button again
|
||||||
but this time, select *Joy Axis*.
|
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
|
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*
|
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
|
you want to bind to the input action on the right. Leave the default values and
|
||||||
press the *Add* button.
|
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,
|
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,
|
and the left joystick's right axis to `move_right`. After binding all keys,
|
||||||
your interface should look like this.
|
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
|
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.
|
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.
|
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.
|
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
|
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.
|
groups of keys and buttons in your projects.
|
||||||
|
|
||||||
In the next part, we'll code and test the player's movement.
|
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)
|
|
||||||
|
@ -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
|
It's time to code! We're going to use the input actions we created in the last
|
||||||
part to move the character.
|
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*
|
it. In the popup, set the *Template* to *Empty* before pressing the *Create*
|
||||||
button.
|
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,
|
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
|
a fall acceleration representing gravity, and a velocity we'll use to move the
|
||||||
@ -19,7 +18,7 @@ character.
|
|||||||
gdscript GDScript
|
gdscript GDScript
|
||||||
|
|
||||||
```
|
```
|
||||||
extends KinematicBody
|
extends KinematicBody
|
||||||
|
|
||||||
# How fast the player moves in meters per second.
|
# How fast the player moves in meters per second.
|
||||||
export var speed = 14
|
export var speed = 14
|
||||||
@ -36,7 +35,6 @@ we want to update and reuse its value across frames.
|
|||||||
|
|
||||||
Note:
|
Note:
|
||||||
|
|
||||||
|
|
||||||
The values are quite different from 2D code because distances are in meters.
|
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
|
While in 2D, a thousand units (pixels) may only correspond to half of your
|
||||||
screen's width, in 3D, it's a kilometer.
|
screen's width, in 3D, it's a kilometer.
|
||||||
@ -200,8 +198,7 @@ gdscript GDScript
|
|||||||
velocity = move_and_slide(velocity, Vector3.UP)
|
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
|
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
|
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*
|
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.
|
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
|
If you closed the scene before, head to the *FileSystem* dock and double-click
|
||||||
`Main.tscn` to re-open it.
|
`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
|
To instantiate the *Player*, right-click on the *Main* node and select *Instance
|
||||||
Child Scene*.
|
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
|
In the popup, double-click *Player.tscn*. The character should appear in the
|
||||||
center of the viewport.
|
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
|
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
|
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*,
|
*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.
|
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
|
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.
|
*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.
|
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
|
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*.
|
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).
|
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
|
On the bottom view, select the *Camera* and turn on camera preview by clicking
|
||||||
the checkbox.
|
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
|
In the top view, move the camera about `19` units on the Z axis (the blue
|
||||||
one).
|
one).
|
||||||
|
|
||||||
|image7|
|
![](img/03.player_movement_code/08.camera_moved.png)
|
||||||
|
|
||||||
Here's where the magic happens. Select the *CameraPivot* and rotate it `45`
|
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
|
degrees around the X axis (using the red circle). You'll see the camera move as
|
||||||
if it was attached to a crane.
|
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
|
You can run the scene by pressing :kbd:`F6` and press the arrow keys to move the
|
||||||
character.
|
character.
|
||||||
|
|
||||||
|image9|
|
![](img/03.player_movement_code/10.camera_perspective.png)
|
||||||
|
|
||||||
We can see some empty space around the character due to the perspective
|
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
|
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
|
*Orthogonal* and the *Size* to `19`. The character should now look flatter and
|
||||||
the ground should fill the background.
|
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
|
With that, we have both player movement and the view in place. Next, we will
|
||||||
work on the monsters.
|
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)
|
|
||||||
|
@ -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
|
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.
|
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
|
monster's 3D model to the scene. You can rename the newly created *mob* node
|
||||||
into *Character*.
|
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,
|
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*.
|
the scene's root, and click *Add Child Node*.
|
||||||
|
|
||||||
|image1|
|
![](img/04.mob_scene/02.add_child_node.png)
|
||||||
|
|
||||||
Add a *CollisionShape*.
|
Add a *CollisionShape*.
|
||||||
|
|
||||||
|image2|
|
![](img/04.mob_scene/03.scene_with_collision_shape.png)
|
||||||
|
|
||||||
In the *Inspector*, assign a *BoxShape* to the *Shape* property.
|
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
|
We should change its size to fit the 3D model better. You can do so
|
||||||
interactively by clicking and dragging on the orange dots.
|
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
|
3D model, you may die at a distance from the monster, and the game will feel
|
||||||
unfair to the players.
|
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
|
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
|
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
|
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.
|
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 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
|
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
|
box, pink this time, appears. When this box completely leaves the screen, the
|
||||||
node will emit a signal.
|
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.
|
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.
|
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
|
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*.
|
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`
|
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
|
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)
|
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
|
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*.
|
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
|
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).
|
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,
|
Select the *VisibilityNotifier* node and on the right side of the interface,
|
||||||
navigate to the *Node* dock. Double-click the *screen_exited()* signal.
|
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*.
|
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,
|
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()`
|
`on_VisibilityNotifier_screen_exited()`. From it, call the `queue_free()`
|
||||||
@ -236,15 +232,3 @@ gdscript GDScript
|
|||||||
func _on_VisibilityNotifier_screen_exited():
|
func _on_VisibilityNotifier_screen_exited():
|
||||||
queue_free()
|
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)
|
|
||||||
|
@ -1,12 +1,11 @@
|
|||||||
|
|
||||||
|
|
||||||
Spawning monsters
|
# Spawning monsters
|
||||||
=================
|
|
||||||
|
|
||||||
In this part, we're going to spawn monsters along a path randomly. By the end,
|
In this part, we're going to spawn monsters along a path randomly. By the end,
|
||||||
you will have monsters roaming the game board.
|
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.
|
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*.
|
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
|
In the left menu, navigate down to *Display -> Window*. On the right, set the
|
||||||
*Width* to `720` and the *Height* to `540`.
|
*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
|
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.
|
*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
|
Select the *Camera* node and click the *Preview* checkbox in the bottom
|
||||||
viewport.
|
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
|
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
|
*Main* node and name it *Cylinders*. We'll use it to group the cylinders. As a
|
||||||
child of it, add a *MeshInstance* node.
|
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.
|
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
|
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.
|
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*
|
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*.
|
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
|
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
|
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.
|
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
|
Place the cylinder so it's right outside the camera's view in the top-left
|
||||||
corner.
|
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.
|
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
|
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
|
Select both cylinders by pressing the :kbd:`Shift` key and clicking on the unselected
|
||||||
one and duplicate them.
|
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.
|
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
|
They're a bit hard to see in white, aren't they? Let's make them stand out by
|
||||||
giving them a new material.
|
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
|
*Scene* dock. To do so, you can click on the first one and Shift click on the
|
||||||
last one.
|
last one.
|
||||||
|
|
||||||
|image12|
|
![](img/05.spawning_mobs/13.selecting_all_cylinders.png)
|
||||||
|
|
||||||
In the *Inspector*, expand the *Material* section and assign a *SpatialMaterial*
|
In the *Inspector*, expand the *Material* section and assign a *SpatialMaterial*
|
||||||
to slot *0*.
|
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
|
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
|
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
|
Expand the *Albedo* section and set the color to something that contrasts with
|
||||||
the background, like a bright orange.
|
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
|
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
|
clicking the grey arrow next to them. Moving forward, you can also toggle their
|
||||||
visibility by clicking the eye icon next to *Cylinders*.
|
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
|
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.
|
the *Add Point* tool, the icon with the green "+" sign.
|
||||||
|
|
||||||
|image16|
|
![](img/05.spawning_mobs/17.points_options.png)
|
||||||
|
|
||||||
Note:
|
Note:
|
||||||
You can hover any icon to see a tooltip describing the tool.
|
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
|
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.
|
click and drag on it to reposition it.
|
||||||
|
|
||||||
|image17|
|
![](img/05.spawning_mobs/18.close_path.png)
|
||||||
|
|
||||||
Your path should look like this.
|
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
|
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
|
*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.
|
*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.
|
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.
|
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
|
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*.
|
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*.
|
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
|
In the *Inspector*, set its *Wait Time* to `0.5` seconds and turn on
|
||||||
*Autostart* so it automatically starts when we run the game.
|
*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
|
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.
|
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
|
With the *MobTimer* still selected, head to the *Node* dock on the right and
|
||||||
double-click the `timeout` signal.
|
double-click the `timeout` signal.
|
||||||
|
|
||||||
|image23|
|
![](img/05.spawning_mobs/23.timeout_signal.png)
|
||||||
|
|
||||||
Connect it to the *Main* node.
|
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
|
This will take you back to the script, with a new empty
|
||||||
`on_MobTimer_timeout()` function.
|
`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
|
You can test the scene by pressing :kbd:`F6`. You should see the monsters spawn and
|
||||||
move in a straight line.
|
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
|
For now, they bump and slide against one another when their paths cross. We'll
|
||||||
address this in the next part.
|
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)
|
|
||||||
|
@ -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
|
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.
|
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
|
the world of `physics layers
|
||||||
( doc_physics_introduction_collision_layers_and_masks )`.
|
( doc_physics_introduction_collision_layers_and_masks )`.
|
||||||
|
|
||||||
Controlling physics interactions
|
## Controlling physics interactions
|
||||||
--------------------------------
|
|
||||||
|
|
||||||
Physics bodies have access to two complementary properties: layers and masks.
|
Physics bodies have access to two complementary properties: layers and masks.
|
||||||
Layers define on which physics layer(s) an object is.
|
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
|
Physics layers are represented by numbers, but we can give them names to keep
|
||||||
track of what's what.
|
track of what's what.
|
||||||
|
|
||||||
Setting layer names
|
### Setting layer names
|
||||||
~~~~~~~~~~~~~~~~~~~
|
|
||||||
|
|
||||||
Let's give our physics layers a name. Go to *Project -> Project Settings*.
|
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
|
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
|
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*,
|
names there. Name the first three layers *player*, *enemies*, and *world*,
|
||||||
respectively.
|
respectively.
|
||||||
|
|
||||||
|image1|
|
![](img/06.jump_and_squash/03.physics_layers.png)
|
||||||
|
|
||||||
Now, we can assign them to our physics nodes.
|
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
|
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
|
*Collision* section. There, you can see the node's layers and masks as a grid of
|
||||||
buttons.
|
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.
|
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
|
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.
|
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
|
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
|
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
|
Note that you can click the "..." button on the right side of the properties to
|
||||||
see a list of named checkboxes.
|
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
|
Next up are the *Player* and the *Mob*. Open `Player.tscn` by double-clicking
|
||||||
the file in the *FileSystem* dock.
|
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
|
"world". You can leave the default *Layer* property as the first layer is the
|
||||||
"player" one.
|
"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
|
Then, open the *Mob* scene by double-clicking on `Mob.tscn` and select the
|
||||||
*Mob* node.
|
*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*,
|
Set its *Collision -> Layer* to "enemies" and unset its *Collision -> Mask*,
|
||||||
leaving the mask empty.
|
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
|
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"
|
monsters to collide with and slide against each other, turn on the "enemies"
|
||||||
@ -96,12 +92,10 @@ mask.
|
|||||||
|
|
||||||
Note:
|
Note:
|
||||||
|
|
||||||
|
|
||||||
The mobs don't need to mask the "world" layer because they only move
|
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.
|
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*
|
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
|
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
|
Notice that the Y axis is positive upwards. That's unlike 2D, where the Y axis
|
||||||
is positive downward.
|
is positive downward.
|
||||||
|
|
||||||
Squashing monsters
|
## Squashing monsters
|
||||||
------------------
|
|
||||||
|
|
||||||
Let's add the squash mechanic next. We're going to make the character bounce
|
Let's add the squash mechanic next. We're going to make the character bounce
|
||||||
over monsters and kill them at the same time.
|
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
|
Click on it to reveal a field where you can write a tag name. Enter "mob" in the
|
||||||
field and click the *Add* button.
|
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
|
An icon appears in the *Scene* dock to indicate the node is part of at least one
|
||||||
group.
|
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
|
We can now use the group from the code to distinguish collisions with monsters
|
||||||
from collisions with the floor.
|
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.
|
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.
|
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)
|
|
||||||
|
@ -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.
|
We can kill enemies by jumping on them, but the player still can't die.
|
||||||
Let's fix this.
|
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
|
kinds of collisions. Instead, though, we will use an *Area* node, which
|
||||||
works well for hitboxes.
|
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
|
Head back to the *Player* scene and add a new *Area* node. Name it
|
||||||
*MobDetector*. Add a *CollisionShape* node as a child of 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.
|
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
|
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
|
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
|
player jumps, the shape will be too high up for the enemies to collide
|
||||||
with it.
|
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
|
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
|
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
|
it to detect collisions. Then, remove the *Collision -> Layer* and set
|
||||||
the mask to the "enemies" layer.
|
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
|
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
|
one to the *Player* node. In the *Node* tab, double-click the
|
||||||
`body_entered` signal and connect it to the *Player*.
|
`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
|
The *MobDetector* will emit `body_entered` when a *KinematicBody* or a
|
||||||
*RigidBody* node enters it. As it only masks the "enemies" physics
|
*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
|
*Player* and the *Mob*\ 's collision shapes. You may need to move them
|
||||||
and resize them to achieve a tight game feel.
|
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
|
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
|
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,
|
Open `Main.tscn`, select the *Player* node, and in the *Node* dock,
|
||||||
connect its `hit` signal to the *Main* node.
|
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.
|
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
|
see how you can make the game feel much more alive with minimalistic
|
||||||
animations.
|
animations.
|
||||||
|
|
||||||
Code checkpoint
|
## Code checkpoint
|
||||||
---------------
|
|
||||||
|
|
||||||
Here are the complete scripts for the *Main*, *Mob*, and *Player* nodes,
|
Here are the complete scripts for the *Main*, *Mob*, and *Player* nodes,
|
||||||
for reference. You can use them to compare and check your code.
|
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.
|
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)
|
|
||||||
|
@ -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
|
In this part, we'll add the score, music playback, and the ability to restart
|
||||||
the game.
|
the game.
|
||||||
@ -15,11 +14,11 @@ edit your User Interface (UI).
|
|||||||
|
|
||||||
Add a *Label* node and rename it to *ScoreLabel*.
|
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".
|
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
|
Also, the text is white by default, like our game's background. We need to
|
||||||
change its color to see it at runtime.
|
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
|
Scroll down to *Theme Overrides*, and expand *Colors* and click the black box next to *Font Color* to
|
||||||
tint the text.
|
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.
|
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
|
Finally, click and drag on the text in the viewport to move it away from the
|
||||||
top-left corner.
|
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
|
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
|
and use a theme resource that will propagate to all its children. We'll use it
|
||||||
to set our game's font.
|
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
|
Once again, select the *UserInterface* node. In the *Inspector*, create a new
|
||||||
theme resource in *Theme -> Theme*.
|
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
|
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.
|
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*.
|
By default, a theme only has one property, the *Default Font*.
|
||||||
|
|
||||||
See also:
|
See also:
|
||||||
|
|
||||||
|
|
||||||
You can add more properties to the theme resource to design complex user
|
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
|
interfaces, but that is beyond the scope of this series. To learn more about
|
||||||
creating and editing themes, see `doc_gui_skinning`.
|
creating and editing themes, see `doc_gui_skinning`.
|
||||||
|
|
||||||
Click the *Default Font* property and create a new *DynamicFont*.
|
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,
|
Expand the *DynamicFont* by clicking on it and expand its *Font* section. There,
|
||||||
you will see an empty *Font Data* field.
|
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.
|
This one expects a font file like the ones you have on your computer.
|
||||||
DynamicFont supports the following formats:
|
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 is a bit small. Set the *Settings -> Size* to `22` pixels to increase
|
||||||
the text's size.
|
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
|
Let's work on the score next. Attach a new script to the *ScoreLabel* and define
|
||||||
the `score` variable.
|
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
|
Open the script `Main.gd`. If it's still open, you can click on its name in
|
||||||
the script editor's left column.
|
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*
|
Alternatively, you can double-click the `Main.gd` file in the *FileSystem*
|
||||||
dock.
|
dock.
|
||||||
@ -154,13 +150,12 @@ function.
|
|||||||
|
|
||||||
See also:
|
See also:
|
||||||
|
|
||||||
|
|
||||||
You can learn more about string formatting here: `doc_gdscript_printf`.
|
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
|
You can now play the game and squash a few enemies to see the score
|
||||||
increase.
|
increase.
|
||||||
|
|
||||||
|image11|
|
![](img/08.score_and_replay/09.score_in_game.png)
|
||||||
|
|
||||||
Note:
|
Note:
|
||||||
|
|
||||||
@ -171,8 +166,7 @@ Note:
|
|||||||
object. But when prototyping or when your project is simple, it is fine to
|
object. But when prototyping or when your project is simple, it is fine to
|
||||||
keep your code simple. Programming is always a balancing act.
|
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
|
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.
|
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
|
To make it span over the whole viewport, you can use the *Layout* menu in the
|
||||||
toolbar.
|
toolbar.
|
||||||
|
|
||||||
|image12|
|
![](img/08.score_and_replay/10.layout_icon.png)
|
||||||
|
|
||||||
Open it and apply the *Full Rect* command.
|
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
|
Nothing happens. Well, almost nothing: only the four green pins move to the
|
||||||
corners of the selection box.
|
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
|
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
|
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
|
in the color picker, drag the *A* slider to the left. It controls the color's
|
||||||
alpha channel, that is to say, its opacity.
|
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
|
Next, add a *Label* as a child of *Retry* and give it the *Text* "Press Enter to
|
||||||
retry."
|
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 move it and anchor it in the center of the screen, apply *Layout -> Center*
|
||||||
to it.
|
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
|
We can now head to the code to show and hide the *Retry* node when the player
|
||||||
dies and plays again.
|
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
|
( SceneTree )` object, which allows us to reload and restart the current
|
||||||
scene.
|
scene.
|
||||||
|
|
||||||
Adding music
|
## Adding music
|
||||||
------------
|
|
||||||
|
|
||||||
To add music that plays continuously in the background, we're going to use
|
To add music that plays continuously in the background, we're going to use
|
||||||
another feature in Pandemonium: `autoloads ( doc_singletons_autoload )`.
|
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*.
|
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
|
Click the *Other Node* button to create an *AudioStreamPlayer* and rename it to
|
||||||
*MusicPlayer*.
|
*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
|
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*.
|
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
|
Also, turn on *Autoplay* so the music plays automatically at the start of the
|
||||||
game.
|
game.
|
||||||
|
|
||||||
|image20|
|
![](img/08.score_and_replay/18.music_node_properties.png)
|
||||||
|
|
||||||
Save the scene as `MusicPlayer.tscn`.
|
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,
|
icon to open the file browser and double-click on `MusicPlayer.tscn`. Then,
|
||||||
click the *Add* button on the right to register the node.
|
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
|
If you run the game now, the music will play automatically. And even when you
|
||||||
lose and retry, it keeps going.
|
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:
|
hood. When you run the game, your *Scene* dock changes to give you two tabs:
|
||||||
*Remote* and *Local*.
|
*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.
|
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
|
There, you will see the *Main* node and everything the scene contains and the
|
||||||
instantiated mobs at the bottom.
|
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
|
At the top are the autoloaded *MusicPlayer* and a *root* node, which is your
|
||||||
game's viewport.
|
game's viewport.
|
||||||
@ -369,27 +361,3 @@ gdscript GDScript
|
|||||||
$UserInterface/Retry.show()
|
$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)
|
|
||||||
|
@ -1,18 +1,16 @@
|
|||||||
|
|
||||||
|
|
||||||
Character animation
|
# Character animation
|
||||||
===================
|
|
||||||
|
|
||||||
In this final lesson, we'll use Pandemonium's built-in animation tools to make our
|
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
|
characters float and flap. You'll learn to design animations in the editor and
|
||||||
use code to make your game feel alive.
|
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.
|
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 engine comes with tools to author animations in the editor. You can then use
|
||||||
the code to play and control them at runtime.
|
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.
|
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
|
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
|
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*.
|
Let's create an animation. Click on *Animation -> New*.
|
||||||
|
|
||||||
|image2|
|
![](img/09.adding_animations/02.new_animation.png)
|
||||||
|
|
||||||
Name the animation "float".
|
Name the animation "float".
|
||||||
|
|
||||||
|image3|
|
![](img/09.adding_animations/03.float_name.png)
|
||||||
|
|
||||||
Once you created the animation, the timeline appears with numbers representing
|
Once you created the animation, the timeline appears with numbers representing
|
||||||
time in seconds.
|
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.
|
We want the animation to start playback automatically at the start of the game.
|
||||||
Also, it should loop.
|
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
|
To do so, you can click the button with an "A+" icon in the animation toolbar
|
||||||
and the looping arrows, respectively.
|
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.
|
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
|
This prevents it from folding when you click on the viewport and deselect the
|
||||||
nodes.
|
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.
|
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
|
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.
|
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
|
You can click and drag the slider in the bottom-right to zoom in and out of the
|
||||||
timeline.
|
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
|
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
|
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
|
Select the *Character* and click the key icon next to *Translation* in the
|
||||||
*Inspector*. Do the same for *Rotation Degrees*.
|
*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.
|
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
|
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.
|
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
|
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
|
timeline. In the *Inspector*, set the *Translation*'s *Y* axis to about
|
||||||
`0.65` meters and the *Rotation Degrees*' *X* axis to `8`.
|
`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`
|
Create a keyframe for both properties and shift the translation key to `0.7`
|
||||||
seconds by dragging it on the timeline.
|
seconds by dragging it on the timeline.
|
||||||
|
|
||||||
|image14|
|
![](img/09.adding_animations/13.second_keys.png)
|
||||||
|
|
||||||
Note:
|
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`.
|
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.
|
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
|
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
|
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.
|
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*,
|
You can edit the properties of both keys simultaneously in the *Inspector*,
|
||||||
where you can see an *Easing* property.
|
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
|
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
|
ease-out, that is to say, transition fast initially and slow down as the time
|
||||||
cursor reaches the next keyframe.
|
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
|
Play the animation again to see the difference. The first half should already
|
||||||
feel a bit bouncier.
|
feel a bit bouncier.
|
||||||
|
|
||||||
Apply an ease-out to the second keyframe in the rotation track.
|
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.
|
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.
|
Your animation should look something like this.
|
||||||
|
|
||||||
|image21|
|
![](img/09.adding_animations/20.float_animation.gif)
|
||||||
|
|
||||||
Note:
|
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
|
If the creature is a little too close to the floor, you can move the *Pivot* up
|
||||||
to offset it.
|
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.
|
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.
|
Let's change the animation speed when the character is moving.
|
||||||
|
|
||||||
Open the *Player*'s script by clicking the script icon next to it.
|
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`
|
In `physics_process()`, after the line where we check the `direction`
|
||||||
vector, add the following code.
|
vector, add the following code.
|
||||||
@ -215,8 +211,7 @@ gdscript GDScript
|
|||||||
$Pivot.rotation.x = PI / 6 * velocity.y / jump_impulse
|
$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
|
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.
|
node structure, you can copy them to different scenes.
|
||||||
@ -362,26 +357,3 @@ gdscript GDScript
|
|||||||
queue_free()
|
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)
|
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
|
|
||||||
|
|
||||||
Going further
|
# Going further
|
||||||
=============
|
|
||||||
|
|
||||||
You can pat yourself on the back for having completed your first 3D game with
|
You can pat yourself on the back for having completed your first 3D game with
|
||||||
Pandemonium.
|
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:
|
But before that, here’s a link to download a completed version of the project:
|
||||||
`( https://github.com/GDQuest/pandemonium-3d-dodge-the-creeps )`.
|
`( 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
|
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.
|
feature. It does not contain tutorials about specific game genres or mechanics.
|
||||||
|
@ -1,10 +1,8 @@
|
|||||||
|
|
||||||
|
|
||||||
Vector math
|
# Vector math
|
||||||
===========
|
|
||||||
|
|
||||||
Introduction
|
### Introduction
|
||||||
~~~~~~~~~~~~
|
|
||||||
|
|
||||||
This tutorial is a short and practical introduction to linear algebra as
|
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
|
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,
|
For a broader look at the mathematics,
|
||||||
see https://www.khanacademy.org/math/linear-algebra
|
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
|
In 2D space, coordinates are defined using a horizontal axis (`x`) and
|
||||||
a vertical axis (`y`). A particular position in 2D space is written
|
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,
|
starting point. It does not matter where on the plane you draw the vector,
|
||||||
it always represents a relative direction and magnitude.
|
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
|
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
|
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
|
`Vector3` for 2D and 3D usage, respectively. The same
|
||||||
mathematical rules discussed in this article apply to both types.
|
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.
|
The individual components of the vector can be accessed directly by name.
|
||||||
|
|
||||||
@ -91,8 +86,7 @@ gdscript GDScript
|
|||||||
b.y = 1
|
b.y = 1
|
||||||
```
|
```
|
||||||
|
|
||||||
Adding vectors
|
## Adding vectors
|
||||||
--------------
|
|
||||||
|
|
||||||
When adding or subtracting two vectors, the corresponding components are added:
|
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`.
|
Note that adding `a + b` gives the same result as `b + a`.
|
||||||
|
|
||||||
Scalar multiplication
|
## Scalar multiplication
|
||||||
---------------------
|
|
||||||
|
|
||||||
Note:
|
Note:
|
||||||
Vectors represent both direction and magnitude. A value
|
Vectors represent both direction and magnitude. A value
|
||||||
@ -131,13 +124,11 @@ Note:
|
|||||||
Multiplying a vector by a scalar does not change its direction,
|
Multiplying a vector by a scalar does not change its direction,
|
||||||
only its magnitude. This is how you **scale** a vector.
|
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.
|
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
|
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
|
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
|
Velocity measures the **change** in position per unit of time. The
|
||||||
new position is found by adding velocity to the previous position.
|
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
|
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
|
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:
|
Tip:
|
||||||
To find a vector pointing from `A` to `B` use `B - A`.
|
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
|
A vector with **magnitude** of `1` is called a **unit vector**. They are
|
||||||
also sometimes referred to as **direction vectors** or **normals**. Unit
|
also sometimes referred to as **direction vectors** or **normals**. Unit
|
||||||
vectors are helpful when you need to keep track of a direction.
|
vectors are helpful when you need to keep track of a direction.
|
||||||
|
|
||||||
Normalization
|
## Normalization
|
||||||
-------------
|
|
||||||
|
|
||||||
**Normalizing** a vector means reducing its length to `1` while
|
**Normalizing** a vector means reducing its length to `1` while
|
||||||
preserving its direction. This is done by dividing each of its components
|
preserving its direction. This is done by dividing each of its components
|
||||||
@ -184,14 +172,13 @@ gdscript GDScript
|
|||||||
a = a.normalized()
|
a = a.normalized()
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
Warning:
|
Warning:
|
||||||
|
|
||||||
Because normalization involves dividing by the vector's length,
|
Because normalization involves dividing by the vector's length,
|
||||||
you cannot normalize a vector of length `0`. Attempting to
|
you cannot normalize a vector of length `0`. Attempting to
|
||||||
do so will result in an error.
|
do so will result in an error.
|
||||||
|
|
||||||
Reflection
|
## Reflection
|
||||||
----------
|
|
||||||
|
|
||||||
A common use of unit vectors is to indicate **normals**. Normal
|
A common use of unit vectors is to indicate **normals**. Normal
|
||||||
vectors are unit vectors aligned perpendicularly to a surface, defining
|
vectors are unit vectors aligned perpendicularly to a surface, defining
|
||||||
@ -222,8 +209,7 @@ gdscript GDScript
|
|||||||
move_and_collide(reflect)
|
move_and_collide(reflect)
|
||||||
```
|
```
|
||||||
|
|
||||||
Dot product
|
### Dot product
|
||||||
~~~~~~~~~~~
|
|
||||||
|
|
||||||
The **dot product** is one of the most important concepts in vector math,
|
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
|
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°)
|
When using unit vectors, the result will always be between `-1` (180°)
|
||||||
and `1` (0°).
|
and `1` (0°).
|
||||||
|
|
||||||
Facing
|
## Facing
|
||||||
------
|
|
||||||
|
|
||||||
We can use this fact to detect whether an object is facing toward another
|
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
|
object. In the diagram below, the player `P` is trying to avoid the
|
||||||
@ -284,8 +269,7 @@ gdscript GDScript
|
|||||||
print("A sees P!")
|
print("A sees P!")
|
||||||
```
|
```
|
||||||
|
|
||||||
Cross product
|
### Cross product
|
||||||
~~~~~~~~~~~~~
|
|
||||||
|
|
||||||
Like the dot product, the **cross product** is an operation on two vectors.
|
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
|
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
|
give the same result as `b.cross(a)`. The resulting vectors
|
||||||
point in **opposite** directions.
|
point in **opposite** directions.
|
||||||
|
|
||||||
Calculating normals
|
## Calculating normals
|
||||||
-------------------
|
|
||||||
|
|
||||||
One common use of cross products is to find the surface normal of a plane
|
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
|
or surface in 3D space. If we have the triangle `ABC` we can use vector
|
||||||
@ -341,8 +324,7 @@ gdscript GDScript
|
|||||||
return normal
|
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
|
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.
|
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
|
target direction. The resulting perpendicular vector is the axis of
|
||||||
rotation.
|
rotation.
|
||||||
|
|
||||||
More information
|
### More information
|
||||||
~~~~~~~~~~~~~~~~
|
|
||||||
|
|
||||||
For more information on using vector math in Pandemonium, see the following articles:
|
For more information on using vector math in Pandemonium, see the following articles:
|
||||||
|
|
||||||
|
@ -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.
|
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.
|
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
|
Vector types (`Vector2`) can also be interpolated, they come with handy functions to do it
|
||||||
`Vector2.linear_interpolate()`.
|
`Vector2.linear_interpolate()`.
|
||||||
@ -50,8 +48,7 @@ It will produce the following motion:
|
|||||||
|
|
||||||
![](img/interpolation_vector.gif)
|
![](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).
|
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.
|
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)
|
![](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:
|
Interpolation can be used to smooth movement, rotation, etc. Here is an example of a circle following the mouse using smoothed motion:
|
||||||
|
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
|
|
||||||
|
|
||||||
Random number generation
|
# Random number generation
|
||||||
========================
|
|
||||||
|
|
||||||
Many games rely on randomness to implement core game mechanics. This page
|
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
|
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:
|
Note:
|
||||||
|
|
||||||
|
|
||||||
Computers cannot generate "true" random numbers. Instead, they rely on
|
Computers cannot generate "true" random numbers. Instead, they rely on
|
||||||
`pseudorandom number generators
|
`pseudorandom number generators
|
||||||
( https://en.wikipedia.org/wiki/Pseudorandom_number_generator )` (PRNGs).
|
( 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
|
Pandemonium exposes two ways to generate random numbers: via *global scope* methods or
|
||||||
using the `RandomNumberGenerator` class.
|
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
|
This tutorial uses global scope methods, except when the method only exists in
|
||||||
the RandomNumberGenerator class.
|
the RandomNumberGenerator class.
|
||||||
|
|
||||||
The randomize() method
|
## The randomize() method
|
||||||
----------------------
|
|
||||||
|
|
||||||
In global scope, you can find a `randomize()
|
In global scope, you can find a `randomize()
|
||||||
( @GDScript_method_randomize )` method. **This method should be called only
|
( @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
|
Let's look at some of the most commonly used functions and methods to generate
|
||||||
random numbers in Pandemonium.
|
random numbers in Pandemonium.
|
||||||
@ -142,8 +137,7 @@ gdscript GDScript
|
|||||||
print(random.randi_range(-10, 10))
|
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:
|
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
|
limited set of values. To prevent this, use the `shuffle bag
|
||||||
( doc_random_number_generation_shuffle_bags )` pattern instead.
|
( 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:
|
We can apply similar logic from arrays to dictionaries as well:
|
||||||
|
|
||||||
@ -235,10 +228,7 @@ gdscript GDScript
|
|||||||
return random_metal
|
return random_metal
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Weighted random probability
|
||||||
|
|
||||||
Weighted random probability
|
|
||||||
---------------------------
|
|
||||||
|
|
||||||
The `randf()` method returns a
|
The `randf()` method returns a
|
||||||
floating-point number between 0.0 and 1.0. We can use this to create 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"
|
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.
|
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
|
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
|
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.
|
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
|
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,
|
value that *slowly* changes depending on the input. The input can be a position,
|
||||||
|
@ -1,10 +1,8 @@
|
|||||||
|
|
||||||
|
|
||||||
Matrices and transforms
|
# Matrices and transforms
|
||||||
=======================
|
|
||||||
|
|
||||||
Introduction
|
## Introduction
|
||||||
------------
|
|
||||||
|
|
||||||
Before reading this tutorial, we recommend that you thoroughly read
|
Before reading this tutorial, we recommend that you thoroughly read
|
||||||
and understand the `doc_vector_math` tutorial, as this tutorial
|
and understand the `doc_vector_math` tutorial, as this tutorial
|
||||||
@ -30,8 +28,7 @@ Note:
|
|||||||
to match these conventions, but we will also represent
|
to match these conventions, but we will also represent
|
||||||
the origin vector with a blue color.
|
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,
|
The identity matrix represents a transform with no translation,
|
||||||
no rotation, and no scale. Let's start by looking at the identity
|
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`
|
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.
|
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.
|
Applying a scale is one of the easiest operations to understand.
|
||||||
Let's start by placing the Pandemonium logo underneath our vectors
|
Let's start by placing the Pandemonium logo underneath our vectors
|
||||||
@ -95,8 +91,7 @@ Note:
|
|||||||
In actual projects, you can use the `scaled()`
|
In actual projects, you can use the `scaled()`
|
||||||
method to perform scaling.
|
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
|
We'll start the same way as earlier, with the Pandemonium logo underneath
|
||||||
the identity matrix:
|
the identity matrix:
|
||||||
@ -172,8 +167,7 @@ Note:
|
|||||||
In actual projects, you can use the `rotated()`
|
In actual projects, you can use the `rotated()`
|
||||||
method to perform rotations.
|
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
|
So far we have only been working with the `x` and `y`, vectors, which
|
||||||
are in charge of representing rotation, scale, and/or shearing
|
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
|
it from `Transform` (which is composed of one
|
||||||
`Basis` and one extra `Vector3` for the origin).
|
`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
|
Changing the `origin` vector is called a *translating* the transformation
|
||||||
matrix. Translating is basically a technical term for "moving" the
|
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
|
Pandemonium's 2D uses coordinates based on pixels, so in actual
|
||||||
projects you will want to translate by hundreds of units.
|
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.
|
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
|
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.
|
transform = t # Change the node's transform to what we just calculated.
|
||||||
```
|
```
|
||||||
|
|
||||||
Shearing the transformation matrix (advanced)
|
### Shearing the transformation matrix (advanced)
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
||||||
|
|
||||||
Note:
|
Note:
|
||||||
If you are only looking for how to *use* transformation matrices,
|
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:
|
3Blue1Brown's excellent video about linear transformations:
|
||||||
https://www.youtube.com/watch?v=kYB8IZa5AuE
|
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
|
In actual projects, you will usually be working with transforms inside
|
||||||
transforms by having multiple `Node2D` or `Spatial`
|
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
|
need. We will go over how you could use `Transform2D` or
|
||||||
`Transform` to manually calculate transforms of nodes.
|
`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
|
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
|
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"
|
(0, 0), you can use the "basis_xform" or "basis_xform_inv"
|
||||||
methods instead, which skip dealing with translation.
|
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
|
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
|
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
|
In actual projects, you can use `translate_object_local` in 3D
|
||||||
or `move_local_x` and `move_local_y` in 2D to do this.
|
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
|
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
|
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:
|
3Blue1Brown's excellent video about matrix composition:
|
||||||
https://www.youtube.com/watch?v=XkY2DOUCWMU
|
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
|
The "affine_inverse" function returns a transform that "undoes" the
|
||||||
previous transform. This can be useful in some situations, but it's
|
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.
|
# 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
|
One of the great things about transformation matrices is that they
|
||||||
work very similarly between 2D and 3D transformations.
|
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:
|
3Blue1Brown's excellent video about 3D linear transformations:
|
||||||
https://www.youtube.com/watch?v=rHLEWRxRGiM
|
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
|
The biggest difference between 2D and 3D transformation matrices is
|
||||||
how you represent rotation by itself without the basis vectors.
|
how you represent rotation by itself without the basis vectors.
|
||||||
|
@ -1,7 +1,5 @@
|
|||||||
|
|
||||||
|
# Beziers, curves and paths
|
||||||
Beziers, curves and paths
|
|
||||||
=========================
|
|
||||||
|
|
||||||
Bezier curves are a mathematical approximation of natural geometric shapes. We
|
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
|
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
|
understand how Bezier curves work, let's start from its simplest form: Quadratic
|
||||||
Bezier.
|
Bezier.
|
||||||
|
|
||||||
Quadratic Bezier
|
## Quadratic Bezier
|
||||||
----------------
|
|
||||||
|
|
||||||
Take three points, the minimum required for Quadratic Bezier to work:
|
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)*
|
*(Image credit: Wikipedia)*
|
||||||
|
|
||||||
Cubic Bezier
|
## Cubic Bezier
|
||||||
------------
|
|
||||||
|
|
||||||
Building upon the previous example, we can get more control by interpolating
|
Building upon the previous example, we can get more control by interpolating
|
||||||
between four points.
|
between four points.
|
||||||
@ -124,8 +120,7 @@ Note:
|
|||||||
Cubic Bezier interpolation works the same in 3D, just use `Vector3`
|
Cubic Bezier interpolation works the same in 3D, just use `Vector3`
|
||||||
instead of `Vector2`.
|
instead of `Vector2`.
|
||||||
|
|
||||||
Adding control points
|
## Adding control points
|
||||||
---------------------
|
|
||||||
|
|
||||||
Building upon Cubic Bezier, we can change the way two of the points work to
|
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`
|
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
|
This is how graphics software presents Bezier curves to the users, and how they
|
||||||
work and look in Pandemonium.
|
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).
|
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.
|
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.
|
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.
|
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.
|
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
|
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.
|
`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.
|
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.
|
||||||
|
|
||||||
|
@ -1,10 +1,8 @@
|
|||||||
|
|
||||||
|
|
||||||
Advanced vector math
|
# Advanced vector math
|
||||||
====================
|
|
||||||
|
|
||||||
Planes
|
### Planes
|
||||||
~~~~~~
|
|
||||||
|
|
||||||
The dot product has another interesting property with unit vectors.
|
The dot product has another interesting property with unit vectors.
|
||||||
Imagine that perpendicular to that vector (and through the origin)
|
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
|
sheet of paper that you can orient and is pinned to the origin) instead
|
||||||
of a line.
|
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.
|
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**
|
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.
|
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
|
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
|
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
|
distance from a point to a plane? It's extremely useful! Let's see some
|
||||||
simple examples..
|
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.
|
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
|
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
|
Doing the same in 3D is a little more complex and will be explained
|
||||||
further down.
|
further down.
|
||||||
|
|
||||||
Some examples of planes
|
## Some examples of planes
|
||||||
-----------------------
|
|
||||||
|
|
||||||
Here is a simple example of what planes are useful for. Imagine you have
|
Here is a simple example of what planes are useful for. Imagine you have
|
||||||
a `convex ( https://www.mathsisfun.com/definitions/convex.html )`
|
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
|
smaller convex polygons, or using a technique such as BSP (which is not
|
||||||
used much nowadays).
|
used much nowadays).
|
||||||
|
|
||||||
Collision detection in 3D
|
### Collision detection in 3D
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
||||||
|
|
||||||
This is another bonus bit, a reward for being patient and keeping up
|
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
|
with this long tutorial. Here is another piece of wisdom. This might
|
||||||
@ -377,8 +370,7 @@ gdscript GDScript
|
|||||||
print("Polygons collided!")
|
print("Polygons collided!")
|
||||||
```
|
```
|
||||||
|
|
||||||
More information
|
### More information
|
||||||
~~~~~~~~~~~~~~~~
|
|
||||||
|
|
||||||
For more information on using vector math in Pandemonium, see the following article:
|
For more information on using vector math in Pandemonium, see the following article:
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user