Building An Elevator in Box2D

Introduction

It took me 4 days to build an Elevator via Box2D in Corona SDK. I wrote the actual code and API for it in about 30 minutes. Getting the physics to work proved way more challenging than I anticipated. I’m still learning the ins and outs of Box2D as it relates to game development, so wanted to report on how I went about it and what I learned.

The following article talks about how I’m using Box2D in my game, the 4 approaches I took to building an elevator, and what I eventually decided upon.

Please keep in mind some of the things below are related to Corona SDK’s implementation of Box2D. Thus, some of the issues and compensation measures I took may not need to be done, nor happen, in other implementations and platforms.

Why An Elevator?

Elevators provide an interesting transition in both games and movies. They also provide easier ways for game developers to unload and load a level, provide a rest for the player, and/or make it easy to provide cut scene dialogue to prepare for the player for an upcoming part. In a lot of horror movies, they actually are the focal point of a lot of scary scenes such as Resident Evil, and also provide a perfect setting for comedic relief such as such as Spiderman 2 and Deep Rising (good ole Ipanema).

One of my favorite easter eggs was from Max Payne was in an elevator. You walk into a shoddy NYC apartment building elevator alone, with the notorious off-kilter 3rd person camera. Combined with the absurd brightness in the elevator’s interior amongst a very noir game, the tell tale “elevator muzak” is playing. If you shoot the overhead speaker, Max utters a smirk filled, 2nd word annunciated “Thank you!“.

Romance, fear, death. In such a confined space clearly only meant for short periods of stay, elevators can force us to view the human condition ever more clearly. Many writers play up the claustrophobia aspect, the lack of control as a metaphor for life taking us where it wills to, or preying on our fear of the unknown when you hear bad things just outside the lift walls.

It also helps players in games get from one floor to another in a controlled manner. Kind of like they do in the real world.

How Does Mine Need to Work?

A game I’m building requires that a character inside a building go up a floor in the building he is in. It’s based on a Brooklyn apartment building that has 13 floors with 12 ft ceilings. The character is on the 12th floor and needs to use the standard elevator. It has a call switch on the outside. You press a button and the elevator will arrive at the floor the button was pressed. You can optionally walk into the elevator and press the up or down buttons for the same effect. The elevator does not move on it’s own, only when a button is pressed. It currently only operates on 2 floors.

Simple, right? Code wise, it was. Here’s the Lua class on Github. The only thing to really note is the goUp, goDown, and tick functions. The goUp and goDown simply set flags so the elevator knows where to go. The tick function handles the actual movement via the game loop. Distance joints, which I’ll cover in a minute, are not powered by motors, so I just increment or decrement the length to move it up or down.

Brief On Box2D

I’ve fully committed to Box2D. That means that a lot of things are handled for me such as gravity, forces, and collisions… basically all the physics code. If you’re not familiar with Box2D, here’s a brief synopsis.

From the site:

Box2D is an open source C++ engine for simulating rigid bodies in 2D. Box2D is developed by Erin Catto…

It’s basically a physics engine for games, although it can be used for other things as well. It’s been ported to a lot of other languages and platforms, such as ActionScript, Dart, and JavaScript.

It’s recently regained popularity because it can ease the creation of Angry Birds like games.

Box2D Constraints

You give up a lot of control when you use Box2D and also have to develop a certain way. For example, all bodies (or things in the world that can bump into each other) default to dynamic. This means they are affected by gravity, which you usually default to 9.8 to simulate the real world.

If you’ve read the Jailbitch tutorials and/or others of it’s ilk, you basically write code like:

thisObject.y += speed;
if(thisObject.y >= theGround.y)
{
stopFalling();
thisObject.y = theGround.y;
}

Notice that gravity is applied, by you, on a per object basis. No code, no gravity. No code, no collisions.

This makes building elevators, or “platforms that raise up and down” pretty straightforward. You just increase or decrease the y value of an object. You can detect to see if the character is touching the elevator, and make sure she rises and falls with the platform.

Using Y

In Box2D, all dynamic objects have gravity by default and all the collisions are handled for you. Additionally, how the collisions happen can be tweaked on a per object basis if you like. Things like how heavy something is hitting something soft, the friction, and the bounce. You do not have to write any code for this; it’s just how the world works.

However, making a dynamic object in Box2D rise and fall isn’t as straightforward as you would think. Box2D has a neat feature which allows developers to set a body’s x and y value, and it’ll apply the necessary gravity, forces, and collisions at the next redraw/tick. However, if you want something to move up, you have to treat it JUST like you would in the real world: through force (NSFW).

