atom.screen_loc:
All atoms contain a variable called screen_loc. When the object is added to a client's screen list variable, screen_loc determines how/where it is displayed.
The screen loc variable is one of BYOND's numerous text-based variables. It determines all of the location data from a formatted string of text.
This format is pretty simple:
"[map]:[x1]:[px1],[y1]:[py1] to [x2]:[px2],[y2]:[px2]"
Many of these variables are optional, but what do they all mean?
map: map refers to the map interface element the object will appear in. If you leave this blank, by default the object will appear in the default map element. This only needs to be specified if you are adding things to a secondary map element in your interface.
x1: x1 is the x tile that the object will appear at. The bottom-left corner of the screen is 1,1. The top-right corner is determined by your view size.
y1: y1 is the y tile coordinate that the object will appear at.
px1/py1: px1/py1 are the pixel offsets from the tile location that the object will appear at.
x2/y2: x2/y2 is used to make objects repeat over a square of the map. For every tile on the map between x1,y1, and x2,y2, the object will be displayed. This is useful for creating backgrounds and borders without using a huge number of objects.
px2/py2: This is much like px1/py1, but is used to help determining where the object will repeat to.
Okay, so now we know what each part of this variable means. How do we use it?
Well, you can set this format up a number of ways:
"x1:px1,y1:py1": This will place the object once on the main map interface element.
"x1:px1,y1:py1 to x2:px2,y2:px2": This will repeat the element on the main map interface element from x1,y1 to x2,y2.
"x1,y1 to x2,y2": You don't have to use pixel offsets. This will default to a pixel offset of zero.
"x1:px,y1": You don't even have to use pixel offsets on both axes if you use them on one.
Special values:
For positioning elements, there are a number of special values:
EAST, WEST, NORTH, SOUTH, and CENTER
These keywords will allow you to position screen objects based on the edges of the screen, regardless of the size of the screen.
"WEST,SOUTH" will place a screen object at 1,1
"WEST+1,SOUTH+1" will place the screen object at 2,2
"EAST-1,NORTH-1" will place the screen object 1 tile on each axis away from the top-right corner of the screen.
"WEST-1:16,NORTH+4:8" will place the screen object 1 tile and 16 pixels from the top of the screen, and 4 tiles and 8 pixels form the left side of the screen.
"WEST,1:320" will work just like "WEST,SOUTH+0:320".
Abusing offsets:
Okay, so you can put things on the screen. Great. What happens when we put an object outside of the boundaries of the viewport? Well, that depends on a number of things. We'll get to those in a minute.
If we set the screen loc of an object, then add it to the client's screen at "0,0", by default, the object will be 1 tile west and south of the bottom-left corner of the screen. Normally, you'd expect that the object would simply not appear on the screen right?
Well, by default, any objects outside of the screen's bounds will expand the viewport to the position of the object. If the viewport has the available space to expand, it will not expand the size of the view, just the size of the viewport. That means that you will get space that is not covered by map tiles from the player's view, but will have a large blank space to add objects to in the viewport.
If the viewport doesn't have the available space to expand, the viewport will be pushed a bit off center. If we were to add another object at: "NORTH+1,WEST+1", the viewport would be recentered, and you would no longer be able to see either object.
We can avoid this. There are some tricks to getting objects to not mess with the screen size. You can do this in three ways:
1) Use pixel offsets without tile offsets.
2) Use the atom's pixel_x/pixel_y variables.
3) Use the atom's transform variable.
Now, you know how I said that an object needs a tile/pixel offset to find a position? This is true, but the tile is exclusively used for managing the viewport size. So we can use pixel offsets greater than the size of a tile without any problems at all:
"1:320,1:240" This will move the object 320 pixels to the right, and 240 pixels upwards from the bottom-left corner of the screen (1,1).
It should be noted that objects positioned in this way will not cause borders to stretch as long as the object's tile position is inside of the viewport size.
Matrix and pixel_x/y variables will do the same thing as setting the screen_loc above.
Multiple maps:
Multiple map elements can be used, but you need to specify the id name of the map element when adding objects to it, otherwise, the objects will appear in the default map element.
Now, multiple map elements don't have a view size, and because of this, will never show turfs or objects on the actual dmm.
Multiple map elements are useful for hud elements that you can interact with and show data to the user. They do have a tendency to flicker on login, but you usually don't see that issue for very long at all.
What secondary maps do, however, is they center themselves on the objects that you have added to the screen. So if you add an object at 1,1, and another object at 3,3, the map will be centered on 2,2. By default, they will center themselves on whole tiles only, and are based on the bottom-left-most and top-right-most objects displayed on them.
If you want to display objects without worrying about them being centered or not, I'd suggest using two invisible "stretcher" objects to position the map viewport, then adding objects to the map using the non-stretching approach I showed you above.
//in this example, we want a map element that is 256x256
#define HUD_LAYER 1000
screen_obj
parent_type = /obj
layer = HUD_LAYER
New(var/icon,var/icon_state,var/layer,var/screen_loc)
src.icon = icon
src.icon_state = icon_state
src.layer += layer
src.screen_loc = screen_loc
..()
proc/create_mapview(var/mapname,var/client/c)
//add the stretchers here
c.screen += new/screen_obj(null,"",0,"[mapname]:1,1")
c.screen += new/screen_obj(null,"",0,"[mapname]:8,8")
//now that the stretchers are in place, we can add whatever we want using the non-stretching technique without having to worry about the positioning of the map.
c.screen += new/screen_obj('testicon.dmi',"teststate",0,"[mapname]:1:128,1:128")
I've actually used the above technique to create a global minimap, and only display a small part of it to the user by moving the stretchers around on the map to move the minimap into the desired position. You can abuse this feature to do a lot of clever things.
Per usual, if you have questions, drop them below!
Also, the concept you described of "stretchers" and how you could use them to create some nifty effects with a secondary map, does seem quite interesting indeed. I'll look to test out what i can do with secondary maps along those lines, and then possibly make use of it in a project as well, so thanks for that :D.
Edit: Forgot to note that the line
Ter13 wrote:
..seems wrong? whichever way it's worded, shouldn't it be that it places the screen object 1 tile and 16 away from the left side of the screen and then at 4 tiles 8 pixels higher than the north edge?...