People often have to resort to programs such as Microsoft Paint or Photoshop when editing graphics. But who needs them when you’ve got DM on your side?
First we’ll look at how to make an icon smaller.
In order to get access to every pixel in the icon that we’re going to scale, we have to create a loop. To get every pixel, we would have to create two loops like so:
This would mean going through the icon 1024 times, which would obviously be very slow. This is why instead of going through the icon at a pixel based level, we’ll go line by line. That’ll mean just creating one loop running 32 times.
Lets assume we’ll be scaling an icon to half its original size. This’d mean that a pixel at (32,32) should appear at (16,16). And in the same way, a pixel at (1,1) should appear at (0.5,0.5). Unfortunately, we can’t get decimals in the icon since by definition, a pixel is an indivisible unit. So we’ll have to round all units. There’s a simple formula for where we want our pixels to appear – round(current_pixel_location * factor). We can test this formula; if the factor = 0.5: round(32 * 0.5) = 16 which is of course correct.
In order to get the pixel at one point to move to this location, we must use icon.Shift().
At this point, the main problem is getting a line from the icon that we’re scaling. Wizkidd0123 has made a library called Pixel Functions which is able to isolate lines and single pixels too. The way it works is that you specify which icon you want to isolate something from, which exact line and whether it should be isolated vertically or horizontally.
We’ll start by looping through the y co-ordinates. This’ll mean we have to isolate a line horizontally (think of a graph with the line y = 5 – the line would be horizontal). Next we figure out where this line should end up using our formula. Next we have to move the line there using Shift(). And finally we have to Blend() the displaced line into a final result. The problem is, we don’t know how much we want to Shift() and which direction.
Since we’re taking horizontals, we have to Shift() these vertically. And since we’re scaling to make the icon smaller, we have to Shift() south. The amount by which me must Shift() is given by this formula: current_pixel_location – round(current_pixel_location * factor) . If we test this at factor 0.5 for the line at (32,32), we get: 32 – round(32*0.5) = 32 – 16 = 16. This makes sense because if we move a pixel at (32,32) down by 16 pixels, it’d appear at (32,16) which is where it should appear when the icon is halved. This pixel theory applies fully to lines as well.
We get this:
The icon right now has only been scaled vertically, we have to scale it horizontally too to get a full effect. All this means is that we loop through the x co-ordinates and isolate vertical lines rather than horizontal. We can save each line into an icon and this icon would be the final result.
The key thing here is that we’re now scaling something that has been scaled once – we’re won’t be scaling the original icon.
Ok this was easy enough, but what if we want to scale it and make the final result greater than before? i.e. Have a scale factor greater than 1.
The first thing to realise is that we’ll have to go outside the regions of 32x32 icons. What we have to determine is how many icons are needed. If a 32x32 image is doubled, you get an image with the dimensions 64x64. If we look at this in terms of icons, you get 4. A tripled image would have the dimensions 96x96, which means 9 icons. A quadrupled image would have the dimensions 128x128, which means 16 icons.
It becomes clear now that the total number of icons is the factor^2 (factor to the power of 2). But when the factor isn’t an integer, we get decimal answers. Rounding the answer is not safe because if the total number of icons is 5.4, it’d round to 5 which means there is still a 0.4 that hasn’t been taken care of. For this reason, we have to round every thing up. So even if the total number of icons is 5.01, we have to round this up to 6. A process to do this is commonly known as ceil() (ceiling).
We now have to think of how we’re going to save these icons systematically. A good method is to save it in a 2D list - L[x][y]. What we can do is, say that first lets scale the icon across the screen. Add each of these scaled icons into L[x][1]. Then what we do is take the icons in L[x][1], and scale them up the screen. We can save them as L[x][y]. So effectively, you get a list of icons. That list has a list of icons going across the screen. Those lists have a list of icons directly above them.
Please note that nothing will actually save at L[x], we’re saving at L[x][y].
If we’re doubling the icon, we’ll have 4 icons in total. L[2][2]. 2 icons will be going across the screen. Those 2 get saved at L[1][1] and L[2][1]. Then the icon above L[1][1] gets saved at L[1][2]. The icon above L[2][1] gets saved at L[2][2].
We have to come up with a system that’ll create a list like that for us. The icons going across the screen is sqrt(number_of_icons) and going up the screen is again sqrt(number_of_icons). Therefore, our list of icons would be:
var/list/ icons[sqrt(number_of_icons)][sqrt(number_of_icons)].
Then we have to add Blank.dmi to each of element in the list.
This is a nice way to do it:
The next job is to work out how many pixels wide the icon will be. This is easily achieved by doing sqrt(number_of_icons) * 32 which for integers will be factor * 32. Our loop this time will be go through py = 1 to total_pixels. When we’ve completed 32 lines, we have to tell the process to move on to the next icon across the screen. We first have to create a variable to track how many lines have been completed, lines_completed. To move on to the next icon across, we do L[current_across_location + 1][1]. This means we have to keep track of which across icon we’re on, we can call this current_image. We’ll also have to add a number variable to keep on increasing as the loop goes on, we can call this across_switcher. When ever we switch an icon, lines_completed will be restored too.
Now we have to figure out which line to isolate and the Shift() value. We’ll call create a variable for the line that we’ll isolate, grabber. If we do 1/factor, we can start to figure out which line to isolate. To keep things simple, from now on we’ll assume that the factor is 2. 1/factor = 1/2 = 0.5. Each time a pixel is looped through, we can add 0.5 to grabber. We’ll obviously have to ceil() this value when ever we need want to isolate a line. 0.5 ceiled will be 1. So when px = 1, get line 1. When px = 2, add 0.5 to grabber. Therefore grabber will be 1. So when px = 2, get line 1. We also need to work out a Shift() value. We’ll make a table to see what happens:
| Px | Grabber (ceiled grabber) | Shift | | | | |
| 1 | 0.5 (1) | 0 |
| 2 | 1.0 (1) | 1 |
| 3 | 1.5 (2) | 1 |
| 4 | 2.0 (2) | 2 |
| 5 | 2.5 (3) | 2 |
| 6 | 3.0 (3) | 3 |
What about scale factor 4?
| Px | Grabber (ceiled grabber) | Shift |
| | | |
| 1 | 0.25 (1) | 0 |
| 2 | 0.50 (1) | 1 |
| 3 | 0.75 (1) | 2 |
| 4 | 1.00 (1) | 3 |
| 5 | 1.25 (2) | 3 |
| 6 | 1.50 (2) | 4 |
Our method of ceiling grabber is working out well. But we need a formula for the Shift() value. Look at the table, there is a pattern between Px, ceiled grabber and Shift. Shift = Px – ceil(grabber).
Putting this all together, we get:

