Saving, loading, copying, and pasting
This article is meant to teach modders how to make proper use of the serialisation system in People Playground. It should contain all the information necessary for players to be able to save/load/copy/paste your modded items. This article expects you to have a very basic understanding of C# (attributes, enums, and basic terms).
The concept
In basic terms: serialisation is the process of storing an object on disk while deserialisation is the process of loading an object from disk.* The game has a serialisation system that is specifically designed for People Playground spawnables. Naturally, only spawnables can be saved and loaded. The serialisation system is responsible for saving, loading, uploading, downloading, copying, and pasting contraptions from and to almost all versions of the game.
* (De)serialisation doesn't always go to/from the disk. Objects can be serialised to any storage device, including memory and the network.
What parts are saved?
The location, scale, and rotation of the transform and all its children and parents are saved.
Each component on each GameObject is also saved. All fields and properties are saved, unless one of the following is true:*
- The field/property is private
- The field/property is marked as obsolete using the
[System.Obsolete]
attribute - The field/property is marked to be skipped using the
[NonSerialized]
attribute - The field/property is marked to be skipped using the
[SkipSerialisation]
attribute - The field is of a class type that is not a reference to another object in the scene
- The property has no setter
- The property has no getter
- The property is of a class type
A GameObject can opt-out of being saved all together by having an Optout
component attached to it.
* There are some more criteria / exceptions to this rule but they are not relevant to whatever you're doing, probably.
SkipSerialisation attribute
General rule of thumb: If you are writing a custom component, give the class the
[SkipSerialisation]
attribute.
If you are using any custom components, all fields need to be marked as [SkipSerialisation]
unless it absolutely necessarily needs to be saved. Generally, a variable should be saved if its value changes while the player uses the item. For the vast majority of variables, this is not the case. Saving is actually entirely pointless for most components, in which case you should add the attribute to the class itself. Unnecessary saving will make your mod slow and could even cause contraptions to become corrupt. Save with caution!
For example, here are all the fields/properties of the LEDBulbBehaviour
.
public class LEDBulbBehaviour : MonoBehaviour, Messages.IUse
{
[ColorUsage(false, true)]
public Color Color; // This field is saved!
[SkipSerialisation]
public SpriteRenderer LightSprite;
[SkipSerialisation]
public SpriteRenderer GlowSprite;
[SkipSerialisation]
public PhysicalBehaviour PhysicalBehaviour;
[SkipSerialisation]
public float MinBrightness = .2f;
[SkipSerialisation]
public float MaxCharge = 5;
public bool Activated; // This field is saved!
private float lastIntensity = float.MaxValue;
private bool colourCached = false;
Optout component
The Optout
component is an important piece of the system. It prevents the entire GameObject from being saved, so it will be loaded with its default properties. Generally, this should be used for things like particle effects, lights, and all other strictly visual things.
Prevent corruption, save in moderation
When you add a custom component to a GameObject, that GameObject becomes dependent on your mod. When the player saves the object, this dependency is listed.
This also happens when you add your components to a vanilla spawnable. The stench of your mod is now all over the object and the game will remember this. If your custom component improperly interacts with the serialisation system, it's likely to render the entire contraption permanently unusable (corrupt).
If you are unsure of what to do with your fields and properties: mark them as
SkipSerialisation
.
If you are unsure of what to do with any child GameObjects you create, attach an
Optout
component.
Resource management
Loading textures and sounds is often the cause of serialisation issues in mods. 99% of the time, all resources should be loaded and ready to use within the Main
method of your mod. It is almost always invalid to load resources inside a custom component.
If your mod has many assets that need to be used throughout your scripts, even outside the Main
method, it's a good idea to set it up as follows:
// A structure containing static references to your assets.
public struct SpriteAssets
{
public static Sprite LowSignalNode;
public static Sprite HighSignalNode;
public static Sprite HighSignalGeneratorEnabled;
public static Sprite HighSignalGeneratorDisabled;
}
public static void Main()
{
// Load all these assets at the very start.
SpriteAssets.LowSignalNode = ModAPI.LoadSprite("Signal Node.png");
SpriteAssets.HighSignalNode = ModAPI.LoadSprite("HIGH Signal Node.png");
SpriteAssets.HighSignalGeneratorEnabled = ModAPI.LoadSprite("HIGH Signal Generator Enabled.png");
SpriteAssets.HighSignalGeneratorDisabled = ModAPI.LoadSprite("HIGH Signal Generator.png");
}
public class SignalNode : ORSignalGate
{
private SpriteRenderer spriteRenderer;
private Sprite highSignal, lowSignal;
protected override void Awake()
{
base.Awake();
spriteRenderer = GetComponent<SpriteRenderer>();
// Get the references from the structure
highSignal = SpriteAssets.HighSignalNode;
lowSignal = SpriteAssets.LowSignalNode;
spriteRenderer.sprite = lowSignal;
}
public override void ManagedUpdate()
{
base.ManagedUpdate();
var s = SignalStack > 0 ? highSignal : lowSignal;
// You can now change the sprite without a care in thw world.
if (spriteRenderer.sprite != s)
spriteRenderer.sprite = s;
}
}