If I immediately set an apple above my head at a y of 0, it’ll eventually fall on my head. If I want to stay there, I have to keep setting it to 0 using an enter frame/interval/timer… like a juggler. That’s redonk.

Using Forces

Box2D provides, at least in Corona SDK, a variety ways of applying force; as an impulse/jolt as well as force over time. The 2 in particular are body:applyForce and body:applyLinearImpulse.

The applyLinearImpulse is momentary jolt or shove.

The applyForce is like a jet pack; you have to continually apply it via a timer/tick/enter frame to move something.

As you can see, this makes things more complicated. Instead of simply “moving an object by changing it’s y position” you’re now having to compensate for the gravity of t3h erf.

Using Kinematic Bodies

The other simpler option is to utilize kinematic bodies. Instead of dynamic, kinematic allows a body to be affected by forces, like things bumping into it, the functions above, but NOT gravity. Ideal for an elevator, right? In theory yes. I’ll explain why I didn’t end up going this route in a bit. The only drawback here is that kinematic objects don’t “look” like real world objects. They don’t “sway” in the wind, or of something pushes it it just slightly moves. It doesn’t look “real” and defeats the purpose of using Box2D. While following Newton’s Laws of Motion, it looks like an elevator in space vs. on Earth with gravity.

The whole point of kinematic is for the user to drag them via touch or mouse, for cut scene animations where a character can still affect the physical world but you need to move elsewhere… or for things you specifically don’t want to be affected by gravity.

Using Joints

The 3rd option is constructing a realistic elevator so that Box2D can do it’s job: simulate rigid bodies in a 2D space. The mantra I’ve heard repeated by those more experienced in Box2D is keep it simple (or just abandon Box2D and write it yourself… but um… no). Instead of building a real car in Box2D, just build simple geometry (bodies and joints), and let the image of the car itself make the player think it’s real.

An elevator in Box2D terms is basically a box you can walk in and has a cable that attaches the box to a firm ceiling. This means, you create 2 boxes and a joint.

…but what TYPE of joint? And how do you apply it? THAT is what took 4 days.

Version 1: Distance Joint

A distance joint is a line that connects 2 things. You can shorten the line, and like a fishing pole, will reel in the body it’s attached to while it’s dangling below. Perfect for an elevator right?

Bleh.

My first problem was the box was off-kilter. I made it in a C shape so the player could walk in with a wall to the right, and a ceiling to connect the joint too. Being a non-square, however, this made the elevator’s weight distribution un even. It would immediately rotate even if nothing was on it because the weight of the right wall was heavier then the left side with none.

While increasing the friction the elevator had the consistency of velcro, thus ensuring our character wouldn’t fall out, if the player DID walk left, they’d plummet to their death in the elevator shaft, even if you made the shaft just wide enough to fix the box. While you could shrink the shaft and increase Box2D’s sensitivity, it’d sometimes get stuck.

While I could set isFixedRotation to ensure the elevator no longer rotated, and make the distance joint a little more like a rubber band via its frequency and dampingRatio properties, it still didn’t look as cool.

Version 2: Multiple Distance Joints

Most modern elevators nowadays have a variety of attachments beyond a single cable. Magnetic locks with tracks… and usually more than 1 cable. So, I did the same thing and added 2 more cables to compensate.

At first, I was like eureka. But then I hit problems, and was like, no wai!

For some reason, if I changed the length of the first joint to reel in the elevator, AND set the other 2 joints to have the same length… sometimes it didn’t work. Yes, the values would change, but the elevator would just remain stuck. !? I don’t know if this was my code, the Corona simulator, or… Box2D.

At any rate, our intrepid heros can’t run from zombies if an elevator “sometimes works”. While a great plot device, it’s only good if I can control said plot device.

Version 3: Piston Joint

Piston joints are just that: a piston. You can control and constrain their linear movement so moves just like a normal hydraulic piston on carnival rides, etc. Fine, I’ll lose the elevator swaying/dangling behavior, but with piston constraints, it should make the coding easier, right?

Wrong.

Piston constraints, and Box2D in general in Corona totally breaks down when you introduce groups. In HTML, this is div’s within div’s. In Flash, this is Sprites within Sprites. In Corona, Groups are basically the same thing. When creating a game level, you can put the whole thing in a group, and just move the group to move your level.

Box2D, however, doesn’t like this at all. It assumes all bodies are in a same group. While you CAN put things in different groups, things get visually very strange.

Here is some boxes on top of each other.

