📜 ⬆️ ⬇️

Empowering Tween Animations in NGUI

Hi, my name is Mikhail Kulikov, I am a game developer and I use Unity in my difficult craft. I will not go into the description of what Unity is or how I lived it up to the point that I started using it as an engine. Let me just say that this is a great tool with a lot of advantages and disadvantages, and the main disadvantage, in my opinion, is the lack of tools for comfortable work on the UI. The toolkit that the Unity developers provided in version 4.6 as an open beta does not count. Especially I did not go into it, and there is no particular desire, since I have been using the NGUI plugin for a long time. Today I want to share with you the problems that I had to face while doing tween-animations for the interface, as well as solving these problems.

Problem # 1. Animations and anchors


Or I don’t understand something, or the TweenPosition component doesn’t fit in with the anchors. I mean the following situation. I have a widget that is anchored to the upper left corner of the screen. Changing the screen resolution and aspect ratio, my widget maintains its position well and everything looks great.
4: 3 aspect ratio

16: 9 aspect ratio

When I tried to use TweenPosition to animate the widget's “exit” from the edge of the screen, I realized that nothing would come of it. TweenPosition uses the Vector3 coordinates to indicate the starting and ending positions of the animation. For example, we set the following values ​​for the animation:



When I change the aspect ratio, the widget continues to move along the coordinates stored in TweenPosition , which do not correspond to its new coordinates.
4: 3 aspect ratio

16: 9 aspect ratio

I tried to write a script that would calculate the offset for the From and To vectors, but he didn’t want to be friends with the anchors, and the animation turned into hell. This is no good. We start thinking further.
')
NGUI has a wonderful TweenTransform component that allows you to move an object from point A to point B, where A and B are transforms of any objects. Here is an example:



Remove anchor from widget and set anchor from our game objects A and B



Look what happened:
4: 3 aspect ratio

16: 9 aspect ratio

Now, at any resolutions and aspect ratios, the animation retains its appearance.
Well, it works fine, at least the expected result is achieved. But I want to automate the process. There are a lot of UI elements that require animation in my project and it is terribly boring to perform the operations described above on each of them. We write a simple script that will help us. Let's call it TweenTransformHelper .
//TweenTransformHelper.cs [RequireComponent(typeof(TweenTransform))] public class TweenTransformHelper : MonoBehaviour { public GameObject FromAnchor; public GameObject ToAnchor; } 

I know, the above script probably struck you with its complexity, so the next one will not scare you:
TweenTransformHelperEditor.cs
 //TweenTransformHelperEditor.cs [CustomEditor(typeof (TweenTransformHelper))] public class TweenTransformHelperEditor : Editor { private TweenTransformHelper _tweener; private void Awake() { _tweener = (TweenTransformHelper) target; } public override void OnInspectorGUI() { EditorGUILayout.BeginHorizontal(); if (GUILayout.Button("Create from anchor")) { CreateAnchorFrom(); } if (GUILayout.Button("Destroy")) { DestroyAnchor(_tweener.FromAnchor); } EditorGUILayout.EndHorizontal(); _tweener.FromAnchor = (GameObject) EditorGUILayout.ObjectField(_tweener.FromAnchor, typeof (GameObject)); EditorGUILayout.BeginHorizontal(); if (GUILayout.Button("Create to anchor")) { CreateAnchorTo(); } if (GUILayout.Button("Destroy")) { DestroyAnchor(_tweener.ToAnchor); } EditorGUILayout.EndHorizontal(); _tweener.ToAnchor = (GameObject) EditorGUILayout.ObjectField(_tweener.ToAnchor, typeof (GameObject)); CreateAndApplyTweener(); UpdateUI(); } private void CreateAndApplyTweener() { bool toAnchorNotEqualsNull = _tweener.ToAnchor; bool fromAnchorNotEqualsNull = _tweener.FromAnchor; if (!fromAnchorNotEqualsNull) { EditorGUILayout.HelpBox("From anchor not created!", MessageType.Warning); } else if (!toAnchorNotEqualsNull) { EditorGUILayout.HelpBox("To anchor not created!", MessageType.Warning); } else { if (GUILayout.Button("Apply to tween")) { var tweenComponent = _tweener.GetComponent<TweenTransform>() ?? _tweener.gameObject.AddComponent<TweenTransform>(); tweenComponent.from = _tweener.FromAnchor.transform; tweenComponent.to = _tweener.ToAnchor.transform; tweenComponent.enabled = false; } } } private void UpdateUI() { if (GUI.changed) { EditorUtility.SetDirty(_tweener); } } private void DestroyAnchor(GameObject gameObj) { if (gameObj == null) { return; } DestroyImmediate(gameObj); } private void CreateAnchorTo() { var anchor = CreateAnchor("$anchorTo"); _tweener.ToAnchor = anchor; } private void CreateAnchorFrom() { var anchor = CreateAnchor("$anchorFrom"); _tweener.FromAnchor = anchor; } private GameObject CreateAnchor(string anchorName) { var anchorGameObj = new GameObject(anchorName); anchorGameObj.transform.parent = _tweener.transform; anchorGameObj.transform.localPosition = Vector3.zero; anchorGameObj.transform.localScale = Vector3.one; var widgetScript = anchorGameObj.AddComponent<UIWidget>(); widgetScript.width = widgetScript.height = 100; return anchorGameObj; } } 


