Carrot
본문 바로가기
Unity/멋쟁이사자처럼 부트캠프

[멋쟁이사자처럼부트캠프] 유니티 게임 개발 5기(96일차) - [3D 게임] HP bar 구현 (2)

by 독기품은토끼 2025. 10. 16.
✅ 오늘의 학습 목표
1. HP bar 구현 (2)
2. Enemy 사망 효과 - 날아가는 연출

1. HP bar 구현 (2)

1. UI 표시

 

우선 체력 게이지를 나타낼 수 있는 UI를 만들어주었고

 

using UnityEngine;
using UnityEngine.UI;

public class HPBar : MonoBehaviour
{
    [SerializeField] private Image gauge;

    public void SetHPGauge(float hp)
    {
        gauge.fillAmount = hp;
    }
}
public class GameManager : Singleton<GameManager>
{
    public Canvas Canvas => GetCanvas();

    private IEnumerator LoadSceneAsync(ESceneName sceneName)
    {
        GameState = EGameState.Pause;

        // 로딩 화면 띄우기
        var loadingPanelPrefab = Resources.Load<GameObject>("Loading Panel");
        var loadingPanelObject = Instantiate(loadingPanelPrefab, Canvas.transform);
        var loadingPanelController = loadingPanelObject.GetComponent<LoadingPanelController>();

        // ...
    }

    protected override void OnSceneLoaded(Scene scene, LoadSceneMode mode)
    {
        switch (scene.name)
        {
            case "Main":
                if (_player)
                {
                    Destroy(_player);
                    _player = null;
                }
                break;
            case "Stage01":
            case "Stage02":
                var spawnPoint = GameObject.FindGameObjectWithTag("SpawnPoint").transform;
                if (_player)
                {
                    _player.SetActive(true);
                    _player.transform.position = spawnPoint.position;
                    _player.transform.rotation = spawnPoint.rotation;
                }
                else
                {
                    _player = Instantiate(playerPrefab, spawnPoint.position, spawnPoint.rotation);
                    DontDestroyOnLoad(_player);
                }
                break;
        }

        GameState = EGameState.Play;
    }

    protected override void OnSceneUnloaded(Scene scene)
    {
        _player.SetActive(false);
    }
}

 

GameManager 스크립트에서 적절한 UI를 불러올 수 있도록

public Canvas Canvas => GetCanvas(); 명령문을 추가해주었다.

 

using UnityEngine;

public class HPBarController : MonoBehaviour
{
    [SerializeField] private GameObject hpBarPrefab;

    private HPBar _hpBar;
    private Canvas _canvas;
    private RectTransform _hpBarRectTransform;
    private Camera _camera;
    private Vector3 _offset;

    void Start()
    {
        _camera = Camera.main;
        _canvas = GameManager.Instance.Canvas;
        _hpBar = Instantiate(hpBarPrefab, _canvas.transform).GetComponent<HPBar>();
        _hpBarRectTransform = _hpBar.GetComponent<RectTransform>();
        _offset = new Vector3(0, 1.5f, 0);
    }

    public void SetHp(float hp)
    {
        _hpBar.SetHPGauge(hp);
    }

    // Enemy의 움직임에 따라 캔버스도 위치 변경되도록 LateUpdate 활용
    void LateUpdate()
    {
        var screenPosition = _camera.WorldToScreenPoint(transform.position + _offset);

        // 카메라가 바라보는 시야에 있는 경우에만 HP Bar 보이도록 설정
        bool isVisible = screenPosition.z > 0
            && screenPosition.x > 0 && screenPosition.x < Screen.width
            && screenPosition.y > 0 && screenPosition.y < Screen.height;
        _hpBar.gameObject.SetActive(isVisible);

        if (isVisible)
            _hpBarRectTransform.position = screenPosition;
    }
}

 

2. 게이지 업데이트

using UnityEngine;
using UnityEngine.AI;
using static Constants;

