Sidescroller

by Forum_account
[Share] [Zip]
ID:104440
 

Creating a Platformer With BYOND

BYOND gives you a default movement system. If you open Dream Maker, create a new project, make icons, declare turfs, build a map, and run the game you can walk around. You don't have to program how the movement works, the behavior is built in. My Sidescroller library does the same thing except it provides you with a movement system for sidescrolling action/platform games.

This article explains how the library works and how you can use it to create a game. If you're familiar with Dream Maker and can make a simple game then you can use this library to make a platformer.

Contents

1. Object Sizes
2. Ladders
3. Icon States
4. Bounding Boxes and Centered Icons
5. Jumping
6. Ramps

Using the Library

The library is very easy to set up. Here are step-by-step instructions for setting up the library. If you're familiar with the Dream Maker program you can have a working sidescroller in just a few minutes. This tutorial assumes you've completed those steps to set up the library and create a basic game. If you want to follow along with this tutorial and try code snippets out you should complete those steps now.

We'll now look at how you can utilize more features of the library and add some new features of your own.

Object Sizes

In the world you've created all objects are 32x32 boxes. In a real game your mob's icon will probably be a picture of a person. The mob's icon is 32x32 but the mob only occupies part of that box. To handle this we can modify the mob's pwidth and pheight vars.

Open up mob.dmi and change the icon state. Instead of being a 32x32 blue box make it a 24x24 blue box, like this:

Then add this to the code:

mob
pwidth = 24
pheight = 24

Now run the game. The mob now occupies a smaller space than before.

Ladders

The library has support for ladders. A ladder is a non-dense tile that a mob can climb. To create a ladder you need to draw an icon state for it, then add this code:

turf
ladder
is_ladder = 1
icon_state = "ladder"

The is_ladder variable tells the library that the turf can be climbed. Place some ladders on the map and run the game:

Use the up and down arrow keys to climb ladders.

Icon States

With BYOND's default movement system it's often sufficient for a mob to have a single, 4-directional icon state. When the player moves the direction is updated so the proper image is shown. In an action/platform game you can perform different kinds of actions, like running or jumping, so you need different icon states. The library handles this for you.

You set the mob's icon var and the library will automatically update the mob's icon_state based on what the mob is doing. If you're standing still your icon_state will be set to "standing". If you're moving (on the ground) it'll be set to "moving". If you're hanging on a ladder it'll be set to "climbing". And if you're in the air (but not on a ladder) it'll be set to "jumping".

Let's create these icon states (or you can download this icon, you might need to right click the link and pick "Save As..."):

Note: These states are all 4-directional. The library automatically sets your direction to EAST or WEST so you need to make sure those states have images. For reference, here's what the standing state looks like:

You don't need to add any code. When you run the demo your mob will use these new icon states.

Bounding Boxes and Centered Icons

A mob's bounding box is the space that the mob occupies. The size of the box is determined by the pwidth and pheight variables. The position of the box is assumed to be in the lower-left corner of the mob's icon. However, this isn't always the case. Consider this poorly drawn person:

The mob is roughly 16 pixels wide. If we set the mob's pwidth var to 16, here's what happens:

The mob appears to be partially inside the wall. This is because the mob's bounding box is assumed to be in the lower left corner of the icon but we drew the mob in the center of the icon. To compensate for this we need to shift the mob's icon to the left 8 pixels when the game is running so that the part of the icon that represents the mob is inside the bounding box. We can do this with the mob's pixel_x var:

mob
pwidth = 16
pheight = 19
pixel_x = -8

Now here's what you'll see when you run the game:

The icon is shifted to the left so the mob cannot appear to be inside walls anymore.

Jumping

The library keeps track of the mob's motion as a velocity. A velocity is like a speed but it also has a direction assigned to it. Saying that the mob is moving four pixels per second is a speed, but saying that it's moving upwards at four pixels per second is a velocity. The library uses the vel_x and vel_y vars to keep track of the mob's velocity in the x (horizontal) and y (vertical) directions.

This sounds complicated but it actually makes things easy. When you jump, all that happens is your upwards velocity (vel_y) is increased. To handle jumping the library calls the mob's jump proc, here's the default behavior:

jump()
vel_y = 10

This means that when you jump your mob will move upwards at a rate of 10 pixels per second. Due to gravity it'll slow down and begin to fall. If we wanted to make the mob jump higher we can override the jump proc to create a different behavior. We can add this to our code:

mob
jump()
vel_y = 12

An upwards velocity of 12 is enough to jump two tiles high. Don't get too carried away - the height of the jump increases a lot with each increase to the velocity. Setting vel_y = 14 is enough to jump three tiles high and 16 is enough to jump four tiles high.

