본문 바로가기
유니티/코드

Tweening 구현(1)

by fore4022 2025. 4. 29.

 이번 글은 기록을 목적으로 작성되었습니다.

 

 게임을 만들면서 다양한 효과 구현이나 연출에 대해서 알아보다가 처음 접하게 되었다.
 DoTween이라는 에셋도 있었지만, 나는 Unity에서 공식으로 지원하는 패키지만을 사용하여서 프로젝트를 완성하고 싶어서 직접 구현하게 되었다.


 처음에 만들었던 Tweening, 문제점들을 개선해서 만들고 있는 Tweening의 코드을 공유해보고자 한다.


using System.Collections;
using UnityEngine;
public static class ObjectManipulator
{
    public static IEnumerator SetScale(Transform transform, float targetScale, float duration)
    {
        Vector3 scale = new();
        float totalTime = 0;
        float scaleValue;

        while (totalTime != 1)
        {
            totalTime += Time.deltaTime;

            if (totalTime > duration)
            {
                totalTime = 1;
            }

            scaleValue = Mathf.Lerp(transform.localScale.x, targetScale, totalTime);
            scale.x = scaleValue;
            scale.y = scaleValue;
            transform.localScale = scale;

            yield return null;
        }
    }
    public static IEnumerator TrasnformPosition(Transform transform, Vector2 targetPosition, float duration)
    {
        float totalTime = 0;

        while (totalTime != duration)
        {
            totalTime += Time.deltaTime;

            if (totalTime > duration)
            {
                totalTime = duration;
            }

            transform.position = Vector3.Lerp(transform.position, targetPosition, totalTime / duration);

            yield return null;
        }
    }
    public static IEnumerator TransformRoatation(Transform transform, Vector2 targetRoatation, float duration)
    {
        float totalTIme = 0;

        while (totalTIme != duration)
        {
            totalTIme += Time.deltaTime;

            if (totalTIme > duration)
            {
                totalTIme = duration;
            }

            transform.rotation = Quaternion.Euler(targetRoatation * (totalTIme / duration));

            yield return null;
        }
    }
}

 

 현재 위에 작성된 코드는 내가 Tweening을 접하고, 처음으로 만들어본 Tweening 코드이다.

 선형적인 동작으로 Tweening이 제한되어 있고, 사용이 불편하다.


 확장 메서드, 메서드 체이닝을 통해서 접근성을 개선하였습니다.

 Ease 함수를 구현하여서 다양한 Tweening 효과를 구현할 수 있도록 개선하였습니다.

 

 현재 구현 중으로, 나중에 전체 코드를 올릴 계획입니다.

 확장 메서드와 메서드 체이닝에 대해서는 추후에 정리하도록 하겠습니다.

using UnityEngine;
public static class TweenSystem
{
    public static Tween tween = new();

    public static Transform SetScale(this Transform transform, float targetScale, float duration, Ease ease = Ease.Linear)
    {
        Util.GetMonoBehaviour().StartCoroutine(tween.ScaleOverTime(Easing.Get(ease), transform, targetScale, duration));

        return transform;
    }
    public static Transform SetPosition(this Transform transform, Vector2 targetPosition, float duration, Ease ease = Ease.Linear)
    {
        Util.GetMonoBehaviour().StartCoroutine(tween.PositionOverTime(Easing.Get(ease), transform, targetPosition, duration));

        return transform;
    }
}
using System.Collections;
using UnityEngine;
public delegate float EaseDelegate(float f);
public class Tween
{
    public IEnumerator ScaleOverTime(EaseDelegate ease, Transform transform, float targetScale, float duration)
    {
        Vector2 scale = new();
        float initialScale = transform.localScale.x;
        float currentTime = 0;
        float scaleValue;

        while(currentTime != duration)
        {
            currentTime += Time.deltaTime;

            if(currentTime > duration)
            {
                currentTime = duration;
            }

            scaleValue = Mathf.Lerp(initialScale, targetScale, ease(currentTime / duration));

            scale.x = scaleValue;
            scale.y = scaleValue;
            transform.localScale = scale;

            yield return null;
        }
    }
    public IEnumerator PositionOverTime(EaseDelegate ease, Transform transform, Vector2 targetPosition, float duration)
    {
        Vector3 initialPosition = transform.position;
        float currentTime = 0;

        while(currentTime != duration)
        {
            currentTime += Time.deltaTime;

            if(currentTime > duration)
            {
                currentTime = duration;
            }

            transform.position = Vector3.Lerp(initialPosition, targetPosition, ease(currentTime / duration));

            yield return null;
        }
    }
}
using UnityEngine;
public class Easing
{
    private const float start = 0;
    private const float end = 1;

