In this lesson, we’ll create our first entity the player can place on the ground: the Stirling engine.
We’ll first write an Entity
base class that all entities will extend, then code the Stirling engine, create the grid we’ll use to place entities in the world, and finally code the blueprints’ foundations.
An entity is an object that exists in the physical world and that the player can interact with.
Engines, furnaces, and other machines are entities, as are trees, rocks, and branches. Every entity will have an associated Blueprint
object that we’ll use to display an icon in the inventory and drag and place entities into the game world.
Entities will be able to belong to groups and optionally have components that hold special meaning to those groups.
For example, an engine may have the PowerSource
component and belong to the power_sources
group. This will let the PowerSystem
identify it and its position as a power source to run down wires and power machines. We’ll build the power system in a future lesson.
All entities will derive from the same Entity class. Having a common ancestor allows scripts to identify entities using the is
keyword and gives us a type to specify for variables.
We will revisit this class later when we add a shader that lets us outline a selected entity, but for now, we’ll keep it simple.
Create a new Entity.gd
script in the FileSystem dock with the following code.
class_name Entity extends Node2D ## Specifies the object type that is allowed to deconstruct this entity. ## Deconstructing means harvesting a resource or turning an entity in the world into an item. ## For example, the player must hold an Axe to chop down a tree, ## so we'll store that requirement as a text string here. export var deconstruct_filter: String ## Specifies number of entities to create when deconstructing the object. ## For example, a tree could drop 5 logs. In that case, we'd set the `pickup_count` to `5`. var pickup_count := 1 ## Any initialization step occurs in this override-able `_setup()` function. Overriding it ## is optional, but if the entity requires information from the blueprint, ## such as the direction of power, this is where we will code provide this information. ## We haven't created the blueprint's type yet, so for now we leave it out. We'll code a `BlueprintEntity` type in a moment. func _setup(_blueprint) -> void: pass
The first entity we create is a power generator known as a Stirling engine. It features a gas trapped on either side of a piston in a pressurized chamber. By applying and exchanging heat between the two chambers, the piston moves up and down, which creates mechanical work we can convert into electricity.
Besides being a good test case for creating an entity and placing it in the world, it’ll also allow us to develop and test the power system in future lessons.
Entities that the player can bump into will be StaticBody2D
’s if they are not going to be moving. For most machines you place down, they will not move, but if your game has carts, you may opt for KinematicBody2D
for those.
Create a new scene with a StaticBody2D
named StirlingEngineEntity as its root.
We need four sprites in the sprite sheet to build the engine: the base, the back of the piston, the shaft, and the front of the piston.
To region out each sprite, you can use the sprite’s Region settings in the Inspector, or better, the texture region editor in the bottom panel.
The four sprites will use the same texture, so we’ll create one, set it up, and duplicate it.
Create a Sprite
node and:
tileset.svg
image to its Texture slot.The sprite’s texture should disappear. Duplicate the node three times.
For each of the sprites, click on the corresponding slice in the TextureRegion panel to assign it a texture. You want to assign these four, which you can find between the battery and the yellow direction arrows.
We can rename the sprites nodes to help us see the order in which we should have them: EngineBase, PistonBack, PistonShaft, PistonFront.
We position the base so that the origin is coarsely around the middle point of the base’s floor. This will let the player sort properly and appear in front of or behind the engine as they move against it.
To help prevent clipping accidents where the player manages to get a little too close, we add a CollisionPolygon2D
to the scene and add a thin wall to the back of the shape. It’s not physically accurate, but it helps maintain the illusion of 3D space.
You can see the final collision shape in red below. Note how the back is slightly inflated, so the player can’t move too much into the machine.
To actually plot the points, select the CollisionPolygon2D in the hierarchy, put the cursor in select mode, and use the Create Points tool to click on and lay out the collision shape, following the graphics. I highlighted both tool icons below.
The reason we split the machine into four sprites is to animate it. I wanted the piston to accelerate or decelerate when the machine starts and stops.
To do so, we can use an AnimationPlayer
to design the piston’s animation, and a Tween
to animate the AnimationPlayer’s playback_speed
property and the piston shaft’s color.
Add both an AnimationPlayer
and a Tween
node to the scene.
I designed the working animation “Work,” and it is a loop of the piston moving up and down.
To design the animation, we select the AnimationPlayer and create a new animation by clicking Animation -> New. We then select the PistonFront sprite, select the Animation section at the bottom (since we’re using regions, it may try to switch to TextureRegion automatically), and click the key next to Position in the inspector. Godot prompts us about creating a track for the position, which we can confirm.
We slide the playback head to 0.3 seconds and move the PistonFront sprite up to the top of the shaft and click the key button next to Position again. We move the playback head to 0.6 seconds and move the piston down before setting a key.
We repeat the operation with the PistonBack, moving it the same way as the PistonFront.
Let’s now animate our Stirling engine using a new script, StirlingEngineEntity.gd
. Attach it to the scene’s root node.
For now, we’ll only implement the animation.
We will revisit the engine in a future lesson, after coding the PowerSystem
, GUI
and more. But for now, we want the entity to animate to have something to show on screen and interact with. We will make it an infinite power supply for testing until we’ve implemented the inventory so it can require fuel.
## The Stirling engine consumes fuel and acts as a power source. extends Entity ## The following two constants are respectively the amount of time the tween animation takes ## to ramp up to full speed and to shutdown. ## We will use that to speed up or slow down the animation player const BOOTUP_TIME := 6.0 const SHUTDOWN_TIME := 3.0 onready var animation_player := $AnimationPlayer onready var tween := $Tween onready var shaft := $PistonShaft func _ready() -> void: # Play the animation, which loops. animation_player.play("Work") # We use a tween to control the animation player's `playback_speed`. # It goes up, making it feel like the engine is slowly starting up until it reaches its maximum speed. tween.interpolate_property(animation_player, "playback_speed", 0, 1, BOOTUP_TIME) # We also animate the color of the `shaft` to enhance the animation, going from white to green. tween.interpolate_property(shaft, "modulate", Color.white, Color(0.5, 1, 0.5), BOOTUP_TIME) tween.start()
We have one more task to get this engine positioned in the world and allow the player to walk around it.
In the main simulation scene, under the top YSort node, we create a TileMap
node that we call EntityPlacer. We won’t actually use it to place tiles, but the TileMap
class has useful functions to convert positions between map and world coordinates. We use them to snap entities to the grid.
This node’s job will be large as it takes care of positioning blueprints and entities as the player places them. For now, we need to change some properties: set the Mode to isometric, the Cell Size to 100
by 50
. Enable the Cell -> Y Sort property to make any tile and children of the tilemap get sorted. This feature works with the YSort parent to sort the player.
We instantiate the StirlingEngineEntity as a child of the EntityPlacer and position it somewhere in the world to check the animation out.
As noted earlier, an entity consists of two parts:
The blueprint graphics are brighter colored and are not split up, unlike the entity, since they are not animated. The sprite’s origin point is the same as the entity’s so the preview matches the placed object.
All blueprint entities will either use or inherit the BlueprintEntity
class below. Create a new BlueprintEntity.gd
script with the following code.
class_name BlueprintEntity extends Node2D ## Whether the player can place this object in the world. For example, a lumber axe. ## should not be 'placed' like a machine. export var placeable := true
To introduce a new blueprint, we will create a new scene with a Node2D
as its root with a child Sprite
node and any blueprint-specific information we may need later, like the orientation of power input and output.
We will revisit this script when we add inventory handling. Blueprints will represent the object, and we will need to know the number of items in the stack. But for now, we need little code in the class.
We can now create the StirlingEngineBlueprint
scene. It should have a BlueprintEntity
node named StirlingEngineBlueprint as its root and a Sprite
as a child of it. We can directly create a node of type BlueprintEntity
because we defined the class name above.
For the Sprite, we assign the blueprints.svg
to its Texture. We turn on its Region -> Enabled and, using the TextureRegion bottom panel and its Auto Slice snap mode, we can select the Stirling engine’s texture.
In the next lesson, we’ll start working on the EntityTracker
, which will track and snap the entities to the game grid.
Here are the scripts we added in this lesson. Starting with Entity.gd
.
class_name Entity extends Node2D export var deconstruct_filter: String var pickup_count := 1 func _setup(_blueprint) -> void: pass
Here is StirlingEngineEntity.gd
.
extends Entity const BOOTUP_TIME := 6.0 const SHUTDOWN_TIME := 3.0 onready var animation_player := $AnimationPlayer onready var tween := $Tween onready var shaft := $PistonShaft func _ready() -> void: animation_player.play("Work") tween.interpolate_property(animation_player, "playback_speed", 0, 1, BOOTUP_TIME) tween.interpolate_property(shaft, "modulate", Color.white, Color(0.5, 1, 0.5), BOOTUP_TIME) tween.start()
And BlueprintEntity.gd
.
class_name BlueprintEntity extends Node2D export var placeable := true