Sidescrollers 101 - Part 4
A Feature by Adam Perry

Part 1: Movement, Physics, and You
Part 2: Jumps, Falls, and Walls
Part 3: Animation

Part 4: Enemies and NPCs

Back in action! We are back in action. Are you ready to learn how to add bad guys to your game? I am ready to tell you how to do it.

See Part 3: Animation for last month's plotscript. Since the full script is becoming so large, I'll no longer be including it at the beginning of each article. Remember, if you're lost (and with a script that large, there's no shame in it), you can always catch up via the links at the top.

Enemies: let's make some. In the dual interests of simplicity and brevity, this installment is only going to introduce a single enemy type. You've played Super Mario Bros., right? We're going to make a goomba. If you're familiar with plotscripting, you should be able to make other kinds of enemies with the template we'll introduce here, but we're only going to touch on that briefly here. Another issue might see the inclusion of more kinds of enemies, but not today.

An important note before you delve too deeply into the creation of your game: scripting will be much, much easier if you maintain your NPC IDs from map to map. If your Goomba is NPC 1 on one map, make him NPC 1 on every map. Seriously, this is important. The limit is 100 different NPCs per map at the time of writing, recently lifted from 36. This is more than ample for our needs. Let's reserve the first 20 NPC IDs (that's 0 through 19) for bad guys. That's more than we'll need, but that's good. Our goomba will be NPC 0.

Creating an enemy is a multi-part endeavor: first, we want to make him move -- but only when he's "in scope." If you replay SMB, you'll notice that enemies have a set starting position and they only start moving from that position once they're onscreen. The movement part of the equation isn't so hard -- we took care of the hero's movement in Part 1 and we can use the same logic for the enemies -- and figuring out if an enemy is onscreen is only a little tricky for reasons I'll mention in a bit. The other part of creating enemies is making them interact with the hero. In the case of our goomba enemy, we already know what the interactions look like: if the enemy touches the hero, then the hero dies, unless the enemy bumps into the hero from below the hero.

We'll start with the movement. The goomba has a very simple movement logic: he walks forward until he hits a wall. He can walk off cliffs and fall. He has a constant speed (falling aside), which makes things a little easier on us.

The first thing we need to do is add something to the big loop that processes enemy behavior for each frame. We'll create a function that we'll call do enemies to take care of this.
define constant, begin
5, wiggle room
0, enemy:goomba 0, first enemy id 0, last enemy id end (...) 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 hero-x += hero-vx hero-y += hero-vy (...) script, do enemies, begin
# Iterate through all enemy NPCs and perform their per-cycle actions
end

Finally, we have "other game-playing stuff" to put there. Note the new constant: "enemy:goomba". We'll use this instead of the number 0 to refer to our enemy's NPC ID. Note: If you're not intimately familiar with the difference between NPC instances, NPC IDs, and NPC references, this is a good time to stop reading this and check out the Hamster Republic wiki, which has loads of useful information on these topics. Trust me, it's going to get really confusing if you don't know the terminology. I'll try to make it entirely clear which one I'm referring to. NPC ID 0 is our goomba enemy, as mentioned above. Right now, we just have the one, so the first enemy ID is the same as the last enemy ID: 0. We have constants for these, too.

Right now, do enemies is empty. We'll iterate through every instance of NPC ID 0 and have each one perform a few checks. In Super Mario Bros. (and, indeed, in any sidescroller I can think of), all enemies start out inactive; that is, they don't move until they're onscreen or nearly onscreen. Otherwise, by the time you reached them, they'd already have wandered into a pit somewhere. We'll replicate this behavior with our goomba enemy. (In addition, older games have their enemies go "out of scope": if they wander too far offscreen, they despawn. This was probably due to hardware limitations. We won't duplicate that here: while it would be easy enough to do, there's no reason to do it.) So our enemy's logic looks a little like this:

  1. If the NPC is inactive and offscreen, nothing happens.
  2. If the NPC is inactive and onscreen, make it active.
  3. If the NPC is active, make it move.

In scripting language, that looks like this:

script, do enemies, begin
# Iterate through all enemy NPCs and perform their per-cycle actions
variable(i, j, npc, count)
for (i, first enemy id, last enemy id) do (
count := npc copy count(enemy:goomba) -- 1 # count goes from 0..n-1
for (j, 0, count) do (
npc := npc reference(i, j)
if (enemy is active(npc) == false) then (
if (npc is onscreen(npc)) then (activate enemy(npc))
)

# Check again, since it might have activated since last time
if (enemy is active(npc)) then (
process enemy(npc)
)
)
)
end script, enemy is active, npc, begin
return (npc extra(npc, extra 1))
end

