Step 2¶
In this chapter, we will be adding graphics to your game. You will be able to create a game window and start seeing your game come to life.
The UI Template¶
To start drawing the UI in shards, we will have to set up GFX window which serves as the base for our UI operations. It follows a fixed template that should be placed within our ui-loop
.
@define(main-game-ui {Pass}) ;; Add your UI shards here
@wire( ui-loop {
GFX.MainWindow(
Title: "Yes-No Game"
Width: 1280 Height: 768
Contents: {
Once({
GFX.DrawQueue >= ui-draw-queue
GFX.UIPass(ui-draw-queue) >> render-steps
})
ui-draw-queue | GFX.ClearQueue
UI(Contents: @main-game-ui) | UI.Render(ui-draw-queue)
GFX.Render(Steps: render-steps)
})
} Looped: true)
What does the GFX code mean?
How the GFX window works is beyond the scope of this tutorial. The rough idea is that we are giving instructions to the computer on what to draw to the screen. For more information, check out the documentation on the UI class.
Planning the UI¶
Before writing code for the UI, it is good to have a design in mind. For this tutorial, we have prepared a simple design to follow:
With a design plan, it will be easier to identify the elements that will make up your UI. You can then implement the appropriate shards to draw them.
The Bottom Panel¶
Let us start with the simplest panel, the Bottom Panel with a single Label.
We will pass a string of instructions into the UI.Label
shard, which we then pass into the Bottom Panel shard as its content.
UI.BottomPanel(;; (1)
Contents: {
"Are they the same image? Press the UP arrow if YES, and the DOWN arrow if NO."
UI.Label ;; (2)
})
UI.BottomPanel
is a UI element to hold other UI elements within it. It starts from the bottom of the Window.UI.Label
is a UI element to hold text.
The Top Panel¶
The Top Panel consists of a sequence of Labels and Separators in a Horizontal Group so that they are naturally aligned from left to right.
UI.TopPanel(;; (1)
Contents: {
UI.Horizontal( ;; (2)
Contents: {
"Score: 0" | UI.Label
UI.Separator ;; (3)
"Round: 1" | UI.Label
UI.Separator
"Time Left: 5" | UI.Label
})
})
UI.TopPanel
is a UI element to hold other UI elements within it. It starts from the top of the Window.UI.Horizontal
is a UI element to hold other UI elements within it. It aligns its elements horizontally, from left to right.UI.Separator
is a UI element that appears as a horizontal line within a vertical layout, and appears as a vertical line within a horizontal layout.
We want to be able to change the values used for Score, the Round Number, and Time Left (they can't remain static after all!).
This is where variables come in. We will be defining our variables in the initialize-variables
definition we created earlier.
Define the following variables:
-
total-score
-
current-round
-
time-remaining
Some values will remain the same throughout the game - these are known as constants.
Define the following constants:
-
total-rounds
-
max-timer
@define(total-rounds 10)
@define(max-timer 5)
@define(initialize-variables {
0 >= total-score
1 >= current-round
@max-timer >= time-remaining
})
We can now replace the fixed string numbers in our UI code with variables that will be updated as their values change.
UI.TopPanel(
Contents: {
UI.Horizontal(
Contents: {
"Score: " | UI.Label
total-score | ToString | UI.Label
UI.Separator
"Round: " | UI.Label
current-round | ToString | UI.Label
UI.Separator
"Time Left: " | UI.Label
time-remaining | ToString | UI.Label
})
})
ToString
converts values into strings. In the code above,UI.Label
expects a string input, which is why we have to convert the integer variables into strings.
The Central Panel¶
The central panel shows two images side by side. To draw the images onto the screen, we have to load them into our game's resources. We can do so in the load-resources
definition we created earlier.
Create a folder named "data" at the directory where your game's script is located - we will be placing our images here.
Note
For this tutorial, we will be using 3 images of cats. You are free to choose the images of your own accord though!
Our images have been standardized to have a length of 400 pixels for horizontal images. You can use an image editing tool to resize your images for consistency.
Use the Push
shard to push the images into a sequence.
@define(load-resources {
LoadImage("data/cats/cat01.png") | Push(Name: images) ;; (1)(2)
LoadImage("data/cats/cat02.png") | Push(Name: images)
LoadImage("data/cats/cat03.png") | Push(Name: images)
})
We will randomize the images shown when tackling the logic of the game later. For now, let us display the first two images in the sequence as a placeholder.
To better control where the images are drawn, we place each image in a UI.Area
and specify its position.
UI.CentralPanel(
Contents: {
UI.Horizontal(
Contents: {
UI.Area(
Position: @f2(-250.0 0.0)
Anchor: Anchor::Center
Contents: {
images | Take(0) | UI.Image
})
UI.Area(
Position: @f2(250.0 0.0)
Anchor: Anchor::Center
Contents: {
images | Take(1) | UI.Image
})
})
})
Caution
When using panels, ensure that UI.CentralPanel
is always the last of the panels to be drawn to prevent errors.
Outcome¶
The game's base UI is now ready! Try running the code to see your results.
In the next chapter, we will delve into the game's logic and allow your program to ready itself for each game round.
The code thus far:
@define(total-rounds 10)
@define(max-timer 5)
@define(load-resources {
LoadImage("data/cats/cat01.png") | Push(Name: images) ;; (1)(2)
LoadImage("data/cats/cat02.png") | Push(Name: images)
LoadImage("data/cats/cat03.png") | Push(Name: images)
})
@define(initialize-variables {
0 >= total-score
1 >= current-round
@max-timer >= time-remaining
})
@define( main-game-ui {
UI.BottomPanel(
Contents: { "Are they the same image? Press the UP arrow if YES, and the DOWN arrow if NO." | UI.Label
})
UI.TopPanel(
Contents: {
UI.Horizontal(
Contents: {
"Score: " | UI.Label
total-score | ToString | UI.Label
UI.Separator
"Round: " | UI.Label
current-round | ToString | UI.Label
UI.Separator
"Time Left: " | UI.Label
time-remaining | ToString | UI.Label
})
})
UI.CentralPanel(
Contents: {
UI.Horizontal(
Contents: {
UI.Area(
Position: @f2(-250.0 0.0)
Anchor: Anchor::Center
Contents: {
images | Take(0) | UI.Image
})
UI.Area(
Position: @f2(250.0 0.0)
Anchor: Anchor::Center
Contents: {
images | Take(1) | UI.Image
})
})
})
})
@wire( ui-loop {
GFX.MainWindow(
Title: "Yes-No Game"
Width: 1280 Height: 768
Contents: {
Once({
GFX.DrawQueue >= ui-draw-queue
GFX.UIPass(ui-draw-queue) >> render-steps
})
ui-draw-queue | GFX.ClearQueue
UI(Contents: {
@main-game-ui
}) | UI.Render(ui-draw-queue)
GFX.Render(Steps: render-steps)
})
} Looped: true)
@wire(logic-loop {Pass} Looped: true)
@wire( game-loop {
Once({
@load-resources
@initialize-variables
})
Branch([ui-loop, logic-loop])
} Looped: true)
@mesh(main)
@schedule(main game-loop)
@run(main FPS: 60)