public class EnemyStateDead : EnemyState, ICharacterState
{
    public EnemyStateDead(EnemyController enemyController, Animator animator, NavMeshAgent navMeshAgent)
        : base(enemyController, animator, navMeshAgent) { }

    public void Enter()
    {
        _navMeshAgent.isStopped = true;
        _animator.SetTrigger(EnemyAniParamDead);
    }

    public void Update()
    {

    }

    public void Exit()
    {

    }
}
public class EnemyController : MonoBehaviour
{
    [Header("Status")]
    [SerializeField] private EnemyStatus enemyStatus;
    private HPBarController _hpBarController;

    private void Awake()
    {
        // ...

        // 상태 초기화
        var enemyStateIdle = new EnemyStateIdle(this, _animator, _navMeshAgent);
        var enemyStatePatrol = new EnemyStatePatrol(this, _animator, _navMeshAgent);
        var enemyStateChase = new EnemyStateChase(this, _animator, _navMeshAgent);
        var enemyStateAttack = new EnemyStateAttack(this, _animator, _navMeshAgent);
        var enemyStateHit = new EnemyStateHit(this, _animator, _navMeshAgent);
        var enemyStateDead = new EnemyStateDead(this, _animator, _navMeshAgent);

        _states = new Dictionary<EEnemyState, ICharacterState>
        {
            { EEnemyState.Idle, enemyStateIdle },
            { EEnemyState.Patrol, enemyStatePatrol },
            { EEnemyState.Chase, enemyStateChase },
            { EEnemyState.Attack, enemyStateAttack },
            { EEnemyState.Hit, enemyStateHit },
            { EEnemyState.Dead, enemyStateDead },
        };
        SetState(EEnemyState.Idle);

        // Hp Bar 할당
        _hpBarController = GetComponent<HPBarController>();
    }

    public void SetHit(int damage, Vector3 attackDirection)
    {
        if (_hpBarController)
        {
            enemyStatus.hp -= damage;
            float result = (float) enemyStatus.hp / enemyStatus.maxHp;
            _hpBarController.SetHp(result);

            if (enemyStatus.hp <= 0)
            {
                // 사망
                SetState(EEnemyState.Dead);
            }
            else
            {
                // 피격
                SetState(EEnemyState.Hit);
                StartCoroutine(Knockback(attackDirection));
            }
        }
    }
}

 

Dead 상태 클래스를 생성해준 다음 Hp에 따라 피격처리와 사망처리를 나누어주었다.

 

 

애니메이션도 놓치지않고 연결해주기

 

3. 사망 연출

Chomper가 공격당하다가 죽었을 때 죽는 연출을 추가해주려고 한다.

 

리지드바디를 컴포넌트로 추가해주고

당장은 물리적인 영향은 받지않도록 중력 체크 해제 /  Kinematic 체크 해준다.

 

// EnemyController 스크립트
public void SetHit(int damage, Vector3 attackDirection)
{
    if (_hpBarController)
    {
        enemyStatus.hp -= damage;
        float result = (float) enemyStatus.hp / enemyStatus.maxHp;
        _hpBarController.SetHp(result);

        if (enemyStatus.hp <= 0)
        {
            // 사망
            SetState(EEnemyState.Dead);
            _rigidbody.isKinematic = false;
            _rigidbody.useGravity = true;

            var dir = attackDirection;
            dir.y = 1f;
            dir = dir.normalized;

            var force = dir * 10f;
            _rigidbody.AddForce(force, ForceMode.Impulse);

            _collider.isTrigger = false;
        }
        else
        {
            // 피격
            SetState(EEnemyState.Hit);
            StartCoroutine(Knockback(attackDirection));
        }
    }
}

 

에너미가 죽었을 때 물리적인 영향이 작용될 수 있도록 Kinematic 값과 Gravity값을 수정해주었고

살짝 튀어나가 떨어지도록 AddForce 함수도 사용해주었다.