As explained in the sprite docs Visualizer Systems are how client-side sprites are modified using appearance data from the server.
The old method is by using a class inheriting from AppearanceVisualizer which is specified on the AppearanceComponent. New way is by just using a component for the data used to tweak the visualizer and a system for the actual logic instead of just one class. Benefits are that they can use anything that ECS Systems can (including event subscriptions importantly!)
This doc explains how to migrate an AppearanceVisualizer to a new component & system using a basic example found in this PR: https://github.com/space-wizards-federation/space-station-14/pull/6571/files with some very minor deviation. Includes and such will not be put so you’ll need to do those yourself
Here’s the full visualizer we’re porting:
1. Separate data and logic
First task is to copy the data into a new component and copy the logic into a new system. Don’t worry about porting it fully just yet or if there are errors, we’ll do that later.
Component:
System:
2. ECS-ify data
Now we need to convert the component into the proper ECS state!
- Turn all private fields public
- Remove any properties and just use the backing fields, any logic should go in member methods on the system
- Change the names of all fields to match naming conventions
- Add more datafields if necessary
- Move the corresponding
VisualLayers enum if one exists to the component class as well
So it’ll look like this now:
3. ECS-ify logic
Logic needs to be ported in two ways:
- The
OnAppearanceChange method needs to be converted into the proper entity system override
- Any
InitializeEntity method needs to be converted into a new ComponentInit event handler directed at the component you made
A couple other things need to be done:
- Any dependencies should be moved to the system
- Any manual resolves should be made into dependencies
- Any
IEntityManager resolves should use the EntityManager field that already exists on EntitySystem, or the proxy methods
- Any TryGets for
SpriteComponent should use the Sprite field on the event args instead
- Any references to fields that used to be on the visualizer need to be converted into references to fields on the component
This one just needs the first but I’ll show an example of the second later as well
Appearance change signature is now protected override void OnAppearanceChange(EntityUid uid, T component, ref AppearanceChangeEvent args), so we’ll update the function to reflect that.
We also need to remove the IEntityManager resolve and convert the calls to it into the proxy methods. However, since the method calls used are just to get the SpriteComponent, we can use the field on the event args instead.
This doesn’t use InitializeEntity but if it did the full class would look like this:
4. Update YAML & IgnoredComponents.cs
Now we need to update YAML for our new component as well as the server ignored comps list.
Go to Content.Server/Entry/IgnoredComponents.cs and add a line with your new component name like this:
This is done automatically for server components that don’t exist on the client, but not vice versa since this is usually a more uncommon operation, and you can’t do both. So this just tells the server to not worry about seeing this component in YAML.
Find usages of the old visualizer using CTRL+SHIFT+F or an equivalent in any IDE like so:
Replace it like this:
Keeping the appearance component there is important! Easily missed bug. Basically just un-indent the visuals block and change the name of the component.
You should be done!
5. If possible, generalize.
Instead of adding many separate visualier systems & components, it is often possible to just make a visualier more general by adding an extra yaml data-field. To this end, there is a GenericVisualizerSystem & component, which replaces the older GenericEnumVisualizer. If all you need the visualizer to do is to set some sprite layer data based on simple appearance data entries, you very likely can, and should, just use the generic visualizer instead of creating your own custom one. However, if you need to do fancy things like use animations or have more complex logic, you will still need to create your own.
For example, the functionality of the above cabinet visualizer simply sets a sprite layer states & visibility based on two appearance data entries. Instead of having this system & component, the same functionality could be achieved by using the generic visualizer:
The sprite sprite layer data can set the sprite, state, texture, shader, scale, rotation, offset, visible and color.
Note that the yaml for the appearance values are simply the ToString() results of a the appearance data values.
So bools become “True”/“False”, while an enum like VentPumpState.Off just becomes “Off”. Last modified on June 20, 2026