✅ 오늘의 학습 목표
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 함수도 사용해주었다.