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.