Now, here’s where I move the group the 2nd box is in. You’ll see boxes pile on top of one another even though visually they are floating in space.

This change of global space also affects calls to the pistons setLimit function. Given Lua in Corona doesn’t have a lot of built in methods to affect coordinate space on the fly, you’re pretty much forced to keep things in a single group to keep it simple. While fine, you still have to CONTINUALLY update the coordinate space… and that doesn’t work if you a lot of classes affecting this as well as the fact that joints can move.

Version 4: Kinematic Floor

I knew I could knock this out in 20 minutes in Flash. So… I coded it like I could code it in Flash: Make a box, decrease its y to move it up, increase its y to move it down. In simple tests, this worked great. Again, I lost the realism factor (the whole point of using Box2D beyond saving a lot of code writing for collisions), but whatever, you MUST git-r-done and move on. Analysis paralysis == bad news bears. Kill t3h scope creep, man.

…however, once I applied collision filters, this fell on it’s face. The same exact code works just fine using a dynamic body, but kinematic? NOoooooOOOOOooo….

Debugging collision filters are impossible; they either work, or they don’t, and your only 2 options are to check your math again or give up. The most frustrating part is that collision filters are the main reason I like using Box2D vs. my own code. When they break (or appear to… no way my math is wrong… ever… specially when I’m changing the body type… right?), they erode to whole purpose of using Box2D in the first place.

I have a rule: if you can’t solve something in an hour, it’s time to move on. Whether that’s another problem/bug/feature, or solution in this case, so be it. So that’s what I did. My #’s checked out, but even in the isolation tests I did, the kinematic box would NOT collide with my character and no collision events were dispatched.

Things got REALLY weird when I created complex bodies using filters for each part; you can see the kruft multiple body code in the elevator class on github. Totally unrelated geometry would act very strange. I’m pretty positive it’s a bug in Ansca’s implementation as I suspect most are merely creating a single polygonal body for complex shapes vs. multiple (like I was for my floor, wall, and elevator ceiling).

Maybe if I spent another hour with a lot of sleep and no kids around I could figure it out, but the rule is there for a reason.

Solution

So I went back to Version 1 and just turned isFixedRotation to true. I also found that sometimes the elevator’s distance joint would get stuck at the top or bottom and even with a high maxMotorForce, the joint was simply incapable of dislodging the elevator. Worse, the values WOULD continue to change and I’d receive no code errors, and visually nothing would change. Simply applying some light applyLinearImpulse (a quick jolt) up or down fixed it every time.

Now, when my character passes by an elevator switch or touches the elevator, the GUI appears for him to go up or down. He’d sometimes fall through the 20 pixel floor, so I increased the Box2D position sensitivity from 8 to 10, slowed down the elevator a tad, and this seemed to fix it. Increasing the Elevator floor’s height + density seemed to help as well.

Conclusions

As you can see, what I thought would be simple was made harder by my not knowing the quirks of Box2D, nor having built an Elevator with floor collisions before. That said, Box2D is really fun, and I can see how the more I learn about it, the more time it saves me for certain types of things… like my Die Hard fire hose.

Basically, the things I need to keep in mind and learned:

  1. Keep all Box2D objects in Corona in a single Display Object; inject child dependencies if a class needs to create composited objects
  2. If a joint has a length & motor, but things get stuck, try increasing the body’s density, reduce nearby body’s friction, and applying a tincy bit of impulse to “git er unstuck””.
  3. Create visual test harnesses; a place where you can test your components in isolation. It’s amazing how frustrating debugging reference points are in Corona SDK as a Flash Developer. In Flash, everything is aligned top left unless you move stuff manually. You can’t even edit the registration point. In Corona, my convention is to default everything to top left to emulate how Flash works, and thus make my visual alignment coding appear cleaner… except for groups. Being able to test the elevator alone, and then in game, allowed me to quickly diagnose problems.
  4. To heck with you piston joint. You’re awesome, but until Corona makes it easier for you to adjust being in a different global coordinate space, I’m sticking with distance and touch joints.
  5. I really wish I knew why my kinematic bodies aren’t working with my collision filters. In isolated tests, it works just fine.

I hope this helps you in your Box2D adventures.

One Reply to “Building An Elevator in Box2D”

  1. Outstanding – I love the elevator and the character rope you created is even cooler. Corona SDK has been extremely fun to work in, and my next design will be for a 2d platformer. It’s interesting using box2d as the physics engine to handle most of the things I used to do in Flashpunk/Flixel.

    Looking forward to more corona game dev posts!

Comments are closed.