Ramps

You can also create ramps. Open turfs.dmi and make an icon that looks like this:

Call it "ramp". Then add this code:

turf
ramp
pleft = 0
pright = 32
density = 1
icon_state = "ramp"

The pleft and pright vars are used to define ramps. The pleft var tells the library the height of the turf at its left side. The pright var tells the library the height of the turf at its right side. We drew the ramp as being low on the left side and high on the right side so we set its pleft and pright to match. The height at its right side is 32 because the tiles are 32 pixels tall.

Place the ramp on the map and run the game.

Looks good.
Very nice. Whenever the time comes for me to work on my Dragon Ball Z game again, this is something I was hoping to incorporate. I will look forward to working with it.
Very interesting, I will have to try this out sometime! :)
I'd like to have people using the library, but I guess having people who are looking forward to using the library is better than nothing =)

I think I have it to a point where I'm done making major changes (famous last words, I know). If you start developing a game using the library, an update would either be completely compatible with the code you've written or be minor enough that you're not losing out on much if it's incompatible.
I've played around with it, and it's pretty cool, but for a library it is fairly awkward to implement.

Most people are use to clicking a button and that is the library included and doing all the work they need it to.
This one requires a fair bit more than just clicking a button (though it's not difficult to implement). Which will probably turn off a lot of people.
Oh well. Haha.
The Magic Man wrote:
Most people are use to clicking a button and that is the library included and doing all the work they need it to.
This one requires a fair bit more than just clicking a button

Can you elaborate on this? I'd like to make it as easy as possible to use so I'd appreciate any suggestions you can give.

I created this step-by-step guide for using the library. It looks like a lot of steps but most of them (5 of the 8 steps) are common for all DM projects: create icons, define turfs, make a map, etc.

I suspect that part of the problem is that people aren't familiar with using libraries. Looking around, there are very few that are worth using. The ones that get the most use tend to be for single, specific functions (like F_Damage or SwapMaps)
Great library and tutorial. Keep it up, Forum_account.
There are a few problems with your code

1) x and y offsets only work for mobs, not objects or turfs.

2) You should return 1 on success for Procs like movement and X and Y_Bump, this makes the code more flexible and easier for people to use.

3) You can jump on top of sideways and downward moving bullets without being hit or destroying the bullet.(You can also hit jump and hit the bottom of bullets without anything happening.)

Also, should the key procs really be assigned to every mob, it would make more sense if it was just /mob/player.

Thx for listening, so far its been a good library.
For objects and turfs you can use pixel_x and pixel_y instead of offset_x and offset_y. The reason you can't use pixel_x/y for mobs is because the library changes the mob's pixel_x/y as part of the pixel movement. Since the pixel movement doesn't apply to turfs and objs you should be able to use pixel_x and pixel_y on them. For consistency I could try to add support for offset_x and offset_y for non-mobs but I'm not sure how it'll work. If you set different values for a turf's pixel_x and offset_x, which one is used?

I didn't want to force people to develop around players being of type /mob/player so I made keyboard input work for all types of mobs. This is one of many things I don't like about keyboard input. The way BYOND handles macros it's hard to create default behavior in the library that works for all cases and can easily be extended.

For most procs it's not clear what success means. Also, I tried to design it so you'd never call movement or the bump procs yourself so you'd never be in a position to use their return values. If you have some examples where this would be useful I'd certainly consider adding it.

Many things seemed reasonable when I wrote the library and I quickly realized how silly some things were as soon as I started using the library. It's good to get constructive feedback from people who have used the library because you've probably used it differently than I have. Thanks for the feedback!
An instance where it would be nice to have a return value for movement is this: say we have a horizontally moving platform that touches a wall in its path. A player could be caught between the platform and wall and be pushed into the wall by the platform and then crushed. If I want the player to die when its crushed I have to be able to check if the player moved when the platform pushed it. If the player didn't move he was crushed, else if he did move then nothing special happens.
Also you didn't answer my bullet problem, to solve that would I have to use the ceiling and floor procs that you included?
Thx for answering my questions, I like your library so far.
Donut_eater wrote:
Also you didn't answer my bullet problem, to solve that would I have to use the ceiling and floor procs that you included?

I haven't noticed the issue you described. It could be an issue of the order in which moves are processed - based on how the mob and bullet move it may look like the mob should be hit but the bullet skips over the mob's corner.

Here's the code from the demo for checking if a bullet hits a mob:

pixel_move(vel_x,vel_y)
for(var/mob/m in oview(1,src))
if(m.inside(px,py,pwidth,pheight))
if(m.client)
m.die()
del src


