EasyCastleUNITY

Space Shooter 간단한 UI 적용 (플레이어 Hp Bar) 본문

유니티 심화

Space Shooter 간단한 UI 적용 (플레이어 Hp Bar)

EasyCastleT 2023. 8. 25. 17:06

캔버스 계층구조

몬스터에게 타격을 받으면, hp bar가 줄어든다. 

플레이어 컨트롤러 Start 부분
UI의 fillAmount 수치를 조정한다.

위에서 유의할 점은 바로 ?연산자이다. 

https://learn.microsoft.com/ko-kr/dotnet/csharp/language-reference/builtin-types/nullable-value-types

 

Null 허용 값 형식 - C# 참조

C# nullable 값 형식 및 사용 방법 알아보기

learn.microsoft.com

(클래스 또는 구조체) ? (속성 도는 메서드) 의 형태로 사용한다.

앞의 구문이 null이 아니면 뒤에 있는 구문을 실행한다는 의미이다.

태그를 통해 검색한 값이 null이 아니면 뒤에 GetComponant를 실행하고, null이면, null 값을 반환한다.

즉, 해당 태그를 가지고 있는 게임오브젝트가 없다면, hpBar라는 변수는 null이다. 

using System;
using System.Collections;
using UnityEngine;
using UnityEngine.UI;
using static PlayerControl;

public class PlayerController : MonoBehaviour
{

    public enum eAnimState
    {
        Idle, RunB, RunF, RunL, RunR
    }

    [SerializeField]
    private float moveSpeed = 2.0f;
    [SerializeField]
    private float turnSpeed = 80f;

    //카메라 정보
    private FollowCam followCam;
    private float distance;
    private float height;
    //애니메이터
    private Animation anim;

    //델리게이트 선언
    public delegate void PlayerDieHandler(); //타입 정의
    //event 키워드
    public static event PlayerDieHandler OnPlayerDie; 

    //----------------------------------------
    //초기 생명 값
    private readonly float initHp = 100.0f;
    //현재 생명 값
    [SerializeField] private float hp;
    //----------------------------------------
    //Hp Bar 연결할 변수 
    private Image hpBar;
    private IEnumerator Start()
    {
        this.hpBar = GameObject.FindGameObjectWithTag("Hp_Bar")?.GetComponent<Image>();  //?는 null 체크를 간결하게 해주는 연산자
        this.hp = this.initHp;
        this.DisplayHealth();

        this.followCam = GameObject.FindAnyObjectByType<FollowCam>();
        this.anim = this.GetComponent<Animation>();
        //실행하자마자 바로 돌아가는 것 방지 
        this.turnSpeed = 0.0f;
        yield return new WaitForSeconds(0.3f);
        this.turnSpeed = 80.0f;
    }

    private void Update()
    {
        this.distance = this.followCam.Distance;
        this.height = this.followCam.Height;    
        float h = Input.GetAxis("Horizontal");
        float v = Input.GetAxis("Vertical");
        float r = Input.GetAxis("Mouse X"); //왼쪽으로 마우스 움직이면, -, 오른쪽으로 마우스 움직이면 + 
        Vector3 moveDir = (Vector3.forward * v) + (Vector3.right * h);
        //Debug.Log(moveDir); 
        DrawArrow.ForDebug(this.transform.position, moveDir.normalized, 0f,Color.red);
        this.transform.Translate(moveDir.normalized * this.moveSpeed * Time.deltaTime);
        //마우스 왼쪽으로 움직이면, 왼쪽으로 회전, 마우스 오른쪽으로 움직이면, 오른쪽으로 회전
        Vector3 angle = Vector3.up * turnSpeed * Time.deltaTime * r;
        this.transform.Rotate(angle); //set이 아니라 원래 rotation에서 더하는 방식으로 됨

        this.PlayAnimation(moveDir);
    }

    private void OnDrawGizmos()
    {
        Gizmos.color = Color.white;
        GizmosExtensions.DrawWireArc(this.transform.position, this.transform.forward, 360, 1);
        DrawArrow.ForDebug(this.transform.position,this.transform.forward,0f,Color.green); //플레이어 앞에 길이 1의 화살표 그림
        DrawArrow.ForDebug(this.transform.position, -1 * this.transform.forward * distance, 0f, Color.yellow); //플레이어 뒤에 길이 3의 화살표 그림 
        Vector3 pos = this.transform.position + (-1 * this.transform.forward *distance);
        DrawArrow.ForDebug(pos, Vector3.up * height, 0f, Color.green);
    }

    private void PlayAnimation(Vector3 dir)
    {
        if (dir.x > 0)
        {
            //right
            this.anim.CrossFade(eAnimState.RunR.ToString(), 0.25f);
        }
        else if (dir.x < 0)
        {
            //left
            this.anim.CrossFade(eAnimState.RunL.ToString(), 0.25f);
        }
        else if (dir.z > 0)
        {
            //forward
            this.anim.CrossFade(eAnimState.RunF.ToString(), 0.25f);

        }
        else if (dir.z < 0)
        {
            //back
            this.anim.CrossFade(eAnimState.RunB.ToString(), 0.25f);
        }
        else
        {
            //Idle
            this.anim.CrossFade(eAnimState.Idle.ToString(), 0.25f);
        }
    }

    private void OnTriggerEnter(Collider other)
    {
        if(this.hp > 0.0f && other.CompareTag("Punch"))
        {
            this.hp -= 10.0f;
            this.DisplayHealth();
            Debug.LogFormat("<color=yellow>Player Hp: ({0} / {1})</color>", this.hp, this.initHp);
            //Debug.Log($"Player Hp = {this.hp / initHp}");

            //Player의 생명이 0이하면 사망처리
            if (this.hp <= 0)
            {
                this.PlayerDie();
            }
        }
    }

    private void PlayerDie()
    {
        Debug.Log("PlayerDie!!!");

        ////Monster 태그를 가진 모든 게임오브젝트를 찾아옴
        //GameObject[] arrMonsterGo = GameObject.FindGameObjectsWithTag("Monster");
        ////모든 몬스터의 OnPlayerDie 함수를 순차적으로 호출, 이름을 통해 Monster의 OnPlayerDie 함수를 호출
        //foreach (GameObject go in arrMonsterGo)
        //{
        //    go.SendMessage("OnPlayerDie", SendMessageOptions.DontRequireReceiver); //잘 사용되지 않는 메서드, 
        //}
        //이벤트를 구독 중인 모든 애들에게 알림
        OnPlayerDie(); //알림 (notification), 대리자 호출, 이벤트 호출
    }

    private void DisplayHealth()
    {
        this.hpBar.fillAmount = this.hp / this.initHp;
    }
}

플레이어에서 ui도 처리하고 있어 OOP 적으로는 좋지 않지만, 이렇게 작성하였다. 

실행결과, 몬스터에게 맞으면 피가 줄어들고, 그것을 UI로 명시적으로 보여준다.