dotween
dotween copied to clipboard
Better DOTweenAnimation code events API
DOTweenAnimation is flexible due to the exposed onComplete, onStart, onPlay... events. Designers can easily link logic there, the Unity way. But often we need for the code to subscribe for those events (mostly onComplete). But at the moment, this proves to be a challenge. There are two options: DOTweenAnimation.tween.onComplete or the DOTweenAnimation.onComplete itself.
Using the DOTweenAnimation.tween.onComplete is tricky, because it can be null if not created. Also it may get re-created so user looses it's handlers. So this option feels wrong.
Naturally a programmer would try to subscribe for the DOTweenAnimation.onComplete. That turns out to be tricky as well. Here is what they should do every time:
tweenAnimation.hasOnComplete = true; // Used to show / hide the event in the inspector, but also to skip the invoke.
tweenAnimation.onComplete = tweenAnimation.onComplete ?? new UnityEvent(); // If event was disabled, the code sets this to null.
tweenAnimation.onComplete.AddListener(MyHandler); // Finally subscribe for my handler.
Additionally, the hasOn... flags are used at the end of the CreateTween() method. If set to false, the component won't subscribe for the events. If the user code tries to subscribe for the events after this moment, they will never be invoked. Note that "AutoGenerate" option is on by default which adds salt to the injury.
Ideally, the API shouldn't care when and how you subscribed - just call when someone is listening. We were forced to extend your API. Here is briefly what we did:
// Added these custom events in the DOTweenAnimation component...
public new event UnityAction onComplete { // NOTE: new keyword used here...
add => Subscribe(out hasOnComplete, ref base.onComplete, value); // NOTE: base.onComplete
remove => Unsubscribe(ref hasOnComplete, ref base.onComplete, value);
}
// ... the rest of the events...
private void Subscribe(out bool enabledFlag, ref UnityEvent unityEvent, UnityAction handler)
{
enabledFlag = true;
unityEvent ??= new UnityEvent();
unityEvent.AddListener(handler);
}
//////////////////////////
// Example user code:
tweenAnimation.onComplete += MyHandler; // Happy user here...
We added custom event accessors that modify the original UnityEvents. Note the new keyword used to hide the original event member. We wanted to make the originals protected, but we didn't have the source code of the base ABSAnimationComponent component, so this was the only valid way to hide them to avoid user using the wrong event member.
Turns out this was not enough :( The events are subscribed during tween creation. So if the user code is late (e.g. "AutoGenerate" executes first) it won't work. So I had to change the CreateTween() method as well - always subscribe for the events, no matter the enabled flag:
tween.OnStart(base.onStart.Invoke);
tween.OnPlay(base.onPlay.Invoke);
tween.OnUpdate(base.onUpdate.Invoke);
tween.OnStepComplete(base.onStepComplete.Invoke);
tween.OnComplete(base.onComplete.Invoke);
tween.OnRewind(base.onRewind.Invoke);
All this looks a bit like a hack, feel free to implement this the style it suits you. We believe this is a vital feature for medium to large projects.
You can find the full sample code here: https://pastebin.com/5TSD6yyn
PS: Having DOTweenAnimation.isPlaying => tween?.IsPlaying() ?? false; property would also be nice.
DOTween v1.2..632 DOTweenPro v1.0.310 Unity 2020.3.12f
Cheers
After removing the hasOn... flags use in the CreateTween() method, the event properties got much simpler and we removed the additional methods (since original event fields are never set to null):
public new event UnityAction onComplete {
add => base.onComplete.AddListener(value);
remove => base.onComplete.RemoveListener(value);
}
1
tweenAnimation.hasOnComplete = true;
tweenAnimation.onComplete = tweenAnimation.onComplete ?? new UnityEvent();
tweenAnimation.onComplete.AddListener(MyHandler);
I want to add events on runtime. I tried code above, it doesn't work.
2 https://github.com/Demigiant/dotween/issues/375 Above recommends using "tweenAnimation.GetTweens()[index].OnComplete()". I tried, it works.
I also tried "tweenAnimation.tween.onComplete", it works too.
3 Do you mean by adding events in Editor?(As the code doesn't work in Runtime)
- Can't test now, but looking at the code it should work. Check the
hasOnCompleteusage and you'll see why (unless code changed). - Didn't know about that, it seems to work, but still, the DOTweenAnimation API can be improved as described above or similar.
- No, I meant runtime.
Ahoy! You are right, I should've dealt with this better. I'm working on another issue but as soon as I'm done I'll get back to this (thanks for the code implementation, if I end up using yours I'll add you to the credits)