It's not checking for collisions in specific directions so I'm not sure why you'd see strange behavior for certain types of collisions. What were you doing to observe the behavior you described?

As for the return value that's not a problem to add. It would certainly work for the situation you described but it seems like there should be an even better way to handle situations like that. I'll look into it.
working on your library, I was able have the pixel_move() proc return 1 on success. You have a variable called return_value which equals 1 by default. However if the player bumps into an object (I have the x_bump() and y_bump() procs return 1 by default) return_value gets set to 0. Here's part of the code:
pixel_move(dpx, dpy, trigger_bumps = 1)

var/return_value=1

// We could intelligently look for all nearby tiles that you might hit
// based on your position and direction of the movement, but instead
// we'll just check every nearby object.
for(var/atom/t in oview(1,src))

// We use the src object's can_bump proc to determine what it can
// collide with. We might have more complex rules than just "dense
// objects collide with dense objects". For example, you might want
// bullets and other projectiles to pass through walls that players
// cannot.
if(!can_bump(t)) continue

// this handles bumping sloped objects
if(t.pleft != t.pright)
if(!t.inside(px+dpx,py+dpy,pwidth,pheight)) continue

// check for bumping the sides
if(px + pwidth < t.px)
if(t.py + t.pleft > py + 3)
dpx = t.px - (px + pwidth)
if(x_bump(t))
return_value=0
continue
if(px > t.px + t.pwidth)
if(t.py + t.pright > py + 3)
dpx = t.px + t.pwidth - px
if(x_bump(t))
return_value=0
continue

// check for bumping the top and bottom
var/h = t.height(px+dpx,py+dpy,pwidth,pheight)

if(py + dpy < h)
if(py + pheight < t.py)
vel_y = 0
dpy = t.py - (py + pheight)
if(y_bump(t))
return_value=0
else
// py = h
dpy = h - py
if(y_bump(t))
return_value=0

at the end you will have
return return_value
Does my version of the proc work or are there possible problems with them (I'm talking about the previous message)?

Using pixel_x and pixel_y doesn't work for what I need because instead of moving the bounding box to fit the image it moves the image to fit the bounding box, which is the desired outcome.

Also I've came up with good ideas that you could include in future versions of this library if you desired. You could have basic math formulas like detecting the center of an obj or finding the angle or slope between two points and other useful functions like that.

Thx for listening to me, its a great library so far, I would never have achieved such smooth pixel movement without it.
Donut_eater wrote:
Does my version of the proc work or are there possible problems with them (I'm talking about the previous message)?

Yes, it sounds like your approach would work. My approach was to check the values of dpx and dpy at the end of the pixel_move proc:

if(dpx == 0 && dpy == 0)
return 0
else
return 1


The only difference I can see is that your approach would have pixel_move(0,0) return 1 and my approach would return 0. It's a subtle difference and I'm not sure what makes more sense here. It depends on how you'd be using the return value.

Using pixel_x and pixel_y doesn't work for what I need because instead of moving the bounding box to fit the image it moves the image to fit the bounding box, which is the desired outcome.

I'm not entirely sure what you mean. Could you draw a picture of what you want to achieve?

Also, I think you might need to do something like this:

turf
right_half
pwidth = 16
pheight = 32
New()
..()
px += 16


This should shift the tile to occupy the right half of the tile.

Also I've came up with good ideas that you could include in future versions of this library if you desired. You could have basic math formulas like detecting the center of an obj or finding the angle or slope between two points and other useful functions like that.

You can post ideas or code for procs like this in my forum. There's even a thread set up for it if you'd like to use that.

Thanks again for all the useful feedback, I'm glad you like the library.
Just thought I'd let you know that I'm basing a new project off of this library.

Do you happen to know if there are any major network differences between using BYOND's native pixel movement versus using your crafted pixel movement? Specifically as it relates to movement prediction. I'm making a heavy-action title and I was just wondering if you had any data on it already.

If not, I'll do some of my own testing and let you know the results.
You question can be better served here on his personal forum or even here on BYOND's Community forum.

Also, good luck on your game project. :)
Polatrite wrote:
Do you happen to know if there are any major network differences between using BYOND's native pixel movement versus using your crafted pixel movement? Specifically as it relates to movement prediction. I'm making a heavy-action title and I was just wondering if you had any data on it already.

I'm not sure what you mean by "movement prediction".

The library uses BYOND's native pixel movement by default so the network usage will be essentially the same. You can configure the library to run in a legacy mode where it uses its soft-coded pixel movement instead but I don't recall the difference in network usage being significant.