I do not see the point of describing the work of the script in detail, there is nothing complicated in it. Now, if we add the script to the widget, we will see the following panel:



Create $ anchorTo and $ anchorFrom and click “Apply to tween” (this will automatically fill in the appropriate fields in the TweenTransform ). Now it's small, adjust the snapping to the edges of the screen for $ anchorTo and $ anchorFrom, pre-putting them in the right position.



This is problem # 1 solved. Go ahead.

Problem # 2 Sequential Animations


But what if we want to make a chain of moving objects? With the help of NGUI to do it is elementary. Each TweenTransformer component has an On Finished field that can contain any methods of any component, provided that this method is public. Added methods are called immediately after the animation has finished playing. For example, we can make a sequence of exit entries like this:



Now when you start the animation, we will see the following wonders:



When you activate the screen elements go and it looks good. But what if we want to play the animation in reverse order when the screen is deactivated?

Based on what I found in the plug-in forum discussions, it’s not me alone who faced this problem. The participants in the discussions suggested solving this problem in the following way: somehow getting a list of event calls, reversing it and building calls in the reverse order. This decision seemed to me too complicated, because, as they say, normal heroes always go around. The existing OnFinished event functional of the UITweener class (of which the TweenTransform is derived ) is not enough, because it is called when the animation plays from beginning to end and vice versa. It is impossible to determine in which direction the animation played before being completed. If it were possible, my problem would be solved. In the end, I decided to expand the capabilities of NGUI. Forgive me ArenMook , but I had to manage in his code. In fact, the changes that need to be made to the UITweener class are minimal.

In UITweener.cs we add the following fields:
 List<EventDelegate> mTempForward = null; List<EventDelegate> mTempReverse = null; [HideInInspector] public List<EventDelegate> onFinishedForward = new List<EventDelegate>(); [HideInInspector] public List<EventDelegate> onFinishedReverse = new List<EventDelegate>(); 

And in the Update method after
 if (onFinished != null) { mTemp = onFinished; … } 

add
 if (onFinishedForward != null && direction == Direction.Forward) { mTempForward = onFinishedForward; onFinishedForward = new List<EventDelegate>(); EventDelegate.Execute(mTempForward); for (int i = 0; i < mTempForward.Count; ++i) { EventDelegate ed = mTempForward[i]; if(ed != null && !ed.oneShot) EventDelegate.Add(onFinishedForward, ed, ed.oneShot); } mTempForward = null; } if (onFinishedReverse != null && direction == Direction.Reverse) { mTempReverse = onFinishedReverse; onFinishedReverse = new List<EventDelegate>(); EventDelegate.Execute(mTempReverse); for (int i = 0; i < mTempReverse.Count; ++i) { EventDelegate ed = mTempReverse[i]; if (ed != null && !ed.oneShot) EventDelegate.Add(onFinishedReverse, ed, ed.oneShot); } mTempReverse = null; } 

Go to UITweenerEditor.cs and add a few more lines of code so that the advanced features of the UITweener class are displayed in the editor.

After
 NGUIEditorTools.DrawEvents("On Finished", tw, tw.onFinished); 

add
 NGUIEditorTools.DrawEvents("On Finished forward", tw, tw.onFinishedForward); NGUIEditorTools.DrawEvents("On Finished reverse", tw, tw.onFinishedReverse); 

As a result of these manipulations, the TweenTransform window now looks like this



Having the opportunity to find out in which direction the animation has finished, I can build a sequence that can correctly play back and forward. This is done elementary:



To play the animation forward, you need to call the PlayForward method on the first element of the chain, and in order to play it in reverse order, you need to call PlayReverse on the last element. Now we get the expected result:



Conclusion


NGUI is a great plugin for Unity, with great features, but it, like any complex tool, has its flaws or flaws. But, having in store a little bit of time and desire, they are easy to fix and achieve the desired result.

Source: https://habr.com/ru/post/240185/


All Articles