To go right to the hub entry, click here.
Update: I also created a similar demo that shows how to use these techniques with isometric maps: link
Pixel movement is not built into BYOND. That's not because it's incredibly difficult, but because there are many options for how to implement it. Pixel movement allows for more complex movement situations, more complex than BYOND's built-in tile-based movement system. There are many ways you can resolve these situations, so the implementation details are left to you.
BYOND's default collision model assumes that an atom takes up an entire tile and that you move one whole tile at a time. Because you are moving from one tile to another, your movement is all or nothing: it either fails or succeeds. If you try to move to a dense turf, you cannot. Pixel movement lets you move a fraction of a tile at a time. Because you can be standing at an arbitrary position within a tile and move a fraction of a tile, this creates many more possible movement scenarios:
On the left we see tile based movement. When you want to move up, there are two possibilities: the tile is dense or not dense. We can easily determine which move succeeds and which one fails. On the right we see pixel movement. What happens in these cases? Does the move fail completely or partially succeed? How do we figure this out?
First of all, we don't want the move to completely fail. Consider this situation:
The blue box is 6 pixels from the wall and moves 9 pixels to the right. If the movement failed completely the blue box would not move at all. What we want to happen is that the blue box moves 6 pixels to the right. Because the blue box is moving in one direction this is fairly easy to see why that should happen. Let's consider a more complicated example:
In this example the blue box is moving down and right. It ends up inside the wall, so we need to figure out how it hit the wall. Did it the top of the wall first or the side?
If we could look at the movement in "slow motion" we'd see that it just barely hits the top first:
But how do we make the program figure out which side it hit first?
We know how far inside the obstacle the box ended up and we also know how much the blue box was trying to move. If we compute x/dx this will tell us how far through the blue box's move it hit the side of the obstacle. Similarly, y/dy will tell us how far through the move it hit the top of the obstacle.
Suppose that x/dx is 0.5 and y/dy is 0.4. This means that 40% of the way through the move, the box hit the top of the obstacle. If it didn't hit the top first, it wouldn't have been until 50% of the way through the move that it hit the side. Because we know it hit the top first, we know how to adjust the box's position.
Fancy Collision Detection
In this example I treated all objects as rectangles. It is easy to see when and how rectangles intersect. You might want to use more complicated shapes for checking collisions (circles, rectangles that can rotate, etc.) but this can really complicate things. As long as your objects are rather small, I would suggest using a set of rectangles to approximate the object. A move is successful if all parts of the object move successfully.
One More Thing...
If you have an object moving really fast it can skip right over an obstacle if you're not careful. If you only check the final position of the moving object (and you don't consider what it had to move through to get there) then you might pass through an obstacle.
To avoid this you can keep the movement speeds small as compared to the size of the obstacle. If bullets can move 50 pixels per tick, they could skip right through a wall that's only 32 pixels wide.
To deal with fast-moving objects, break the move into separate parts, where each part moves the object a manageable distance. In the example of a bullet that moves 50 pixels per tick, treat its movement as two separate 25 pixel moves that occur on the same tick.
Sidescrollers
A basic sidescroller is about 80% pixel movement, 20% physics. Once you have a decent pixel-movement system the rest is easy to implement.
Demo
You can download a demo of these techniques here.
_include.dm contains code used by both demos.
_pixel-movement.dm contains code to handle pixel movement. The main proc there is mob.pixel_move, which moves a mob by a specified number of pixels taking obstacles into account.
demo-1.dm and demo-2.dm are separate demos of how to use the pixel movement system. The first demo is very simple. The second demo is a sidescroller. It is intended that you only include one of the demo files when compiling and running the project.
Isometric Pixel Movement
The isometric pixel movement demo can be found here: http://www.byond.com/developer/Forum_account/IsometricPixelMovement
That demo uses the same techniques described in this post. To handle height and jumping we need to add additional calculations to handle the additional dimension.
In the 2D top-down demo you could not bump into the white tiles, they were not dense. In this isometric demo we abandon the idea of "dense tiles". Every tile is dense because it has a floor, the question is: how high is the floor? We introduce the pdepth variable to determine how tall an obstacle is and we update the collision checks to take this into account.
Each tile has a floor, but no ceiling. You can't jump and hit your head on anything so we don't need to worry about collisions in the z-direction when your dpz is positive (when you're moving upwards).