내일배움캠프 Unity 26일차 TIL - 팀 십장생 - 베지에 곡선으로 아이템 드랍하기

Wooooo·2023년 12월 4일
0

내일배움캠프Unity

목록 보기
28/94

오늘의 키워드

몬스터가 죽으면 경험치와 재화를 드랍해야한다.
드랍할 때 몬스터가 죽은 자리에 뿅 하고 생기는 것 보다는 아이템이 쏟아져 나오는 것 처럼 보여지게 하고 싶었다.
베지에 곡선이라는 것을 사용해서 구현해봤다.


클래스 설계

DroppableItem class

먼저, 드랍 되는 아이템들의 최상위 클래스인 DroppableItem이라는 클래스를 만들어줬다.
이 녀석이 담당하는 기능은 드랍 되는 아이템이라면 당연히 해야하는 기능들이다.

  • 생성 직후 베지에 곡선을 이용하여 쏟아지는 연출
  • 플레이어가 일정 범위 안에 들어왔는지 탐색
    • 들어왔다면, 플레이어가 Looting하며 OnLooting() 메서드 호출
  • 가상 메서드 OnLooting()를 작성하여 파생 클래스에서 루팅 후의 로직 구현
using System.Collections;
using Unity.VisualScripting;
using UnityEngine;
using UnityEngine.UIElements;

public class DroppableItem : MonoBehaviour
{
    [Header("Droppable Settings")]
    [SerializeField] protected float _scale = 1f;
    [SerializeField] protected GameObject vfxObject;
    [SerializeField] protected float detectRange = 1f;
    [SerializeField] protected float randomDropRange = 1f;
    [SerializeField] protected float dropCurveProgressTime = 0.5f;
    [SerializeField] protected float lootingProgressTime = 1f;
    [SerializeField] protected Vector2 dropBezierPoint = Vector2.up;

    protected bool isDetecting = false;
    protected Vector2 dropStartPoint;
    protected Vector2 randomDropPoint;
    protected Transform target;

    public float Scale
    {
        get => _scale;
        set { _scale = value; SetScale(); }
    }

    protected virtual void OnEnable()
    {
        SetScale();
        target = GameManager.Instance?.player?.transform;
        StartCoroutine(DroppingProgress());
    }

    public virtual void SetScale() => transform.localScale = Vector3.one * _scale;

    protected virtual void Update()
    {
        if (isDetecting && target)
        {
            if (Vector2.Distance(target.position, transform.position) < detectRange)
            {
                StartCoroutine(LootingProgress());
            }
        }
    }

    protected virtual IEnumerator DroppingProgress()
    {
        isDetecting = false;
        dropStartPoint = transform.position;
        randomDropPoint = Random.insideUnitCircle * randomDropRange + dropStartPoint;

        for (float t = 0f; t < dropCurveProgressTime; t += Time.deltaTime)
        {
            float progress = t / dropCurveProgressTime;
            Vector2 bezierPoint1 = Vector2.Lerp(dropStartPoint, dropBezierPoint + dropStartPoint, progress);
            Vector2 bezierPoint2 = Vector2.Lerp(dropBezierPoint + dropStartPoint, randomDropPoint, progress);
            transform.position = Vector2.Lerp(bezierPoint1, bezierPoint2, progress);

            // draw bezier curve
            Debug.DrawLine(dropStartPoint, dropBezierPoint + dropStartPoint, Color.yellow);
            Debug.DrawLine(dropBezierPoint + dropStartPoint, randomDropPoint, Color.yellow);
            Debug.DrawLine(bezierPoint1, bezierPoint2, Color.green);

            yield return null;
        }

        isDetecting = true;
    }

    protected virtual IEnumerator LootingProgress()
    {
        isDetecting = false;
        Vector2 lootingStartPoint = transform.position;
        for (float t = 0f; t < lootingProgressTime; t += Time.deltaTime)
        {
            float progress = t / lootingProgressTime;
            transform.position = Vector2.Lerp(lootingStartPoint, target.position, progress);
            yield return null;
        }
        vfxObject.transform.SetParent(null, true);
        OnLooting();
        Destroy(vfxObject, 1f);
        Destroy(gameObject);
    }

    protected virtual void OnLooting()
    {

    }
}



Droppable_EXP class

몬스터가 드랍하는 경험치를 구현한다. DroppableItem을 상속받는다.
대부분의 기능은 부모 클래스가 구현하고 있으므로, 비주얼, 이펙트적인 부분과 먹었을 때의 효과를 구현한다. OnLooting() 메서드는 아직 미구현 상태...

using UnityEngine;

public class Droppable_EXP : DroppableItem
{
    int value = 1;
    float colorTime;
    SpriteRenderer bigCircle;
    SpriteRenderer smallCircle;
    TrailRenderer trail;

