이번엔 UI의 Image에 Sprite를 사용하여 게임의 HP(체력)를 표현해 보겠습니다. 우선 준비물로 Sprite파일이 하나 필요합니다.
전 위의 Sprite 파일을 사용할겁니다. 이 파일의 Inspector 창에서 다룰 것들입니다.
- Texture Type: 해당 스프라이트, 이미지 파일의 텍스쳐의 타입을 정하는 것입니다. 이번 글에서는 HP만을 다룰 것이기 때문에 Sprite(2D and UI)로 지정했습니다.
- Sprite Mode: 위의 이미지 파일에는 여러 가지 요소들이 함께 포함되어 있습니다. 필요한 걸 잘라 사용할 것이기 때문에 Multiple로 지정합니다. (한 가지 요소 파일이라면 Single)
- Generate Physics: 물리적 요소인가에 대한 ON/OFF 기능입니다. OFF로 지정하겠습니다.
모두 수정했다면 Apply를 눌러 변경사항을 적용해줍니다. 이제 빨간 박스로 표시해 둔 붉은색 게이지와 관련된 3가지 요소를 따로 잘라보겠습니다. Open Sprite Editor를 눌러 해당 창을 열어줍니다.
이런 창이 등장합니다. 여기서 해야할 것은 사용할 요소들에 딱 맞게 좌클릭으로 드래그하여 짤라주는 작업입니다. 각각 이름을 지정해 줍니다. 유니티 버그가 있어서 이름을 적는데 제대로 표시가 되지 않는다면 메모장에 이름을 따로 적고 그것을 복사 붙여 넣기 하는 걸 추천드립니다. 그렇게 위에서 순서대로
- S_UI_Layout_HPBar_RedBar //직관적 HP 게이지
- S_UI_Layout_HPBar_YellowBar //RedBar의 뒤에서 뒤따라가는 게이지
- S_UI_Layout_HPBar_Back //Red,Yellow 게이지가 모두 존재하는 제일 뒤의 이미지
우측 상단 Apply를 눌러 적용해줍니다. 아래의 그림처럼 이미지 파일 밑으로 저렇게 3개의 요소가 있다면 성공입니다. 이제 필요한 이미지는 모두 구했으니 Hierarchy창을 사용합니다. 이전 글처럼 우클릭 후 UI -> Image를 클릭하여 Canvas 1개와 Image 3개를 만들어주고, 이미지를 추가해 줍니다. 여기서 중요한 점은 Image 오브젝트들의 크기 비율이 각각 요소들의 크기 비율과 동일해야 합니다.
208x25의 요소 크기 비율을 416x50로 같은 비율의 크기만 2배 증가했습니다. 이렇게 하지 않으면 해당 이미지가 찌그러지거나 붕괴됩니다. 때문에 이를 유의해야합니다. 이와 같은 문제를 해결하는 9-Slice 기법 방법이 있습니다. 이는 잠시 후 다루겠습니다. 그리고 중요한 걸 다루겠습니다. 여기서 게이지 역할 2가지 오브젝트들은 모두 아래의 그림처럼 움직여야합니다.
여기서 Pivot이라는 기능이 사용됩니다. Pivot은 해당 Image의 x, y Rect Transform값의 기준점 역할을 수행합니다. 만약 Pivot x값을 기본값 0.5로 사용했다면 공격을 받으면 중간을 기준으로 줄어들 겁니다. 때문에 우리는 Red, Yellow의 Pivot의 Pivot x값을 0으로 잡겠습니다. 이제 Script 3개를 만들겠습니다. HP이미지를 담고 있는 폴더파일, Player, Manager에 Script를 넣고 코딩합니다. 우리가 표현할 HP 조건들을 나열하겠습니다.
- Player 오브젝트가 데미지를 입으면 붉은 게이지가 해당 데미지만큼 한 순간 깎이게 됩니다.
- 놀란 게이지는 붉은 게이지를 따라 천천히 크기를 줄거나 커집니다.
- 체력이 0이 되면 죽은 것으로 Red, Yellow 게이지는 움직이지 않습니다.
- 현재, 최대체력의 비율을 통해 체력바 표시할 계획입니다.
이제 코드별로 다룰 HPBar 기능들을 정의하겠습니다. 대체적인 설명들은 주석으로 적어놓았습니다.
- Player: 최대체력, 현재체력, 사망, 데미지, 체력 회복의 "상태" 를 정의합니다.
- Manager: player, hpBar, 데미지, 체력회복값 지정 및 데미지, 체력 회복 조건문을 정의합니다.
- UIHPBar:
//Player.cs
using UnityEngine;
public class Player : MonoBehaviour
{
[SerializeField] private float maxHp = 100f; //최대 체력
private float curHp = 0f; //현재 체력
private bool isDead = false; //죽었는지 확인
public float MaxHp { get { return maxHp; } } //다른 스크립트에서 maxHp값 다룰 때 MaxHp
public float CurHp { get { return curHp; } } //다른 스크립트에서 curHp값 다룰 때 CurHp
public bool IsDead { get { return isDead; } } //다른 스크립트에서 isDead값 다룰 때 Isdead
private void Start() //루틴이 시작할 때마다 기능
{
curHp = maxHp; //시작 때 현재 체력값 = 최대 체력값
isDead = false; //죽지않음
}
public void Damage(float _dmg) //데미지 메서드
{
curHp -= _dmg; //현재 체력은 기존 현재체력에서 데미지 수치만큼 뺀 값
if (curHp <= 0f) //게이지가 왼쪽으로 넘치는 걸 방지
{
curHp = 0f;
isDead = true; //죽음
Debug.Log("Player is dead!");
}
}
public void Heal(float _heal) //체력 회복 메서드
{
if (isDead) return; //
curHp += _heal; //현재 체력은 기존 현재체력에서 힐 수치만큼 더한 값
if (curHp > maxHp) curHp = maxHp; //게이지가 오른쪽으로 넘치는 걸 방지
}
}
using UnityEngine;
public class Manager : MonoBehaviour
{
[SerializeField] private Player player = null; //player 지정
[SerializeField] private UIHPBar hpBar = null; //관리할 hpBar 지정
[SerializeField, Range(0f, 100f)] private float damage = 10f; //데미지값
[SerializeField, Range(0f, 100f)] private float heal = 15f; //체력 회복값
private void Update()
{
if(Input.GetKeyDown(KeyCode.A)) //데미지 함수
{
player.Damage(damage);
hpBar.UpdateHpBar(player.MaxHp, player.CurHp);
Debug.Log("Hit! (" + damage + ")");
}
if(Input.GetKeyDown(KeyCode.D)) //체력 회복 함수
{
player.Heal(heal);
hpBar.UpdateHpBar(player.MaxHp, player.CurHp);
Debug.Log("Heal! (" + heal + ")");
}
}
}
using UnityEngine;
using UnityEngine.UI; //UI를 다루기 위해 필요한 줄
using System.Collections; //Coroutine 사용 줄
public class UIHPBar : MonoBehaviour
{
[SerializeField] private RectTransform yellowRectTr = null; //노란줄
[SerializeField] private Image redImg = null; //빨간줄의 이미지
private float maxWidth = 0f; //최대 너비값
private float maxHeight = 0f; //최대 높이값
private void Awake()
{
//.sizeDelta. : UI 크기 다루는 코드
maxWidth = yellowRectTr.sizeDelta.x; //maxWidth = 노란줄의 너비값
maxHeight = yellowRectTr.sizeDelta.y; //maxHeight = 노란줄의 높이값
}
public void UpdateHpBar(float _maxHp, float _curHp) //체력간 비율
{
UpdateHpBar(_curHp / _maxHp);
}
public void UpdateHpBar(float _amount) //_amount = _curHp / _maxHp (체력 간 비율)
{
float prevWidth = yellowRectTr.sizeDelta.x; //prevWidth = 이전 너비, 노란줄 너비값
float newWidth = maxWidth * _amount; //newWidth = 최대체력 * 체력 간 비율
StopAllCoroutines(); //모든 코루틴 정지
if(newWidth < prevWidth) //체력이 깎이는 중 상황
{
StartCoroutine(UpdateHpBarCoroutine(prevWidth,newWidth));
}
else //나머지
{
yellowRectTr.sizeDelta = new Vector2(newWidth, maxHeight);
}
//붉은줄의 사이즈는 (최대 너비값 * 체력 간 비율, 최대 높이);
redImg.GetComponent<RectTransform>().sizeDelta =
new Vector2(maxWidth * _amount, maxHeight);
}
//노란줄과 관련된 코루틴
private IEnumerator UpdateHpBarCoroutine(float _prevWidth, float _newWidth)
{
Vector2 size = new Vector2(_prevWidth, maxHeight); //size = (이전 너비, 최대 높이)
yellowRectTr.sizeDelta = size; //노란줄 크기는 size값 (이전 너비, 최대 높이)
float t = 0f;
while (t < 1f)
{
t += Time.deltaTime;
size.x = Mathf.Lerp(_prevWidth, _newWidth, t); //선형 보간
//t=0 _prevWidth 반환
//t=1 _newWidth 반환
//0 < t < 1 _prev, _new 사이값 반환
yellowRectTr.sizeDelta = size; //노란줄 크기는 size
yield return null; //Coroutine 반환값
}
}
}
그 후, A와 D를 통해 잘 구현되는지 확인하면 됩니다.
끝
'유니티(Unity) 프로그래밍' 카테고리의 다른 글
Unity - Animation (1) (0) | 2024.10.28 |
---|---|
UI-Text, Font, Button (0) | 2024.10.22 |
UI Canvas (0) | 2024.10.21 |
코루틴 (Coroutine)을 통한 Picking (5) | 2024.10.03 |
Unity 카메라 (1) | 2024.09.29 |