This week's installment will be regarding overlays, and I'll be touching on some tricks that will allow you to do things a bit more easily than I see you guys trying to do in the source code I often run across.
Overlays. What are they good for?
Overlays are great for clothing and special effects that last a little while, and move with the player. Most BYOND games use them fairly well, with a few notable exceptions.
The basics of adding overlays are pretty simple, but we will first explain how they work on the inside.
How they work
On the surface, most people think overlays are a list of objects, but actually, they are a list of something called appearances. Appearances are a special internal type in BYOND, and are generated based on a number of appearance-related variables.
Appearances are a lot like very limited atoms, but they only keep track of the following variables (list is not 100% complete):
icon
icon_state
text
pixel_x
pixel_y
pixel_z
color
blend_mode
alpha
layer
overlays
underlays
transform
maptext
maptext_width
maptext_height
Appearances are sort of like a visual snapshot of an object. When you change the above variables of atoms, DM searches for the correct appearance reference to use.
This is why overlays allows you to add objects, and to add images, icons, text strings, etc. to the list. It's because we're always storing a list of appearances, and not all these bizarre types. Internally, the overlay += / -= operations figure out the appearance that's being manipulated and respond accordingly.
Knowing all of this allows us to get around a few bugs we often see in BYOND games, and it also allows us to abuse a few more to make our lives easier.
Layering
By default, when you add overlays to an object, they will render according to:
1) Layer
1) Underlay
2) Overlay
3) Sequence
That means that the layer of the appearance is the dominant trait in determining how the image should layer, second/third is whether it's in the underlays or overlays list, and last, the order it was added to underlays/overlays.
FLOAT_LAYER
The float layer is a special layer that's useful for making overlays "float" on top of something. By default, the "float layer" of an overlay means the item will share the exact same layer value of the root object.
This means that I can add an overlay using float layer to the player's overlays, then change the player's layer, and the overlay will appear to change layers with the player.
You can also set a differential value by subtracting a number from FLOAT_LAYER.
Let's put this into practice:
#define HAT_LAYER FLOAT_LAYER
#define SHIRT_LAYER FLOAT_LAYER-0.01
#define PANTS_LAYER FLOAT_LAYER-0.02
#define SHOES_LAYER FLOAT_LAYER-0.03
obj
overlay
New(icon,layer)
..(loc=null)
src.icon = icon
src.layer = layer
underlay
New(icon,layer)
..(loc=null)
src.icon = icon
src.layer = layer
obj
item
equipment
var
equipped = FALSE
equip_icon
equip_layer
tmp
obj/overlay/overlay
proc
equip()
if(ismob(src.loc))
var/mob/m = src.loc
else
src.equipped = FALSE
return
if(!m.equipment.Find(src))
if(m.equip(src))
src.equipped(m)
else
src.equipped = FALSE
unequipped()
m.unequipped(src)
if(overlay)
m.overlays -= overlay
equipped = FALSE
equipped(mob/m)
m.equipped(src)
if(equip_icon)
overlay = new/obj/overlay(src.equip_icon,src.equip_layer)
m.overlays += overlay
equipped = TRUE
Read(savefile/F)
. = ..()
if(src.equipped==TRUE)
src.equip()
Bonus: for the complete equipment system, see this comment: http://www.byond.com/forum/?post=1187381#comment4165925
That's all there is to it. Using the float layer, you can better manage equipment than using MOB_LAYER+1 and such to set up your overlays manually. You also get the benefit of the overlays staying in place after the player's layer changes.
Efficiency tricks: Abusing appearances
One side-effect of the way overlay lists work, is that you can abuse their use of appearances to save you a lot of CPU time when generating a number of similar objects:
var/string = "Hello"
var/strlen = length(string)
var/obj/word = new()
var/obj/o = new()
o.icon = 'font.dmi'
for(var/pos in 1 to strlen)
o.icon_state = "[text2ascii(string,pos)]"
word.overlays += o
o.pixel_x = letter_widths[o.icon_state]
You'll notice in the above example I'm adding the same object to the overlays list of another object over and over again. This saves us a lot of CPU, and the outcome looks the same as using a different object for each letter. This is because when we change the icon_state and pixel_x of the object, it changes the appearance of the object, and we're adding that new appearance to the overlays of the word object.
Immutability
There's a trend in BYOND games to subtract things from overlays five or six times because for some reason, some overlays just tend to stick around when they are no longer needed. It's not a BYOND bug, it's a mutability problem.
This problem is best shown rather than explained:
var/obj/o = new/obj()
o.icon = 'derp.dmi'
o.icon_state = "42"
src.overlays += o
o.icon_state = "33"
src.overlays -= o
You'll notice that the icon_state "42" doesn't go away. That's because we changed the appearance of the object before subtracting it. If you add an object to an overlays list and want to remove it later, you can't change the object reference. If you want to allow that object to go away, but still want to remove that appearance from the overlays later, you can do something kind of sneaky:
var/obj/o = new/obj()
o.icon = 'derp.dmi'
o.icon_state = "42"
src.overlays += o
var/appearance = overlays[overlays.len]
o.icon-state = "33"
src.overlays += o
src.overlays -= appearance
You'll notice that this time, we managed to get rid of the overlay. That's because I kept a reference to the appearance itself.
And that's all for this week. Please leave suggestions for topics for Snippet Sunday #2 below!