플레이어가 적으로부터 피해를 받은경우 오버레이 캔버스에서 이를 시각적으로 표현하고, 체력이 낮은 경우 또한 이미지를 이용하여 시각적으로 사용자에게 상태를 보여줄 수 있습니다. 이 기능을 구현한 후 정리하였습니다.
📺 미리 보기
📖 구현 내용
- 플레이어가 피해를 받은 경우 HitOverlay() 함수를 호출하여 일시적인 피해 효과를 보여줍니다.
- 플레이어의 체력이 변경될경우 CheckHp() 함수를 호출하여 현재 체력의 비율을 검사해 낮은 체력 경고 이미지를 활성화 합니다.
- 낮은 체력 경고 이미지가 활성화된경우 여러개의 이미지를 서로 페이드하여 움직이는 이미지 효과를 연출합니다.
⚒️ 구현
- 체력 피해를 시각적으로 보여줄 매니저와 여러개의 이미지를 Lerp하여 페이드하는 기능 스크립트가 있습니다.
· DamageOverlayManager
- 이 스크립트는 싱글톤으로 전역에서 호출하여 사용합니다.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class DamageOverlayManager : Singleton<DamageOverlayManager>
{
[Header("피해를 받아 오버레이가 활성화되는 지속시간")]
[SerializeField] private float mHitOverlayDuration = 0.5f;
[Header("피해를 받은경우 일시적으로 보여줄 이미지")]
[SerializeField] private Image mHitOverlayImage;
[Range(0f, 1f)][SerializeField] private float mHitOverlayMaxAlpha;
[Header("체력이 낮을경우 활성화 할 이미지")]
[SerializeField] private CanvasGroup mLowHealthWarningGroup;
[Range(0f, 1f)][SerializeField] private float mLowHealthWarningRatio;
[Range(0f, 0.9f)][SerializeField] private float mLowHealthWarningMaxAlpha;
[SerializeField] private float mLowHealthWarningLoopDuration = 0.5f;
private bool mIsLowHealthWarningEnabled; // 현재 Low Health Warning이 활성화 되어 있는가?
private float mCurrentHealthRatio;
private Coroutine? mCoHitOverlay, mCoLoopLowHealthWarning, mCoReleaseLowHealthWarning = null;
/// <summary>
/// 현재 HP를 검사하여 UI 업데이트 시도
/// </summary>
public void CheckHp(float currentHp, float maxHp)
{
mCurrentHealthRatio = currentHp / maxHp;
// 체력 비율 검사
if (mCurrentHealthRatio < mLowHealthWarningRatio)
{
Debug.Log(mCurrentHealthRatio);
if (mIsLowHealthWarningEnabled == false)
{
mIsLowHealthWarningEnabled = true;
if (mCoLoopLowHealthWarning is not null)
StopCoroutine(mCoLoopLowHealthWarning);
mCoLoopLowHealthWarning = StartCoroutine(CoLoopLowHealthWarning());
}
}
else
{
if (mIsLowHealthWarningEnabled == true)
{
mIsLowHealthWarningEnabled = false;
if (mCoLoopLowHealthWarning is not null)
StopCoroutine(mCoLoopLowHealthWarning);
if (mCoReleaseLowHealthWarning is not null)
StopCoroutine(mCoReleaseLowHealthWarning);
mCoReleaseLowHealthWarning = StartCoroutine(CoReleaseLowHealthWarning());
}
}
}
private IEnumerator CoLoopLowHealthWarning()
{
while (true)
{
float process;
float currentAlpha;
float processDiv = 1 / mLowHealthWarningLoopDuration * 2f;
// 페이드 인
currentAlpha = mLowHealthWarningGroup.alpha;
process = 0f;
while (process < 1.0f)
{
process += Time.deltaTime * processDiv;
mLowHealthWarningGroup.alpha = Mathf.Lerp(currentAlpha, Mathf.Lerp(0f, mLowHealthWarningMaxAlpha, 1.0f - mCurrentHealthRatio), process);
yield return null;
}
// 페이드 아웃
currentAlpha = mLowHealthWarningGroup.alpha;
process = 0f;
while (process < 1.0f)
{
process += Time.deltaTime * processDiv;
mLowHealthWarningGroup.alpha = Mathf.Lerp(currentAlpha, Mathf.Lerp(0f, mLowHealthWarningMaxAlpha + 0.1f, 1.0f - mCurrentHealthRatio) + 0.1f, process);
yield return null;
}
}
}
private IEnumerator CoReleaseLowHealthWarning()
{
float process;
float currentAlpha;
// 페이드 인
currentAlpha = mLowHealthWarningGroup.alpha;
process = 0f;
while (process < 1.0f)
{
process += Time.deltaTime;
mLowHealthWarningGroup.alpha = Mathf.Lerp(currentAlpha, 0f, process);
yield return null;
}
}
/// <summary>
/// 피해를 받아 오버레이를 일시적으로 활성화
/// </summary>
public void HitOverlay()
{
if (mCoHitOverlay is not null)
StopCoroutine(mCoHitOverlay);
mCoHitOverlay = StartCoroutine(CoHitOverlay());
}
private IEnumerator CoHitOverlay()
{
float process;
float currentAlpha;
float processDiv = 1 / mHitOverlayDuration * 2f;
Color currentColor = mHitOverlayImage.color;
// 페이드 인
currentAlpha = mHitOverlayImage.color.a;
process = 0f;
while (process < 1.0f)
{
process += Time.deltaTime * processDiv;
currentColor.a = Mathf.Lerp(currentAlpha, mHitOverlayMaxAlpha, process);
mHitOverlayImage.color = currentColor;
yield return null;
}
// 페이드 아웃
currentAlpha = mHitOverlayImage.color.a;
process = 0f;
while (process < 1.0f)
{
process += Time.deltaTime * processDiv;
currentColor.a = Mathf.Lerp(currentAlpha, 0f, process);
mHitOverlayImage.color = currentColor;
yield return null;
}
}
}
[Header("피해를 받아 오버레이가 활성화되는 지속시간")]
[SerializeField] private float mHitOverlayDuration = 0.5f;
[Header("피해를 받은경우 일시적으로 보여줄 이미지")]
[SerializeField] private Image mHitOverlayImage;
[Range(0f, 1f)][SerializeField] private float mHitOverlayMaxAlpha;
- 피해를 받은경우 일시적으로 붉은 화면을 표현하기 위해 사용하는 변수들입니다.
[Header("체력이 낮을경우 활성화 할 이미지")]
[SerializeField] private CanvasGroup mLowHealthWarningGroup;
[Range(0f, 1f)][SerializeField] private float mLowHealthWarningRatio;
[Range(0f, 0.9f)][SerializeField] private float mLowHealthWarningMaxAlpha;
[SerializeField] private float mLowHealthWarningLoopDuration = 0.5f;
- 낮은 체력 경고에 관련된 변수로, 플레이어가 피해를 받거나, 체력을 회복하여 일정 체력 비율 이상이거나, 이하인경우경고 이미지를 토글합니다.
public void CheckHp(float currentHp, float maxHp)
- 현재 체력을 확인하여 낮은 체력 경고를 활성화하거나, 비활성화합니다.
private IEnumerator CoLoopLowHealthWarning()
- 낮은 체력 경고가 활성화된경우, 코루틴을 이용하여 Loop합니다.
private IEnumerator CoReleaseLowHealthWarning()
- 플레이어가 회복되어 낮은 체력 경고가 해제되는경우 이 코루틴이 호출되어 Loop를 해제하고, Clear합니다.
public void HitOverlay()
- 플레이어가 피해를 받을경우, 일시적으로 활성화되는 붉은 화면 이미지를 연출합니다.
private IEnumerator CoHitOverlay()
- HitOverlay()가 호출되면 이어서 호출되는 코루틴으로 일정 시간동안 투명도를 조절하는 코루틴입니다.
· ImageLerper
- 이 스크립트는 2개의 이미지 컴포넌트를 이용하여 다수의 이미지 스프라이트들을 보간하여 Lerp하는 모듈입니다.
using UnityEngine;
using UnityEngine.UI;
public class ImageLerper : MonoBehaviour
{
[SerializeField] private Sprite[] mSprites; // Lerp에 사용할 스프라이트 배열
[SerializeField] private float mLerpDuration = 1.0f; // Lerp에 걸리는 시간
private Image[] mImageComponents; // Image 컴포넌트 배열
private int mCurrentIndex = 0;
private float mLerpStartTime;
private bool mIsLerping = false;
private void Start()
{
mImageComponents = GetComponentsInChildren<Image>(true);
if (mImageComponents.Length < 2 || mSprites.Length < 2)
{
Debug.LogError("최소 두개의 이미지 컴포넌트와 Sprite가 필요합니다.");
return;
}
// 초기 상태 설정
mImageComponents[0].sprite = mSprites[0];
mImageComponents[1].sprite = mSprites[1];
}
private void Update()
{
if (!mIsLerping)
{
mIsLerping = true;
mLerpStartTime = Time.time;
}
// Lerp 진행
float elapsedTime = Time.time - mLerpStartTime;
float t = Mathf.Clamp01(elapsedTime / mLerpDuration);
// Lerp 진행 상황에 따라 투명도 조절
Color lerpedColor = mImageComponents[0].color;
lerpedColor.a = Mathf.Lerp(1.0f, 0.0f, t);
mImageComponents[0].color = lerpedColor;
lerpedColor = mImageComponents[1].color;
lerpedColor.a = Mathf.Lerp(0.0f, 1.0f, t);
mImageComponents[1].color = lerpedColor;
// Lerp가 완료되면 다음 스프라이트 쌍으로 이동
if (t >= 1.0f)
{
mCurrentIndex = (mCurrentIndex + 1) % mSprites.Length;
mImageComponents[0].sprite = mSprites[mCurrentIndex];
mImageComponents[1].sprite = mSprites[(mCurrentIndex + 1) % mSprites.Length];
lerpedColor.a = 1.0f;
mImageComponents[0].color = lerpedColor;
lerpedColor.a = 0.0f;
mImageComponents[1].color = lerpedColor;
mIsLerping = false;
}
}
}
✅ 적용
· UI 레이아웃
- 모든 이미지는 Stretch로 설정한 후 위 그림과 같이 인스펙터를 정의합니다.
- Parent_Low health Warning은 낮은 체력 경고를 띄울 캔버스 그룹으로 하위에 이미지가 두개 있으며, 이곳에서 사용할 이미지들을 등록합니다.
🕹️ Unity Affiliate
- Unity Affiliate Program 파트너로서 아래의 배너를 통해 접속하신 경우 수수료를 받을 수 있습니다.
- 아래 배너의 에셋들은 '실시간 무료 에셋 랭킹'을 나타냅니다.
'unity game modules' 카테고리의 다른 글
[C#] MySqlConnector를 통해 DB와 통신하기 (0) | 2024.01.28 |
---|---|
[유니티] Graphics device is null. 오류 해결 (0) | 2024.01.18 |
[유니티] 퀘스트 시스템(5) - 풀사이즈 UI (0) | 2023.04.11 |
[유니티] 퀘스트 시스템(4) - 컴팩트 UI (0) | 2023.04.11 |
[유니티] 퀘스트 시스템(3) - 콘텐츠 매니저 (0) | 2023.04.11 |