유니티/코드

Tweening 구현(2)

fore4022 2025. 5. 12. 11:55

Tweening 구현(1)

 

Tweening 구현(1)

이번 글은 기록을 목적으로 작성되었습니다. 게임을 만들면서 다양한 효과 구현이나 연출에 대해서 알아보다가 처음 접하게 되었다. DoTween이라는 에셋도 있었지만, 나는 Unity에서 공식으로 지원

fore4022.tistory.com


 Ease 부분은 이번 글에서 생략했습니다.

 아직은 많이 부족한 부분이 있는 것 같습니다. 그래도 만들어진 메서드들을 사용해서 만든 것을 보고난 후에는 기분이 너무 좋았습니다. 더 많은 것을 해보고 싶다는 마음이 드네요.


using UnityEngine;
public static class TweenSystem
{
    // Scale
    public static Component SetScale(this Component comp, float targetScale, float duration, Ease ease = Ease.Linear, TweenOperation op = TweenOperation.Join)
    {
        return TweenSystemManage.Execute(comp, op, TweenType.Scale, new(targetScale), duration, ease);
    }
    public static Component SetScale(this Component comp, float targetScale, float duration, TweenOperation op, Ease ease = Ease.Linear)
    {
        return TweenSystemManage.Execute(comp, op, TweenType.Scale, new(targetScale), duration, ease);
    }
    public static Component SetScale(this Component comp, float targetScale, float duration, float delay, Ease ease = Ease.Linear)
    {
        return TweenSystemManage.Execute(comp, TweenOperation.Insert, TweenType.Scale, new(targetScale), duration, ease, delay);
    }

    // Position
    public static Component SetPosition(this Component comp, Vector2 targetPosition, float duration, Ease ease = Ease.Linear, TweenOperation op = TweenOperation.Join)
    {
        return TweenSystemManage.Execute(comp, op, TweenType.Position, new(targetPosition), duration, ease);
    }
    public static Component SetPosition(this Component comp, Vector2 targetPosition, float duration, TweenOperation op, Ease ease = Ease.Linear)
    {
        return TweenSystemManage.Execute(comp, op, TweenType.Position, new(targetPosition), duration, ease);
    }
    public static Component SetPosition(this Component comp, Vector2 targetPosition, float duration, float delay, Ease ease = Ease.Linear)
    {
        return TweenSystemManage.Execute(comp, TweenOperation.Insert, TweenType.Position, new(targetPosition), duration, ease, delay);
    }

    // Rotation
    public static Component SetRotation(this Component comp, Vector3 targetRotation, float duration, Ease ease = Ease.Linear, TweenOperation op = TweenOperation.Join)
    {
        return TweenSystemManage.Execute(comp, op, TweenType.Rotation, new(targetRotation), duration, ease);
    }
    public static Component SetRotation(this Component comp, Vector3 targetRotation, float duration, TweenOperation op, Ease ease = Ease.Linear)
    {
        return TweenSystemManage.Execute(comp, op, TweenType.Rotation, new(targetRotation), duration, ease);
    }
    public static Component SetRotation(this Component comp, Vector3 targetRotation, float duration, float delay, Ease ease = Ease.Linear)
    {
        return TweenSystemManage.Execute(comp, TweenOperation.Insert, TweenType.Rotation, new(targetRotation), duration, ease, delay);
    }

    // Manage
    public static Component StopTween(this Component comp)
    {
        TweenSystemManage.SetStatus(comp, false);

        return comp;
    }
    public static Component PlayTween(this Component comp)
    {
        TweenSystemManage.SetStatus(comp, true);

        return comp;
    }
    public static void KillTween(this Component comp)
    {
        TweenSystemManage.Kill(comp);
    }
}
using System;
using System.Collections;
using UnityEngine;
public static class Tweening
{
    private delegate void Tween_TF(Transform transform, NumericValue initial, NumericValue target, float value);
    private delegate void Tween_RTF(RectTransform rectTransform, NumericValue initial, NumericValue target, float value);

    private static readonly Type _rectTransform = typeof(RectTransform);

    public static IEnumerator OverTime(TweenType type, TweenData data, Transform transform, EaseDelegate ease, NumericValue targetValue, float duration, float delay = 0)
    {
        Tween_TF del = null;
        NumericValue initialValue = new();
        TweenStatus status = null;
        float currentTime = 0;
        bool isRectTransform = transform.GetType() == _rectTransform;

        yield return new WaitUntil(() => TweenSystemManage.GetStatus(transform) != null);

        status = TweenSystemManage.GetStatus(transform);

        yield return new WaitForEndOfFrame();

        if(delay > 0)
        {
            while (currentTime < delay)
            {
                if (status.flag)
                {
                    currentTime += Time.deltaTime;
                }

                yield return null;
            }

            currentTime = 0;
        }

        switch(type)
        {
            case TweenType.Scale:
                initialValue.Float = transform.localScale.x;
                del += Scale;
                break;
            case TweenType.Position:
                initialValue.Vector = transform.localPosition;
                del += Position;
                break;
            case TweenType.Rotation:
                initialValue.Vector = transform.localRotation.eulerAngles;
                del += Rotation;
                break;
        }

        while (currentTime != duration)
        {
            if(status.flag)
            {
                if(isRectTransform)
                {
                    currentTime = Mathf.Min(currentTime + Time.unscaledDeltaTime, duration);

                    del(transform as RectTransform, initialValue, targetValue, ease(currentTime / duration));
                }
                else
                {
                    currentTime = Mathf.Min(currentTime + Time.deltaTime, duration);
                    
                    del(transform, initialValue, targetValue, ease(currentTime / duration));
                }
            }

            yield return null;
        }

        TweenSystemManage.Release(transform, data);
    }

