게임에서 데미지 인디케이터는 플레이어가 전투 상황을 더 잘 이해하고 적에 대한 전략을 수립할 수 있도록 돕습니다. 이는 플레이어의 강력함과 적의 상황을 더 잘 파악하며, 자신의 능력치와 전략을 최적화할 수 있도록 돕습니다.
📺 미리보기
💬 서론
- 본 글에서 다루는 기능은 오브젝트 풀링을 사용합니다.
- 오브젝트 풀링을 사용하여 무거운 인스턴싱을 최소화하고 사용이 완료된 대상을 재사용합니다.
- 오브젝트 풀링에 대한 내용은 "[유니티] 오브젝트 풀 (Object Pool)"에서 확인할 수 있습니다.
📖 구현 내용
- 플레이어가 입힌 피해량 중 실제로 적에게 들어간 피해량을 시각화하여 보여줍니다.
- 3D 월드에 배치되어 나타나며, Overlay를 사용하여 다른 오브젝트에 가려지지 않습니다.
- 표시되는 색상을 쉽게 바꿀 수 있어 피해량 뿐만 아니라 체력 및 마나 회복, 마나소모 등 여러가지 기능도 함께 사용할 수 있습니다.
- 오브젝트 풀링을 사용하는 모델입니다.
- 데미지 라벨이 항상 카메라를 바라봅니다.
⚒️ 구현
· DamageIndicator.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using TMPro;
[RequireComponent(typeof(Rigidbody))]
public class DamageIndicator : MonoBehaviour
{
[Header("프리팹의 리지드바디")]
[SerializeField] Rigidbody mRigidBody;
[Header("프리팹의 데미지 라벨")]
[SerializeField] TextMeshPro mDamageLabel;
[Header("프리팹의 자식 객체 (Look Camera)")]
[SerializeField] Transform mChildTransform;
private Coroutine? mCoFadeLabel;
private void Update()
{
mChildTransform.LookAt(Camera.main.transform);
}
public void Init(Vector3 pos, float amount, Color color, float size)
{
// 만약 호출된 값이 0이라면 리턴
if(Mathf.RoundToInt(amount) == 0)
{
gameObject.SetActive(false);
return;
}
// 재사용시 기존의 상태를 초기화
mRigidBody.angularVelocity = Vector3.zero;
mRigidBody.velocity = Vector3.zero;
transform.localScale = Vector3.one * size;
// 위치 설정
transform.position = pos;
// 텍스트 설정
mDamageLabel.text = Mathf.Abs(Mathf.RoundToInt(amount)).ToString();
mDamageLabel.color = color;
// 연출을 위한 힘 추가
mRigidBody.AddForce(new Vector3(Random.Range(-5f, 5f), 5f, Random.Range(-5f, 5f)), ForceMode.Impulse);
if(mCoFadeLabel is not null)
StopCoroutine(mCoFadeLabel);
mCoFadeLabel = StartCoroutine(CoFadeLabel());
}
private IEnumerator CoFadeLabel()
{
float process = 0f;
Vector3 initSize = transform.localScale;
while(process < 1.0f)
{
process += Time.deltaTime;
transform.localScale = Vector3.Lerp(initSize, Vector3.zero, process);
mDamageLabel.alpha = Mathf.Lerp(1.0f, 0.0f, process);
yield return null;
}
gameObject.SetActive(false);
}
}
[Header("프리팹의 리지드바디")]
[SerializeField] Rigidbody mRigidBody;
- 데미지 인디케이터 오브젝트는 충돌 검사를 하지 않는 리지드바디를 사용합니다.
[Header("프리팹의 데미지 라벨")]
[SerializeField] TextMeshPro mDamageLabel;
- 데미지를 표시할 텍스트 라벨입니다.
[Header("프리팹의 자식 객체 (Look Camera)")]
[SerializeField] Transform mChildTransform;
- 항상 카메라를 응시할 자식 객체입니다.
private void Update()
{
mChildTransform.LookAt(Camera.main.transform);
}
- 오브젝트가 활성화 되어있을 때 항상 카메라를 바라보게합니다.
public void Init(Vector3 pos, float amount, Color color, float size)
{
// 만약 호출된 값이 0이라면 리턴
if(Mathf.RoundToInt(amount) == 0)
{
gameObject.SetActive(false);
return;
}
// 재사용시 기존의 상태를 초기화
mRigidBody.angularVelocity = Vector3.zero;
mRigidBody.velocity = Vector3.zero;
transform.localScale = Vector3.one * size;
// 위치 설정
transform.position = pos;
// 텍스트 설정
mDamageLabel.text = Mathf.Abs(Mathf.RoundToInt(amount)).ToString();
mDamageLabel.color = color;
// 연출을 위한 힘 추가
mRigidBody.AddForce(new Vector3(Random.Range(-5f, 5f), 5f, Random.Range(-5f, 5f)), ForceMode.Impulse);
if(mCoFadeLabel is not null)
StopCoroutine(mCoFadeLabel);
mCoFadeLabel = StartCoroutine(CoFadeLabel());
}
- 오브젝트 풀링에서 꺼내져 활성화 될 때, 호출되는 초기화 함수입니다.
- 매개변수로 시작 위치, 표시할 양(데미지 및 기타), 색상과 크기를 전달받습니다.
- amount가 0일 수 있습니다. 이럴 땐 리턴을 하여 호출을 무시합니다.
// 재사용시 기존의 상태를 초기화
mRigidBody.angularVelocity = Vector3.zero;
mRigidBody.velocity = Vector3.zero;
transform.localScale = Vector3.one * size;
- 리지드바디는 비활성화가 되어도 힘 속성을 유지합니다.
- 이 힘 속성을 초기화하여 완전히 새로운 상태로 사용할 수 있도록 합니다.
- 스케일은 코루틴에서 점점 작아지도록 구현하였기에 값 또한 초기화해줍니다.
// 연출을 위한 힘 추가
mRigidBody.AddForce(new Vector3(Random.Range(-5f, 5f), 5f, Random.Range(-5f, 5f)), ForceMode.Impulse);
- 리지드바디의 AddForce를 이용하여 힘을 줍니다.
- X, Z 방향은 무작위 방향으로 힘을 주고, Y축으로는 고정된 5만큼의 힘을 주어 위로 떠오르면서 무작위 방향으로 날아가게 설정합니다.
- Impulse 속성을 이용하여 순간적인 강한 힘을 부여합니다.
- "[유니티] 충돌 연출 및 RigidBody.AddForce()의 이해" 글에서 Rigidbody.Addforce의 네가지 힘 속성을 비교할 수 있습니다.
private IEnumerator CoFadeLabel()
{
float process = 0f;
Vector3 initSize = transform.localScale;
while(process < 1.0f)
{
process += Time.deltaTime;
transform.localScale = Vector3.Lerp(initSize, Vector3.zero, process);
mDamageLabel.alpha = Mathf.Lerp(1.0f, 0.0f, process);
yield return null;
}
gameObject.SetActive(false);
}
- 인디케이터를 자연스럽게 사라지게 합니다.
- 크기를 서서히 줄이고, 알파값을 0으로 가깝게하여 서서히 사라지게 연출합니다.
· 프리팹
- 위 이미지와 같이 구성하였습니다.
- DamageIndicator.cs를 붙이면 자동으로 리지드바디가 생성됩니다. Gravity 속성을 켜줍니다.
- Front로 봤을 때 좌우가 반전된 상태로 세팅합니다.
- 그 이유는 Look Camera를 할 때, 좌우가 180도 돌아간 상태로 보여야 정상적으로 보이기 때문입니다.
✅ 사용
- 오브젝트 풀을 사용하여 호출합니다.
· 오브젝트 풀
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class DamageIndicatorPool : OBJPool<DamageIndicator, DamageIndicatorPool>
{
/// <summary>
/// 풀에서 비활성 데미지 인디케이터 객체를 가져오고 초기화
/// </summary>
/// <param name="pos"></param>
/// <param name="amount"></param>
/// <param name="scale"></param>
/// <returns></returns>
public DamageIndicator GetFromPool(Vector3 pos, float amount, Color color, float scale = 1.0f)
{
DamageIndicator indicator = GetFromPool();
indicator.Init(pos, amount, color, scale);
return indicator;
}
//풀(mPool)로부터 오브젝트를 가져온다.
override public DamageIndicator GetFromPool(int id = 0)
{
for (int i = 0; i < mPool[id].Count; ++i)
{
if (!mPool[id][i].gameObject.activeInHierarchy)
{
mPool[id][i].gameObject.SetActive(true);
return mPool[id][i];
}
}
return MakeNewInstance(id);
}
}
- 오브젝트 풀링을 사용하여 비활성 객체를 가져오고 초기화합니다.
- GetFromPool을 오버로딩하여 풀에서 가져옴과 동시에 초기화할 수 있도록 구현하였습니다.
· 체력
override public bool ModifyCurrentHp(float amount)
{
bool isDead = base.StatData.ModifyCurrentHp(amount);
EnemyInfoDraw.UpdateHPBar(base.StatData.HpCurrent / base.StatData.hpMax);
// 데미지 인디케이터
DamageIndicatorPool.Instance.GetFromPool(transform.position, amount, amount < 0 ? Color.red : Color.green);
return isDead;
}
- 플레이어에게 피격을 당한 경우 또는 회복을 한경우 FSM에서 사용하는 체력 예시입니다.
- amount만큼 체력이 변경되는데, 이 상태에서 음수값이면 체력이 닳는 상황입니다.
- 체력이 닳는 상황이면 Color을 red로 하고, 그것이 아니면 green으로 하여 체력을 회복하는 연출을 할 수 있습니다.
· 마나
override public void ModifyCurrentMp(float value)
{
// 마나 조정
base.StatData.ModifyCurrentMp(value);
// 데미지 인디케이터
DamageIndicatorPool.Instance.GetFromPool(transform.position, value, value < 0 ? Color.magenta : Color.blue);
// UI 업데이트
mUIStatDraw.UpdateManaCircle(base.StatData.MpCurrent/ base.StatData.mpMax);
}
- 플레이어가 마나를 소모하거나, 회복하는 함수입니다.
- 마나를 소모하면 마젠타 색상, 마나를 회복하면 파란색으로 보여줍니다.
'unity game modules' 카테고리의 다른 글
[유니티] 퀘스트 시스템(1) - 퀘스트 데이터 (0) | 2023.04.11 |
---|---|
[유니티] 오브젝트의 중심을 기준으로 회전 (1) | 2023.04.08 |
[유니티] 로딩 화면 구현 (0) | 2023.04.02 |
[유니티] 게임 저장 시스템(3) - 매니저 (0) | 2023.04.02 |
[유니티] 게임 저장 시스템(2) - 저장 및 불러오기 예시 (0) | 2023.04.02 |
게임에서 데미지 인디케이터는 플레이어가 전투 상황을 더 잘 이해하고 적에 대한 전략을 수립할 수 있도록 돕습니다. 이는 플레이어의 강력함과 적의 상황을 더 잘 파악하며, 자신의 능력치와 전략을 최적화할 수 있도록 돕습니다.
📺 미리보기
💬 서론
- 본 글에서 다루는 기능은 오브젝트 풀링을 사용합니다.
- 오브젝트 풀링을 사용하여 무거운 인스턴싱을 최소화하고 사용이 완료된 대상을 재사용합니다.
- 오브젝트 풀링에 대한 내용은 "[유니티] 오브젝트 풀 (Object Pool)"에서 확인할 수 있습니다.
📖 구현 내용
- 플레이어가 입힌 피해량 중 실제로 적에게 들어간 피해량을 시각화하여 보여줍니다.
- 3D 월드에 배치되어 나타나며, Overlay를 사용하여 다른 오브젝트에 가려지지 않습니다.
- 표시되는 색상을 쉽게 바꿀 수 있어 피해량 뿐만 아니라 체력 및 마나 회복, 마나소모 등 여러가지 기능도 함께 사용할 수 있습니다.
- 오브젝트 풀링을 사용하는 모델입니다.
- 데미지 라벨이 항상 카메라를 바라봅니다.
⚒️ 구현
· DamageIndicator.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using TMPro;
[RequireComponent(typeof(Rigidbody))]
public class DamageIndicator : MonoBehaviour
{
[Header("프리팹의 리지드바디")]
[SerializeField] Rigidbody mRigidBody;
[Header("프리팹의 데미지 라벨")]
[SerializeField] TextMeshPro mDamageLabel;
[Header("프리팹의 자식 객체 (Look Camera)")]
[SerializeField] Transform mChildTransform;
private Coroutine? mCoFadeLabel;
private void Update()
{
mChildTransform.LookAt(Camera.main.transform);
}
public void Init(Vector3 pos, float amount, Color color, float size)
{
// 만약 호출된 값이 0이라면 리턴
if(Mathf.RoundToInt(amount) == 0)
{
gameObject.SetActive(false);
return;
}
// 재사용시 기존의 상태를 초기화
mRigidBody.angularVelocity = Vector3.zero;
mRigidBody.velocity = Vector3.zero;
transform.localScale = Vector3.one * size;
// 위치 설정
transform.position = pos;
// 텍스트 설정
mDamageLabel.text = Mathf.Abs(Mathf.RoundToInt(amount)).ToString();
mDamageLabel.color = color;
// 연출을 위한 힘 추가
mRigidBody.AddForce(new Vector3(Random.Range(-5f, 5f), 5f, Random.Range(-5f, 5f)), ForceMode.Impulse);
if(mCoFadeLabel is not null)
StopCoroutine(mCoFadeLabel);
mCoFadeLabel = StartCoroutine(CoFadeLabel());
}
private IEnumerator CoFadeLabel()
{
float process = 0f;
Vector3 initSize = transform.localScale;
while(process < 1.0f)
{
process += Time.deltaTime;
transform.localScale = Vector3.Lerp(initSize, Vector3.zero, process);
mDamageLabel.alpha = Mathf.Lerp(1.0f, 0.0f, process);
yield return null;
}
gameObject.SetActive(false);
}
}
[Header("프리팹의 리지드바디")]
[SerializeField] Rigidbody mRigidBody;
- 데미지 인디케이터 오브젝트는 충돌 검사를 하지 않는 리지드바디를 사용합니다.
[Header("프리팹의 데미지 라벨")]
[SerializeField] TextMeshPro mDamageLabel;
- 데미지를 표시할 텍스트 라벨입니다.
[Header("프리팹의 자식 객체 (Look Camera)")]
[SerializeField] Transform mChildTransform;
- 항상 카메라를 응시할 자식 객체입니다.
private void Update()
{
mChildTransform.LookAt(Camera.main.transform);
}
- 오브젝트가 활성화 되어있을 때 항상 카메라를 바라보게합니다.
public void Init(Vector3 pos, float amount, Color color, float size)
{
// 만약 호출된 값이 0이라면 리턴
if(Mathf.RoundToInt(amount) == 0)
{
gameObject.SetActive(false);
return;
}
// 재사용시 기존의 상태를 초기화
mRigidBody.angularVelocity = Vector3.zero;
mRigidBody.velocity = Vector3.zero;
transform.localScale = Vector3.one * size;
// 위치 설정
transform.position = pos;
// 텍스트 설정
mDamageLabel.text = Mathf.Abs(Mathf.RoundToInt(amount)).ToString();
mDamageLabel.color = color;
// 연출을 위한 힘 추가
mRigidBody.AddForce(new Vector3(Random.Range(-5f, 5f), 5f, Random.Range(-5f, 5f)), ForceMode.Impulse);
if(mCoFadeLabel is not null)
StopCoroutine(mCoFadeLabel);
mCoFadeLabel = StartCoroutine(CoFadeLabel());
}
- 오브젝트 풀링에서 꺼내져 활성화 될 때, 호출되는 초기화 함수입니다.
- 매개변수로 시작 위치, 표시할 양(데미지 및 기타), 색상과 크기를 전달받습니다.
- amount가 0일 수 있습니다. 이럴 땐 리턴을 하여 호출을 무시합니다.
// 재사용시 기존의 상태를 초기화
mRigidBody.angularVelocity = Vector3.zero;
mRigidBody.velocity = Vector3.zero;
transform.localScale = Vector3.one * size;
- 리지드바디는 비활성화가 되어도 힘 속성을 유지합니다.
- 이 힘 속성을 초기화하여 완전히 새로운 상태로 사용할 수 있도록 합니다.
- 스케일은 코루틴에서 점점 작아지도록 구현하였기에 값 또한 초기화해줍니다.
// 연출을 위한 힘 추가
mRigidBody.AddForce(new Vector3(Random.Range(-5f, 5f), 5f, Random.Range(-5f, 5f)), ForceMode.Impulse);
- 리지드바디의 AddForce를 이용하여 힘을 줍니다.
- X, Z 방향은 무작위 방향으로 힘을 주고, Y축으로는 고정된 5만큼의 힘을 주어 위로 떠오르면서 무작위 방향으로 날아가게 설정합니다.
- Impulse 속성을 이용하여 순간적인 강한 힘을 부여합니다.
- "[유니티] 충돌 연출 및 RigidBody.AddForce()의 이해" 글에서 Rigidbody.Addforce의 네가지 힘 속성을 비교할 수 있습니다.
private IEnumerator CoFadeLabel()
{
float process = 0f;
Vector3 initSize = transform.localScale;
while(process < 1.0f)
{
process += Time.deltaTime;
transform.localScale = Vector3.Lerp(initSize, Vector3.zero, process);
mDamageLabel.alpha = Mathf.Lerp(1.0f, 0.0f, process);
yield return null;
}
gameObject.SetActive(false);
}
- 인디케이터를 자연스럽게 사라지게 합니다.
- 크기를 서서히 줄이고, 알파값을 0으로 가깝게하여 서서히 사라지게 연출합니다.
· 프리팹
- 위 이미지와 같이 구성하였습니다.
- DamageIndicator.cs를 붙이면 자동으로 리지드바디가 생성됩니다. Gravity 속성을 켜줍니다.
- Front로 봤을 때 좌우가 반전된 상태로 세팅합니다.
- 그 이유는 Look Camera를 할 때, 좌우가 180도 돌아간 상태로 보여야 정상적으로 보이기 때문입니다.
✅ 사용
- 오브젝트 풀을 사용하여 호출합니다.
· 오브젝트 풀
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class DamageIndicatorPool : OBJPool<DamageIndicator, DamageIndicatorPool>
{
/// <summary>
/// 풀에서 비활성 데미지 인디케이터 객체를 가져오고 초기화
/// </summary>
/// <param name="pos"></param>
/// <param name="amount"></param>
/// <param name="scale"></param>
/// <returns></returns>
public DamageIndicator GetFromPool(Vector3 pos, float amount, Color color, float scale = 1.0f)
{
DamageIndicator indicator = GetFromPool();
indicator.Init(pos, amount, color, scale);
return indicator;
}
//풀(mPool)로부터 오브젝트를 가져온다.
override public DamageIndicator GetFromPool(int id = 0)
{
for (int i = 0; i < mPool[id].Count; ++i)
{
if (!mPool[id][i].gameObject.activeInHierarchy)
{
mPool[id][i].gameObject.SetActive(true);
return mPool[id][i];
}
}
return MakeNewInstance(id);
}
}
- 오브젝트 풀링을 사용하여 비활성 객체를 가져오고 초기화합니다.
- GetFromPool을 오버로딩하여 풀에서 가져옴과 동시에 초기화할 수 있도록 구현하였습니다.
· 체력
override public bool ModifyCurrentHp(float amount)
{
bool isDead = base.StatData.ModifyCurrentHp(amount);
EnemyInfoDraw.UpdateHPBar(base.StatData.HpCurrent / base.StatData.hpMax);
// 데미지 인디케이터
DamageIndicatorPool.Instance.GetFromPool(transform.position, amount, amount < 0 ? Color.red : Color.green);
return isDead;
}
- 플레이어에게 피격을 당한 경우 또는 회복을 한경우 FSM에서 사용하는 체력 예시입니다.
- amount만큼 체력이 변경되는데, 이 상태에서 음수값이면 체력이 닳는 상황입니다.
- 체력이 닳는 상황이면 Color을 red로 하고, 그것이 아니면 green으로 하여 체력을 회복하는 연출을 할 수 있습니다.
· 마나
override public void ModifyCurrentMp(float value)
{
// 마나 조정
base.StatData.ModifyCurrentMp(value);
// 데미지 인디케이터
DamageIndicatorPool.Instance.GetFromPool(transform.position, value, value < 0 ? Color.magenta : Color.blue);
// UI 업데이트
mUIStatDraw.UpdateManaCircle(base.StatData.MpCurrent/ base.StatData.mpMax);
}
- 플레이어가 마나를 소모하거나, 회복하는 함수입니다.
- 마나를 소모하면 마젠타 색상, 마나를 회복하면 파란색으로 보여줍니다.
'unity game modules' 카테고리의 다른 글
[유니티] 퀘스트 시스템(1) - 퀘스트 데이터 (0) | 2023.04.11 |
---|---|
[유니티] 오브젝트의 중심을 기준으로 회전 (1) | 2023.04.08 |
[유니티] 로딩 화면 구현 (0) | 2023.04.02 |
[유니티] 게임 저장 시스템(3) - 매니저 (0) | 2023.04.02 |
[유니티] 게임 저장 시스템(2) - 저장 및 불러오기 예시 (0) | 2023.04.02 |