script, activate enemy, npc, begin
set npc extra(npc, extra 1, true)
end

script, npc is onscreen, npc, begin
if (npc pixel x(npc) >> camera pixel x -- 20 &&
camera pixel x >> npc pixel x(npc) -- 320 &&
npc pixel y(npc) >> camera pixel y -- 20 &&
camera pixel y >> npc pixel y(npc) -- 200) then (
return(true)
) else (
return(false)
)
end

script, process enemy, npc, begin
switch(get npc id(npc)) do (
case(enemy:goomba) do (
# Make our goomba walk
)
)
end

That's a quick and dirty translation from our written description above to Hamsterspeak code. I won't go so far as to call it straightforward, but please make an attempt to understand what's going on. Notice that there are three new functions. We could have lumped all of the logic in the do enemies function, but separating it out makes it easier to read and understand. Note that we're using extra 1 to store whether an NPC is active (0 if it's inactive). If you're not familiar with NPC extras, you should look up the documentation -- they're variables attached to NPC instances that you can use for any purpose you want. Very handy. We're going to use extra 2 soon, too.

Several steps into our script, the enemy still isn't moving. Well, that's up next. Let's expand our script to make the enemy walk.

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 end define constant, begin 5, wiggle room 0, enemy:goomba 4, goomba walk speed 0, first enemy id 0, last enemy id end ... # TODO: Other game-playing stuff goes here.
do enemies
enemy anim += 1
if (enemy anim >> 3) then (enemy anim := 0)
... script, process enemy, npc, begin switch(get npc id(npc)) do ( case(enemy:goomba) do ( # Make our goomba walk set npc frame(npc, enemy anim / 2)
if (npc direction(npc) == left) then (
put npc(npc, npc pixel x(npc) -- goomba walk speed, npc pixel y(npc))
if (npc can left(npc) == false) then (set npc direction(npc, right))
) else (
if (npc direction(npc) == right) then (
put npc(npc, npc pixel x(npc) + goomba walk speed, npc pixel y(npc))
if (npc can right(npc) == false) then (set npc direction(npc, left))
)
)
put npc(npc, npc pixel x(npc), npc pixel y(npc) + npc extra(npc, extra 2) / 10)
if (npc can fall(npc)) then (
set npc extra(npc, extra 2, npc extra(npc, extra 2) + gravity)
if (npc extra(npc, extra 2) >> hero-max-vy) then (set npc extra(npc, extra 2, hero-max-vy))
) else (
set npc extra(npc, extra 2, 0)
)
) ) end

The enemy is animated now (you can tweak the lines related to enemy anim if you don't like it -- right now, he changes frames every other cycle). He also walks until he hits something and falls if there's nothing underfoot. Great! Note that unlike our hero, the goomba will actually face the direction he's moving. So you can draw the goomba like a normal sprite.

As promised, now we're using the enemy's second extra variable. It's storing the enemy's y-velocity. It increases when he's falling and resets to 0 when he's not.

Goombas can move through each other, but not through walls. (This behavior changes in later games, but it's true of the original Super Mario Bros.) When they hit a wall, there's a one-cycle delay as they change directions.

I referenced some new functions: npc can left, npc can right, and npc can fall. These are exactly like our existing can left, can right, and can fall functions, and we'll throw in a npc can rise function for good measure (the goomba won't use it, but another enemy might later).

script, npc can fall, npc, begin
variable (ny) # npc's y-position in maptiles
ny := npc pixel y(npc) / 20 + 1
if(
(read pass block((npc pixel x(npc) + wiggle room) / 20, ny), and, north wall)
||
(read pass block((npc pixel x(npc) + 20 -- wiggle room) / 20, ny), and, north wall)
)
then (
put npc(npc, npc pixel x(npc), npc pixel y(npc) -- npc pixel y(npc),mod,20)
return(false)
)
else (return(true))
end

script, npc can rise, npc, begin
variable (ny)
ny := npc pixel y(npc) / 20
if(
(read pass block((npc pixel x(npc) / 10 + wiggle room) / 20, ny), and, south wall)
||
(read pass block((npc pixel x(npc) / 10 + 20 -- wiggle room) / 20, ny), and, south wall)
||
(npc pixel y(npc) == 0)
)
then (
put npc(npc, npc pixel x(npc), npc pixel y(npc) -- npc pixel y(npc),mod,20 + 20)
return(false)
)
else (return(true))
end

script, npc can left, npc, begin
variable (nx)
nx := (npc pixel x(npc)--10) / 20
if(
(readpassblock(nx,(npc pixel y(npc)) / 20), and, east wall)
||
(readpassblock(nx,(npc pixel y(npc) + 19) / 20), and, east wall)
||
(npc pixel x(npc)==0)
) then (
variable(new x)
new x := 0
if (npc pixel x(npc),mod,20 >> 10) then (newx := 20)
put npc(npc, npc pixel x(npc) -- (npc pixel x(npc),mod,20) + new x, npc pixel y(npc))

return(false)
)
else (return(true))
)

script, npc can right, npc, begin
variable (nx)
nx:= npc pixel x(npc) / 20 + 1
if (
(read pass block(nx, npc pixel y(npc) / 20), and, west wall)
||
(read pass block(nx, (npc pixel y(npc) + 19) / 20), and, westwall)
)
then (
put npc(npc, npc pixel x(npc) -- (npc pixel x(npc),mod,20), npc pixel y(npc))

return(false)
)
else (return(true))
end

There's just one last thing to do now: deal with collision. We want to make it kill the goomba if the hero hits him from the top and kill the hero otherwise. (You might want the hero to be able to take more than one hit. We'll deal with that in a later installment.) The check for this will go in process enemy.

