Tutorial 2
Our First System
In this tutorial, we will create our first System
in engo
. We will also be using basic input methods such as the
mouse and keyboard.
Recap
Remember what we did in the last tutorial?
We created aScene
which renders a huge City icon onto the screen.
Final Code
The final code for tutorial 2 is available here at GitHub.
Step 1: The Concept
Before creating any System
, it is often good to ask yourself: what is the purpose (or task) of this System
? As
we are building a traffic-managing game which has multiple cities, it would make sense to create a system which creates
new cities.
To keep it simple for the sake of this tutorial, we shall create a System
which creates a new City at the location
of the cursor, whenever someone presses F1.
Step 2: Defining and Adding our System
Defining the System
Before adding any functions, let’s start by creating a dummy System
and adding it to our game.
A System
is anything that implements all these functions:
Let’s start off with a very simple implementation of this interface:
Our CityBuildingSystem
builds Cities on top of the cursor. This requires use of the keyboard and mouse. At first,
we don’t need to worry about entities, so we’ll ignore the Remove
function for now. We haven’t added any entities,
so we can safely ignore removing them.
Let’s keep code quality in mind, and create a separate package for our Systems, called systems
. This means we create
the directory $GOPATH/github.com/EngoEngine/TrafficManager/systems
. Within this, we create a new file, with the
exact contents of the CityBuildingSystem
described above. However, we do add package systems
to the top of the file.
Adding the System
Now that we’ve created the System
, let’s add it to our game. The usual way to add System
s to a game, is by doing
it within the Setup
function of the Scene
you’re going to use. Since we don’t want this to interfere with the
big City icon we created in the previous tutorial, we are not only going to add the System
, but also remove
the Entity
we created in the last tutorial.
How do we know it works?
At the moment, we can’t be sure it actually works, right? Each system can optionally implement
New(*ecs.World)
, which will be called whenever the System is added to the scene.Let’s create a
New
function for ourCityBuildingSystem
like this:
When we now run our game, we can see the white background, no City icon (because we removed it), and the console outputs “CityBuildingSystem was added to the Scene”.
Step 3: Figuring out when F1 is pressed
We want to spawn a City whenever someone presses F1, so it makes sense that we want to know when this happens.
First, we need to tell the Engo to listen for the F1 key press. We’ll do this by using the
engo.Input.RegisterButton(name string, keys Key...)
function. Multiple keys can be assigned to one identifier.
Add the following line to the Setup
function for your Scene
.
We will check “did the gamer press F1”, on every frame. The Update
function of our CityBuildingSystem
gets called every frame by the World
. So our checking-code needs to be written there. Engo has a neat feature which
allows you to lookup the state of keys, such as:
Down()
: when the button is pressed; will be true as long as the user holds the button,JustPressed()
: when the button was just pressed; will be true for one frame, and cannot become true again unless the button was released first,JustReleased()
: same asJustPressed()
, but with releasing the button instead of pressing it,
We don’t want to risk placing two cities on top of each other in a very short time period (it’s very likely that the
key is pressed longer than one frame, because a frame usually lasts between 16.6ms and 6.94ms). Therefore, we shall use
the JustPressed()
function.
How do we know it works?
Let’s run the game, and press F1 and find out! It should print “The gamer pressed F1” just once, when we hold the F1-key. It should print it twice when we double-tap it. It shouldn’t print it as long as we don’t press F1.
Spawning a City
Now we know when to spawn a City, we can actually write the code to do so.
Remember the code we used for the large City-icon we removed?
There are a few things we want to change, before using this in our CityBuildingSystem
:
- The size: 303 by 641 is a bit too big. Let’s change this to 30 by 64.
- The location: we want to spawn it at the location of the cursor
- The
world
is unknown in theUpdate
function, so we have to work around that
The size
As stated, we can easily change the size, by changing the numbers. However, changing the values at the
SpaceComponent
isn’t enough. The texture is still way too big; we can change this by setting the scale to 0.1
instead of 1
:
The location
In order to spawn them at the correct location, we need to know where the cursor is. Our first guess might be to use
the engo.Input.Mouse
struct which is available. However, this one returns the actual (X, Y)
location relative to the
screen size, not the in-game grid system. We have a special MouseSystem
available for just that.
The MouseSystem
The first thing you want to do, is add the MouseSystem
to your Scene
:
Note that we added the other systems before the CityBuildingSystem
. This is to ensure any systems we might depend
upon, are already initialized when we’re initializing the CityBuildingSystem
.
The MouseSystem
is mainly written to keep track of mouse-events for entities; you can check whether your Entity
has been hovered, clicked, dragged, etc. In order to use it, we therefore need an Entity
which uses the MouseSystem
.
This one needs to hold a MouseComponent
, at which the results/data will be saved.
We first will update our CityBuildingSystem
to contain the new Entity
:
Then, we want to ensure this mouseTracker
entity gets initialized and added to the World
whenever we start using
this CityBuildingSystem
:
Note that we added the Track: true
variable to the MouseComponent
. This allows us to know the position of the
mouse, regardless of where it is. If we were to leave it at false
(the default), it would only contain anything
useful if the mouse was hovering the Entity
. We are also giving two parameters nil
within the Add
function
of the MouseSystem
. That is because (as described in the documentation of that Add
function), one can also provide
a SpaceComponent
and RenderComponent
, if one wants to know specifics about that particular entity (like
hover-events). This is not our intention at the moment, we just want to know about the position of the cursor. Therefore
we can safely pass nil
for those two parameters.
Getting information from the MouseSystem
The MouseSystem
updates the mouseTracker.MouseComponent
every frame. Everything we need to know, is in there.
But we still don’t have a world
reference within our Update
function. We are given one inside our New
function,
so let’s save that reference.
Now, we can reference it by saying cb.world
, within our Update
function.
Now, if we were to run this whole project, and place a few cities using the F1-key, we would get something like this: