Spawning monsters¶
En esta parte, generaremos monstruos aleatoriamente según la ruta. Cuando termines, tendrás monstruos vagando la tablero.
Double-click on Main.tscn
in the FileSystem dock to open the Main scene.
Antes de dibujando el camino, vamos a cambiar el resolución por el juego. Nuestro juego tiene un tamaño defecto de ventana a 1024x600
. Vamos a cambiarla a 720x540`, un poca más pequeña.
Go to Project -> Project Settings.
In the left menu, navigate down to Display -> Window. On the right, set the
Width to 720
and the Height to 540
.
Creating the spawn path¶
Like you did in the 2D game tutorial, you're going to design a path and use a PathFollow node to sample random locations on it.
Pero en 3D, es un poco mas complicado dibujar la ruta. Queremos que esté alrededor de la vista, para que los monstruos aparezcan un poco fuera de la escena. Pero si dibujamos una ruta, no la podremos ver en la vista previa de la cámara.
To find the view's limits, we can use some placeholder meshes. Your viewport should still be split into two parts, with the camera preview at the bottom. If that isn't the case, press Ctrl + 2 (Cmd + 2 on macOS) to split the view into two. Select the Camera node and click the Preview checkbox in the bottom viewport.
Adding placeholder cylinders¶
Let's add the placeholder meshes. Add a new Spatial node as a child of the Main node and name it Cylinders. We'll use it to group the cylinders. As a child of it, add a MeshInstance node.
In the Inspector, assign a CylinderMesh to the Mesh property.
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.
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.
You now want to move the cylinder along the ground plane, looking at the camera preview in the bottom viewport. I recommend using grid snap to do so. You can toggle it by clicking the magnet icon in the toolbar or pressing Y.
Place the cylinder so it's right outside the camera's view in the top-left corner.
We're going to create copies of the mesh and place them around the game area. Press Ctrl + D (Cmd + D on macOS) to duplicate the node. You can also right-click the node in the Scene dock and select Duplicate. Move the copy down along the blue Z axis until it's right outside the camera's preview.
Select both cylinders by pressing the Shift key and clicking on the unselected one and duplicate them.
Muévelos a la derecha por arrastrando el eje X en rojo.
¿Es un poco difícil verlos sobre blanco, no? Vamos hacer que resalten con un nuevo material.
En 3D, los materiales definen las propiedades visuales de una superficie como el color, cómo refleja la luz, y más. Podemos usarlos para cambiar el color de una malla.
Podemos actualizar los cuatro cilintros a la vez. Seleciona todas las instancias de malla en el panel de Escena. Para ello, haz clic en la primera y Shift clic en la última.
En el Inspector, espande la sección Material y asigna un SpatialMaterial a la ranura 0.
Haga clic en el icono esfera para abrir el recurso material. Obtiene una vista previa por el material y una lista larga de secciones con propiedades. Puede usarlos para crear todos tipos de superficies, como metal, roca, y agua.
Expand the Albedo section and set the color to something that contrasts with the background, like a bright orange.
We can now use the cylinders as guides. Fold them in the Scene dock by clicking the grey arrow next to them. Moving forward, you can also toggle their visibility by clicking the eye icon next to Cylinders.
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.
Nota
You can hover any icon to see a tooltip describing the tool.
Click in the center of each cylinder to create a point. Then, click the Close Curve icon in the toolbar to close the path. If any point is a bit off, you can click and drag on it to reposition it.
Tu ruta debería verse así.
To sample random positions on it, we need a PathFollow node. Add a PathFollow as a child of the Path. Rename the two nodes to SpawnPath and SpawnLocation, respectively. It's more descriptive of what we'll use them for.
Ahora, estamos listos para programar el mecanismo de generación.
Spawning monsters randomly¶
Haz clic derecho sobre el nodo Main y adjúntale un nuevo script.
We first export a variable to the Inspector so that we can assign Mob.tscn
or any other monster to it.
Entonces, porque generaramos aleatoriamente los monstruos, queremos aleatorizar los numeros cada vez que empezamos el juego. Si no, los monstruos siempre genararían con la misma secuencia.
extends Node
export (PackedScene) var mob_scene
func _ready():
randomize()
public class Main : Node
{
// Don't forget to rebuild the project so the editor knows about the new export variable.
#pragma warning disable 649
// We assign this in the editor, so we don't need the warning about not being assigned.
[Export]
public PackedScene MobScene;
#pragma warning restore 649
public override void _Ready()
{
GD.Randomize();
}
}
We want to spawn mobs at regular time intervals. To do this, we need to go back
to the scene and add a timer. Before that, though, we need to assign the
Mob.tscn
file to the mob_scene
property.
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.
Add a new Timer node as a child of Main. Name it MobTimer.
In the Inspector, set its Wait Time to 0.5
seconds and turn on
Autostart so it automatically starts when we run the game.
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.
We can connect to this signal from the Main node to spawn monsters every
0.5
seconds.
With the MobTimer still selected, head to the Node dock on the right and
double-click the timeout
signal.
Conéctelo al nodo Main.
This will take you back to the script, with a new empty
_on_MobTimer_timeout()
function.
Let's code the mob spawning logic. We're going to:
Instantiate the mob scene.
Sample a random position on the spawn path.
Get the player's position.
Call the mob's
initialize()
method, passing it the random position and the player's position.Add the mob as a child of the Main node.
func _on_MobTimer_timeout():
# Create a new instance of the Mob scene.
var mob = mob_scene.instance()
# Choose a random location on the SpawnPath.
# We store the reference to the SpawnLocation node.
var mob_spawn_location = get_node("SpawnPath/SpawnLocation")
# And give it a random offset.
mob_spawn_location.unit_offset = randf()
var player_position = $Player.transform.origin
mob.initialize(mob_spawn_location.translation, player_position)
add_child(mob)
// We also specified this function name in PascalCase in the editor's connection window
public void OnMobTimerTimeout()
{
// Create a new instance of the Mob scene.
Mob mob = (Mob)MobScene.Instance();
// Choose a random location on the SpawnPath.
// We store the reference to the SpawnLocation node.
var mobSpawnLocation = GetNode<PathFollow>("SpawnPath/SpawnLocation");
// And give it a random offset.
mobSpawnLocation.UnitOffset = GD.Randf();
Vector3 playerPosition = GetNode<Player>("Player").Transform.origin;
mob.Initialize(mobSpawnLocation.Translation, playerPosition);
AddChild(mob);
}
Above, randf()
produces a random value between 0
and 1
, which is
what the PathFollow node's unit_offset
expects.
Here is the complete Main.gd
script so far, for reference.
extends Node
export (PackedScene) var mob_scene
func _ready():
randomize()
func _on_MobTimer_timeout():
var mob = mob_scene.instance()
var mob_spawn_location = get_node("SpawnPath/SpawnLocation")
mob_spawn_location.unit_offset = randf()
var player_position = $Player.transform.origin
mob.initialize(mob_spawn_location.translation, player_position)
add_child(mob)
public class Main : Node
{
#pragma warning disable 649
[Export]
public PackedScene MobScene;
#pragma warning restore 649
public override void _Ready()
{
GD.Randomize();
}
public void OnMobTimerTimeout()
{
Mob mob = (Mob)MobScene.Instance();
var mobSpawnLocation = GetNode<PathFollow>("SpawnPath/SpawnLocation");
mobSpawnLocation.UnitOffset = GD.Randf();
Vector3 playerPosition = GetNode<Player>("Player").Transform.origin;
mob.Initialize(mobSpawnLocation.Translation, playerPosition);
AddChild(mob);
}
}
You can test the scene by pressing F6. You should see the monsters spawn and move in a straight line.
For now, they bump and slide against one another when their paths cross. We'll address this in the next part.