Since our scale factor is 2, our list will look like:
L[1][1] = Horizontally scaled icon
L[1][2] = Blank icon
L[2][1] = Horizontally scaled icon
L[2][2] = Blank icon.
The next part is to scale the horizontally scaled icons vertically. First we create a loop, looping through all the icons across the screen. Next we have to scale each of these across icons vertically. Thankfully, this means running a similar loop as the horizontal one. But we have to change the condition for how current_image is switched.
In the image above, you’ll notice we created a variable called across that we never used. The use comes in now. First we do a while(across) loop, removing 1 each time that icon has been scaled. This means we get the bottom right icon and scale it up until the bottom left icon has been scaled up as well.
The image being scaled will always be icons[across][1]. And now across_switcher will play the role of vertical_switcher, but we’ll keep on calling it across_switcher for simplicity’s sake.

As for saving into the list, all we have to do is L[across][across_switcher], That’s literally all there is to change.
As a final result, you should get this:

The method only works when the factor is greater than 1, so what we can do is to check whether the factor is greater than 1 or not. If it is, use the above method. Otherwise, use the method that we first started working with.
Now you may wonder what you can possibly do with a list of strangely organized icons. The possibilities are endless but in order to unpack the list, you can do this:

So then, who needs to go into Microsoft Paint and spend ages importing and exporting images into Dream Maker when you can scale icons using DM it self?
Posted by Programming Articles on Wednesday, October 19, 2005 02:54AM
- 12 comments
(link)
/
Members say:
yea +0,
nay -0
Eliminating the Tile
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.
Posted by Programming Articles on Friday, October 07, 2005 10:01PM
- 4 comments
(link)
/
Members say:
yea +0,
nay -0
Interaction
Many of us have tried to interact between different types of atom and have mostly succeeded. But success doesn’t mean you did it right.
turf/Enter()
Enter() is only called when something moves onto the turf. It is useful because one can decide whether or not to let the thing Enter()ing enter or not. It is no place to be doing any programming in which you’re going to do anything not related to simply allowing entry or not – such as teleportation to other locations or display messages to the thing Enter()ing. Enter() should only be used to decide whether or not to let the thing Enter()ing enter or not. To do this, you must return values. 1 if you want the thing to enter or 0 if you don’t want the thing to enter. One example showing this would be:
Enter() can also be used to get past density checks. If you return 1 and if density exists, you would still Enter() the turf.
In no case however must you assume that the thing Enter()ing will be a player, or even a mob for that matter. It is vital that you run checks before doing anything. Here’s an example showing what not to do:
Ok sure this’ll run fine when you go and test it, but have you really thought of all possibilities? What happens if a non-client Enter()s? Or what if it’s an obj that is Enter()ing? You’re just assuming that there’s a usr there – and in programming, you don’t assume.
First, we need a way to refer to the thing that is Enter()ing. You can do that easily by defining a variable in turf/Enter(). But you can’t just do turf/Enter(mob/M) because it could easily be an obj that’s Enter()ing. You would get a runtime error like runtime error: undefined variable /obj. We know only something that can Move() will be able to Enter() our turf, therefore we can say that atom/movable will always be safe.
Next we must check to see whether or not the thing Enter()ing is a mob or an obj. A simple ismob() and isobj() test will figure that out. After running one, you should refer to the variable atom/movable/A as var/mob/M or var/obj/O if you plan on accessing mob/obj specific variables and/or procs.
turf/Entered()
This is where you should do programming that happens after the thing has successfully Enter()ed - such as teleportation or displaying messages to the thing that Entered(). It is called only when Enter() returns 1. It won’t be called if Enter() returned 0.
Everything said for Enter() applies to Entered() so there’s no point in repeating what is already written above.
A very useful function of Entered() is that we can know where the thing that Entered() is coming from. To do this, simply put a second argument for turf/Entered() and then you can refer to the old location. Uses include getting the distance between the two locations, checking if they came from the right place and sending them back to the old place too.
Ok so we’ve figured out how to get interaction between turfs and movable atoms, but what about interaction between just movable atoms?
Bump()
This is one of the most misunderstood procs I’ve ever seen. The way it works is that the atom you define Bump() for will be the thing that has to move and collide with something to call Bump() and the thing you define within the Bump() will be the thing collided.
For example:
In this, considering something has caused obj/Knife to move, if the Knife collides with a mob or an obj that has density, Bump() for the Knife will be called.
Again you must not assume that the thing you’re Bump()ing is a player/client/mob/obj, you need to know! Similar checks used in turf/Enter() will be necessary here. First define the thing that is getting collided. Next, check if it’s a mob or an obj and then define a variable for it accordingly.
There is no place for usr in Bump()
An example of a strong Bump() system:
Bumped()
In some cases, you may not want to Bump() something but rather have that thing be Bumped() instead. There is no Bumped() proc defined in the DM language, but it is simple enough to make your own. Here is an efficient one I’ve found on the forums:
Bumped() works exactly like Bump() only instead of defining the thing Bump()ed, we define the thing that’s Bump()ing.
If you do include Bumped() in your projects, be sure to realize that both Bump() and Bumped() won’t be called in a collision between two atoms.
In this case, all you’ll ever see is “The knife hit the victim”. You won’t ever see “Victim got hit by the knife” because Bump() takes precedence over Bumped().
In conclusion of all interaction: Always have variables know what are the two things involved in the interaction. Run validity checks. Do relevant-to-the-game programming last.
Don’t use usr!
Posted by Programming Articles on Saturday, October 01, 2005 02:56PM
- 3 comments
(link)
/
Members say:
yea +0,
nay -0