개발/유니티

[유니티] 체력과 경험치를 수치로 나타내기 & 체력바 위에 표시

kimchangmin02 2025. 8. 9. 11:48

근데 다른 스크립트에서 현재 플레이어의 체력을 가져오려고 하는데 get함수가 없는데
그냥 player.currentHp하면 되나
근데 뭔가 import해야하나 다른 스크립트에서 이 스크립트의 체력 가져오려면

 

다른 스크립트에서 플레이어 체력 가져오기: GetComponent가 정답입니다

네, 다른 스크립트에서 현재 플레이어의 체력을 가져오는 것에 대해 궁금하시군요. 결론부터 말씀드리면, 단순히 player.currentHealth와 같이 직접 접근할 수 있지만, 그 전에 해당 스크립트에 대한 참조를 얻는 과정이 필요합니다. import는 필요하지 않습니다.

핵심: GetComponent로 스크립트 참조 얻기

PlayerStats.cs 스크립트에 있는 currentHealth 변수는 public으로 선언되어 있어 다른 스크립트에서 접근할 수 있습니다.[1] 하지만 접근하려면 먼저 PlayerStats 스크립트가 어떤 게임 오브젝트에 붙어있는 컴포넌트인지 알려줘야 합니다. 이 역할을 하는 것이 바로 GetComponent 함수입니다.

다른 스크립트에서 플레이어의 체력을 가져오는 가장 일반적인 방법은 다음과 같습니다.

  1. PlayerStats를 담을 변수 선언: 먼저 PlayerStats 스크립트를 저장할 변수를 선언합니다.
  2. 플레이어 게임 오브젝트 찾기: GameObject.FindGameObjectWithTag("Player") 와 같은 함수로 'Player' 태그를 가진 게임 오브젝트를 찾습니다.
  3. GetComponent로 스크립트 가져오기: 찾은 게임 오브젝트에서 GetComponent<PlayerStats>()를 호출하여 해당 컴포넌트(스크립트)에 대한 참조를 얻습니다.
  4. 변수 접근: 이제 가져온 참조를 통해 currentHealth 변수에 접근할 수 있습니다.

GameObject playerObject = GameObject.FindGameObjectWithTag("Player");
근데 이런방식으로 플레이어를 가져오지말고
그냥 플레이어 스크립트의 변수를 가져오면 안되나

그러면 실시간 변경사항이 저장이 안되나

