Part 1: Movement, Physics, and You
Part 2: Jumps, Falls, and Walls
Part 3: Animation
Part 4: Enemies and NPCs
Part 5: The HUD
Up until now, you've had to cram your game onto a single map. Today, that changes. Are you ready to tear down all your boundaries?
See Part 5 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. It might be worth your while to browse through the archive at the top of the article.
Last time, we created a HUD: life meters and more. We're diving into increasingly game-specific territory: not all games have a HUD, not all games have goomba-types. Well, let's pull out for a bit. Every game does need some way of transitioning between maps. Generically, we'll call these "doors," but they come in three types:
A less ambitious SS101 might cover just one or two of these types, but not this article. I'm going to do all three!
Note that this is not necessarily a good idea for your game -- it's rare to see a game use all three of these. Super Mario Bros. uses location triggers (flagpoles) and action triggers (pipes); Metroid uses screen-wraps exclusively; Sonic the Hedgehog uses location triggers exclusively. Offhand, Super Mario Bros. 2 is the only game I can think of that uses all three, and then you could argue that the screen-wraps were only used because of hardware limitations. Less is often more in this case.
Note that screen-wraps are essentially a specific instance of location triggers, but it won't make sense to treat them that way in the code. Behaviorally, screen-wraps are often displayed as Zelda-style scrolling, with the new screen pushing the old screen out of the way. There's no easy way to do that in the OHRRPGCE, though, so we won't try. (Famously, Sword of Jade does use that scrolling effect, but only within the same map. It still has the fade out/fade in transition from map to map.)
Here's a quick rundown on how each type of door will work in our script:
Screen-wraps are the easiest type to detect -- and by "detect," I mean "figure out when it's being used." It's easy to figure out that the player is at the edge of the screen. On the other hand, there's no built-in method of figuring out what to do with the player at that point, so we'll need a special convention for deciding which door to use.
Location triggers and action triggers will be handled in the same way: as invisible NPCs. The difference is that we'll check for the location triggers every cycle and we'll only check the action triggers when the "door button" is held. For our purposes, the door button will be the up key. These NPCs will call a script that uses a door -- the OHRRPGCE's built-in doors, that is.
Note that you can also use action-triggered NPCs for non-door purposes! This is your ticket to creating a world that the player can interact with. Use these NPCs for all the normal NPC uses: they can be switches to be flipped, people and creatures to talk to, or signs to give the player direction.
We'll talk more about those door types later. For now, let's jump into the script and implement screen-wraps. I mentioned we'd need a convention for deciding which door to use. We'll use door 0 for the top of the screen, door 1 for the right side of the screen, door 2 for the bottom of the screen, and door 3 for the left side of the screen. Conveniently, these numbers are the values Hamsterspeak uses for the constants up, right, down, and left.
# TODO: Other game-playing stuff goes here. |
Bingo, we have screenwraps. The downside of this approach is that you'll need to make sure to reserve the first four doors on each map (doors 0-3). If the doors don't go anywhere, then the edges of the screen act as walls, except for the bottom, which becomes an instant-death pit. (Feel free to change this behavior.) Another caveat is that you can't link to the map you came from. Also, you should be kind to your players: either make every fall fatal or make them all link to another map. Don't make them guess.
Now for the location triggers and action triggers. We'll do them in one go, since they're identical. Recall that we've reserved NPC IDs 0 through 19 for enemies. We'll use IDs 20 through 29 for location triggers and 30 through 49 for action triggers.
global variable, begin |
"Why do I need 20 action triggers," you ask? Good news! As previously mentioned, you can use them as regular NPCs, not just doors. You might want more of them. Go crazy.
But as far as setting up your door NPCs is concerned, you'll want to make them blank NPCs (no walkabout graphic) and give them the npc door plotscript, with the door number as the script argument.
That's it for doors! By special request, though, here's a bonus feature. James has asked that I mention gamepad support. While I don't personally have a gamepad, it's easy to allow gamepad users to use their gamepads.
if (key is pressed(key:right) || key is pressed(joy:x right)) then ( |
Simple as that, you have gamepad support. (Note that joy:button 2 might be a better choice for a jump button.)
And that concludes this month's tutorial. Warning: large script below.
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 21, used door 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 20, first location trigger 29, last location trigger 30, first action trigger 49, last action trigger 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) || key is pressed(joy:x right)) then ( hero-direction := right hero-vx += hero-speed ) if (key is pressed(key:left) || key is pressed(joy:x left)) then ( hero-direction := left hero-vx -= hero-speed ) if ((key is pressed(key:alt) || key is pressed(joy:button 1)) && 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 if (hero-x + 200 >> map width * 200) then (screenwrap(right)) if (hero-x << 0) then (screenwrap(left)) if (hero-y + 200 >> map height * 200) then (use door(down)) if (hero-y << 0) then (screenwrap(up)) check npcs if (used door) then ( # Player changed maps. Update location variables. hero-x := hero pixel x * 10 hero-y := hero pixel y * 10 hero-vx := 0 hero-vy := 0 used door := false ) 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) |
It gets bigger every time! Don't forget to check out the example game. The link is below.
Next time: Various improvements to the script. No votes were received this month, sadly! Make sure to vote for the next topic.
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 8:
Thanks for reading and remember to vote on your favorite topic! The link to download the SS101 example game is below.