Open KybernetikGames opened 1 year ago
I've now implemented this feature in the upcoming Animancer v8.0.
Unfortunately, this is a breaking change. Any event names set up in an older version of Animancer will be lost.
Here's a summary of how it works:
InternedString
is a struct
containing a string
which uses string.Intern
to allow reference equality checks instead of needing to compare every individual character.NamedKey
is a ScriptableObject
containing an InternedString
based on its name
.AnimancerEvent.Sequence.Serializable
now has an array of NamedKey
s instead of string
s for the names:
AnimancerEvent.Sequence
now uses InternedString
s instead of string
s for the names. It has implicit casts to and from string
for convenience and so old code still works, but you can also store them in static
fields so that the cost of calling string.Intern
is only paid once on startup instead of every time you use it:private static readonly InternedString HitEventName = "Hit";
NamedKey
s are also used for the new Mixer Parameter Binding system.
Use Case
This issue is split out from https://github.com/KybernetikGames/animancer/issues/262 since it could be implemented separately from the main idea presented there.
That suggestion is rather controversial because unlike https://github.com/KybernetikGames/animancer/issues/257 this is already an existing feature and I couldn't reasonably take strings away from existing users. Even if we ignore the data loss and manual rework this would cause for anyone who updates, I'm not entirely convinced that the ScriptableObject workflow would be better.
With strings it's only a 2 or 3 step process:
[EventNames]
attribute.But with ScriptableObjects you need to:
Key
asset.KeyAsset
?KeyObject
?Key
when setting up your event. This will be slower than picking a name from the dropdown when using an[EventNames]
attribute.Key
field to your script. If the place you want to use it isn't serializable, you'll need to put the field somewhere serializable and mess around with passing the reference around to where you need it.Key
.Key
when setting the callback.So even though the end result with ScriptableObjects gives better safety and potentially better performance, all those extra steps are both time sinks and opportunities to make mistakes so such a system would be a hard sell to users who just want to get stuff done and move on.
But all hope is not lost:
Solution Options
Use Both
Supporting both
string
andKey
alongside each other might not be too bad. The event names are already stored in a separate array from the event times so that it only needs space up to the last event with a name, meaning if you don't use any names then the array is empty. So doing the same with a ScriptableObject array would mean the overhead is only one empty array per event sequence (for serialized data size, deserialization performance, and runtime memory usage) unless you use a mix of both types (which seems unlikely).Adding an overhead for everyone regardless of which approach they use isn't ideal and offering more different ways to do things makes it harder for new users to learn. It might still be worth it, but like I said over in the mixer issue I don't like this sort of duplication.
Data Conversion
If we don't keep both, it will need to convert any old data. I'm not usually a fan of keeping obsolete stuff in my scripts just to give users a warning instead of an error when they update, but in this case that would mean losing everyone's event names which isn't really acceptable if it can be avoided. So I'd probably need to keep the old field and include an automatic updater script which loads every prefab, scriptable object, and scene in your project to go through and replace any named events with
Key
s.Create Keys in the Inspector
When setting up an event in the Inspector, its
Key
field could have aNew
button which replaces the reference field with a text field where you can type a name and press enter to create aKey
with that name in a default location.This would streamline step 1 in the steps above.
Unique Scriptable Object Names
If we require that every
Key
have a unique name (which seems like a reasonable restriction even if only to reduce potential confusion) then we could potentially have a system like this:Key
s are added by name to a staticDictionary<string, Key>
.Key
in an Asset Bundle so it isn't automatically available on startup, but it should be possible to handle with a bit of extra effort for the user.string
toKey
could simply look up the key in the dictionary. That means you can still use magic strings or string constants in your scripts and it will seamlessly grab theKey
to use for the actual lookup.This would take a lot of effort to implement, but would make steps 3 and 4 in the above list optional, bringing the
Key
system down much closer to the simplicity of the purestring
system.