Step 2¶
Changing Character Images with Button Inputs - Overview¶
In the previous step, we successfully drew our character in a window. Eventually, we want this image to be animated.
But slow down now!
Before we reach that stage, let's first start with having our image change to another.
In this step, we will learn how to have our character image change on button input.
Step 2.1: Adding Button Inputs¶
For a start, let's have our program create a message when we press a button. To do this, create another define
named button-inputs
.
We will use the shards Inputs.KeyDown
and Msg
to have our program show a message output in the terminal when we press directional buttons.
@define( button-inputs{
Inputs.KeyDown(
Key: "left"
Action: {
Msg("left")
}
)
Inputs.KeyDown(
Key: "right"
Action: {
Msg("right")
}
)
Inputs.KeyDown(
Key: "up"
Action: {
Msg("up")
}
)
})
@button-inputs
@define( button-inputs{
Inputs.KeyDown(
Key: "left"
Action: {
Msg("left")
}
)
Inputs.KeyDown(
Key: "right"
Action: {
Msg("right")
}
)
Inputs.KeyDown(
Key: "up"
Action: {
Msg("up")
}
)
})
@define( initialize-images {
LoadImage("GlodImages/Character1.png") = character-image
})
@wire(main-wire {
Once({
@initialize-images
})
GFX.MainWindow(
Contents: {
Once({
GFX.DrawQueue >= ui-draw-queue
GFX.UIPass(ui-draw-queue) >> render-steps
})
UI(
UI.Area(
Position: @f2(0.0 0.0)
Anchor: Anchor::Center
Contents: {
character-image | UI.Image
}
)
) | UI.Render(ui-draw-queue)
@button-inputs
GFX.Render(Steps: render-steps)
}
) = main-window
} Looped: true)
@mesh(main)
@schedule(main main-wire)
@run(main FPS: 60)
Why don't you try running the program now? Do you see the messages in the terminal?
Great job if you did! We are off to a good start.
Step 2.2: Uploading all our images¶
Next, instead of just having messages appear on the terminal, let's start trying to get the image to change. Similar to step 1, we first load the images into variables. Add these images into the GlodImages folder and name them appropriately.
- Download the Images here.
@define( initialize-images {
LoadImage("GlodImages/Character1.png") = character-image
LoadImage("GlodImages/Character1_Left.png") = character-left
LoadImage("GlodImages/Character1_Right.png") = character-right
LoadImage("GlodImages/Character1_Jumping.png") = character-jumping
})
@define( button-inputs{
Inputs.KeyDown(
Key: "left"
Action: {
Msg("left")
}
)
Inputs.KeyDown(
Key: "right"
Action: {
Msg("right")
}
)
Inputs.KeyDown(
Key: "up"
Action: {
Msg("up")
}
)
})
@define( initialize-images {
LoadImage("GlodImages/Character1.png") = character-image
LoadImage("GlodImages/Character1_Left.png") = character-left
LoadImage("GlodImages/Character1_Right.png") = character-right
LoadImage("GlodImages/Character1_Jumping.png") = character-jumping
})
@wire(main-wire {
Once({
@initialize-images
})
GFX.MainWindow(
Contents: {
Once({
GFX.DrawQueue >= ui-draw-queue
GFX.UIPass(ui-draw-queue) >> render-steps
})
UI(
UI.Area(
Position: @f2(0.0 0.0)
Anchor: Anchor::Center
Contents: {
character-image | UI.Image
}
)
) | UI.Render(ui-draw-queue)
@button-inputs
GFX.Render(Steps: render-steps)
}
) = main-window
} Looped: true)
@mesh(main)
@schedule(main main-wire)
@run(main FPS: 60)
Now, let's draw the new images onto the screen. Place them under the same UI.Area
.
character-left | UI.Image(Scale: @f2(0.2))
character-jumping | UI.Image(Scale: @f2(0.2))
character-right | UI.Image(Scale: @f2(0.2))
@define( button-inputs{
Inputs.KeyDown(
Key: "left"
Action: {
Msg("left")
}
)
Inputs.KeyDown(
Key: "right"
Action: {
Msg("right")
}
)
Inputs.KeyDown(
Key: "up"
Action: {
Msg("up")
}
)
})
@define( initialize-images {
LoadImage("GlodImages/Character1.png") = character-image
LoadImage("GlodImages/Character1_Left.png") = character-left
LoadImage("GlodImages/Character1_Right.png") = character-right
LoadImage("GlodImages/Character1_Jumping.png") = character-jumping
})
@wire(main-wire {
Once({
@initialize-images
})
GFX.MainWindow(
Contents: {
Once({
GFX.DrawQueue >= ui-draw-queue
GFX.UIPass(ui-draw-queue) >> render-steps
})
UI(
UI.Area(
Position: @f2(0.0 0.0)
Anchor: Anchor::Center
Contents: {
character-left | UI.Image(Scale: @f2(0.2))
character-jumping | UI.Image(Scale: @f2(0.2))
character-right | UI.Image(Scale: @f2(0.2))
}
)
) | UI.Render(ui-draw-queue)
@button-inputs
GFX.Render(Steps: render-steps)
}
) = main-window
} Looped: true)
@mesh(main)
@schedule(main main-wire)
@run(main FPS: 60)
Try running the program. Don't panic! You are supposed to see 3 images stacked on top of each other.
The next step is to have the window draw only one image at a time.
Step 2.3: Changing our Image¶
To have only one image drawn each time, we will:
-
Create a
character-direction
variable for Glod. -
Draw only one image based on the character state using the
Match
shard.
States
Creating states is a way to have our characters and stages hold different modes, forms and levels.
For example, a boss can have a normal mode and an enraged mode. We create the variables normal-mode
and enraged-mode
.
When normal-mode
is true
and enraged-mode
is false
, the boss is in normal mode. Vice-versa, when enraged-mode
is true
and normal-mode
is false
, the boss will go into enraged mode.
We will use the same logic to create states for our character and have the image shown on screen change when its state is changed.
First, create the character-direction
variable. We use an @i2
variable instead of just an int
because an @i2
can store the information for both directions on the x-axis and also y-axis.
@i2(0 0) >= character-direction ;;
Next, we use a Match
shard to display the correct image based on the character-direction
variable. Let's focus on just the left and right direction first. We will handle jumping later on.
Match
The Match
shard is like a very long If-Else statement. It takes the input provided and runs through the list of cases provided. If the input equals one of the cases, the corresponding arm is evaluated.
It is good practice to have a none
case because if the input does not equal to any of the cases provided, an error is thrown. With the none
case, in this scenario, the none
arm is evaluated.
character-direction
Match([
@i2(0 0) {character-image}
@i2(-1 0) {character-left}
@i2(1 0) {character-right}
none {character-image}
] Passthrough: false)
@define( button-inputs{
Inputs.KeyDown(
Key: "left"
Action: {
Msg("left")
}
)
Inputs.KeyDown(
Key: "right"
Action: {
Msg("right")
}
)
Inputs.KeyDown(
Key: "up"
Action: {
Msg("up")
}
)
})
@define( initialize-images {
LoadImage("GlodImages/Character1.png") = character-image
LoadImage("GlodImages/Character1_Left.png") = character-left
LoadImage("GlodImages/Character1_Right.png") = character-right
LoadImage("GlodImages/Character1_Jumping.png") = character-jumping
})
@wire(main-wire {
Once({
@initialize-images
@i2(0 0) >= character-direction
})
GFX.MainWindow(
Contents: {
Once({
GFX.DrawQueue >= ui-draw-queue
GFX.UIPass(ui-draw-queue) >> render-steps
})
UI(
UI.Area(
Position: @f2(0.0 0.0)
Anchor: Anchor::Center
Contents: {
character-direction
Match([
@i2(0 0) {character-image}
@i2(-1 0) {character-left}
@i2(1 0) {character-right}
none {character-image}
] Passthrough: false)
UI.Image(Scale: @f2(0.2))
}
)
) | UI.Render(ui-draw-queue)
@button-inputs
GFX.Render(Steps: render-steps)
}
)
} Looped: true)
@mesh(main)
@schedule(main main-wire)
@run(main FPS: 60)
Passthrough
When Passthrough
is set to false, the shard outputs the evaluated value of the matching arm. However, to do this, each arm also must evaluate to a value of the same @type
. In this case, each arm outputs an image object, hence Match
will output the resulting image object.
If you run the code now, you will see only one image this time.
However, the image does not change.
This is because our character-direction
has a value of @i2(0 0) and stays at @i2(0 0). Our Match
shard will only output the image that is shown when character-direction
is @i2(0 0).
To change the image, we must first change the value of the character-direction
variable.
First, we create two new variables, x-direction and y-direction. We can put this in the once where we initialize our images and our character-direction variable.
Once({
@initialize-images
@i2(0 0) >= character-direction
0 >= x-direction
0 >= y-direction
})
Next, we change our newly created variables when we press our left, right or up keys in our input.
@define( button-inputs{
Inputs.KeyDown(
Key: "left"
Action: {
Msg("left")
-1 > x-direction
}
)
Inputs.KeyDown(
Key: "right"
Action: {
Msg("right")
1 > x-direction
}
)
Inputs.KeyDown(
Key: "up"
Action: {
Msg("up")
1 > y-direction
}
)
})
Lastly, we update the character direction @i2
using the updated x-direction, y-direction.
@i2(x-direction y-direction) > character-direction
@define( initialize-images{
LoadImage("GlodImages/Character1.png") = character-image
LoadImage("GlodImages/Character1_Left.png") = character-left
LoadImage("GlodImages/Character1_Right.png") = character-right
LoadImage("GlodImages/Character1_Jumping.png") = character-jumping
})
@define( button-inputs{
Inputs.KeyDown(
Key: "left"
Action: {
Msg("left")
-1 > x-direction
}
)
Inputs.KeyDown(
Key: "right"
Action: {
Msg("right")
1 > x-direction
}
)
Inputs.KeyDown(
Key: "up"
Action: {
Msg("up")
1 > y-direction
}
)
})
@wire(main-wire {
Once({
@initialize-images
@i2(0 0) >= character-direction
0 >= x-direction
0 >= y-direction
})
GFX.MainWindow(
Contents: {
Once({
GFX.DrawQueue >= ui-draw-queue
GFX.UIPass(ui-draw-queue) >> render-steps
})
UI(
UI.Area(
Position: @f2(0.0 0.0)
Anchor: Anchor::Center
Contents: {
character-direction
Match([
@i2(0 0) {character-image}
@i2(-1 0) {character-left}
@i2(1 0) {character-right}
none {character-image}
] Passthrough: false)
UI.Image(Scale: @f2(0.2))
}
)
) | UI.Render(ui-draw-queue)
@button-inputs
@i2(x-direction y-direction) > character-direction
GFX.Render(Steps: render-steps)
}
)
} Looped: true)
@mesh(main)
@schedule(main main-wire)
@run(main FPS: 60)
Now if you run the code and press the left and right directional buttons, you will see Glod's image change! Congratulations. Lastly, we need to handle the jumping image. Handling jumping is slightly more tricky. First, let's add the arm handling the case for Glod jumping into the Match
shard.
Match([
@i2(0 0) {character-image}
@i2(-1 0) {character-left}
@i2(1 0) {character-right}
@i2(0 1) {character-jumping}
none {character-image}
] Passthrough: false)
@define( initialize-images{
LoadImage("GlodImages/Character1.png") = character-image
LoadImage("GlodImages/Character1_Left.png") = character-left
LoadImage("GlodImages/Character1_Right.png") = character-right
LoadImage("GlodImages/Character1_Jumping.png") = character-jumping
})
@define( button-inputs{
Inputs.KeyDown(
Key: "left"
Action: {
Msg("left")
-1 > x-direction
}
)
Inputs.KeyDown(
Key: "right"
Action: {
Msg("right")
1 > x-direction
}
)
Inputs.KeyDown(
Key: "up"
Action: {
Msg("up")
1 > y-direction
}
)
})
@wire(main-wire {
Once({
@initialize-images
@i2(0 0) >= character-direction
0 >= x-direction
0 >= y-direction
})
GFX.MainWindow(
Contents: {
Once({
GFX.DrawQueue >= ui-draw-queue
GFX.UIPass(ui-draw-queue) >> render-steps
})
UI(
UI.Area(
Position: @f2(0.0 0.0)
Anchor: Anchor::Center
Contents: {
character-direction
Match([
@i2(0 0) {character-image}
@i2(-1 0) {character-left}
@i2(1 0) {character-right}
@i2(0 1) {character-jumping}
none {character-image}
] Passthrough: false)
UI.Image(Scale: @f2(0.2))
}
)
) | UI.Render(ui-draw-queue)
@button-inputs
@i2(x-direction y-direction) > character-direction
GFX.Render(Steps: render-steps)
}
)
} Looped: true)
@mesh(main)
@schedule(main main-wire)
@run(main FPS: 60)
If you press the directional keys now, you will quickly realise that there is a problem. Firstly, our Match shard, as it is, is not taking into consideration all the cases. It does not handle cases when character-direction
is @i2(1 1) (jumping and facing right) or, @i2(-1 1) (jumping and facing left).
To solve this, we have to add the necessary additional arms to Match
and the additional images to represent these jumping states.
@define( initialize-images{
LoadImage("GlodImages/Character1.png") = character-image
LoadImage("GlodImages/Character1_Left.png") = character-left
LoadImage("GlodImages/Character1_Right.png") = character-right
LoadImage("GlodImages/Character1_Jumping_Left.png") = character-jumping-left
LoadImage("GlodImages/Character1_Jumping_Right.png") = character-jumping-right
LoadImage("GlodImages/Character1_Jumping.png") = character-jumping
})
Match([
@i2(0 0) {character-image}
@i2(-1 0) {character-left}
@i2(1 0) {character-right}
@i2(-1 1) {character-jumping-left}
@i2(1 1) {character-jumping-right}
@i2(0 1) {character-jumping}
none {character-image}
] Passthrough: false)
@define( initialize-images{
LoadImage("GlodImages/Character1.png") = character-image
LoadImage("GlodImages/Character1_Left.png") = character-left
LoadImage("GlodImages/Character1_Right.png") = character-right
LoadImage("GlodImages/Character1_Jumping_Left.png") = character-jumping-left
LoadImage("GlodImages/Character1_Jumping_Right.png") = character-jumping-right
LoadImage("GlodImages/Character1_Jumping.png") = character-jumping
})
@define( button-inputs{
Inputs.KeyDown(
Key: "left"
Action: {
Msg("left")
-1 > x-direction
}
)
Inputs.KeyDown(
Key: "right"
Action: {
Msg("right")
1 > x-direction
}
)
Inputs.KeyDown(
Key: "up"
Action: {
Msg("up")
1 > y-direction
}
)
})
@wire(main-wire {
Once({
@initialize-images
@i2(0 0) >= character-direction
0 >= x-direction
0 >= y-direction
})
GFX.MainWindow(
Contents: {
Once({
GFX.DrawQueue >= ui-draw-queue
GFX.UIPass(ui-draw-queue) >> render-steps
})
UI(
UI.Area(
Position: @f2(0.0 0.0)
Anchor: Anchor::Center
Contents: {
character-direction
Match([
@i2(0 0) {character-image}
@i2(-1 0) {character-left}
@i2(1 0) {character-right}
@i2(-1 1) {character-jumping-left}
@i2(1 1) {character-jumping-right}
@i2(0 1) {character-jumping}
none {character-image}
] Passthrough: false)
UI.Image(Scale: @f2(0.2))
}
)
) | UI.Render(ui-draw-queue)
@button-inputs
@i2(x-direction y-direction) > character-direction
GFX.Render(Steps: render-steps)
}
)
} Looped: true)
@mesh(main)
@schedule(main main-wire)
@run(main FPS: 60)
The other problem we have to solve, is Glod getting stuck on the "jump" image. Currently, there is nothing that resets our y-direction back to 0. Normally, after a character jumps, the state that checks if the character is jumping, will reset back when they land. For now, we will reset our y-direction back to 0 when we let go of the up directional key. To do this, we use the Inputs.KeyUp
shard. Inputs.KeyUp
outputs true
on the frame that the key specified is released.
Inputs.KeyUp(
Key: "up"
Action: {
0 > y-direction
}
Consume: false
SkipConsumed: true
)
@define( initialize-images{
LoadImage("Character1_Left.png") = character-image
LoadImage("Character1_Left.png") = character-left
LoadImage("Character1_Right.png") = character-right
LoadImage("Character1_Jumping_Left.png") = character-jumping-left
LoadImage("Character1_Jumping_Right.png") = character-jumping-right
LoadImage("Character1_Jumping.png") = character-jumping
})
@define( button-inputs{
Inputs.KeyDown(
Key: "left"
Action: {
Msg("left")
-1 > x-direction
}
)
Inputs.KeyDown(
Key: "right"
Action: {
Msg("right")
1 > x-direction
}
)
Inputs.KeyDown(
Key: "up"
Action: {
Msg("up")
1 > y-direction
}
)
Inputs.KeyUp(
Key: "up"
Action: {
0 > y-direction
}
Consume: false
SkipConsumed: true
)
})
@wire(main-wire {
Once({
@initialize-images
@i2(0 0) >= character-direction
0 >= x-direction
0 >= y-direction
})
GFX.MainWindow(
Contents: {
Once({
GFX.DrawQueue >= ui-draw-queue
GFX.UIPass(ui-draw-queue) >> render-steps
})
UI(
UI.Area(
Position: @f2(0.0 0.0)
Anchor: Anchor::Center
Contents: {
character-direction
Match([
@i2(0 0) {character-image}
@i2(-1 0) {character-left}
@i2(1 0) {character-right}
@i2(-1 1) {character-jumping-left}
@i2(1 1) {character-jumping-right}
@i2(0 1) {character-jumping}
none {character-image}
] Passthrough: false)
UI.Image(Scale: @f2(0.2))
}
)
) | UI.Render(ui-draw-queue)
@button-inputs
@i2(x-direction y-direction) > character-direction
GFX.Render(Steps: render-steps)
}
)
} Looped: true)
@mesh(main)
@schedule(main main-wire)
@run(main FPS: 60)
Now our jumping state resets when the "Up" key is released!
Recap¶
Try running the code. Voilà! You now have an image that changes based on the button pressed.
We achieved this by:
-
Creating a
character-direction
variable. -
Using a
Match
shard to dictate which image is drawn in the window based on the value ofcharacter-direction
.
In the next step we will breathe more life into adorable Glod by animating him!
Looking forward to seeing you in the next step!🙂