    public static EaseDelegate Get(Ease ease)
    {
        EaseDelegate del = null;

        switch (ease)
        {
            case Ease.Linear:
                del += Linear;
                break;
            case Ease.InQuad:
                del += InQuad;
                break;
            case Ease.OutQuad:
                del += OutQuad;
                break;
            case Ease.InOutQuad:
                del += InOutQuad;
                break;
            case Ease.InCubic:
                del += InCubic;
                break;
            case Ease.OutCubic:
                del += OutCubic;
                break;
            case Ease.InOutCubic:
                del += InOutCubic;
                break;
            case Ease.InQuart:
                del += InQuart;
                break;
            case Ease.OutQuart:
                del += OutQuart;
                break;
            case Ease.InOutQuart:
                del += InOutQuart;
                break;
            case Ease.InQuint:
                del += InQuint;
                break;
            case Ease.OutQuint:
                del += OutQuint;
                break;
            case Ease.InOutQuint:
                del += InOutQuint;
                break;
            case Ease.InSine:
                del += InSine;
                break;
            case Ease.OutSine:
                del += OutSine;
                break;
            case Ease.InOutSine:
                del += InOutSine;
                break;
            case Ease.InExpo:
                del += InExpo;
                break;
            case Ease.OutExpo:
                del += OutExpo;
                break;
            case Ease.InOutExpo:
                del += InOutExpo;
                break;
            case Ease.InCirc:
                del += InCirc;
                break;
            case Ease.OutCirc:
                del += OutCirc;
                break;
            case Ease.InOutCirc:
                del += InOutCirc;
                break;
            case Ease.InBounce:
                del += InBounce;
                break;
            case Ease.OutBounce:
                del += OutBounce;
                break;
            case Ease.InOutBounce:
                del += InOutBounce;
                break;
            case Ease.InBack:
                del += InBack;
                break;
            case Ease.OutBack:
                del += OutBack;
                break;
            case Ease.InOutBack:
                del += InOutBack;
                break;
            case Ease.InElastic:
                del += InElastic;
                break;
            case Ease.OutElastic:
                del += OutElastic;
                break;
            case Ease.InOutElastic:
                del += InOutElastic;
                break;
        }

        return del;
    }
    private static float Linear(float value)
    {
        return Mathf.Lerp(start, end, value);
    }
    private static float OutLinear(float value)
    {
        return Mathf.Lerp(end, start, value);
    }
    private static float InQuad(float value)
    {
        return end * value * value + start;
    }
    private static float OutQuad(float value)
    {
        return -end * value * (value - 2) + start;
    }
    private static float InOutQuad(float value)
    {
        value /= .5f;

        if(value < 1)
        {
            return end * 0.5f * value * value + start;
        }

        value--;

        return -end * 0.5f * (value * (value - 2) - 1) + start;
    }
    private static float InCubic(float value)
    {
        return end * value * value * value + start;
    }
    private static float OutCubic(float value)
    {
        value--;

        return end * (value * value * value + 1) + start;
    }
    private static float InOutCubic(float value)
    {
        value /= 0.5f;

        if(value < 1)
        {
            return end * 0.5f * value * value * value + start;
        }

        value -= 2;

        return end * 0.5f * (value * value * value + 2) + start;
    }
    private static float InQuart(float value)
    {
        return end * value * value * value * value + start;
    }
    private static float OutQuart(float value)
    {
        value--;

        return -end * (value * value * value * value - 1) + start;
    }
    private static float InOutQuart(float value)
    {
        value /= .5f;

        if (value < 1)
        {
            return end * 0.5f * value * value * value * value + start;
        }

        value -= 2;

        return -end * 0.5f * (value * value * value * value - 2) + start;
    }
    private static float InQuint(float value)
    {
        return end * value * value * value * value * value + start;
    }
    private static float OutQuint(float value)
    {
        value--;

        return end * (value * value * value * value * value + 1) + start;
    }
    private static float InOutQuint(float value)
    {
        value /= 0.5f;

        if(value < 1)
        {
            return end * 0.5f * value * value * value * value * value + start;
        }
        
        value -= 2;

        return end * 0.5f * (value * value * value * value * value + 2) + start;
    }
    private static float InSine(float value)
    {
        return -end * Mathf.Cos(value * (Mathf.PI * 0.5f)) + end + start;
    }
    private static float OutSine(float value)
    {
        return end * Mathf.Sin(value * (Mathf.PI * 0.5f)) + start;
    }
    private static float InOutSine(float value)
    {
        return -end * 0.5f * (Mathf.Cos(Mathf.PI * value) - 1) + start;
    }
    private static float InExpo(float value)
    {
        return end * Mathf.Pow(2, 10 * (value - 1)) + start;
    }
    private static float OutExpo(float value)
    {
        return end * (-Mathf.Pow(2, -10 * value) + 1) + start;
    }
    private static float InOutExpo(float value)
    {
        value /= 0.5f;

        if(value < 1)
        {
            return end * 0.5f * Mathf.Pow(2, 10 * (value - 1)) + start;
        }

        value--;

        return end * 0.5f * (-Mathf.Pow(2, -10 * value) + 2) + start;
    }
    private static float InCirc(float value)
    {
        return -end * (Mathf.Sqrt(1 - value * value) - 1) + start;
    }
    private static float OutCirc(float value)
    {
        value--;

        return end * Mathf.Sqrt(1 - value * value) + start;
    }
    private static float InOutCirc(float value)
    {
        value /= .5f;

        if(value < 1)
        {
            return -end * 0.5f * (Mathf.Sqrt(1 - value * value) - 1) + start;
        }

        value -= 2;

        return end * 0.5f * (Mathf.Sqrt(1 - value * value) + 1) + start;
    }
    private static float InBounce(float value)
    {
        float d = 1f;

        return end - OutBounce(d - value) + start;
    }
    private static float OutBounce(float value)
    {
        value /= 1f;

        if (value < (1 / 2.75f))
        {
            return end * (7.5625f * value * value) + start;
        }
        else if (value < (2 / 2.75f))
        {
            value -= (1.5f / 2.75f);

            return end * (7.5625f * (value) * value + .75f) + start;
        }
        else if (value < (2.5 / 2.75))
        {
            value -= (2.25f / 2.75f);

            return end * (7.5625f * (value) * value + .9375f) + start;
        }
        else
        {
            value -= (2.625f / 2.75f);

            return end * (7.5625f * (value) * value + .984375f) + start;
        }
    }
    private static float InOutBounce(float value)
    {
        float d = 1f;

        if(value < d * 0.5f)
        {
            return InBounce(value * 2) * 0.5f + start;
        }
        else
        {
            return OutBounce(value * 2 - d) * 0.5f + end * 0.5f + start;
        }
    }
    private static float InBack(float value)
    {
        float s = 1.70158f;

        value /= 1;

        return end * (value) * value * ((s + 1) * value - s) + start;
    }
    private static float OutBack(float value)
    {
        float s = 1.70158f;

        value = (value) - 1;

        return end * ((value) * value * ((s + 1) * value + s) + 1) + start;
    }
    private static float InOutBack(float value)
    {
        float s = 1.70158f;

        value /= 0.5f;

        if((value) < 1)
        {
            s *= 1.525f;

            return end * 0.5f * (value * value * (((s) + 1) * value - s)) + start;
        }

        value -= 2;
        s *= 1.525f;

        return end * 0.5f * ((value) * value * (((s) + 1) * value + s) + 2) + start;
    }
    private static float InElastic(float value)
    {
        float d = 1f;
        float p = d * .3f;
        float s;
        float a = 0;

        if(value == 0)
        {
            return start;
        }

        if((value /= d) == 1)
        {
            return start + end;
        }

        if (a == 0f || a < Mathf.Abs(end))
        {
            a = end;
            s = p / 4;
        }
        else
        {
            s = p / (2 * Mathf.PI) * Mathf.Asin(end / a);
        }

        return -(a * Mathf.Pow(2, 10 * (value -= 1)) * Mathf.Sin((value * d - s) * (2 * Mathf.PI) / p)) + start;
    }
    private static float OutElastic(float value)
    {
        float d = 1f;
        float p = d * .3f;
        float s;
        float a = 0;

        if(value == 0)
        {
            return start;
        }

        if((value /= d) == 1)
        {
            return start + end;
        }

        if (a == 0f || a < Mathf.Abs(end))
        {
            a = end;
            s = p * 0.25f;
        }
        else
        {
            s = p / (2 * Mathf.PI) * Mathf.Asin(end / a);
        }

        return (a * Mathf.Pow(2, -10 * value) * Mathf.Sin((value * d - s) * (2 * Mathf.PI) / p) + end + start);
    }
    private static float InOutElastic(float value)
    {
        float d = 1f;
        float p = d * .3f;
        float s;
        float a = 0;

        if(value == 0)
        {
            return start;
        }

        if((value /= d * 0.5f) == 2)
        {
            return start + end;
        }

        if (a == 0f || a < Mathf.Abs(end))
        {
            a = end;
            s = p / 4;
        }
        else
        {
            s = p / (2 * Mathf.PI) * Mathf.Asin(end / a);
        }

        if(value < 1)
        {
            return -0.5f * (a * Mathf.Pow(2, 10 * (value -= 1)) * Mathf.Sin((value * d - s) * (2 * Mathf.PI) / p)) + start;
        }

        return a * Mathf.Pow(2, -10 * (value -= 1)) * Mathf.Sin((value * d - s) * (2 * Mathf.PI) / p) * 0.5f + end + start;
    }
}
public enum Ease
{
    Linear,
    InQuad,
    OutQuad,
    InOutQuad,
    InCubic,
    OutCubic,
    InOutCubic,
    InQuart,
    OutQuart,
    InOutQuart,
    InQuint,
    OutQuint,
    InOutQuint,
    InSine,
    OutSine,
    InOutSine,
    InExpo,
    OutExpo,
    InOutExpo,
    InCirc,
    OutCirc,
    InOutCirc,
    InBounce,
    OutBounce,
    InOutBounce,
    InBack,
    OutBack,
    InOutBack,
    InElastic,
    OutElastic,
    InOutElastic
}

'유니티 > 코드' 카테고리의 다른 글

Tweening 구현(2)  (0) 2025.05.12
ScriptableObject 배열 길이 제한하기  (0) 2025.03.18
Non-MonoBehaviour Class에서 Coroutine 실행  (0) 2024.12.31
GetComponentsInChildren 구현  (0) 2024.10.12
어드레서블 에셋 씬 로드 구현  (0) 2024.09.11