Creating the Stirling engine

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.

The base Entity class

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 Stirling engine

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:

  1. Assign the tileset.svg image to its Texture slot.
  2. Turn on Region -> Enabled.
  3. In the bottom panel, expand the TextureRegion editor and set its Snap Mode to Auto Slice. This uses an algorithm that detects space around images and splits it accordingly.

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.

Animating the piston

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.

The StirlingEngine class

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

Placing the engine in the world

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.

Blueprint entities

As noted earlier, an entity consists of two parts:

  1. The entity as it appears in the game world.
  2. The blueprint that represents it in the inventory or when hovering the grid to choose where to place the entity.

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.

The BlueprintEntity base class

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.

The Stirling engine blueprint

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.

Code reference

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