플레이어가 적으로부터 피해를 받은경우 오버레이 캔버스에서 이를 시각적으로 표현하고, 체력이 낮은 경우 또한 이미지를 이용하여 시각적으로 사용자에게 상태를 보여줄 수 있습니다. 이 기능을 구현한 후 정리하였습니다.

 

📺 미리 보기

 

📖 구현 내용

  • 플레이어가 피해를 받은 경우 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 파트너로서 아래의 배너를 통해 접속하신 경우 수수료를 받을 수 있습니다.
  • 아래 배너의 에셋들은 '실시간 무료 에셋 랭킹'을 나타냅니다.
bonnate