We’ve given the Stirling engine its own inventory slot, and it takes nothing but fuel.
All that’s left is to connect the pieces with some coding glue to consume a piece of fuel and tie the engine’s functionality into its fuel consumption.
When the engine’s inventory slot detects a new item, it should:
GUIComponent
to emit the gui_status_changed
signal.After some time, based on what we’re burning, that energy runs out. When it reaches 0, we check if there’s still something in the inventory.
If not, then we shut down the engine. Otherwise, we burn another item and repeat the process.
We put everything in _ready()
to test the power system, but now that its functionality is fuel-bound, we’ll extract the code into functions.
We’ll make most of the work happen in a pair of functions: _setup_work()
and _consume_fuel()
.
Let’s start with the latter. Open up StirlingEngineEntity.gd
to get coding burning fuel.
First, remove the _ready()
function, and then you can add the following code to the class.
## How much time in seconds we have left with the last piece of consumed fuel. var available_fuel := 0.0 ## How much maximum time the last piece of fuel we consumed provided ## We store this value to calculate the percentage of fuel left to burn and ## update the bar. var last_max_fuel := 0.0 onready var gui := $GUIComponent ## Either removes `amount` of fuel from the available fuel, or attempts to take ## a piece of item from its inventory to refill the available fuel. func _consume_fuel(amount: float) -> void: available_fuel = max(available_fuel - amount, 0.0) # If we run out of fuel, and there is something burn-able in the inventory if available_fuel <= 0.0 and gui.gui.fuel: # We get the amount it should give from the Recipes static class and # reset the available fuel to that amount. last_max_fuel = Recipes.Fuels[Library.get_entity_name_from(gui.gui.fuel)] available_fuel = last_max_fuel # We reduce the stack by 1. If it becomes 0, we destroy it. Otherwise, # we update its inventory label to show the reduction. gui.gui.fuel.stack_count -= 1 if gui.gui.fuel.stack_count == 0: gui.gui.fuel.queue_free() gui.gui.fuel = null else: gui.gui.update_labels() else: # If we still have fuel left, we configure the power source and ensure # animations are running. _setup_work() # We set the shader on the inventory to the percentage of remaining fuel # compared to the maximum for the piece of fuel we last consumed. gui.gui.set_fuel((available_fuel / last_max_fuel) if last_max_fuel > 0.0 else 0.0)
With that, when we want to eat a piece of fuel to power the Stirling engine, we’ll take one off the available stack of items.
The next function, _setup_work()
, controls the machine starting, animating, and stopping. To manage the entity’s state, we use the animation player and check for available fuel.
## Plays animations and updates the power source's `efficiency` via the ## `_update_efficiency` method. func _setup_work() -> void: # If the animation player is not playing and we have fuel available, we # first play the piston animation. if not animation_player.is_playing() and (gui.gui.fuel or available_fuel > 0.0): animation_player.play("Work") # We configure the tween to ramp up the animation speed, ramp up the # power source's `efficiency`, and change the shaft color. tween.interpolate_property(animation_player, "playback_speed", 0, 1, BOOTUP_TIME) tween.interpolate_method(self, "_update_efficiency", 0, 1, BOOTUP_TIME) tween.interpolate_property(shaft, "modulate", Color.white, Color(0.5, 1, 0.5), BOOTUP_TIME) tween.start() # We call `_consume_fuel()` with a value of 0 to trigger the update # cycle that takes one of the burnable fuels from the inventory slot. _consume_fuel(0.0) # If the animation is playing and we do *not* have fuel available. elif ( animation_player.is_playing() and animation_player.current_animation == "Work" and not (gui.gui.fuel or available_fuel > 0.0) ): # We first temporarily turn off animation looping. # # This allows the animation player to send the "animation_finished" # signal and puts the piston into the lower position. # # This is because our shutdown animation starts with the piston in the # lower position, and if we played the animation without accounting # for that, it'd snap into place and that would look wrong. var work_animation: Animation = animation_player.get_animation( animation_player.current_animation ) work_animation.loop = false # We yield to make sure the piston does one full animation cycle before # we shut down. yield(animation_player, "animation_finished") work_animation.loop = true # Then, we play shutdown, reset the color, and ramp the power source # efficiency down to 0. animation_player.play("Shutdown") animation_player.playback_speed = 1.0 tween.interpolate_property(shaft, "modulate", shaft.modulate, Color(1, 1, 1), SHUTDOWN_TIME) tween.interpolate_method(self, "_update_efficiency", 1, 0, SHUTDOWN_TIME) tween.start()
The “Shutdown” animation is a new animation to give a visual slow-down effect.
To trigger those functions, we need to connect the PowerSource
’s power_updated
signal and the GUIComponent
’s gui_status_changed
to the StirlingEngineEntity
.
Connect both components’ signals to the root node.
In StirlingEngineEntity.gd
, we call _setup_work()
and _consume_fuel()
through those signal callbacks.
func _on_GUIComponent_gui_status_changed() -> void: _setup_work() func _on_PowerSource_power_updated(power_draw, delta) -> void: _consume_fuel(delta)
The Stirling engine will now wait to receive fuel to refill itself and start to run.
When it runs out of items to consume, it should shut down and reduce the amount of power it’s outputting down to 0.
No more breaking the laws of physics! Perpetual motion machines do not exist.
We’re in the home stretch now.
The next and final lessons focus on a new system to automate crafting with machines. I called it the work system as it allows machines to craft items, that is, to do some work for us.
With that, they’ll take that useless ore we can mine and turn it into useful ingots.
To that end, we’ll create two new coal-based and electric furnaces.
Here’s the complete StirlingEngineEntity.gd
script.
extends Entity const BOOTUP_TIME := 6 const SHUTDOWN_TIME := 3 var available_fuel := 0.0 var last_max_fuel := 0.0 onready var animation_player := $AnimationPlayer onready var tween := $Tween onready var shaft := $PistonShaft onready var power := $PowerSource onready var gui := $GUIComponent func get_info() -> String: return "%.1f j/s" % power.get_effective_power() func _update_efficiency(value: float) -> void: power.efficiency = value Events.emit_signal("info_updated", self) func _consume_fuel(amount: float) -> void: available_fuel = max(available_fuel - amount, 0.0) if available_fuel <= 0.0 and gui.gui.fuel: last_max_fuel = Recipes.Fuels[Library.get_entity_name_from(gui.gui.fuel)] available_fuel = last_max_fuel gui.gui.fuel.stack_count -= 1 if gui.gui.fuel.stack_count == 0: gui.gui.fuel.queue_free() gui.gui.fuel = null else: gui.gui.update_labels() else: _setup_work() gui.gui.set_fuel((available_fuel / last_max_fuel) if last_max_fuel > 0.0 else 0.0) func _setup_work() -> void: if not animation_player.is_playing() and (gui.gui.fuel or available_fuel > 0.0): animation_player.play("Work") tween.interpolate_property(animation_player, "playback_speed", 0, 1, BOOTUP_TIME) tween.interpolate_method(self, "_update_efficiency", 0, 1, BOOTUP_TIME) tween.interpolate_property(shaft, "modulate", Color.white, Color(0.5, 1, 0.5), BOOTUP_TIME) tween.start() _consume_fuel(0.0) elif ( animation_player.is_playing() and animation_player.current_animation == "Work" and not (gui.gui.fuel or available_fuel > 0.0) ): var work_animation: Animation = animation_player.get_animation( animation_player.current_animation ) work_animation.loop = false yield(animation_player, "animation_finished") work_animation.loop = true animation_player.play("Shutdown") animation_player.playback_speed = 1.0 tween.interpolate_property(shaft, "modulate", shaft.modulate, Color(1, 1, 1), SHUTDOWN_TIME) tween.interpolate_method(self, "_update_efficiency", 1, 0, SHUTDOWN_TIME) tween.start() func _on_GUIComponent_gui_status_changed() -> void: _setup_work() func _on_PowerSource_power_updated(power_draw, delta) -> void: _consume_fuel(delta)