    [Header("Color Settings")]
    [SerializeField] Gradient bigCircleColor;
    [SerializeField] Gradient smallCircleColor;

    protected override void OnEnable()
    {
        base.OnEnable();
        bigCircle = transform.GetChild(0).GetComponent<SpriteRenderer>();
        smallCircle = transform.GetChild(1).GetComponent<SpriteRenderer>();
        trail = GetComponentInChildren<TrailRenderer>();
        trail.startWidth = _scale * smallCircle.transform.localScale.x;
    }

    protected override void Update()
    {
        base.Update();

        colorTime += Time.deltaTime;
        if (colorTime > 1f) colorTime -= 1f;
        bigCircle.color = bigCircleColor.Evaluate(colorTime);
        smallCircle.color = smallCircleColor.Evaluate(colorTime);
        trail.startColor = smallCircleColor.Evaluate(colorTime);
        trail.endColor = smallCircleColor.Evaluate(colorTime);
    }

    protected override void OnLooting()
    {

    }
}



Droppable_Money class

몬스터가 드랍하는 재화를 구현한다. DroppableItem을 상속받는다.
마찬가지로 대부분의 기능은 부모 클래스가 구현하고 있으므로, 비주얼, 이펙트적인 부분과 먹었을 때의 효과를 구현한다. OnLooting() 메서드 역시 아직 미구현 상태...

using UnityEngine;

public class Droppable_Money : DroppableItem
{
    int value = 1;
    float spinTime;
    Transform sprite;
    TrailRenderer trail;
    ParticleSystem particle;

    [Header("Sprite Settings")]
    [SerializeField] float maxScale = 0.66f;
    [SerializeField] float spinSpeed = 1f;

    protected override void OnEnable()
    {
        base.OnEnable();
        sprite = transform.GetChild(0);
        trail = GetComponentInChildren<TrailRenderer>();
        trail.startWidth = _scale * sprite.transform.localScale.x * 0.5f;
        particle = GetComponentInChildren<ParticleSystem>();
        var particleMain = particle.main;
        particleMain.startSize = 0.33f * _scale;
        var particleshape = particle.shape;
        particleshape.radius = _scale * _scale;
    }

    protected override void Update()
    {
        base.Update();

        spinTime += Time.deltaTime * spinSpeed;
        float x = Mathf.Abs(Mathf.Sin(spinTime));
        sprite.localScale = new Vector3(x * maxScale, 1f, 1f);
    }

    protected override void OnLooting()
    {

    }
}

베지에 곡선 (Bezier Curve)

베지에 곡선은 여러 개의 점을 선형보간하여 그려내는 곡선이다.
사용방법은 되게 간단한 반면에 꽤나 그럴싸한 곡선을 그려낼 수 있다는 장점이 있다.
예전에 호밍 미사일 같은 투사체를 베지에 곡선으로 구현해본 적이 있는데, 꽤 만족스러운 연출이 나왔었다.

[Unity] 베지어 곡선(Bezier Curves) 구현해보기

동작 원리

점 A, B, C가 있다면, 선형보간을 이용하여 다음과 같은 점들을 추가로 구한다.

  • 점 D : A to B의 벡터를 선형보간
  • 점 E : B to C의 벡터를 선형보간
  • 점 F : D to E의 벡터를 선형보간

선형보간이므로 시간에 따라 점들의 위치가 변하고, 결과적으로 점 A에서 시작하여 점 B쪽으로 꺾이며 점 C로 향하는 곡선이 그려진다.


아이템 쏟아져 나오는 것 처럼 보이게 하기

점 A는 시작점이다.
점 B는 시작점에서 1만큼 위다. (위로 던져지는 것처럼 보이게 하기 위해서)
점 C는 아이템이 최종적으로 떨어질 위치로, 일정 범위 내의 랜덤한 좌표값으로 정해준다.

    protected virtual IEnumerator DroppingProgress()
    {
        isDetecting = false;
        dropStartPoint = transform.position;
        randomDropPoint = Random.insideUnitCircle * randomDropRange + dropStartPoint;

        for (float t = 0f; t < dropCurveProgressTime; t += Time.deltaTime)
        {
            float progress = t / dropCurveProgressTime;
            Vector2 bezierPoint1 = Vector2.Lerp(dropStartPoint, dropBezierPoint + dropStartPoint, progress);
            Vector2 bezierPoint2 = Vector2.Lerp(dropBezierPoint + dropStartPoint, randomDropPoint, progress);
            transform.position = Vector2.Lerp(bezierPoint1, bezierPoint2, progress);
            yield return null;
        }

        isDetecting = true;
    }

코루틴을 사용해서 곡선을 그리도록 해봤다.

테스트 화면


참고

https://leekangw.github.io/posts/49/

profile
game developer

0개의 댓글