    // Scale
    public static void Scale(Transform transform, NumericValue initial, NumericValue target, float value)
    {
        transform.localScale = Calculate.GetVector(Mathf.Lerp(initial.Float, target.Float, value));
    }
    public static void Scale(RectTransform rectTransform, NumericValue initial, NumericValue target, float value)
    {
        rectTransform.localScale = Calculate.GetVector(Mathf.Lerp(initial.Float, target.Float, value));
    }

    // Position
    public static void Position(Transform transform, NumericValue initial, NumericValue target, float value)
    {
        transform.localPosition = Vector3.Lerp(initial.Vector, target.Vector, value);
    }
    public static void Position(RectTransform rectTransform, NumericValue initial, NumericValue target, float value)
    {
        rectTransform.localPosition = Vector3.Lerp(initial.Vector, target.Vector, value);
    }

    // Rotation
    public static void Rotation(Transform transform, NumericValue initial, NumericValue target, float value)
    {
        transform.localRotation = Quaternion.Euler(initial.Vector + target.Vector * value);
    }
    public static void Rotation(RectTransform rectTransform, NumericValue initial, NumericValue target, float value)
    {
        rectTransform.localRotation = Quaternion.Euler(initial.Vector + target.Vector * value);
    }
}
using System;
using System.Collections.Generic;
using UnityEngine;
public static class TweenSystemManage
{
    private static Dictionary<Component, Sequence> _schedule = new();
    private static Dictionary<Component, TweenStatus> _status = new();

    private static readonly Type _transform = typeof(Transform);

    public static TweenStatus GetStatus(Component comp)
    {
        if(_status.TryGetValue(comp, out TweenStatus value))
        {
            return value;
        }
        else
        {
            return null;
        }
    }
    public static void SetStatus(Component comp, bool status)
    {
        _status[comp].flag = status;
    }
    public static Component Execute(Component comp, TweenOperation op, TweenType type, NumericValue numeric, float duration, Ease ease, float delay = 0)
    {
        Transform trans = GetTransform(comp);
        TweenData data = new();

        if(trans == null)
        {
            return null;
        }
        else
        {
            switch(op)
            {
                case TweenOperation.Append:
                    data.Set(type, trans, Easing.Get(ease), numeric, duration);
                    break;
                case TweenOperation.Insert:
                    data.Set(Util.GetMonoBehaviour().StartCoroutine(Tweening.OverTime(type, data, trans, Easing.Get(ease), numeric, duration, delay)));
                    break;
                case TweenOperation.Join:
                    if(_schedule.ContainsKey(trans))
                    {
                        if(_schedule[trans].Count() > 1)
                        {
                            data.Set(type, trans, Easing.Get(ease), numeric, duration);
                            break;
                        }
                    }

                    data.Set(Util.GetMonoBehaviour().StartCoroutine(Tweening.OverTime(type, data, trans, Easing.Get(ease), numeric, duration, delay)));
                    break;
            }
        }

        if(_schedule.TryGetValue(trans, out Sequence schedule) && op != TweenOperation.Append)
        {
            schedule.PeekLast().Add(data);
        }
        else 
        {
            List<TweenData> sched = new() { data };

            if(op != TweenOperation.Append)
            {
                schedule = new();

                _schedule.Add(trans, schedule);
                _status.Add(trans, new(true));
            }

            schedule.Enqueue(sched);
        }

        return comp;
    }
    public static void Release(Transform transform, TweenData data)
    {
        _schedule[transform].Dequeue(transform, data);
    }
    public static void Clear(Transform transform)
    {
        _schedule.Remove(transform);
        _status.Remove(transform);
    }
    public static void Kill(Component comp)
    {
        Transform trans = GetTransform(comp);

        if(trans == null)
        {
            return;
        }

        if(_schedule.TryGetValue(trans, out Sequence sequence))
        {
            foreach(TweenData data in sequence.Values()[0])
            {
                if(data.coroutine != null)
                {
                    Util.GetMonoBehaviour().StopCoroutine(data.coroutine);
                }
            }

            Clear(trans);
        }
    }
    private static Transform GetTransform(Component comp)
    {
        if (comp.Equals(_transform))
        {
            return comp as Transform;
        }
        else
        {
            return comp.GetComponent<Transform>();
        }
    }
}
using System.Collections.Generic;
using UnityEngine;
public class Sequence
{
    private Queue<List<TweenData>> tweenQueue = new();

    public int Count()
    {
        return tweenQueue.Count;
    }
    public List<TweenData>[] Values()
    {
        return tweenQueue.ToArray();
    }
    public List<TweenData> PeekLast()
    {
        return tweenQueue.ToArray()[tweenQueue.Count - 1];
    }
    public List<TweenData> Peek()
    {
        if(tweenQueue.Count == 0)
        {
            tweenQueue.Enqueue(new());
        }

        return tweenQueue.Peek();
    }
    public void Enqueue(List<TweenData> list)
    {
        tweenQueue.Enqueue(list);
    }
    public void Dequeue(Transform transform, TweenData data)
    {
        tweenQueue.Peek().Remove(data);

        if(tweenQueue.Peek().Count == 0)
        {
            tweenQueue.Dequeue();

            if(tweenQueue.Count == 0)
            {
                TweenSystemManage.Clear(transform);
            }
            else
            {
                foreach(TweenData _data in tweenQueue.Peek())
                {
                    _data.Set(Util.GetMonoBehaviour().StartCoroutine(Tweening.OverTime(_data.type, _data, _data.trans, _data.easeDel, _data.numeric, _data.duration)));
                }
            }
        }
    }
}