Part 1: Movement, Physics, and You
Part 2: Jumps, Falls, and Walls
Part 3: Animation
Part 4: Enemies and NPCs
Time for the HUD. "HUD" means "heads-up display," a fancy term for the status bars, life meters, and other displays that convey information to the player. This installation will take advantage of slices, which is currently a WIP feature. If the ypsiliform version of the OHRRPGCE hasn't released by the time you read this, you might need a nightly build to take advantage of the latest changes.
See Part 4 for last month's plotscript. Since the full script has become so large, I'll no longer be including it at the beginning of each article. Remember to get up to speed via the links at the top of the article.
Last time, we created a goomba enemy. You can jump on it to kill it; if you bump into it some other way, you die instantly. We'll be tweaking that behavior slightly in this issue, since we'll be giving the hero a life meter -- and with it, the ability to take a hit.
Before we dive in, let's decide what elements the HUD will include. An effective way to do this is to make a sketch (yes, on paper) of what the screen will look like: just the layout, not the hero or the level or anything. Unfortunately, I can't see your sketch, so I'm going to go with something fairly generic.
In particular, I'll be putting a portrait of the hero at the top left, and to the right of that, a life meter. Below the life meter, I'll display the number of remaining lives. On the top right, I'll put a timer and a score indicator. (At this point, you might want to skip to the bottom and download the example game so that you can get a look at what I'm describing here. In fact, I recommend it.)
Creating a HUD used to be a more laborious affair: you'd have to arrange NPCs just right, then update them every cycle to make sure they were positioned right. Slices (essentially a fancy term for "sprites"), and, less recently, strings (programmer parlance for a variable that holds words instead of just a number) make our job much easier. Let's start off by defining a few things.
global variable, begin |
Okay, so maybe your hero has a different amount of life, or maybe you're not using points or a timer. It's hard to account for what you're going to do in your game, and while I can't accomodate every kind of game in this one article, hopefully the things you learn here are explained clearly enouch so that you can accommodate them to your own game.
Next up, we're going to... put all the information on the screen! In a refreshing departure from last issue's build-up, we're jumping right to the payoff this time. Before you see the script, though, it's time for a word on slices.
Slices (also referred to as "plotsprites") allow us to load up and display any of the sprites that you've drawn. Hero sprites? Enemy sprites? Attack sprites? Textbox border sprites? All of these and more are fair game. Just about the only thing you can't load into a plotsprite is a maptile. You can manipulate these graphics, move them around, and such. Very useful! Let's take a look at plotsprites in action.
global variable, begin |
Simple as that, the portrait and lifebar appear. Notice that I used a small enemy sprite for the lifebar. I didn't have to use that specifically, but I wanted something wider than a walkabout. Similarly, I could have used anything else instead of a portrait sprite for the portrait, but the portrait made the most sense in this case.
We still need to add the strings, but first, let's make the lifebar work. We'll make it so that the player doesn't instantly die from each hit and update the lifebar when life is lost.
global variable, begin |
Now our hero can get hurt and the result is reflected on the lifebar. Note that this implementation of update lifebar assumes that you've arranged your lifebar graphics in the same order as the example game -- that is, full to empty. It'll work for a lifebar of any length as long as you've got the right number of graphics (one for each possible HP value, from max to zero). If you expect the length of your lifebar to vary throughout the game, you'll probably need to edit that script.
Now, let's add the strings. We'll use five strings: one to display the number of lives left, one that says "SCORE," one that displays the score, one that says "TIME," and one that displays the time. If you've used strings before, this should all be very straightforward.
To display the time, we're going to use a timer. If you've never used timers, check out the set timer command. It'll save us a lot of work.
define constant, begin |
The timer takes care of updating the time string, so all we have to worry about is updating the lives and the score. But we don't actually have lives yet – that'll have to wait for the next issue. I gave the player 100 points per goomba squish. Generous, aren't I? Right now, that's the only way to get points.
The HUD is finished! Wasn't that quick? We probably won't touch it again anytime soon, but you should give some thought to customizing it to suit your purposes. How about adding the following touches to your own game?
Full plotscript approaches! What do you do?
include, plotscr.hsd
include, scancode.hsi
global variable, begin
1, friction
2, gravity
3, hero-x
4, hero-y
5, hero-vx
6, hero-vy
7, hero-speed
8, hero-max-vx
9, hero-max-vy
10, hero-jump-speed
11, hero-animation
12, hero-direction
13, enemy anim
14, hero-hp
15, hero-max-hp
16, score
17, lives left
18, hero-portrait
19, lifebar
20, hero-invincibility
end
define constant, begin
5, wiggle room
0, enemy:goomba
4, goomba walk speed
20, corpse time
0, first enemy id
0, last enemy id
0, string:lives
1, string:score label
2, string:score value
3, string:time label
4, string:time value
10, hero invincibility duration
end
plotscript, new game, begin
initialize
do game
game over
end
script, initialize, begin
suspend player
gravity := 25
friction := 15
hero-x := hero pixel x(me) * 10
hero-y := hero pixel y(me) * 10
hero-vx := 0
hero-vy := 0
hero-speed := 25
hero-jump-speed := -145
hero-max-vx := 100
hero-max-vy := 100
hero-direction := right
hero-max-hp := 3
hero-hp := hero-max-hp
score := 0
lives left := 3
hero-portrait := load portrait sprite(0)
lifebar := load small enemy sprite(0)
place sprite(hero-portrait, 5, 5)
place sprite(lifebar, 55, 5)
$0 = "x "
append number(string:lives, lives left)
show string at(string:lives, 55, 20)
$1 = "SCORE"
show string at(string:score label, 160, 5)
append number(string:score value, score)
show string at(string:score value, 160, 15)
$3 = "TIME"
show string at(string:time label, 250, 5)
set timer(1, 200, 10, @time up, string:time value)
show string at(string:time value, 250, 15)
end
script, do game, begin
variable(playing)
variable(hero can jump)
playing := true
# Let's do The Loop!
while (playing) do (
# Accept player input
if (key is pressed(key:esc)) then (playing := false)
if (key is pressed(key:right)) then (
hero-direction := right
hero-vx += hero-speed
)
if (key is pressed(key:left)) then (
hero-direction := left
hero-vx -= hero-speed
)
if (key is pressed(key:alt) && hero can jump) then (hero-vy := hero-jump-speed)
# Reduce speed if our hero's going too fast
if (hero-vy >> hero-max-vy) then (hero-vy := hero-max-vy)
if (hero-vx >> hero-max-vx) then (hero-vx := hero-max-vx)
if (hero-vx << hero-max-vx * -1) then (hero-vx := hero-max-vx * -1)
# TODO: Other game-playing stuff goes here.
do enemies
enemy anim += 1
if (enemy anim >> 3) then (enemy anim := 0)
if (hero-invincibility >> 0) then (hero-invincibility -= 1)
hero-x += hero-vx
hero-y += hero-vy
hero can jump := false
if (can fall) then (hero-vy += gravity)
else (
if (key is pressed(key:alt) == false) then (hero can jump := true)
# Apply friction
if (hero-vx << friction && hero-vx >> friction * -1
&& key is pressed(key:left) == false && key is pressed(key:right) == false)
then (hero-vx := 0)
if (hero-vx >= friction && key is pressed(key:right) == false)
then (hero-vx -= friction)
if (hero-vx <= friction * -1 && key is pressed(key:left) == false)
then (hero-vx += friction)
)
if (hero-vx <= 0) then (can left)
if (hero-vx >= 0) then (can right)
if (hero-vy <= 0) then (can rise)
put hero(me, hero-x/10, hero-y/10)
animate hero
wait(1)
)
end
script, can fall, begin
variable (hy) # hero's y-position in maptiles
hy := hero-y / 200 + 1
if(
(read pass block((hero-x / 10 + wiggle room) / 20, hy), and, north wall)
||
(read pass block((hero-x / 10 + 20 -- wiggle room) / 20, hy), and, north wall)
)
then (
if (hero-vy>=0)
then (
hero-y := hero-y -- (hero-y, mod, 200)
hero-vy := 0
)
return(false)
)
else (return(true))
end
script, can rise, begin
variable (hy)
hy := (hero-y) / 200
if(
(read pass block((hero-x / 10 + wiggle room) / 20, hy), and, south wall)
||
(read pass block((hero-x / 10 + 20 -- wiggle room) / 20, hy), and, south wall)
||
|
It gets bigger every time! Don't forget to check out the example game. The link is below.
Next time: Doors, levels, checkpoints! All of this and more! Whether you're planning a strict level-to-level flow like Super Mario Bros., freeform Metroidvania exploration, a "world map" approach like SMB3, or something else entirely, you won't want to miss the next issue. The best part? After the next issue, you've got everything you need to make a full-length game.
Voting time! You vote this time for the topic you want me to cover the time after next. Send me a PM on Slime Salad or e-mail me to vote. Here are your choices for part 6:
Thanks for reading and remember to vote on your favorite topic! The link to download the SS101 example game is below.