가령 자바로 치면
player.getHp(처럼

 

왜 태그로 가져오는거지


 

결론부터 말씀드리면, "그냥 플레이어 스크립트의 변수를 바로 가져올 수는 없습니다." 왜냐하면, 스크립트 파일 그 자체(PlayerStats.cs)는 설계도일 뿐이고, 실제 게임 세상에 존재하는 플레이어 게임 오브젝트에 붙어있는 PlayerStats 컴포넌트가 진짜 실체이기 때문입니다.

설계도(Class) vs. 실제 건물(Instance/Component)

이 개념을 이해하는 것이 가장 중요합니다.

  • PlayerStats.cs (클래스): 이것은 "플레이어의 스탯은 이렇게 생겼다"라고 정의하는 설계도입니다. maxHealth, currentHealth 같은 변수와 TakeDamage 같은 기능이 있다는 정보만 담고 있습니다. 설계도 자체에는 "현재 체력이 75"와 같은 실제 데이터가 들어있지 않습니다.
  • 게임 씬(Scene)의 플레이어 오브젝트에 붙어있는 PlayerStats (인스턴스/컴포넌트): 이것은 설계도를 바탕으로 지어진 실제 건물입니다. 게임이 실행되면, 씬에 있는 플레이어 게임 오브젝트가 바로 이 '실제 건물'이 되며, 그 건물에 PlayerStats라는 컴포넌트(인스턴스)가 부착되어 있습니다. 실시간으로 변하는 currentHealth 값은 바로 이 인스턴스에 저장됩니다.

따라서 다른 스크립트가 플레이어의 체력을 알고 싶다면, 설계도(PlayerStats.cs 파일)를 쳐다보는 것이 아니라, 게임 세상에 있는 실제 플레이어(게임 오브젝트)를 찾아가서, 그에게 붙어있는 PlayerStats 부품을 얻어낸 다음, 그 부품의 currentHealth 값을 물어봐야 합니다.

GameObject.FindGameObjectWithTag("Player")가 바로 "게임 세상에서 실제 플레이어(건물)를 찾아내는" 과정인 것입니다.

실시간 변경사항은 저장이 되나요?

네, 완벽하게 실시간으로 반영됩니다.

playerStats = playerObject.GetComponent<PlayerStats>(); 코드는 playerStats라는 변수에 currentHealth 값을 복사해서 저장하는 것이 아닙니다.

대신, 플레이어 게임 오브젝트에 붙어있는 PlayerStats 컴포넌트 **자체의 주소(참조)**를 저장합니다. 마치 리모컨을 얻는 것과 같습니다.

  • playerStats 라는 리모컨을 가지고,
  • playerStats.currentHealth 라고 버튼을 누르면,
  • 실제 플레이어 컴포넌트의 현재 체력 값이 실시간으로 보이는 것입니다.

플레이어가 데미지를 입어 currentHealth가 100에서 90으로 바뀌면, playerStats.currentHealth를 통해 조회하는 값도 즉시 90으로 나옵니다.

 


        if (playerStats != null && currentPlayerHealth != null)
왜 두번째 조건은 필요없는거지

currentPlayerHealth는 int 타입이고, int는 절대로 null 값을 가질 수 없기 때문입니다.

 


근데 가령 그 스크립트에 해당 변수가 없을수도 있잖아
그걸 피하곻 싶어서 만든 조건인데
이걸 확인하는 방법은 없는건가

 

 C#과 유니티 컴파일러가 그런 상황이 발생하는 것을 원천적으로 막아주기 때문입니다.

C#은 '계약' 기반의 언어입니다

이것이 바로 '정적 타입(Statically-typed)' 언어의 가장 큰 장점입니다.

  1. 컴파일 시점의 검사: 코드를 실행하기 전, 유니티 에디터(내부적으로는 C# 컴파일러)는 모든 스크립트 파일을 읽고 문법에 오류가 없는지, 변수나 함수 이름이 올바른지 전부 확인하는 '컴파일' 과정을 거칩니다.
  2. 오류 발생: 만약 Enemy.cs 스크립트에서 playerStats.currentHealth라는 코드를 작성했는데, PlayerStats.cs 파일에 public int currentHealth;라는 변수가 존재하지 않으면, 컴파일러는 즉시 오류를 발견하고 다음과 같은 메시지를 띄웁니다.
  3. error CS1061: 'PlayerStats' does not contain a definition for 'currentHealth' ...
    (PlayerStats 타입에는 'currentHealth'에 대한 정의가 없습니다.)
  4. 실행 불가: 이 컴파일 오류가 해결되기 전까지 게임은 절대로 실행되지 않습니다. 플레이 버튼을 눌러도 오류가 있다는 메시지만 콘솔 창에 표시될 뿐입니다.

따라서, 코드가 성공적으로 컴파일되어 게임이 실행되는 시점이라면, playerStats 변수가 가리키는 PlayerStats 스크립트 안에는 currentHealth라는 이름의 변수가 반드시 존재한다고 100% 보장됩니다.

그래서 개발자는 스크립트 안에 변수가 존재하는지 여부는 걱정할 필요가 없고, 그 스크립트를 가리키는 참조 변수가 비어있는지(null인지)만 확인하면 되는 것입니다.

 


근데 새로운 스크립트를 만들엇는데
플레이어로 부터 체력을 가져와서 text ui에 체력 표시하는것인데
이렇게 스크립트는 만들엇는데
뭔가 이렇게 변수에 저장만 한다고 해서 뭔가가 될것같지는 않은데

아 이 변수를 하이아키랑 연결하면 되나
그럴려면
private이 아니라 public으로 바꿔야하나<유니티에서 연결해야하니깐

 

지금 코드는 플레이어의 체력 값을 가져와서 currentPlayerHealth라는 변수에 저장만 하고 끝납니다. 컴퓨터는 이 숫자를 화면의 어디에, 어떻게 표시해야 할지 전혀 알지 못합니다.

말씀하신 대로, "이 스크립트를 실제 UI 텍스트와 연결" 해주는 과정이 반드시 필요합니다. 그리고 그 연결을 유니티 에디터의 인스펙터(Inspector) 창에서 해주려면, 변수를 public으로 선언하거나 [SerializeField] 어트리뷰트를 붙여줘야 합니다.

이것이 바로 유니티 컴포넌트 기반 디자인의 핵심입니다.


UI에 체력을 표시하기 위한 완벽한 단계

현재 코드에서 딱 두 가지만 추가하면 원하시는 기능이 완성됩니다.

  1. 표시할 UI Text 컴포넌트를 담을 변수 선언 (public으로!)
  2. 가져온 체력 값으로 UI Text의 내용을 변경하는 코드 추가

아래 단계를 그대로 따라 해보세요.

1단계: hpNumber.cs 스크립트 수정하기

어떤 UI 텍스트를 제어할지 스크립트에게 알려줘야 합니다.

C#
using UnityEngine;
using UnityEngine.UI; // Text를 사용하려면 이 줄이 반드시 필요합니다!
// 만약 TextMeshPro를 사용한다면 using TMPro; 를 추가하세요.

public class hpNumber : MonoBehaviour
{
    // [인스펙터에서 연결할 부분 1]
    // 실제 플레이어 오브젝트를 연결할 슬롯
    public PlayerStats playerStats;

    // [인스펙터에서 연결할 부분 2]
    // 체력을 표시할 Text UI 컴포넌트를 연결할 슬롯
    public Text healthText; // TextMeshPro를 쓴다면 public TextMeshProUGUI healthText;

    // Update는 매 프레임 실행되므로 Start는 없어도 됩니다.
    // Start는 게임 시작 시 딱 한 번만 실행되기 때문에,
    // 체력이 변할 때마다 업데이트하려면 Update에 코드가 있어야 합니다.

    void Update()
    {
        // 1. playerStats와 healthText가 둘 다 제대로 연결되었는지 확인 (안전장치)
        if (playerStats != null && healthText != null)
        {
            // 2. 플레이어의 현재 체력을 가져옴
            int currentPlayerHealth = playerStats.currentHealth;
            int maxPlayerHealth = playerStats.maxHealth;

            // 3. 가져온 체력 값으로 Text UI의 내용을 변경!
            //    이 부분이 핵심입니다.
            healthText.text = currentPlayerHealth.ToString() + " / " + maxPlayerHealth.ToString();
            // 또는 최신 C# 방식: $" {currentPlayerHealth} / {maxPlayerHealth} ";
        }
    }
}

2단계: 유니티 에디터에서 연결하기

이제 코드가 준비되었으니, 유니티 에디터에서 실제 게임 오브젝트들을 끌어다 연결해줘야 합니다.

  1. 스크립트 추가: 체력을 표시할 Text UI 게임 오브젝트를 선택하고, 방금 수정한 hpNumber.cs 스크립트를 컴포넌트로 추가해 주세요.
  2. 인스펙터 확인: hpNumber 컴포넌트를 보면, 우리가 public으로 선언했던 변수들이 빈칸(슬롯)으로 나타납니다.
    • Player Stats (None)
    • Health Text (None)
  3. 드래그 앤 드롭으로 연결:
    • 첫 번째 슬롯: 하이어라키(Hierarchy) 창에서 플레이어 게임 오브젝트를 마우스로 끌어서 Player Stats 슬롯에 놓습니다.
    • 두 번째 슬롯: **자기 자신(Text UI 게임 오브젝트)**을 끌어서 Health Text 슬롯에 놓습니다. (자기 자신의 Text 컴포넌트가 자동으로 연결됩니다.)

이제 게임을 실행하면 Update 함수가 매 프레임마다 다음 작업을 수행합니다.

  • playerStats로 연결된 실제 플레이어에게 가서 currentHealth를 물어봅니다.
  • healthText로 연결된 실제 Text UI에게 "너의 내용을 이 숫자로 바꿔!"라고 명령합니다.

healthText.text = currentPlayerHealth.ToString() + " / " + maxPlayerHealth.ToString(); 

 

근데 자바는 숫자가 자동으로 문자열이 되던데
c#은 안그런가
toString이 필요한가

 

C#은 자바보다 타입(Type)에 더 엄격해서, 일반적으로 숫자를 문자열과 합칠 때 ToString()을 명시적으로 호출해주는 것이 원칙입니다.

자바와 C#의 미묘하지만 중요한 차이점을 설명해 드리겠습니다.

C#의 원칙: "의도를 명확히 하라"

C# 컴파일러는 프로그래머의 의도가 불분명한 코드를 최대한 피하려고 합니다. + 연산자는 두 가지 주요 의미를 가집니다.

  1. 숫자 + 숫자: 덧셈 (예: 5 + 3 8)
  2. 문자열 + 문자열: 문자열 연결 (예: "Hello" + "World" "HelloWorld")

이때, 문자열 + 숫자 와 같은 코드를 만나면 C#은 "개발자가 덧셈을 하려다 실수한 건가? 아니면 문자열로 합치려는 건가?"를 추측하지 않고, 개발자가 직접 "이 숫자를 문자열로 바꿔서 합쳐주세요" 라고 명확하게 지시하기를 기대합니다. 그 지시가 바로 .ToString() 입니다.

C#
// C#의 관점:
// 1. currentPlayerHealth는 숫자(int)
// 2. " / "는 문자열(string)
// 3. 숫자와 문자열을 '+' 하라는 명령은 모호하다.
// 4. 그러므로 currentPlayerHealth를 .ToString()으로 바꿔서 의도를 명확히 해라.
healthText.text = currentPlayerHealth.ToString() + " / " + maxPlayerHealth.ToString();

자바의 원칙: "편의성을 우선시하라"

반면에 자바는 조금 더 유연합니다. + 연산 시 피연산자 중 하나라도 String 타입이 있으면, 나머지를 모두 String으로 자동 변환(Implicit Conversion)하여 연결하는 규칙을 가지고 있습니다.

 

 


 

이제 레벨업 (체력증가)누르면 실제로 체력 증가 되도록<나중에 할것