Sidescrollers 101 - Part 2
feature by Adam Perry
Part
1: Movement, Physics, and You
Part 2: Jumps, Falls, and Walls
Welcome to our second installment of Sidescrollers 101. Today,
we'll finish the sidescrolling essentials: we'll make the hero jump and
fall as well as let him collide with walls without going through them.
In other words, you can make your own game after this lesson! Here's
the script as of last time:
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 end
plotscript, new game, begin initialize do game game over end
script, initialize, begin suspend player gravity := 35 friction := 15 hero-x := hero pixel x(me) * 10 hero-y := hero pixel y(me) * 10 hero-vx := 0 hero-vy := 0 hero-speed := 10 hero-max-vx := 100 hero-max-vy := 100 end
script, do game, begin variable(playing) 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-vx += hero-speed) if (key is pressed(key:left)) then (hero-vx -= hero-speed)
# Reduce speed if our hero's going too fast 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.
hero-x += hero-vx hero-y += hero-vy
if (can fall) then (hero-vy += gravity) else ( # Apply friction if (hero-vx << friction && hero-vx >> friction * -1 && key is pressed(left) == false && key is pressed(right) == false) then (hero-vx := 0) if (hero-vx >= friction && key is pressed(right) == false) then (hero-vx -= friction) if (hero-vx <= friction * -1 && key is pressed(left) == false) then (hero-vx += friction) )
put hero(me, hero-x/10, hero-y/10) wait(1) ) end
script, can fall, begin # TODO: Figure out if the hero can fall # For now, we'll just say he can't return(false) end
|
If you don't remember what's going on here, you might want to
take another look at Part 1 in last month's issue (linked above for
your convenience).
Walls!
We've already got gravity in our script. The problem is that
we're always telling the game that the hero can't fall. If you changed can
fall to true last month, you got some falling action, but
through the walls. Well, our first order of business today is to make
the hero fall normally, stopping on standable surfaces. Here's the full
can fall script:
define constant, begin 5, wiggle room 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 pixel y -- (hero pixel y, mod, 20)) * 10 hero-vy := 0 ) return(true) ) else (return(false)) end
|
Okay, once again, a complicated script. The only thing you
really need to know is that there's a new number called wiggle
room, which is the amount of pixels a character is smaller
than the full 20 pixels across. The bigger this number is, the more
easily the hero will fall through gaps.
The technical stuff here is that we're checking the wallmap
underneath the hero's feet. Because the hero might be standing on two
tiles -- the one under his left and the one under his right -- we're
checking both. If the hero was falling, the script aligns him with the
floor beneath him. Got it? Good.
can left, can right,
and can rise
So the hero can stand on things. Cool. But he can still move
through walls. Let's fix that with a couple of new scripts that I'll
call can left, can right, and can
rise for lack of a better naming scheme. They'll do about
what you expect them to do, which is to say about what can
fall does but in a different direction.
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) || (hero-y == 0) ) then ( hero-y := (hero pixel y -- hero pixel y,mod,20 + 20) * 10 if (hero-vy<<0) then (hero-vy:=0) return(false) ) else (return(true)) end
script, can left, begin variable (hx) hx := (hero-x--10) / 200 if( (readpassblock(hx,(hero-y) / 200), and, east wall) || (readpassblock(hx,(hero-y + 199) / 200), and, east wall) || (hero-x==0) ) then ( variable(new x) new x := 0 if (hero-x,mod,200 >> 100) then (newx := 200) if (hero-vx << 100, and, (hero-x,mod,200 >> 50)) then (newx := 200) hero-x := hero-x -- (hero-x,mod,200) + new x
if (hero-vx << 0) then (hero-vx := 0) return(false) ) else (return(true)) )
script, can right, begin variable (hx) hx:= hero-x / 200 + 1 if ( (read pass block(hx, hero-y / 200), and, west wall) || (read pass block(hx, (hero-y + 199) / 200), and, westwall) ) then ( hero-x := (heropixelx,mod,20) * 10
if (hero-vx >> 0) then (hero-vx := 0) return(false) ) else (return(true)) end
|
Complicated scripts again, but the good news is that they're a
lot like the previous one. For technical reasons that give me a
headache and that I won't get into, the can left
script is more complicated when the hero is moving left very quickly,
which is why there are a few extra lines there.
Integration time!
Good news: these are some of the most aggravating scripts to
write. You get the benefit of my experience here. This is not the first
version of these scripts -- it took me a long time to get them to work
right. With them out of the way, you'll be able to have a very smooth
sidescroller. Let's put them into the script, shall we? While we're at
it, let's throw in jumping. This is a long script; the new parts are in
bold.
global variable, begin
10, hero-jump-speed
end
script, initialize, begin
suspend player
gravity := 35
friction := 15
hero-x := hero pixel x(me) * 10
hero-y := hero pixel y(me) * 10
hero-vx := 0
hero-vy := 0
hero-speed := 10
hero-jump-speed := -110
hero-max-vx := 100
hero-max-vy := 100
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-vx += hero-speed)
if (key is pressed(key:left))
then (hero-vx -= hero-speed)
if (key is
pressed(key:alt) && hero can jump) then (hero-vx :=
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.
hero-x += hero-vx
hero-y += hero-vy
hero can jump := false
if (can fall) then (hero-vy +=
gravity)
else (
hero
can jump := true
# Apply
friction
if (hero-vx
<< friction && hero-vx >>
friction * -1
&&
key is pressed(left) == false && key is pressed(right)
== false)
then
(hero-vx := 0)
if (hero-vx
>= friction && key is pressed(right) == false)
then
(hero-vx -= friction)
if (hero-vx
<= friction * -1 && key is pressed(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)
wait(1)
)
end
|
Important note: I've added a new variable called hero-jump-speed.
Mess around with this to get the value you want. It needs to be
negative, though, because the hero is moving up when he jumps. Other
than that, the new bits are pretty straightforward, right?
End of lesson: Our script
A short but intense lesson! This is is what our script looks
like at the end of the day:
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 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 := -110 hero-max-vx := 100 hero-max-vy := 100 end
define constant, begin 5, wiggle room 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-vx += hero-speed) if (key is pressed(key:left)) then (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.
hero-x += hero-vx hero-y += hero-vy
hero can jump := false if (can fall) then (hero-vy += gravity) else ( hero can jump := true # Apply friction if (hero-vx << friction && hero-vx >> friction * -1 && key is pressed(left) == false && key is pressed(right) == false) then (hero-vx := 0) if (hero-vx >= friction && key is pressed(right) == false) then (hero-vx -= friction) if (hero-vx <= friction * -1 && key is pressed(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) 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) || (hero-y == 0) ) then ( hero-y := hero-y -- hero-y,mod,200 + 200 if (hero-vy<<0) then (hero-vy:=0) return(false) ) else (return(true)) end
script, can left, begin variable (hx) hx := (hero-x--10) / 200 if( (readpassblock(hx,(hero-y) / 200), and, east wall) || (readpassblock(hx,(hero-y + 199) / 200), and, east wall) || (hero-x==0) ) then ( variable(new x) new x := 0 if (hero-x,mod,200 >> 100) then (newx := 200) if (hero-vx << 100, and, (hero-x,mod,200 >> 50)) then (newx := 200) hero-x := hero-x -- (hero-x,mod,200) + new x
if (hero-vx << 0) then (hero-vx := 0) return(false) ) else (return(true)) )
script, can right, begin variable (hx) hx:= hero-x / 200 + 1 if ( (read pass block(hx, hero-y / 200), and, west wall) || (read pass block(hx, (hero-y + 199) / 200), and, westwall) ) then ( hero-x := hero-x -- (hero-x,mod,200)
if (hero-vx >> 0) then (hero-vx := 0) return(false) ) else (return(true)) end
|
You can make a full sidescroller now. Cool, huh? But there's
always more that you can add to your game.
Next time: Animation. Because a one-frame
hero just isn't that exciting.