![]() |
Cesium for Unity 1.15.2
|
Guidelines and tips for creating MonoBehaviours in Cesium for Unity.
If you don't need C++-specific state, static methods are much more efficient. Consider adding the staticOnly=true
parameter to the [ReinteropNativeImplementation]
attribute, so that you - and other developers in the future - can't accidentally create C++ state.
OnEnable
. Avoid Start
and Awake
. The reason is that Start
and Awake
are not invoked on domain reload, such as when editing a script, but OnEnable
is. So by using only OnEnable
, we can essentially treat a domain reload the same as a normal reload of the scene.OnEnable
for different components may be called in arbitrary order. So if you're implementing an OnEnable
that requires some other component to already be initialized, you must trigger this initialization manually. For example, if your OnEnable
needs to use a CesiumGeoreference
, call the georeference's Initialize
method near the beginning of your OnEnable
.ICesiumRestartable
and its Restart
method. This method is called by the UI when Unity has updated the serialized fields in unspecified ways, and so the state of the object should be recreated from the serialized fields.OnEnable
should simply call Restart
.Reset
, usually by simply calling Restart
. This method is called when Unity has directly written to the serialized fields to reset them, and so the Restart
method is the right way to synchronize the object's state.Restart
(e.g. on CesiumGlobeAnchor
) and Initialize
(e.g. on CesiumGeoreference
)? Restart
completely recreates the object's state from the current values of the serialized fields. The current state is assumed to be invalid and thrown away. Initialize
, on the other hand, prepares the object for first use but does nothing if the object is already initialized.OnEnable
and OnDisable
usually work as a pair, with initialization happening in OnEnable
and cleanup happening in OnDisable
. Unity guarantees that OnDisable
is called before an enabled component is destroyed. But be careful if some of the initialization can happen elsewhere other than OnEnable
, such as in Reset
, Restart
, or Initialize
. If that happens for a disabled component, OnDisable
will never be called and so the cleanup may never happen. The solution is to skip some or all of the initialization work if the component is not isActiveAndEnabled
.Carefully consider every field that you add to the class. In general, only the essential fields necessary to reconstruct the state of the object should by marked [SerializeField]
. Cached and derived fields should instead be marked [NonSerialized]
. Fields without any attribute should be extremely rare.
Characteristic | [SerializeField] | No attribute | [NonSerialized] |
---|---|---|---|
Saved / Loaded with the Scene | ✅ | ❌ | ❌ |
Preserved on script change / AppDomain reload | ✅ | ✅ | ❌ |
Transfers from Edit mode to Play mode | ✅ | ❌ | ❌ |
Unity's serialization system is extremely simplistic (presumably in the name of performance), and makes backward compatibility difficult. Our solution is powerful but a bit involved. Here's how it works.
Let's say we want to implement backward compatible loading for CesiumGlobeAnchor
instances saved with Cesium for Unity v0.2.0. First, we add a new CesiumGlobeAnchorBackwardCompatibility0dot2dot0.cs
file, but don't launch Unity yet! In that file, define a new class named CesiumGlobeAnchorBackwardCompatibility0dot2dot0
, derived from the original class, CesiumGlobeAnchor
and implementing the interface IBackwardCompatibilityComponent<CesiumGlobeAnchor>
. Add the following attributes to the class declaration:
[ExecuteInEditMode]
- So that the class's OnEnable
(which we'll write shortly) is called in the Editor.[AddComponentMenu("")]
- So that this class will not show up in the "Add Component" panel in the Editor.[DefaultExecutionOrder(-1000000)]
- So that this class's OnEnable
runs really early, before other classes.Define all of the properties that existed in the old version of the CesiumGlobeAnchor
, but rename them by appending the version (0dot2dot0
) to the end. Add a [FormerlySerializedAs]
attribute with the previous name. For example:
If a field is an enum that has been eliminated entirely, or if its enum values were changed from the old version, declare the old enum type nested inside the backward compatibility class. Since CesiumGlobeAnchorPositionAuthority
was removed from CesiumGlobeAnchor
, a backwards-compatible enum is defined in CesiumGlobeAnchorBackwardCompatibility0dot2dot0
:
Next, declare an Editor
class, nested inside CesiumGlobeAnchorBackwardCompatibility0dot2dot0
, that merely provides an Upgrade button, and an OnEnable
that automatically upgrades. Put both inside an ifdef for UNITY_EDITOR
:
Finally, implement IBackwardCompatibilityComponent<CesiumGlobeAnchor>
to write the upgrade logic itself:
Finally, rename CesiumGlobeAnchor.cs.meta
to CesiumGlobeAnchorBackwardCompatibility0dot2dot0.cs.meta
. Now launch Unity. Unity will create a new CesiumGlobeAnchor.cs.meta
with a new GUID. Any scenes saved with a CesiumGlobeAnchor
using the old GUID will instead end up loading a CesiumGlobeAnchorBackwardCompatibility0dot2dot0
instead. When we invoke CesiumBackwardCompatibility<CesiumGlobeAnchor>.Upgrade()
on the new backward compatibility component, either when this object is enabled in the Editor or when the user explicitly clicks the Upgrade
button in a prefab, a few things happen:
CesiumGlobeAnchor
) is created.Upgrade
method is called to initialize the new CesiumGlobeAnchor
from the backward compatibility instance.CesiumGlobeAnchorBackwardCompatibility0dot2dot0
) and the new (CesiumGlobeAnchor
) instances is added to a dictionary, and a call to UpdateScene
is registered for the next Editor tick.In the next Editor tick, after one or more backward compatible components have been upgraded, the following happens: