개발일기
유니티#15 UI 활용 본문

UI... 만들어야겠지?
DamageTextCanvas를 만들어주고, Canvas 컴포넌트에서 Render Mode : World Space로 해준다

UI의 배치 기준을 설정해주는 건데, 저게 Screen Space - Overlay로 되어있으면 화면을 기준으로 하고, World Space면 보통의 오브젝트처럼 소환이 된다는 거다

저 대화창은 Screen Space - Overlay라서 그대로 화면에 투영되는 거고, 우리는 UI 오브젝트를 실제 저 위치( World Space )에 소환해주는 거다
이제 여기서 TakeDamage()가 있는 Enemy를 건드려주면 되는 건데 다음과 같다
//Enemy.cs
using UnityEngine;
using TMPro;
using UnityEngine.UI;
using System.Collections.Generic;
public class Enemy : MonoBehaviour
{
public float speed = 1;
Rigidbody2D rigid;
public float maxHp = 100f;
public float hp = 100f;
public Canvas damageTextCanvas;
public Canvas enemyHpSliderCanvas;
// 데미지 텍스트 오브젝트
private List<GameObject> tmpObject = new List<GameObject>();
private List<TextMeshProUGUI> tmpText = new List<TextMeshProUGUI>();
private List<float> textCreationTime = new List<float>();
private List<Vector3> initialPosition = new List<Vector3>();
// hp슬라이더 오브젝트
private GameObject sliderObject;
private Slider slider;
// Start is called once before the first execution of Update after the MonoBehaviour is created
void Start()
{
// 데미지 텍스트 오브젝트, Canvas 찾기
GameObject canvasObject1 = GameObject.Find("DamageTextCanvas");
damageTextCanvas = canvasObject1.GetComponent<Canvas>();
// hp슬라이더 오브젝트, Canvas 찾기
GameObject canvasObject2 = GameObject.Find("EnemyHpSliderCanvas");
enemyHpSliderCanvas = canvasObject2.GetComponent<Canvas>();
rigid = GetComponent<Rigidbody2D>();
}
// Update is called once per frame
void Update()
{
if (Mathf.Abs(rigid.linearVelocity.x) < speed) {
rigid.AddForce(new Vector2(Mathf.Sin(Time.deltaTime * 360 * Mathf.Deg2Rad), 0), ForceMode2D.Impulse);
}
// 오브젝트 업데이트
for (int i = 0; i < tmpObject.Count; i++) {
UpdateFloatingTextPosition(i);
}
UpdateSliderPosition();
}
public void TakeDamage(float damage) {
hp -= damage;
CreateFloatingText(damage);
CreateHpSlider();
slider.value = hp / maxHp;
// HP가 0 이하일 경우 에너미 오브젝트 삭제
if (hp <= 0) {
gameObject.layer=0;
Destroy(gameObject,1f);
if (sliderObject!=null && slider!=null) {
Destroy(sliderObject);
}
}
}
private void CreateFloatingText(float damage)
{
GameObject newObject = new GameObject("DynamicTMPText");
newObject.transform.SetParent(damageTextCanvas.transform, false);
TextMeshProUGUI newText = newObject.AddComponent<TextMeshProUGUI>();
newText.text = $"{damage}";
newText.fontSize = 1;
newText.alignment = TextAlignmentOptions.Center;
Vector3 worldPosition = this.transform.position + new Vector3(0, 1, 0);
RectTransform rectTransform = newObject.GetComponent<RectTransform>();
rectTransform.sizeDelta = new Vector2(300, 100);
rectTransform.position = worldPosition;
rectTransform.position = new Vector3(rectTransform.position.x, rectTransform.position.y, -5f);
tmpObject.Add(newObject);
tmpText.Add(newText);
initialPosition.Add(rectTransform.position);
textCreationTime.Add(Time.time);
Destroy(newObject, 1.0f);
}
private void UpdateFloatingTextPosition(int index)
{
if (tmpObject[index] != null && tmpText[index] != null) {
float elapsedTime = Time.time - textCreationTime[index];
Vector3 updatedPosition = tmpObject[index].transform.position;
updatedPosition.y = initialPosition[index].y + Mathf.Log(elapsedTime * 2 + 1);
tmpObject[index].transform.position = updatedPosition;
Color color=tmpText[index].color;
color.a = Mathf.Clamp01(1 - elapsedTime);
tmpText[index].color = color;
}
}
private void CreateHpSlider()
{
if (sliderObject!=null) {
UpdateSliderPosition();
return;
}
sliderObject = new GameObject("HpSlider");
sliderObject.transform.SetParent(enemyHpSliderCanvas.transform, false);
slider = sliderObject.AddComponent<Slider>();
RectTransform rectTransform = sliderObject.GetComponent<RectTransform>();
rectTransform.sizeDelta = new Vector2(160f, 20f);
rectTransform.position = this.transform.position+new Vector3(0f,1f,0f);
rectTransform.localScale = new Vector3(0.02f,0.02f,1f);
slider.minValue = 0;
slider.maxValue = 1;
slider.value = hp / maxHp;
slider.direction = Slider.Direction.LeftToRight;
// Slider의 Background 설정
GameObject background = new GameObject("Background");
background.transform.SetParent(sliderObject.transform, false);
Image bgImage = background.AddComponent<Image>();
RectTransform bgRect = background.GetComponent<RectTransform>();
bgRect.sizeDelta = new Vector2(160f,10f);
bgImage.color = Color.white; // 기본 배경 색상 (변경 X)
// Slider의 Fill Area 설정
GameObject fillArea = new GameObject("Fill Area");
fillArea.transform.SetParent(sliderObject.transform, false);
RectTransform fillAreaRect = fillArea.AddComponent<RectTransform>();
fillAreaRect.anchorMin = new Vector2(0, 0.5f);
fillAreaRect.anchorMax = new Vector2(1, 0.5f);
fillAreaRect.offsetMin = new Vector2(0, -5);
fillAreaRect.offsetMax = new Vector2(0, 5);
GameObject fill = new GameObject("Fill");
fill.transform.SetParent(fillArea.transform, false);
Image fillImage = fill.AddComponent<Image>();
fillImage.color = Color.red; // Fill 색상 설정
RectTransform fillRect = fill.GetComponent<RectTransform>();
fillRect.anchorMin = Vector2.zero;
fillRect.anchorMax = Vector2.one;
fillRect.offsetMin = Vector2.zero;
fillRect.offsetMax = Vector2.zero;
slider.fillRect = fillRect; // Slider에 Fill 연결
// Slider의 Handle Slide Area 비활성화
GameObject handleSlideArea = new GameObject("Handle Slide Area");
handleSlideArea.transform.SetParent(sliderObject.transform, false);
handleSlideArea.SetActive(false);
Destroy(sliderObject, 10f);
}
private void UpdateSliderPosition()
{
if (sliderObject!=null && sliderObject!=null) {
sliderObject.transform.position=this.transform.position+new Vector3(0f,1f,0f);
}
}
}Canvas는 Uniy editor에서 직접 끌어다 넣어주면 되는 거고
mpObjet는 리스트, 스크립트에서 생성한 text 오브젝트를 index에 따라 저장
그런데TextMeshProUGUI가 컴포넌트로 설정된
textCreationTime이랑 initialPosition, UpdateFloatingTextPosition()은 텍스트 생성 후
좀 이쁘라고 넣어주는 거니까 굳이 필요 없으면 빼주자
코드 보다보니까 궁금해진건데 slider는 리스트로 관리 안하는데 어케 관리하는 거지
어케 잘 되는갑다. 신기하네
어쨌든 slider도 똑같은데, slider에는 설정해줄 하위 오브젝트들이 많으니까 rectTransform 활용법만 제대로 알고 가자

이제 할 거
- 한글 폰트 적용
- 대화에 선택지 추가, 퀘스트 추가
- 인벤토리, 아이템 추가
- 스킬 쿨타임, HP, MP UI(시스템) 추가
- 스킬 봉인, 해금 추가
+ 약간의 수정으로 좀 더 까리나게 만들 수 있다
//Enemy.cs
public void TakeDamage(float damage, int hitTime=1) //hitTime의 기본값은 1
{
StartCoroutine(ExecuteDamageHits(damage, hitTime));
// 그 외 생략...
}
// ...
private IEnumerator ExecuteDamageHits(float damage, int hitTime) {
for (int i = 0; i < hitTime; i++) {
CreateFloatingText(damage, i*0.5f);
yield return new WaitForSeconds(0.1f); // 0.1초 간격으로 실행
}
}CreateFloatingText()가 있는 자리를 StartCoroutine으로 바꿔주고, ExecuteDamageHits를 선언해주고
skillHandler에서 약간의 설정을 바꿔주면

'Unity' 카테고리의 다른 글
| 게임프로그래밍 공동교육과정 수강하며 배운 것 (0) | 2025.04.08 |
|---|---|
| 유니티#16 SkillHandler 스크립트 정리, 인터페이스(추상 클래스) (0) | 2025.01.06 |
| 유니티#14 구조체와 리팩토링은 신이야 (0) | 2025.01.02 |
| 유니티#13 NPC 구현 (0) | 2025.01.02 |
| 유니티#12 Hierarchy 정리, Tilemap 활용 (0) | 2025.01.01 |
