I’ve recently started using assembly definitions, but I’m facing an issue. I have a ScriptableObject that contains a list of animation frames. Each frame has a sprite and a display time. I want to add a default frame time, which should be set by a designer in a separate ScriptableObject called AnimationConfig. This configuration is a singleton, meaning the settings are the same for all animations.
Here’s what I’m trying to do: In the frame declaration, I set the float duration value to -1 by default. Then, in the OnValidate method, I use the following code:
protected void OnValidate()
{
var config = AssetDatabaseHelper.FindAssets<AnimationConfig>().First();
foreach (var frame in Frames)
if (frame.Duration < 0)
frame.Duration = config.DefaultFrameDuration;
}
Unfortunately, this solution requires the UnityEditor.Rendering namespace, which prevents the code from being built. The problem is that UnityEditor.Rendering contains AssetDatabaseHelper, which I’m using to find the AnimationConfig. Normally, I would wrap this method with #if UNITY_EDITOR like this:
#if UNITY_EDITOR
protected void OnValidate()
{
var config = AssetDatabaseHelper.FindAssets<AnimationConfig>().First();
foreach (var frame in Frames)
if (frame.Duration < 0)
frame.Duration = config.DefaultFrameDuration;
}
#endif
This would solve the problem, except now I’m dealing with assembly definitions. Should I add a reference to UnityEditor.Rendering in the assembly containing this code? This seems counterintuitive because the code inside #if UNITY_EDITOR won’t be used in the build, so that reference shouldn’t be necessary. Is my issue clear? How should I resolve this?
The way I tend to handle situations like this, is by just raising an event on the runtime assembly side, and then implementing everything else in an editor assembly.
In a runtime assembly:
public class FramesAsset : ScriptableObject
{
public static event Action<FramesAsset> Validate;
public void OnValidate() => Validate?.Invoke(this);
...
}
In an editor assembly:
[InitializeOnLoad]
public static class FramesAssetValidator
{
static FramesAssetValidator()
=> FramesAsset.Validate += OnValidate;
static void OnValidate(FramesAsset asset)
{
var config = AssetDatabaseHelper.FindAssets<AnimationConfig>().First();
foreach (var frame in asset.Frames)
if (frame.Duration < 0)
frame.Duration = config.DefaultFrameDuration;
}
}
It feels a bit hacky, but it works.
(If anybody has any more elegant solutions, I’m all ears )
Thanks, that’s a solid workaround and definitely a clever approach! I appreciate the suggestion. However, if anyone else has a more elegant or straightforward solution, I’m all ears.
For the few times you really need to do this, then you kinda just have to do the thing @sisus_co is suggesting and inject the edit time code into the runtime assembly. It feels icky, but while assembly definitions are somewhat flexible, they don’t have the concept of “conditionally referene this other assembly”
However, very often, like in your case, I’d argue that it’s not what you want to do. Why are you validating the duration in OnValidate? Why are you using <0 as a flag for “not initialized”? That should be happening in the editor for your ScriptableObject - when you add frames, initialiize them to the correct default value, not to -1 or whatever. In that editor, you have access to editor assemblies without any issue.
The assembly definition is compiled both for the build and for the editor. If it wants to use some editor code, it needs to have a reference to that assembly.
Basically, you have two versions of your assembly: One compiled in the editor and used while editing/playing, and another one created during the build and used in the player. I don’t think it’s counter-intuitive to have the first reference an editor-only assembly.
Assembly Definitions don’t support conditional or editor-only references. But they will silently ignore any missing assemblies and won’t include editor-only assemblies in builds. So there’s no issue referencing editor-only assemblies in your player assemblies, the editor-only references will not be in the build and, as long as you use conditional compilation to remove the editor-only code, there won’t be any compile errors because of these missing references.
I usually want related code to be together, so putting simple editor-only code together with the player code often feels the most simple and easily understandable to me. Splitting the code into separate player/editor assemblies and adding abstractions to allow this separation often just adds unnecessary complexity. If the editor code is complex or is part of a bigger system, having a dedicated editor assembly can make sense.