As an added bonus, how about a little death animation? When the goomba enemy is killed, he'll stay onscreen facing down for a second. Make sure to give him an appropriate squishy look on his down sprite.

define constant, begin
5, wiggle room
0, enemy:goomba
4, goomba walk speed
20, corpse time 0, first enemy id 0, last enemy id end ... script, process enemy, npc, begin switch(get npc id(npc)) do ( case(enemy:goomba) do ( # Make our goomba walk set npc frame(npc, enemy anim / 2) if (npc direction(npc) == left) then ( put npc(npc, npc pixel x(npc) -- goomba walk speed, npc pixel y(npc)) if (npc can left(npc) == false) then (set npc direction(npc, right)) ) else ( if (npc direction(npc) == right) then ( put npc(npc, npc pixel x(npc) + goomba walk speed, npc pixel y(npc)) if (npc can right(npc) == false) then (set npc direction(npc, left)) ) else (
# Run down the time until we delete the enemy
set npc extra(npc, extra 1, npc extra(npc, extra 1) -- 1)
if (npc extra(npc, extra 1) == 0) then (
destroy npc(npc)
exit returning(0) # Quit out of this script
)
)
) put npc(npc, npc pixel x(npc), npc pixel y(npc) + npc extra(npc, extra 2) / 10) if (npc can fall(npc)) then ( set npc extra(npc, extra 2, npc extra(npc, extra 2) + gravity) if (npc extra(npc, extra 2) >> hero-max-vy) then (set npc extra(npc, extra 2, hero-max-vy)) ) else ( set npc extra(npc, extra 2, 0) ) # Check for hero collisions
if ((hero-x / 10) -- npc pixel x(npc) << 20 &&
npc pixel x(npc) -- (hero-x / 10) << 20 &&
(hero-y / 10) -- npc pixel y(npc) << 20 &&
npc pixel y(npc) -- (hero-y / 10) << 20 &&
npc direction(npc) <> down) then (
# Collision! Check if hero fell onto goomba.
if (npc pixel y(npc) -- 20 >> (hero-y -- hero-vy) / 10) then (
# Goomba dies
set npc direction(npc, down)
set npc extra(npc, extra 1, corpse time)
# Hero bounces
hero-vy := hero-jump-speed / 2
if (key is pressed(key:alt)) then (hero-vy := hero-jump-speed)
) else (
hurt hero
)
)
) ) end script, process enemy, npc, begin switch(get npc id(npc)) do ( case(enemy:goomba) do ( # Make our goomba walk set npc frame(npc, enemy anim / 2) if (npc direction(npc) == left) then ( put npc(npc, npc pixel x(npc) -- goomba walk speed, npc pixel y(npc)) if (npc can left(npc) == false) then (set npc direction(npc, right)) ) else ( if (npc direction(npc) == right) then ( put npc(npc, npc pixel x(npc) + goomba walk speed, npc pixel y(npc)) if (npc can right(npc) == false) then (set npc direction(npc, left)) ) else (
# Run down the time until we delete the enemy
set npc extra(npc, extra 1, npc extra(npc, extra 1) -- 1)
if (npc extra(npc, extra 1) == 0) then (
destroy npc(npc)
exit returning(0) # Quit out of this script
)
)
) if (npc can fall(npc)) then ( set npc extra(npc, extra 2, npc extra(npc, extra 2) + gravity) if (npc extra(npc, extra 2) >> hero-max-vy) then (set npc extra(npc, extra 2, hero-max-vy)) put npc(npc, npc pixel x(npc), npc pixel y(npc) + npc extra(npc, extra 2) / 10) ) else ( set npc extra(npc, extra 2, 0) ) # Check for hero collisions
if ((hero-x / 10) -- npc pixel x(npc) << 20 &&
npc pixel x(npc) -- (hero-x / 10) << 20 &&
(hero-y / 10) -- npc pixel y(npc) << 20 &&
npc pixel y(npc) -- (hero-y / 10) << 20 &&
npc direction(npc) <> down) then (
# Collision! Check if hero fell onto goomba.
if (npc pixel y(npc) -- 20 >> (hero-y -- hero-vy) / 10) then (
# Goomba dies
set npc direction(npc, down)
set npc extra(npc, extra 1, corpse time)
# Hero bounces
hero-vy := hero-jump-speed / 2
if (key is pressed(key:alt)) then (hero-vy := hero-jump-speed)
) else (
hurt hero
)
)
) ) end script, hurt hero, begin
# For now, taking damage = death
fade screen out(63, 0, 0)
game over
end

A few notes: the corpse is represented by the NPC facing down. It stays onscreen for corpse time cycles, then the NPC is deleted. Optionally, you could extend the corpse time or make the corpses stay onscreen indefinitely. Corpses are still subject to gravity, but they can't hurt the hero. (This is the same behavior as with SMB's goombas.)

And now we have ourselves a goomba. This is our trickiest installment yet, and with any luck the trickiest to come. A lot of the work was infrastructure, so even if we made a more complicated enemy, there would be less new stuff to write.

Full plotscript coming up. Make sure to try out the sample file. Try making your own changes! For example:


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
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
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
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)

    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-vy <= 0) then (can rise)
    if (hero-vx <= 0) then (can left)
    if (hero-vx >= 0) then (can right)
    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)
    ||
    (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

script, animate hero, begin
if (hero-direction == right) then (
set hero frame(me, 1)
) else (
set hero frame(me, 0)
)

if (hero-vy << 0) then (
# Jumping
set hero direction(me, 2)
) else (
if (hero-vy >> 0) then (
# Falling
set hero direction(me, 3)
) else (
if (hero-vx <> 0) then (
# Cycle between walking and standing
if (hero-vx << 0) then (hero-animation -= hero-vx)
if (hero-vx >> 0) then (hero-animation += hero-vx)
if (hero-animation >= hero-max-vx * 2) then (hero-animation -= hero-max-vx * 2)
if (hero-animation >= hero-max-vx) then
(
set hero direction(me, 0)
) else (
set hero direction(me, 1)
)
) else (
# Standing
set hero direction(me, 0)
)
)
)
end

script, do enemies, begin
# Iterate through all enemy NPCs and perform their per-cycle actions
variable(i, j, npc, count)
for (i, first enemy id, last enemy id) do (
count := npc copy count(enemy:goomba) -- 1 # count goes from 0..n-1
for (j, 0, count) do (
npc := npc reference(i, j)
if (enemy is active(npc) == false) then (
if (npc is onscreen(npc)) then (activate enemy(npc))
)

# Check again, since it might have activated since last time
if (enemy is active(npc)) then (
process enemy(npc)
)
)
)
end

script, enemy is active, npc, begin
return (npc extra(npc, extra 1))
end

script, activate enemy, npc, begin
set npc extra(npc, extra 1, true)
end

script, npc is onscreen, npc, begin
if (npc pixel x(npc) >> camera pixel x -- 20 &&
camera pixel x >> npc pixel x(npc) -- 320 &&
npc pixel y(npc) >> camera pixel y -- 20 &&
camera pixel y >> npc pixel y(npc) -- 200) then (
return(true)
) else (
return(false)
)
end

script, process enemy, npc, begin
switch(get npc id(npc)) do (
case(enemy:goomba) do (
# Make our goomba walk
set npc frame(npc, enemy anim / 2)
if (npc direction(npc) == left) then (
put npc(npc, npc pixel x(npc) -- goomba walk speed, npc pixel y(npc))
if (npc can left(npc) == false) then (set npc direction(npc, right))
) else (
if (npc direction(npc) == right) then (
put npc(npc, npc pixel x(npc) + goomba walk speed, npc pixel y(npc))
if (npc can right(npc) == false) then (set npc direction(npc, left))
) else (
# Run down the time until we delete the enemy
set npc extra(npc, extra 1, npc extra(npc, extra 1) -- 1)
if (npc extra(npc, extra 1) == 0) then (
destroy npc(npc)
exit returning(0) # Quit out of this script
)
)
)
put npc(npc, npc pixel x(npc), npc pixel y(npc) + npc extra(npc, extra 2) / 10)
if (npc can fall(npc)) then (
set npc extra(npc, extra 2, npc extra(npc, extra 2) + gravity)
if (npc extra(npc, extra 2) >> hero-max-vy) then (set npc extra(npc, extra 2, hero-max-vy))
) else (
set npc extra(npc, extra 2, 0)
)

# Check for hero collisions
if ((hero-x / 10) -- npc pixel x(npc) << 20 &&
npc pixel x(npc) -- (hero-x / 10) << 20 &&
(hero-y / 10) -- npc pixel y(npc) << 20 &&
npc pixel y(npc) -- (hero-y / 10) << 20 &&
npc direction(npc) <> down) then (
# Collision! Check if hero fell onto goomba.
if (npc pixel y(npc) -- 20 >> (hero-y -- hero-vy) / 10) then (
# Goomba dies
set npc direction(npc, down)
set npc extra(npc, extra 1, corpse time)
# Hero bounces
hero-vy := hero-jump-speed / 2
if (key is pressed(key:alt)) then (hero-vy := hero-jump-speed)
) else (
hurt hero
)
)
)
)
end

script, hurt hero, begin
# For now, taking damage = death
fade screen out(63, 0, 0)
game over
end

script, npc can fall, npc, begin
variable (ny) # npc's y-position in maptiles
ny := npc pixel y(npc) / 20 + 1
if(
(read pass block((npc pixel x(npc) + wiggle room) / 20, ny), and, north wall)
||
(read pass block((npc pixel x(npc) + 20 -- wiggle room) / 20, ny), and, north wall)
)
then (
put npc(npc, npc pixel x(npc), npc pixel y(npc) -- npc pixel y(npc),mod,20)
return(false)
)
else (return(true))
end

script, npc can rise, npc, begin
variable (ny)
ny := npc pixel y(npc) / 20
if(
(read pass block((npc pixel x(npc) / 10 + wiggle room) / 20, ny), and, south wall)
||
(read pass block((npc pixel x(npc) / 10 + 20 -- wiggle room) / 20, ny), and, south wall)
||
(npc pixel y(npc) == 0)
)
then (
put npc(npc, npc pixel x(npc), npc pixel y(npc) -- npc pixel y(npc),mod,20 + 20)
return(false)
)
else (return(true))
end

script, npc can left, npc, begin
variable (nx)
nx := (npc pixel x(npc)--10) / 20
if(
(readpassblock(nx,(npc pixel y(npc)) / 20), and, east wall)
||
(readpassblock(nx,(npc pixel y(npc) + 19) / 20), and, east wall)
||
(npc pixel x(npc)==0)
) then (
variable(new x)
new x := 0
if (npc pixel x(npc),mod,20 >> 10) then (newx := 20)
put npc(npc, npc pixel x(npc) -- (npc pixel x(npc),mod,20) + new x, npc pixel y(npc))

return(false)
)
else (return(true))
)

script, npc can right, npc, begin
variable (nx)
nx:= npc pixel x(npc) / 20 + 1
if (
(read pass block(nx, npc pixel y(npc) / 20), and, west wall)
||
(read pass block(nx, (npc pixel y(npc) + 19) / 20), and, westwall)
)
then (
put npc(npc, npc pixel x(npc) -- (npc pixel x(npc),mod,20), npc pixel y(npc))

return(false)
)
else (return(true))
end

Next time: 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 there's not a new version of the OHRRPGCE by then, you might need a nightly build to take advantage of the latest changes.

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.

Download the Example Game