Step 4¶
Moving the Character with Button Inputs - Overview¶
In the previous chapter, we breathed some life into Glod and transformed him from a static image into a fully animated character.
Amazing!
However, we can take things further. Let's free Glod from his stationary curse and make him move! To do this we will:
- Place a variable in the
Position
tag ofUI.Area
. - Change this variable to make Glod move.
- Use velocity and acceleration concepts to make Glod jump.
Step 4.1: Shifting Anchors¶
First, let's edit our code a little. Currently, Glod is situated in the center of the screen. While this can work, we can instead change his current Anchor::Center
to Anchor::Bottom
, and bring him down to Earth by changing his position. We achieve this by creating some variables.
While any anchor will work as long as we change the position accordingly, we will be using Anchor::Bottom
to keep things consistent and manage the positions of the different game elements more effectively.
Create the pos-x
and pos-y
variables to hold Glod's x and y positions.
0.0 >= pos-x
0.0 >= pos-y
pos-x
and pos-y
is then fed into the Position
parameter of the UI.Area
shard. When the pos-x
and pos-y
values are changed, the UI.Area
will move and hence move our character.
Remember to change the Anchor
to Anchor::Bottom
!
UI.Area(
Position: @f2(pos-x pos-y)
Anchor: Anchor::Bottom
Contents:
@define( initialize-images {
LoadImage("GlodImages/Character1_Left.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
;; ---------- Character Idle sequence (Facing Left) ----------------
LoadImage("GlodImages/Character_Idle/Idle_Left/Character1_Idle_Left_1.png") >> idle-left-image-sequence
LoadImage("GlodImages/Character_Idle/Idle_Left/Character1_Idle_Left_2.png") >> idle-left-image-sequence
LoadImage("GlodImages/Character_Idle/Idle_Left/Character1_Idle_Left_3.png") >> idle-left-image-sequence
LoadImage("GlodImages/Character_Idle/Idle_Left/Character1_Idle_Left_4.png") >> idle-left-image-sequence
LoadImage("GlodImages/Character_Idle/Idle_Left/Character1_Idle_Left_5.png") >> idle-left-image-sequence
LoadImage("GlodImages/Character_Idle/Idle_Left/Character1_Idle_Left_6.png") >> idle-left-image-sequence
LoadImage("GlodImages/Character_Idle/Idle_Left/Character1_Idle_Left_7.png") >> idle-left-image-sequence
;; ---------- Character Idle sequence (Facing Right) ----------------
LoadImage("GlodImages/Character_Idle/Idle_Right/Character1_Idle_1.png") >> idle-right-image-sequence
LoadImage("GlodImages/Character_Idle/Idle_Right/Character1_Idle_2.png") >> idle-right-image-sequence
LoadImage("GlodImages/Character_Idle/Idle_Right/Character1_Idle_3.png") >> idle-right-image-sequence
LoadImage("GlodImages/Character_Idle/Idle_Right/Character1_Idle_4.png") >> idle-right-image-sequence
LoadImage("GlodImages/Character_Idle/Idle_Right/Character1_Idle_5.png") >> idle-right-image-sequence
LoadImage("GlodImages/Character_Idle/Idle_Right/Character1_Idle_6.png") >> idle-right-image-sequence
LoadImage("GlodImages/Character_Idle/Idle_Right/Character1_Idle_7.png") >> idle-right-image-sequence
;; -------------- Walking sequence (Facing Left) -----------------
LoadImage("GlodImages/Character_Walking/Walking_Left/Character1_Walking_Left_1.png") >> walking-left-image-sequence
LoadImage("GlodImages/Character_Walking/Walking_Left/Character1_Walking_Left_2.png") >> walking-left-image-sequence
LoadImage("GlodImages/Character_Walking/Walking_Left/Character1_Walking_Left_3.png") >> walking-left-image-sequence
LoadImage("GlodImages/Character_Walking/Walking_Left/Character1_Walking_Left_4.png") >> walking-left-image-sequence
LoadImage("GlodImages/Character_Walking/Walking_Left/Character1_Walking_Left_5.png") >> walking-left-image-sequence
LoadImage("GlodImages/Character_Walking/Walking_Left/Character1_Walking_Left_6.png") >> walking-left-image-sequence
LoadImage("GlodImages/Character_Walking/Walking_Left/Character1_Walking_Left_7.png") >> walking-left-image-sequence
;; ----------- Walking sequence (Facing Right) ---------------
LoadImage("GlodImages/Character_Walking/Walking_Right/Character1_Walking_Right_1.png") >> walking-right-image-sequence
LoadImage("GlodImages/Character_Walking/Walking_Right/Character1_Walking_Right_2.png") >> walking-right-image-sequence
LoadImage("GlodImages/Character_Walking/Walking_Right/Character1_Walking_Right_3.png") >> walking-right-image-sequence
LoadImage("GlodImages/Character_Walking/Walking_Right/Character1_Walking_Right_4.png") >> walking-right-image-sequence
LoadImage("GlodImages/Character_Walking/Walking_Right/Character1_Walking_Right_5.png") >> walking-right-image-sequence
LoadImage("GlodImages/Character_Walking/Walking_Right/Character1_Walking_Right_6.png") >> walking-right-image-sequence
LoadImage("GlodImages/Character_Walking/Walking_Right/Character1_Walking_Right_7.png") >> walking-right-image-sequence
})
@wire( animate {
Once({
0.08 = idle-animation-speed
})
image-index | Math.Add(1) > image-index
Count(active-animation) = index-max
image-index
When(Predicate: IsMoreEqual(index-max) Action: {
0 > image-index
})
Pause(idle-animation-speed)
} Looped: true)
@define( button-inputs {
Inputs.IsKeyDown(
Key: "left"
)
When(Predicate: Is(true) Action: {
-1 > x-direction
})
Inputs.KeyUp(
Key: "left"
Action: {
0 > x-direction
}
Consume: false
SkipConsumed: true
)
Inputs.IsKeyDown(
Key: "right"
)
When(Predicate: Is(true) Action: {
1 > x-direction
})
Inputs.KeyUp(
Key: "right"
Action: {
0 > x-direction
}
Consume: false
SkipConsumed: true
)
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
0 >= image-index
idle-left-image-sequence >= idle-animation
character-jumping-left >= jumping-image
idle-left-image-sequence >= active-animation
0.0 >= pos-x
0.0 >= pos-y
})
GFX.MainWindow(
Contents: {
Once({
GFX.DrawQueue >= ui-draw-queue
GFX.UIPass(ui-draw-queue) >> render-steps
})
UI(
UI.Area(
Position: @f2(pos-x pos-y)
Anchor: Anchor::Bottom
Contents: {
character-direction
Match([
@i2(0 0) {idle-animation > active-animation | Take(image-index)} ;;Take from the idle animation when in idle
@i2(-1 0) {
idle-left-image-sequence > idle-animation ;; Store the left idle animation into idle-animation when facing left
character-jumping-left > jumping-image
walking-left-image-sequence > active-animation | Take(image-index)
}
@i2(1 0) {
idle-right-image-sequence > idle-animation ;; Store the right idle animation into idle-animation when facing right
character-jumping-right > jumping-image
walking-right-image-sequence > active-animation | Take(image-index)
}
none {jumping-image} ;; we can just use none to handle the jumping state as all other cases at this moment, the character is jumping
] Passthrough: false)
UI.Image(Scale: @f2(0.2))
}
)
) | UI.Render(ui-draw-queue)
@button-inputs
@i2(x-direction y-direction) > character-direction
Step(animate)
GFX.Render(Steps: render-steps)
}
)
} Looped: true)
@mesh(main)
@schedule(main main-wire)
@run(main FPS: 60)
Step 4.2: Moving left and right¶
Now that our variables are set up, it's time to make Glod move! Let's start by moving him to the left and right.
To do this, we simply have to change the pos-x
value that we have just created whenever the left and right buttons are pressed.
First, create a new variable named character-x-velocity
. This is the value our pos-x
will be increased or decreased by.
3.0 >= character-x-velocity ;;increase or decrease this value to control how fast Glod moves
pos-x | Math.Add(character-x-velocity) > pos-x
Since we already have a a variable x-direction
that stores the direction of Glod along the x-axis, we multiply character-x-velocity
by x-direction
, so that Glod will move left or right accordingly.
;; ------- Button Inputs ----------
pos-x | Math.Add((character-x-velocity | Math.Multiply(x-direction))) > pos-x
@define( initialize-images {
LoadImage("GlodImages/Character1_Left.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
;; ---------- Character Idle sequence (Facing Left) ----------------
LoadImage("GlodImages/Character_Idle/Idle_Left/Character1_Idle_Left_1.png") >> idle-left-image-sequence
LoadImage("GlodImages/Character_Idle/Idle_Left/Character1_Idle_Left_2.png") >> idle-left-image-sequence
LoadImage("GlodImages/Character_Idle/Idle_Left/Character1_Idle_Left_3.png") >> idle-left-image-sequence
LoadImage("GlodImages/Character_Idle/Idle_Left/Character1_Idle_Left_4.png") >> idle-left-image-sequence
LoadImage("GlodImages/Character_Idle/Idle_Left/Character1_Idle_Left_5.png") >> idle-left-image-sequence
LoadImage("GlodImages/Character_Idle/Idle_Left/Character1_Idle_Left_6.png") >> idle-left-image-sequence
LoadImage("GlodImages/Character_Idle/Idle_Left/Character1_Idle_Left_7.png") >> idle-left-image-sequence
;; ---------- Character Idle sequence (Facing Right) ----------------
LoadImage("GlodImages/Character_Idle/Idle_Right/Character1_Idle_1.png") >> idle-right-image-sequence
LoadImage("GlodImages/Character_Idle/Idle_Right/Character1_Idle_2.png") >> idle-right-image-sequence
LoadImage("GlodImages/Character_Idle/Idle_Right/Character1_Idle_3.png") >> idle-right-image-sequence
LoadImage("GlodImages/Character_Idle/Idle_Right/Character1_Idle_4.png") >> idle-right-image-sequence
LoadImage("GlodImages/Character_Idle/Idle_Right/Character1_Idle_5.png") >> idle-right-image-sequence
LoadImage("GlodImages/Character_Idle/Idle_Right/Character1_Idle_6.png") >> idle-right-image-sequence
LoadImage("GlodImages/Character_Idle/Idle_Right/Character1_Idle_7.png") >> idle-right-image-sequence
;; -------------- Walking sequence (Facing Left) -----------------
LoadImage("GlodImages/Character_Walking/Walking_Left/Character1_Walking_Left_1.png") >> walking-left-image-sequence
LoadImage("GlodImages/Character_Walking/Walking_Left/Character1_Walking_Left_2.png") >> walking-left-image-sequence
LoadImage("GlodImages/Character_Walking/Walking_Left/Character1_Walking_Left_3.png") >> walking-left-image-sequence
LoadImage("GlodImages/Character_Walking/Walking_Left/Character1_Walking_Left_4.png") >> walking-left-image-sequence
LoadImage("GlodImages/Character_Walking/Walking_Left/Character1_Walking_Left_5.png") >> walking-left-image-sequence
LoadImage("GlodImages/Character_Walking/Walking_Left/Character1_Walking_Left_6.png") >> walking-left-image-sequence
LoadImage("GlodImages/Character_Walking/Walking_Left/Character1_Walking_Left_7.png") >> walking-left-image-sequence
;; ----------- Walking sequence (Facing Right) ---------------
LoadImage("GlodImages/Character_Walking/Walking_Right/Character1_Walking_Right_1.png") >> walking-right-image-sequence
LoadImage("GlodImages/Character_Walking/Walking_Right/Character1_Walking_Right_2.png") >> walking-right-image-sequence
LoadImage("GlodImages/Character_Walking/Walking_Right/Character1_Walking_Right_3.png") >> walking-right-image-sequence
LoadImage("GlodImages/Character_Walking/Walking_Right/Character1_Walking_Right_4.png") >> walking-right-image-sequence
LoadImage("GlodImages/Character_Walking/Walking_Right/Character1_Walking_Right_5.png") >> walking-right-image-sequence
LoadImage("GlodImages/Character_Walking/Walking_Right/Character1_Walking_Right_6.png") >> walking-right-image-sequence
LoadImage("GlodImages/Character_Walking/Walking_Right/Character1_Walking_Right_7.png") >> walking-right-image-sequence
})
@wire( animate {
Once({
0.08 = idle-animation-speed
})
image-index | Math.Add(1) > image-index
Count(active-animation) = index-max
image-index
When(Predicate: IsMoreEqual(index-max) Action: {
0 > image-index
})
Pause(idle-animation-speed)
} Looped: true)
@define( button-inputs {
Inputs.IsKeyDown(
Key: "left"
)
When(Predicate: Is(true) Action: {
-1 > x-direction
})
Inputs.KeyUp(
Key: "left"
Action: {
0 > x-direction
}
Consume: false
SkipConsumed: true
)
Inputs.IsKeyDown(
Key: "right"
)
When(Predicate: Is(true) Action: {
1 > x-direction
})
Inputs.KeyUp(
Key: "right"
Action: {
0 > x-direction
}
Consume: false
SkipConsumed: true
)
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
0 >= image-index
idle-left-image-sequence >= idle-animation
character-jumping-left >= jumping-image
idle-left-image-sequence >= active-animation
0.0 >= pos-x
0.0 >= pos-y
3.0 >= character-x-velocity
})
pos-x | Math.Add((character-x-velocity | Math.Multiply(x-direction))) > pos-x
GFX.MainWindow(
Contents: {
Once({
GFX.DrawQueue >= ui-draw-queue
GFX.UIPass(ui-draw-queue) >> render-steps
})
UI(
UI.Area(
Position: @f2(pos-x pos-y)
Anchor: Anchor::Bottom
Contents: {
character-direction
Match([
@i2(0 0) {idle-animation > active-animation | Take(image-index)} ;;Take from the idle animation when in idle
@i2(-1 0) {
idle-left-image-sequence > idle-animation ;; Store the left idle animation into idle-animation when facing left
character-jumping-left > jumping-image
walking-left-image-sequence > active-animation | Take(image-index)
}
@i2(1 0) {
idle-right-image-sequence > idle-animation ;; Store the right idle animation into idle-animation when facing right
character-jumping-right > jumping-image
walking-right-image-sequence > active-animation | Take(image-index)
}
none {jumping-image} ;; we can just use none to handle the jumping state as all other cases at this moment, the character is jumping
] Passthrough: false)
UI.Image(Scale: @f2(0.2))
}
)
) | UI.Render(ui-draw-queue)
@button-inputs
@i2(x-direction y-direction) > character-direction
Step(animate)
GFX.Render(Steps: render-steps)
}
)
} Looped: true)
@mesh(main)
@schedule(main main-wire)
@run(main FPS: 60)
Step 4.3: Adding horizontal boundaries¶
Oh no! We now have a problem. 🥲
If we continue moving Glod left or right, you might notice that he goes off-screen.
Let's set up a boundary to ensure that Glod does not fall off the edge and into the abyss. To do this, we use Clamp
that limits how far our pos-x
can go.
;; ---------- Character Boundary ------------ ;; add this right after we change our x values
pos-x
Clamp(Min: -600.0 Max: 600.0) > pos-x ;; change the `min` and `max` parameters to fit your screen accordingly.
@define( initialize-images {
LoadImage("GlodImages/Character1_Left.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
;; ---------- Character Idle sequence (Facing Left) ----------------
LoadImage("GlodImages/Character_Idle/Idle_Left/Character1_Idle_Left_1.png") >> idle-left-image-sequence
LoadImage("GlodImages/Character_Idle/Idle_Left/Character1_Idle_Left_2.png") >> idle-left-image-sequence
LoadImage("GlodImages/Character_Idle/Idle_Left/Character1_Idle_Left_3.png") >> idle-left-image-sequence
LoadImage("GlodImages/Character_Idle/Idle_Left/Character1_Idle_Left_4.png") >> idle-left-image-sequence
LoadImage("GlodImages/Character_Idle/Idle_Left/Character1_Idle_Left_5.png") >> idle-left-image-sequence
LoadImage("GlodImages/Character_Idle/Idle_Left/Character1_Idle_Left_6.png") >> idle-left-image-sequence
LoadImage("GlodImages/Character_Idle/Idle_Left/Character1_Idle_Left_7.png") >> idle-left-image-sequence
;; ---------- Character Idle sequence (Facing Right) ----------------
LoadImage("GlodImages/Character_Idle/Idle_Right/Character1_Idle_1.png") >> idle-right-image-sequence
LoadImage("GlodImages/Character_Idle/Idle_Right/Character1_Idle_2.png") >> idle-right-image-sequence
LoadImage("GlodImages/Character_Idle/Idle_Right/Character1_Idle_3.png") >> idle-right-image-sequence
LoadImage("GlodImages/Character_Idle/Idle_Right/Character1_Idle_4.png") >> idle-right-image-sequence
LoadImage("GlodImages/Character_Idle/Idle_Right/Character1_Idle_5.png") >> idle-right-image-sequence
LoadImage("GlodImages/Character_Idle/Idle_Right/Character1_Idle_6.png") >> idle-right-image-sequence
LoadImage("GlodImages/Character_Idle/Idle_Right/Character1_Idle_7.png") >> idle-right-image-sequence
;; -------------- Walking sequence (Facing Left) -----------------
LoadImage("GlodImages/Character_Walking/Walking_Left/Character1_Walking_Left_1.png") >> walking-left-image-sequence
LoadImage("GlodImages/Character_Walking/Walking_Left/Character1_Walking_Left_2.png") >> walking-left-image-sequence
LoadImage("GlodImages/Character_Walking/Walking_Left/Character1_Walking_Left_3.png") >> walking-left-image-sequence
LoadImage("GlodImages/Character_Walking/Walking_Left/Character1_Walking_Left_4.png") >> walking-left-image-sequence
LoadImage("GlodImages/Character_Walking/Walking_Left/Character1_Walking_Left_5.png") >> walking-left-image-sequence
LoadImage("GlodImages/Character_Walking/Walking_Left/Character1_Walking_Left_6.png") >> walking-left-image-sequence
LoadImage("GlodImages/Character_Walking/Walking_Left/Character1_Walking_Left_7.png") >> walking-left-image-sequence
;; ----------- Walking sequence (Facing Right) ---------------
LoadImage("GlodImages/Character_Walking/Walking_Right/Character1_Walking_Right_1.png") >> walking-right-image-sequence
LoadImage("GlodImages/Character_Walking/Walking_Right/Character1_Walking_Right_2.png") >> walking-right-image-sequence
LoadImage("GlodImages/Character_Walking/Walking_Right/Character1_Walking_Right_3.png") >> walking-right-image-sequence
LoadImage("GlodImages/Character_Walking/Walking_Right/Character1_Walking_Right_4.png") >> walking-right-image-sequence
LoadImage("GlodImages/Character_Walking/Walking_Right/Character1_Walking_Right_5.png") >> walking-right-image-sequence
LoadImage("GlodImages/Character_Walking/Walking_Right/Character1_Walking_Right_6.png") >> walking-right-image-sequence
LoadImage("GlodImages/Character_Walking/Walking_Right/Character1_Walking_Right_7.png") >> walking-right-image-sequence
})
@wire( animate {
Once({
0.08 = idle-animation-speed
})
image-index | Math.Add(1) > image-index
Count(active-animation) = index-max
image-index
When(Predicate: IsMoreEqual(index-max) Action: {
0 > image-index
})
Pause(idle-animation-speed)
} Looped: true)
@define( button-inputs {
Inputs.IsKeyDown(
Key: "left"
)
When(Predicate: Is(true) Action: {
-1 > x-direction
})
Inputs.KeyUp(
Key: "left"
Action: {
0 > x-direction
}
Consume: false
SkipConsumed: true
)
Inputs.IsKeyDown(
Key: "right"
)
When(Predicate: Is(true) Action: {
1 > x-direction
})
Inputs.KeyUp(
Key: "right"
Action: {
0 > x-direction
}
Consume: false
SkipConsumed: true
)
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
0 >= image-index
idle-left-image-sequence >= idle-animation
character-jumping-left >= jumping-image
idle-left-image-sequence >= active-animation
0.0 >= pos-x
0.0 >= pos-y
3.0 >= character-x-velocity
})
pos-x | Math.Add((character-x-velocity | Math.Multiply((x-direction | ToFloat)))) > pos-x
Clamp(Min: -600.0 Max: 600.0) > pos-x
GFX.MainWindow(
Contents: {
Once({
GFX.DrawQueue >= ui-draw-queue
GFX.UIPass(ui-draw-queue) >> render-steps
})
UI(
UI.Area(
Position: @f2(pos-x pos-y)
Anchor: Anchor::Bottom
Contents: {
character-direction
Match([
@i2(0 0) {idle-animation > active-animation | Take(image-index)} ;;Take from the idle animation when in idle
@i2(-1 0) {
idle-left-image-sequence > idle-animation ;; Store the left idle animation into idle-animation when facing left
character-jumping-left > jumping-image
walking-left-image-sequence > active-animation | Take(image-index)
}
@i2(1 0) {
idle-right-image-sequence > idle-animation ;; Store the right idle animation into idle-animation when facing right
character-jumping-right > jumping-image
walking-right-image-sequence > active-animation | Take(image-index)
}
none {jumping-image} ;; we can just use none to handle the jumping state as all other cases at this moment, the character is jumping
] Passthrough: false)
UI.Image(Scale: @f2(0.2))
}
)
) | UI.Render(ui-draw-queue)
@button-inputs
@i2(x-direction y-direction) > character-direction
Step(animate)
GFX.Render(Steps: render-steps)
}
)
} Looped: true)
@mesh(main)
@schedule(main main-wire)
@run(main FPS: 60)
Clamp
takes in a value and makes sure that it does not exceed the minimum and maximum values.
Use theClamp
shard after adding to our pos-x
value, the min
parameter to be -600.0, and the max
parameter to be 600.0.
Yay! Now Glod will not walk off into the void! 😙
Step 4.4: Making Glod Jump¶
Now, let's make Glod jump!
Instead of just using velocity, we can add acceleration into the mix. This will make Glod's jump feel more realistic.
Jumping using Velocity & Acceleration
For smooth realistic jumping, we have to decrease velocity by acceleration whenever we jump. Decreasing velocity by acceleration decreases the rate of increase of pos-y
.
This simulates gravity, making our jump feel more realistic.
First, create the variables that we will use.
0.0 >= character-y-velocity ;; (1)
2.0 >= character-y-acceleration
Similar to character-x-velocity
, we will add character-y-velocity
to our pos-y
variable. This time however, we will also add character-y-acceleration
to character-y-velocity
.
;; ------------ Character Gravity Logic ---------------
pos-y | Math.Subtract(character-y-velocity) > pos-y
character-y-velocity | Math.Subtract(character-y-acceleration) > character-y-velocity
Lastly we modify the value of character-y-velocity
whenever the Up directional button is pressed.
Inputs.KeyDown(
Key: "up"
Action: {
Msg("up")
1 > y-direction
30.0 > character-y-velocity
false > grounded
}
)
@define( initialize-images {
LoadImage("GlodImages/Character1_Left.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
;; ---------- Character Idle sequence (Facing Left) ----------------
LoadImage("GlodImages/Character_Idle/Idle_Left/Character1_Idle_Left_1.png") >> idle-left-image-sequence
LoadImage("GlodImages/Character_Idle/Idle_Left/Character1_Idle_Left_2.png") >> idle-left-image-sequence
LoadImage("GlodImages/Character_Idle/Idle_Left/Character1_Idle_Left_3.png") >> idle-left-image-sequence
LoadImage("GlodImages/Character_Idle/Idle_Left/Character1_Idle_Left_4.png") >> idle-left-image-sequence
LoadImage("GlodImages/Character_Idle/Idle_Left/Character1_Idle_Left_5.png") >> idle-left-image-sequence
LoadImage("GlodImages/Character_Idle/Idle_Left/Character1_Idle_Left_6.png") >> idle-left-image-sequence
LoadImage("GlodImages/Character_Idle/Idle_Left/Character1_Idle_Left_7.png") >> idle-left-image-sequence
;; ---------- Character Idle sequence (Facing Right) ----------------
LoadImage("GlodImages/Character_Idle/Idle_Right/Character1_Idle_1.png") >> idle-right-image-sequence
LoadImage("GlodImages/Character_Idle/Idle_Right/Character1_Idle_2.png") >> idle-right-image-sequence
LoadImage("GlodImages/Character_Idle/Idle_Right/Character1_Idle_3.png") >> idle-right-image-sequence
LoadImage("GlodImages/Character_Idle/Idle_Right/Character1_Idle_4.png") >> idle-right-image-sequence
LoadImage("GlodImages/Character_Idle/Idle_Right/Character1_Idle_5.png") >> idle-right-image-sequence
LoadImage("GlodImages/Character_Idle/Idle_Right/Character1_Idle_6.png") >> idle-right-image-sequence
LoadImage("GlodImages/Character_Idle/Idle_Right/Character1_Idle_7.png") >> idle-right-image-sequence
;; -------------- Walking sequence (Facing Left) -----------------
LoadImage("GlodImages/Character_Walking/Walking_Left/Character1_Walking_Left_1.png") >> walking-left-image-sequence
LoadImage("GlodImages/Character_Walking/Walking_Left/Character1_Walking_Left_2.png") >> walking-left-image-sequence
LoadImage("GlodImages/Character_Walking/Walking_Left/Character1_Walking_Left_3.png") >> walking-left-image-sequence
LoadImage("GlodImages/Character_Walking/Walking_Left/Character1_Walking_Left_4.png") >> walking-left-image-sequence
LoadImage("GlodImages/Character_Walking/Walking_Left/Character1_Walking_Left_5.png") >> walking-left-image-sequence
LoadImage("GlodImages/Character_Walking/Walking_Left/Character1_Walking_Left_6.png") >> walking-left-image-sequence
LoadImage("GlodImages/Character_Walking/Walking_Left/Character1_Walking_Left_7.png") >> walking-left-image-sequence
;; ----------- Walking sequence (Facing Right) ---------------
LoadImage("GlodImages/Character_Walking/Walking_Right/Character1_Walking_Right_1.png") >> walking-right-image-sequence
LoadImage("GlodImages/Character_Walking/Walking_Right/Character1_Walking_Right_2.png") >> walking-right-image-sequence
LoadImage("GlodImages/Character_Walking/Walking_Right/Character1_Walking_Right_3.png") >> walking-right-image-sequence
LoadImage("GlodImages/Character_Walking/Walking_Right/Character1_Walking_Right_4.png") >> walking-right-image-sequence
LoadImage("GlodImages/Character_Walking/Walking_Right/Character1_Walking_Right_5.png") >> walking-right-image-sequence
LoadImage("GlodImages/Character_Walking/Walking_Right/Character1_Walking_Right_6.png") >> walking-right-image-sequence
LoadImage("GlodImages/Character_Walking/Walking_Right/Character1_Walking_Right_7.png") >> walking-right-image-sequence
})
@wire( animate {
Once({
0.08 = idle-animation-speed
})
image-index | Math.Add(1) > image-index
Count(active-animation) = index-max
image-index
When(Predicate: IsMoreEqual(index-max) Action: {
0 > image-index
})
Pause(idle-animation-speed)
} Looped: true)
@define( button-inputs {
Inputs.IsKeyDown(
Key: "left"
)
When(Predicate: Is(true) Action: {
-1 > x-direction
})
Inputs.KeyUp(
Key: "left"
Action: {
0 > x-direction
}
Consume: false
SkipConsumed: true
)
Inputs.IsKeyDown(
Key: "right"
)
When(Predicate: Is(true) Action: {
1 > x-direction
})
Inputs.KeyUp(
Key: "right"
Action: {
0 > x-direction
}
Consume: false
SkipConsumed: true
)
Inputs.KeyDown(
Key: "up"
Action: {
Msg("up")
1 > y-direction
30.0 > character-y-velocity
false > grounded
}
)
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
0 >= image-index
idle-left-image-sequence >= idle-animation
character-jumping-left >= jumping-image
idle-left-image-sequence >= active-animation
0.0 >= pos-x
0.0 >= pos-y
3.0 >= character-x-velocity
0.0 >= character-y-velocity
2.0 >= character-y-acceleration
true >= grounded
})
pos-x | Math.Add((character-x-velocity | Math.Multiply((x-direction | ToFloat)))) > pos-x
Clamp(Min: -600.0 Max: 600.0) > pos-x
pos-y | Math.Subtract(character-y-velocity) > pos-y
character-y-velocity | Math.Subtract(character-y-acceleration) > character-y-velocity
GFX.MainWindow(
Contents: {
Once({
GFX.DrawQueue >= ui-draw-queue
GFX.UIPass(ui-draw-queue) >> render-steps
})
UI(
UI.Area(
Position: @f2(pos-x pos-y)
Anchor: Anchor::Bottom
Contents: {
character-direction
Match([
@i2(0 0) {idle-animation > active-animation | Take(image-index)} ;;Take from the idle animation when in idle
@i2(-1 0) {
idle-left-image-sequence > idle-animation ;; Store the left idle animation into idle-animation when facing left
character-jumping-left > jumping-image
walking-left-image-sequence > active-animation | Take(image-index)
}
@i2(1 0) {
idle-right-image-sequence > idle-animation ;; Store the right idle animation into idle-animation when facing right
character-jumping-right > jumping-image
walking-right-image-sequence > active-animation | Take(image-index)
}
none {jumping-image} ;; we can just use none to handle the jumping state as all other cases at this moment, the character is jumping
] Passthrough: false)
UI.Image(Scale: @f2(0.2))
}
)
) | UI.Render(ui-draw-queue)
@button-inputs
@i2(x-direction y-direction) > character-direction
Step(animate)
GFX.Render(Steps: render-steps)
}
)
} Looped: true)
@mesh(main)
@schedule(main main-wire)
@run(main FPS: 60)
Since our Anchor
is set to Anchor::Bottom
, it places the UI.Area
at the bottom centre of the screen. This is now our origin at (0,0).
In Shards, increasing the value of the y-axis pushes our UI.Area
down while decreasing it pushes it up.
Step 4.5: Adding vertical boundaries¶
Try running your code now!
Oh no, we have a problem. Glod falls through the floor! 😨
Don't worry, we can fix this!
Similar to how we made a boundary to ensure that Glod does not walk off-screen, we can add a boundary to ensure he doesn't fall through the floor. To do this, we just have to set our pos-y
value back to 0 whenever it goes beyond 0.0. Additionally, let's add a grounded
variable so that our jump and simulated gravity only activates when Glod is jumping and make our code more efficient.
You might also have noticed that Glod can keep jumping as long as we repeatedly press the jump key. Since we do not want this behaviour, we can easily fix this by only increasing the character-y-velocity
when the up key is pressed only if grounded
is true
true >= grounded ;; initialize our grounded variable
Inputs.KeyDown(
Key: "up"
Action: {
Msg("up")
1 > y-direction
grounded
When(Predicate: Is(true) Action: {
30.0 > character-y-velocity
false > grounded ;; make grounded false when up key is pressed
})
}
)
grounded
If(Predicate: Is(false) Then: {
pos-y | Math.Subtract(character-y-velocity) > pos-y
character-y-velocity | Math.Subtract(character-y-acceleration) > character-y-velocity
}) ;; only activate our simulated gravity when we are jumping
pos-y
When(Predicate: IsMoreEqual(0.0) Action: {
true > grounded
0.0 > pos-y
}) ;; reset grounded back to true when we touch the ground and make our pos-y 0
Lastly, let's tighten our code by fixing a few other bugs that you might have also noticed for niche cases.
-
If you do not let go of the up key. Glod remains in the jump animation even after he lands.
-
If you continue to hold the up key and then press the left or right keys, Glod moves left or right but remains in the jump position.
To fix this, we just have to reset Glod's y-direction
back to 0 when he lands.
pos-y
When(Predicate: IsMoreEqual(0.0) Action: {
true > grounded
0.0 > pos-y
0 > y-direction
})
@define( initialize-images {
LoadImage("GlodImages/Character1_Left.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
;; ---------- Character Idle sequence (Facing Left) ----------------
LoadImage("GlodImages/Character_Idle/Idle_Left/Character1_Idle_Left_1.png") >> idle-left-image-sequence
LoadImage("GlodImages/Character_Idle/Idle_Left/Character1_Idle_Left_2.png") >> idle-left-image-sequence
LoadImage("GlodImages/Character_Idle/Idle_Left/Character1_Idle_Left_3.png") >> idle-left-image-sequence
LoadImage("GlodImages/Character_Idle/Idle_Left/Character1_Idle_Left_4.png") >> idle-left-image-sequence
LoadImage("GlodImages/Character_Idle/Idle_Left/Character1_Idle_Left_5.png") >> idle-left-image-sequence
LoadImage("GlodImages/Character_Idle/Idle_Left/Character1_Idle_Left_6.png") >> idle-left-image-sequence
LoadImage("GlodImages/Character_Idle/Idle_Left/Character1_Idle_Left_7.png") >> idle-left-image-sequence
;; ---------- Character Idle sequence (Facing Right) ----------------
LoadImage("GlodImages/Character_Idle/Idle_Right/Character1_Idle_1.png") >> idle-right-image-sequence
LoadImage("GlodImages/Character_Idle/Idle_Right/Character1_Idle_2.png") >> idle-right-image-sequence
LoadImage("GlodImages/Character_Idle/Idle_Right/Character1_Idle_3.png") >> idle-right-image-sequence
LoadImage("GlodImages/Character_Idle/Idle_Right/Character1_Idle_4.png") >> idle-right-image-sequence
LoadImage("GlodImages/Character_Idle/Idle_Right/Character1_Idle_5.png") >> idle-right-image-sequence
LoadImage("GlodImages/Character_Idle/Idle_Right/Character1_Idle_6.png") >> idle-right-image-sequence
LoadImage("GlodImages/Character_Idle/Idle_Right/Character1_Idle_7.png") >> idle-right-image-sequence
;; -------------- Walking sequence (Facing Left) -----------------
LoadImage("GlodImages/Character_Walking/Walking_Left/Character1_Walking_Left_1.png") >> walking-left-image-sequence
LoadImage("GlodImages/Character_Walking/Walking_Left/Character1_Walking_Left_2.png") >> walking-left-image-sequence
LoadImage("GlodImages/Character_Walking/Walking_Left/Character1_Walking_Left_3.png") >> walking-left-image-sequence
LoadImage("GlodImages/Character_Walking/Walking_Left/Character1_Walking_Left_4.png") >> walking-left-image-sequence
LoadImage("GlodImages/Character_Walking/Walking_Left/Character1_Walking_Left_5.png") >> walking-left-image-sequence
LoadImage("GlodImages/Character_Walking/Walking_Left/Character1_Walking_Left_6.png") >> walking-left-image-sequence
LoadImage("GlodImages/Character_Walking/Walking_Left/Character1_Walking_Left_7.png") >> walking-left-image-sequence
;; ----------- Walking sequence (Facing Right) ---------------
LoadImage("GlodImages/Character_Walking/Walking_Right/Character1_Walking_Right_1.png") >> walking-right-image-sequence
LoadImage("GlodImages/Character_Walking/Walking_Right/Character1_Walking_Right_2.png") >> walking-right-image-sequence
LoadImage("GlodImages/Character_Walking/Walking_Right/Character1_Walking_Right_3.png") >> walking-right-image-sequence
LoadImage("GlodImages/Character_Walking/Walking_Right/Character1_Walking_Right_4.png") >> walking-right-image-sequence
LoadImage("GlodImages/Character_Walking/Walking_Right/Character1_Walking_Right_5.png") >> walking-right-image-sequence
LoadImage("GlodImages/Character_Walking/Walking_Right/Character1_Walking_Right_6.png") >> walking-right-image-sequence
LoadImage("GlodImages/Character_Walking/Walking_Right/Character1_Walking_Right_7.png") >> walking-right-image-sequence
})
@wire( animate {
Once({
0.08 = idle-animation-speed
})
image-index | Math.Add(1) > image-index
Count(active-animation) = index-max
image-index
When(Predicate: IsMoreEqual(index-max) Action: {
0 > image-index
})
Pause(idle-animation-speed)
} Looped: true)
@define( button-inputs {
Inputs.IsKeyDown(
Key: "left"
)
When(Predicate: Is(true) Action: {
-1 > x-direction
})
Inputs.KeyUp(
Key: "left"
Action: {
0 > x-direction
}
Consume: false
SkipConsumed: true
)
Inputs.IsKeyDown(
Key: "right"
)
When(Predicate: Is(true) Action: {
1 > x-direction
})
Inputs.KeyUp(
Key: "right"
Action: {
0 > x-direction
}
Consume: false
SkipConsumed: true
)
Inputs.KeyDown(
Key: "up"
Action: {
Msg("up")
1 > y-direction
grounded
When(Predicate: Is(true) Action: {
30.0 > character-y-velocity
false > grounded ;; make grounded false when up key is pressed
})
}
)
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
0 >= image-index
idle-left-image-sequence >= idle-animation
character-jumping-left >= jumping-image
idle-left-image-sequence >= active-animation
0.0 >= pos-x
0.0 >= pos-y
3.0 >= character-x-velocity
0.0 >= character-y-velocity
2.0 >= character-y-acceleration
true >= grounded
})
pos-x | Math.Add((character-x-velocity | Math.Multiply((x-direction | ToFloat)))) > pos-x
Clamp(Min: -600.0 Max: 600.0) > pos-x
grounded
If(Predicate: Is(false) Then: {
pos-y | Math.Subtract(character-y-velocity) > pos-y
character-y-velocity | Math.Subtract(character-y-acceleration) > character-y-velocity
})
pos-y
When(Predicate: IsMoreEqual(0.0) Action: {
true > grounded
0.0 > pos-y
})
GFX.MainWindow(
Contents: {
Once({
GFX.DrawQueue >= ui-draw-queue
GFX.UIPass(ui-draw-queue) >> render-steps
})
UI(
UI.Area(
Position: @f2(pos-x pos-y)
Anchor: Anchor::Bottom
Contents: {
character-direction
Match([
@i2(0 0) {idle-animation > active-animation | Take(image-index)} ;;Take from the idle animation when in idle
@i2(-1 0) {
idle-left-image-sequence > idle-animation ;; Store the left idle animation into idle-animation when facing left
character-jumping-left > jumping-image
walking-left-image-sequence > active-animation | Take(image-index)
}
@i2(1 0) {
idle-right-image-sequence > idle-animation ;; Store the right idle animation into idle-animation when facing right
character-jumping-right > jumping-image
walking-right-image-sequence > active-animation | Take(image-index)
}
none {jumping-image} ;; we can just use none to handle the jumping state as all other cases at this moment, the character is jumping
] Passthrough: false)
UI.Image(Scale: @f2(0.2))
}
)
) | UI.Render(ui-draw-queue)
@button-inputs
@i2(x-direction y-direction) > character-direction
Step(animate)
GFX.Render(Steps: render-steps)
}
)
} Looped: true)
@mesh(main)
@schedule(main main-wire)
@run(main FPS: 60)
Congratulations! Glod can now move and jump safely! 😊
Recap¶
Try running your code now! Glod should be happily running and jumping without any glitches. 😁
To recap:
-
We made Glod move by tying a variable to the
UI.Area
position and changing said variable. -
Simulated gravity by using velocity and acceleration.
-
We ensured that Glod does not walk or fall into the void by creating boundaries.
We now have a fully functional moving character!
In the next chapter, let's have Glod collect some coins! 🤑