ID:4421
 
Many developers on BYOND complain about the 32x32 tile based movement system BYOND uses. There are however simple ways around it, but it means redesigning most of BYOND’s nice and friendly variables and processes.

Firstly, it’s important to realise the variables that are concerned in this. There’re the locations variables; x, y, z and loc. You’ll have to make your own equivalent of all but the loc variable and z variable (the loc variable places you on the map and we shouldn't edit it. For the purposes of this article, and probably your game, we don't need to keep track of z). Next we’ll have to keep track of pixel_x and pixel_y into our own variable.

Let’s call the location variables rx and ry (r meaning real). Let’s call the pixel offsets px and py (p meaning pixel).

How are we going to start moving you ask? Using the client direction procs, such as client/North(). These are called when you hit a key on your keyboard. They then call client/Move(). client/Move() then calls client.mob.Move() which is essentially mob/Move(). First, let’s start moving pixel by pixel:



That’s pretty much all there’s to eliminating the 32x32 movement system. But the problem is that we have to know what our co-ordinates are on the map each time we move a pixel. You can’t just keep on increasing the mob’s location by 1 since all we’ve done is move 1 pixel, not 1 tile. It isn’t as simple as saying, after pixel_y is 32, add 1 to the location. If you imagine a 32x32 object, it reaches half way through the next tile after only 16 pixel moves. So once it reaches over half way, it’s fair to say that’s more of it is on the other tile therefore we’re going to make its location the other tile too.



We’re no longer going to use the default x and y location variables because setting them means that we actually move there too. This is undesirable because if your pixel_y is 17 and your y variable has increased by 1, it’d look like you moved 1 step rather than 1 pixel! So what we do is, we keep track of what our real location would be (so that we can refer to it if ever needed) and we completely abandon the x and y variables. So what we’re trying to do in client/North() is to increase our pixel offset by 1 and keep track of what our location should be. In order to do that, we also need to keep track of what our pixel_y variable is. We can’t simply use pixel_y to keep track of things for reasons that’ll become obvious soon.

This is what you should get:



The next problem comes at the limitations of pixel_y. pixel_x and pixel_y are signed integers, which means that their value is limited. You can’t use pixel_y after 127. So what we’ll do is, move the mob to where it REALLY should be and thus restore pixel_y back down to 0. The y location may be greater than world.maxy, so in that case, we’ll restore it to world.maxy instead.



The next thing to take into consideration is collision detection. You would normally detect collision through the Bump() proc, but if we’re not moving tile by tile (and are therefore breaking BYOND’s default movement system) how are two things meant to collide? The answer is that we draw a border around each and every object. What a border means is that if that border is crossed, we can say collision has occurred. If you’re working with 32x32 icons, you’ll probably want to place this around the edges. But you can if you want, regardless of icon size, place it anywhere in the middle of the 32x32 icon.



The red object is moving into the yellow object. The yellow object’s blue border has been crossed. This means collision has occurred. It is vital that you understand this concept of crossing the border.

How do we implement this into BYOND? First you’ve got to define the variables holding the border (no you don’t literally draw the border on the icons). Four variables: bottom, top, left and right. If you want the bottom edge of a 32x32 icon to be the bottom border, make bottom equal 32. If you want the left edge of a 32x32 icon to be the left border, make left equal 32. You should have the following variables:



The variables defined for collisions have the value of the outside edges of a 32x32 icon. There is a good reason for why we choose 32 for bottom + left and 0 for top and right – it’s for the calculations that will be needed to be done. If you wanted collision to occur at the middle of the icon, you’d define bottom as 16 and left as 16. If you wanted collision to occur 4 pixels above the bottom of the icon, define bottom as 28 (32-4). If you want this collision to stop 12 pixels below the top of the icon, define top as 12. Left and right work similarly too.

Essentially, bottom is used to define where collision starts when using client/North() and top is used to define where it stops. left is used to define where collision starts when using client/East() and right is used to define where it stops. This is reversed if we’re doing client/South() – top is used to define where it starts and bottom is used to define where it stops. Same for client/West().

Let’s assume we’re currently fully on a 32x32 tile (so our pixel offsets haven’t been changed). If we move north, we’re saying that there’s a possibility of a collision directly above us (ry + 1). So we need to check if that turf is dense, if it is let’s end all possible movement (you can change this in your game). If it isn’t dense, we need to search it for a mob and an obj. If we find one, lets compare our pixel_y to where collision is supposed to occur in that atom/movable. For example, if collision occurs in that atom/movable right at the border (so bottom = 32), any value greater than 0 for py should work (if we’ve taken 1 pixel step, then we must have cross its border by 1 pixel and so we must have collided).



You’ll now notice that this works fine if our pixel offset is 16 or below, but what about if our py is –16 (pixel_y = 17)? We have to handle it separately because we’ll now be using the top variable and the formula is different:



And there you have it, pixel movement with collision detection! To do this for client/East(), just change the y’s to x’s, bottoms to lefts and tops to rights. To do this for client/South(), just reverse everything done in client/North(). To do this for client/West(), just reverse everything done in client/East().

In addition to collision detection, you’ll want to implement how and when Enter() is called, I suggest to do it when ever the icon of the mob is fully overlaying a tile or when you’re 16 pixels into the tile (pixel_y = 16).

You may also want to increase how many pixels you move per step; 1 is very slow. It'll unfortunately mean more than just doing pixel_y += 2.

Finally, please note this is just guidance towards eliminating the tile - it is not a bulletproof library to be implemented into your game. There are probably more than half a dozen problems you'll run into in an actual game if you use just what is posted here.
Very cool! I'm definitly thinking about putting this into Heroes. I think i'm slowly understanding pixel movement now =P.
Very nice, nice job. Also, by the way maybe make a forum where people can post stuff that they would like to see.

->Calus CoRPS<-
Just a few notes.

There is no reason why you can't use the normal x and y variables that I can think of, and abandoning them merely makes things harder on you as you can't use functions that rely on an object's real location such as view and range.

If you implement the pixel movement part properly, there shouldn't be any need to check for a maxxed out pixel_x/y variable unless you also alter it elsewhere as well.

You should check more than just the tile directly in front of you for collision, as your border can cross that of objects diagonally ahead and to the left or right of your mob.
Oh, another thing...

You should also add or subtract from pixel_x/y instead of just setting it directly to 16 or -16. This way it will still work if you want to change the step size to greater than 1 pixel, which you most likely will.