ID:1443657
 
Recently, I've been seeing a lot of misconceptions regarding screen objects and how they work, so have no fear, we're going to take a look at some of the finer points, and try to dispel some of the bad habits I've seen passed down over the ages.

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!
Well, this piece seems like a pretty good explanation of how to use screen_loc for onscreen objects, so good on you for taking the time Ter. I particularly gained from the covering of a map's viewport and how (on-screen)object placement can affect it, the thing about objects placed at 0,0 appearing as if placed at 1,1 did make me wonder.. but now i see whats up.

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:
"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.

..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?...
Can you provide an example of keeping something centered based on a mob's position? For example, with my minimap I'm creating, I have created a marker that stays at the mobs position correctly, but I cannot quite get the right math to make the map view stay centered on them by adjusting the top_right and bottom_left objects I made.
The minimap is at a scale of 2 pixels per tile.

hud
minimap
icon = '\minimap.png'
screen_loc = "minimap:SOUTHWEST"
var/hud/minimap/controls/bottom_left
var/hud/minimap/controls/top_right
var/obj/marker


New(mob/o)
..(o)
bottom_left = new
top_right = new
marker = new
marker.icon = '\Icons/Turfs/test.dmi'
marker.dir = owner.dir
marker.layer = src.layer+0.02
o.client.screen += src
o.client.screen += top_right
o.client.screen += bottom_left
o.client.screen += marker

update()

proc
update()
var/x = owner.x
var/y = owner.y
//var/z = owner.z
var/center_px = (round(x*2))-16
var/center_py = (round(y*2))-16
marker.dir = owner.dir
marker.screen_loc = "minimap:1:[center_px],1:[center_py]"
// if you are at 100,100
// 100,100 on the minimap which is 2 pixels per tile, is 200,200 pixel position
// 200/32 to get the hud tile is 6.25
// rounded to 6
bottom_left.pos(((x*2)/32)-10, ((y*2)/32)-10)
top_right.pos(((x*2)/32)+10, ((y*2)/32)+10)


controls
icon = null
New()
proc/pos(x, y)
screen_loc = "minimap:[round(x)],[round(y)]"
Just wondering, but when you say 2 pixels per tile, do you mean 4 pixels per tile? Otherwise rather than 200 by 200 pixels wouldn't the minimap be 200 x 100 or 100 x 200?

Unless by 'a scale of 2 pixels per tile' you meant 2 x 2 pixels to represent each tile.. in which case nvm ;D.

I gtg now, but hopefully i can report back in a bit and try to actually help out if i can.. just in case ter takes some time before he can get to back to you on this.
Yes, I meant "a scale of 2 pixels per tile". Every 32x32 tile was shrunk to a 2x2 box. The marker is flawless, but positioning these invisible objects is irking me :|
I found a workaround without using the stretchers.

My map is 400x400, and to find where the player is, I do:

player.x * 2 - 16
player.y * 2 - 16

Don't ask about the details, but that is the math. Using that math on the center of the map, 200x200, you get 384. In my minimap, the center of the map is displayed by default. So, I just shifted the pixel offsets of the map itself to keep things centered.

        proc
update()
var/x = owner.x
var/y = owner.y
// 200,200 is the center of the map
// by the below math, the center point is 384, 384

var/center_px = ((round(x*2))-16)
var/center_py = ((round(y*2))-16)
x_offset = 384 - center_px
y_offset = 384 - center_py
marker.dir = owner.dir
marker.screen_loc = "minimap:1:[center_px+x_offset],1:[center_py+y_offset]"
src.screen_loc = "minimap:1:[x_offset],1:[y_offset]"
Why and what is the : when refering to say x1:px1
In response to Akando5959
Akando5959 wrote:
Why and what is the : when refering to say x1:px1

It's the required format for screen_loc. If you are going to use pixel offsets on screen objects, the text string requires a specific format. See the reference for further information on the format.
So basically its a required syntax for a screenloc?

So I could read 4: +28

As start at the 4th pixle and offset it by +28 to give an actual x screen loc of 32?
I suggest giving it another read-through. I feel like I covered this information pretty clearly. If there is a specific portion that is unclear, please point it out specifically to me so I can make it more clear through improvements to the tutorial.
In response to Akando5959
"[x]:[px],[y]:[py]"

(x, y) are in tiles, like atom.x, y.

(px, py) are in pixels, like atom.pixel_x, _y.

This format isn't required at all. The x and y terms can be either a number, or one of "NORTH", "SOUTH", "EAST", "WEST", and there's also the "to" syntax as well. Check the reference already.
In response to Kaiochao
Kaiochao wrote:
This format isn't required at all. The x and y terms can be either a number, or one of "NORTH", "SOUTH", "EAST", "WEST", and there's also the "to" syntax as well. Check the reference already.

Well, the format is absolutely required, but it's flexible.

I feel like I described all of this information pretty in-depth. If it's unclear, please, for the sake of improvement, point out where it's unclear with quotes from the specific article, and explain why it doesn't make sense.