몬스터가 죽으면 경험치와 재화를 드랍해야한다.
드랍할 때 몬스터가 죽은 자리에 뿅 하고 생기는 것 보다는 아이템이 쏟아져 나오는 것 처럼 보여지게 하고 싶었다.
베지에 곡선이라는 것을 사용해서 구현해봤다.
먼저, 드랍 되는 아이템
들의 최상위 클래스인 DroppableItem
이라는 클래스를 만들어줬다.
이 녀석이 담당하는 기능은 드랍 되는 아이템이라면 당연히 해야하는 기능들이다.
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()
{
}
}
몬스터가 드랍하는 경험치를 구현한다. 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()
{
}
}
몬스터가 드랍하는 재화를 구현한다. 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()
{
}
}
베지에 곡선은 여러 개의 점을 선형보간하여 그려내는 곡선이다.
사용방법은 되게 간단한 반면에 꽤나 그럴싸한 곡선을 그려낼 수 있다는 장점이 있다.
예전에 호밍 미사일 같은 투사체를 베지에 곡선으로 구현해본 적이 있는데, 꽤 만족스러운 연출이 나왔었다.
점 A, B, C가 있다면, 선형보간을 이용하여 다음과 같은 점들을 추가로 구한다.
선형보간이므로 시간에 따라 점들의 위치가 변하고, 결과적으로 점 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;
}
코루틴을 사용해서 곡선을 그리도록 해봤다.