What is an appearance?Every atom, or image, has an appearance. Its appearance is made up of all the vars that could possibly impact the way it's displayed. This includes:
- icon, icon_state
- name, desc, suffix
- density (not actually visual, but stored here anyway)
- pixel offsets (movables handle these specially)
- mouse pointers, mouse_opacity
- override (images only)
- color, alpha, blend_mode
- overlays, underlays
- plane (509)
- appearance_flags (510)
Notice how verbs are in the mix? That's why you can alter an atom's verbs list: You're actually creating a new appearance that includes the verbs you want.
Understanding overlaysSo now you know the special format that overlays and underlays use. These lists only store appearances, and in fact are not regular lists at all. They're an internal kind of ID-only list that conserves memory. And for the fun part: ID lists, like appearances, are also immutable and unique. This means that if you have two different mobs whose only overlay is a full health bar, their overlays lists will be the exact same list. (The verbs list is also an ID list, but for procs.)
Knowing this, what happens when you add an obj to another atom's overlays list? Or a type path? Or just an icon or an icon_state? All of those are legal to add to overlays.
Let's go through the process of adding an overlay to a mob.
var/obj/O = new
O.icon = 'hat.dmi'
O.pixel_y = 16
O.layer = FLOAT_LAYER
overlays += O
The mob's overlays list does not contain this temporary obj O, but a copy of its appearance. This is what happens, in order:
- A new obj is created; it is assigned the default appearance used by a regular /obj. We'll call this appearance A.
- O's new icon means it needs a new appearance. A new one is created, or an old one is looked up, that is identical to A but has hat.dmi as the icon. Call this B. (What if /obj already had hat.dmi as the icon? No change, and B is A.)
- O's pixel offsets are changed. This does not affect its normal appearance, because movables store pixel offsets separately. (Why? To prevent "churn" in games where pixel offsets are the main thing that changes. This was before we had the step vars.) A pixel-offset appearance will only be calculated if it's needed.
- Changing O's layer changes its appearance again: from B to new appearance C. If B isn't being used anymore, it's deleted.
- Adding to src's overlays means we need a copy of O's current appearance, including pixel offsets. That offset appearance is calculated now, which gives us appearance D.
- A new ID list is created, or looked up, that matches src.overlays but has D added onto the end.
- src gets a new appearance. If its appearance was P, now it will be Q instead; Q is the same as P but uses the new overlays list.
- The proc ends. O is deleted because it has no more references. Appearance D is still in use, but if nothing else is using C, it's deleted too.
Appearances and animationWhen you call animate(), you include all of the vars you want to change. It's clear now what this is doing: A new appearance is created, and animation just interpolates from one to another.
Each stage of an animation has a "from" appearance and a "to", which we'll call A and B for clarity. When Dream Seeker displays an animated atom, it looks up which stage it should be using at the current time index. Then it figures out how far along it must be from A to B, and compares the two appearances to see what's different. For anything that's different, it will interpolate smoothly if it can (like color, transform, etc.). The atom will always still have a "true" appearance, which is wherever its animation ended. If the animation doesn't loop forever, eventually it will finish there and the animation will be erased.
Appearances and savefilesWait a sec, you say: How does this factor into savefiles? Are they saving whole appearances? Nope, they are not.
When an atom is saved, its appearance-related vars are saved--if they differ from what's normal for that atom's type. That is, layer will show up in the savefile if you changed it, but otherwise it won't. That's the same for any other vars.
The overlays and underlays lists, though, don't fare so well. When the savefile format was first invented, it was given no "standard" way to save appearance info. As a result, savefiles do the simplest thing they can: merging all overlays into a single icon, and the same for underlays. When the savefile is loaded again, what might have been ten overlays loads up as one, which is just an icon and layer=FLOAT_LAYER.
For this reason, I always recommend that you clear overlays when saving, and rebuild them yourself after loading. It avoids bloat in both your savefiles and the icon cache, and it's just good maintenance.
Using the new appearance varVersion 508 introduces the atom.appearance var (also used by image), which can be used to grab a copy of an atom's current appearance, or update it in one shot. These are the only vars that will not be affected when you set an appearance:
mob/verb/Doppelganger(mob/M in oview())
if(magic < 10 || dg_reset) break
magic -= 10
// save the current appearance
dg_reset = appearance
// copy the spell target's appearance
appearance = M.appearance
appearance = dg_reset
dg_reset = null
There are some subtleties to this of course. If used in a real game, you'd want to make sure to revert to the original appearance (if any) when saving. You might want to be able to cast the Doppelganger spell again before time runs out, which would involve just a little more sophistication to the timer. But the gist is that it's trivial to change your appearance with the spell; without having to remember a couple dozen different vars, instead you can change them all at once.
Or here's a weird one for you:
var/i = overlays.Find(olay)
var/obj/O = new
O.appearance = olay
O.color = clr
O.alpha = 255 // no fair including alpha in the color
overlays[i] = O
The temporary obj O is created just to give us a canvas to work on for creating a new appearance. The olay argument is a the appearance we'd like to find in the mob's overlays list, then replace with a new one.
The appearance var can also be used in animate(). Very handy if you want to alter more than one var at once without a complicated proc call, and it saves both space and time. Fewer arguments to load means that animate() will be called sooner, and it also will have to create fewer intermediate appearances than it would by changing one var at a time.