Step 5¶
For our game to engage and interact with users, we need it to be responsive to user input.
In this chapter, we will be looking at how user input is handled in Shards.
User Input¶
User input is managed within the GFX.MainWindow
of your game. For this game, the only user input required would be the ↑ and ↓ directional key.
We can use Inputs.KeyDown
to execute code whenever the user presses down on a specified key.
@wire( ui-loop {
GFX.MainWindow(
Title: "Yes-No Game"
Width: 1280 Height: 768
Contents: {
...
Inputs.KeyDown( ;; (3)
Key: "up"
Action: {Pass} ;; (1)
)
Inputs.KeyDown(
Inputs.KeyDown(
Key: "down"
Action: {Pass} ;; (2)
)
)
- This code is executed when the user presses the ↑ directional key.
- This code is executed when the user presses the ↓ directional key.
Inputs.KeyDown
will run the code in itsAction
parameter when theKey
specified is pressed down by the user.
Now that we are able to obtain the user's input, we can proceed to check if the user pressed the correct button.
Checking the Input¶
Based on the images chosen, we can determine whether the user needs to select the ↑ or ↓ directional key.
In initialize-variables
, create a variable named same-image
. This will be used to check if the user pressed the correct key later.
@define(initialize-variables {
...
;; Other Shared Variables
0 >= left-image-index
0 >= right-image-index
Count(images) >= total-images
true >= same-image ;; (1)
})
- Tracks whether the same image is used.
Navigate to where we chose the images in initialize-round
.
Check if the chosen images are the same, and assign true
or false
to same-image
accordingly.
@define( initialize-round {
RandomInt(Max: total-images) > left-image-index
RandomInt(Max: total-images) > right-image-index
left-image-index
If(Predicate: { ;; (1)
Is(right-image-index) ;; (2)
} Then: {
true > same-image
} Else: {
false > same-image
})
@reset-round-variables
})
If
checks thePredicate
given and runs the code withinThen
if it is true. If false, the code withinElse
is run instead.- Check if the images on the left and right are the same or different.
Next, in Input.KeyDown, check if they:
-
Pressed ↑ when
same-image
is true -
Pressed ↓ when
same-image
is false
We award them a point by increasing the value of total-score
, as long as game-over
is still false. We also want to call end-round
with Step
whenever the user provides the correct input to go to the next round and refresh the images.
Inputs.KeyDown(
Key: "up"
Action: {
same-image
When(Predicate: {
Is(true)
And
game-over | Is(false)
} Action: {
total-score | Math.Add(1) > total-score
Step(end-round)
})
}
)
Inputs.KeyDown(
Key: "down"
Action: {
same-image
When(Predicate: {
Is(false)
And
game-over | Is(false)
} Action: {
total-score | Math.Add(1) > total-score
Step(end-round)
})
}
)
Cheers! Your game can now receive user input, tabulate the score, and allows players to play up to 10 rounds each time.
@define(reset-game-variables {
@reset-round-variables
0 > total-score
1 > current-round
false > game-over
true > new-round
})
@wire(end-round {
Once({
0 >= new-round-number ;; (1)
})
current-round | Math.Add(1) > new-round-number ;; (2)
new-round-number
If(Predicate: IsMore(@total-rounds) Then: { ;; (3)
true > game-over ;; (4)
} Else: {
new-round-number > current-round ;; (5)
true > new-round ;; (6)
})
} Looped: true)
@define(reset-round-variables {
false > new-round
@max-timer > time-remaining
})
@define( initialize-round {
RandomInt(Max: total-images) > left-image-index
RandomInt(Max: total-images) > right-image-index
left-image-index
If(Predicate: { ;; (1)
Is(right-image-index) ;; (2)
} Then: {
true > same-image
} Else: {
false > same-image
})
@reset-round-variables
})
@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 {
;; Variables to reset each round
true >= new-round
@max-timer >= time-remaining
;; Variables to reset each game
0 >= total-score
1 >= current-round
false >= game-over
;; Other Shared Variables
0 >= left-image-index
0 >= right-image-index
Count(images) >= total-images
true >= same-image
})
@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(left-image-index) | UI.Image
})
UI.Area(
Position: @f2(250.0 0.0)
Anchor: Anchor::Center
Contents: {
images | Take(right-image-index) | 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
Inputs.KeyDown(
Key: "up"
Action: {
same-image
When(Predicate: {
Is(true)
And
game-over | Is(false)
} Action: {
total-score | Math.Add(1) > total-score
Step(end-round)
})
}
)
Inputs.KeyDown(
Key: "down"
Action: {
same-image
When(Predicate: {
Is(false)
And
game-over | Is(false)
} Action: {
total-score | Math.Add(1) > total-score
Step(end-round)
})
}
)
UI(Contents: {
@main-game-ui
}) | UI.Render(ui-draw-queue)
GFX.Render(Steps: render-steps)
})
} Looped: true)
@wire(logic-loop {
game-over
When(Predicate: {
Is(false) ;; (4)
And
new-round | Is(true) ;; (1)
} Action: {
@initialize-round ;; (3)
})
} Looped: true)
@wire( game-loop {
Once({
@load-resources
@initialize-variables
})
new-round
Branch([ui-loop logic-loop])
} Looped: true)
@mesh(main)
@schedule(main game-loop)
@run(main FPS: 60)