ID:2949303
 

Meet the Pixloc


Let's meet a new addition to BYOND in 516: The pixloc. The pixloc is a new primitive type that allows you to figure out exactly where something is. Pixlocs are not objects. They are primitive data structures that the engine has special rules for handling when used as data in functions, or in math operations. They contain several variables you can access to help you gather information about your world.

loc
step_x
step_y
x
y
z
Does this look familiar? It looks an awful lot like an atom's variables. Well, before 516, it was very difficult to say: "I want to put an object on this pixel", or "I want to see what's on the pixel exactly 37 pixels to the left of me." You had to write out a big long equation to convert the atom's position into global pixel coordinates:

x = ref.x * TILE_WIDTH + ref.step_x - ref.bound_x
y = ref.y * TILE_HEIGHT + ref.step_y - ref.bound_y

Then you had to do your math:

x += 37

Then you had to convert it back into tile/step offset:

var/turf/loc = locate(x/TILE_WIDTH,y/TILE_HEIGHT,ref.z)
var/step_x = x - loc.x * TILE_WIDTH
var/step_y = y - loc.y * TILE_HEIGHT

And even then, that's not half the work, because you still need to figure out whether or not the location is a valid location on the map, and you need to account for the bounds of the target atom, and so on.

Pixlocs solve a lot of this headache by being a convenient primitive type that you can use to pull data out of, and many of BYOND's built-in functions have been overhauled to take them as arguments now. For instance, I can grab the location of an atom's bottom-left corner by doing:

var/pixloc/saved_loc = ref.pixloc

I can then create a new object exactly at that location:

new/obj/something(saved_loc)

I can move another object to exactly that location:

something_else.pixloc = saved_loc

I can then alter that pixloc, and put another object exactly 32 pixels to the right:

saved_loc.x += 32
another_thing.pixloc = saved_loc

Pixlocs are insanely useful. They make it so that you no longer have to think about the grid at all if you aren't working in turfs. For movables, for projectiles, pixlocs will be your new bread and butter.

It's not overhead, it's a shortcut!


The pixloc variables I shared above have three primary variables: x, y, z. x and y are floating point numbers representing a pixel position on the map. The bottom-left corner of the map is 1,1. Partial-pixels are allowed, as BYOND natively supports subpixel movement as of several years ago. The z variable is an integer, and will reference which z layer the atom is on.

The other variables are virtual. They aren't technically stored in a pixloc. Instead, when you access them, the engine uses the x,y, and z variables to provide this information for you. Accessing pixloc.loc internally performs a locate() call, which will give you the turf at the specified location. Accessing pixloc.step_x and step_y will give you the pixel step remainder for that axis. This is equivalent to doing (x-1) % TILE_WIDTH.

Atoms do not actually "have" a pixloc either. The atom.pixloc variable is virtual. Reading the pixloc variable from the atom creates a new pixloc for you to work with. As such, changing a pixloc after reading it from a mob will not move that mob.

Other math operations can be performed on pixlocs. min(), max(), clamp(), round(), floor(), ceil(), trunc(), and fract() all now support pixlocs as arguments. Let's say you have two objects, and you want to get the bottom-left and top-right points of a square that would encompass them:

var/pixloc/p1 = pixloc(100,1,1)
var/pixloc/p2 = pixloc(1,100,1)
var/pixloc/top_right = max(p1,p2) //returns pixloc(100,100,1)
var/pixloc/bottom_left = min(p1,p2) //returns pixloc(1,1,1)

We could also use floor() or ceil() on a pixloc to remove the decimal points from the step locations. Or, we could get the nearest point on a 4 pixel grid for the current atom:

var/pixloc/target = round(src.pixloc,4) + vector(1,1)

Pixlocs are compatible with vectors in some math operations. For instance, you can add or subtract a vector from a pixloc, and get a new pixloc:

var/pixloc/p = pixloc(1,1,1)
var/vector/v = vector(32,0)
var/pixloc/n = p + v

//The value of p is unchanged, but we get a new pixloc n, with a value of (33,1,1)

When you subtract two pixlocs, you wind up getting a vector instead of a pixloc.

var/pixloc/p1 = pixloc(100,100,1)
var/pixloc/p2 = pixloc(10,10,1)
world.log << p1 - p2 //outputs vector(90, 90)

Vectors and pixlocs are best used together, but I'll be covering that over in my vector tutorial when that's finished.

bound_pixloc()


We can also get a point on the edge of an atom's bounding area using a new proc called bound_pixloc(). bound_pixloc() takes two arguments, the atom you want to get the edge of, and the direction of that edge. There are 9 possible points that you can get:
NORTH (1) - The top-center point
SOUTH (2) - The bottom-center point
EAST  (4) - The center-right point
WEST  (8) - The center-left point
NORTHEAST (5) - The top-right point
SOUTHEAST (6) - The bottom-right point
SOUTHWEST (10) - The bottom-left point (default)
NORTHWEST (9) - The top-right point
0 - the center of the bounding box


Multiple ways to skin a pixloc


You can get pixlocs to work with in a variety of ways. As shown above, you can create them explicitly by passing global pixel coordinates and a z coordinate, or you can grab them from any nearby object by referencing that object's pixloc variable. But you can also construct them in a variety of ways:

var/pixloc/p1 = pixloc(atom)
var/pixloc/p2 = atom.pixloc //these are both identical

You can also pass pixel x and y offsets with the atom form of pixloc, or a vector:

pixloc(atom,x,y)
pixloc(atom,vector(x,y))

You can also copy a pixloc from another pixloc:

pixloc(some_pixloc)

And you can also copy with an offset:

pixloc(some_pixloc,x,y)
pixloc(some_pixloc,vector(x,y))

Sometimes though, an object won't have a valid pixloc. For instance, if the object isn't directly on the map. In this case, the atom's pixloc, and any pixloc procs, will wind up returning null.


Moving on


The most useful part about pixlocs:

They are supported by Move(). You can supply a whole ass Pixloc to the Move() function, and the atom will try to move there. If the distance to that point is less than or equal to step_size, it will step to that location. If it is greater, the movable will attempt to jump to that location.

var/pixloc/p = pixloc(100,100,1)
ref.Move(pixloc,Dir)

Another function that has been changed to support pixlocs is bounds(). bounds() now has a pixloc form that takes two arguments, bounds(pixloc1,pixloc2), which will return all objects within the bounding box defined by both pixlocs.


Summary


Pixlocs are a humble, but deeply meaningful addition to DM. They may not seem like much, but they will massively change how you write code in the engine, and they will make breaking free of the tile grid a real possibility. No more do we need crazy movement frameworks to make something that performs beautifully and looks like a modern game. Embrace the pixloc. Embrace the future.
Straight from the horses mouth

Login to reply.