FPS서바이벌 디펜스

6.HUD

ruripanda 2025. 4. 22. 18:43

본 강좌는 케이디님의 유튜브강좌 영상을 보고 유니티6로 구현한 것 입니다 이렇게 멋진 강좌를 만드신 케이디님을 존경합니다

using System.Runtime.CompilerServices;
using UnityEngine;
using UnityEngine.UI;

public class HUD : MonoBehaviour
{
    //필요한 컴포넌트
    [SerializeField]
    private GunController theGunController;
    private Gun currentGun;

    //필요하면 HUD호출
    [SerializeField]
    private GameObject go_BulletHUD;

    //총알 개수 반영
    [SerializeField]
    private Text[] text_Bullet;

    void Update()
    {
        CheckBullet();
    }

    private void CheckBullet()
    {
        currentGun = theGunController.GetGun();
        text_Bullet[0].text = currentGun.carryBulletCount.ToString();
        text_Bullet[1].text = currentGun.reloadBulletCount.ToString();
        text_Bullet[2].text = currentGun.currentBulletCount.ToString();
    }
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class GunController : MonoBehaviour
{
    //현재 장착된 총
    [SerializeField]
    private Gun currentGun;
    //연사속도 계산
    private float currentFireRate;//정조준 위치를 위한 값

    //상태변수
    private bool isReload = false;
    [HideInInspector]//인스팩터에세 숨김
    public bool isFineSightMode = false;//정조준 위치 bool 값

    // 본래 포지션 값.
    [SerializeField]
    private Vector3 originPos;//원래 조준위치

    //효과음
    private AudioSource audioSource;

    //레이져 충돌 정도 받아옴
    private RaycastHit hitInfo;

    //필요한 카메라 컴포넌트
    [SerializeField]
    private Camera theCam;

    //피격 이팩트
    [SerializeField]
    private GameObject hit_effect_prefab;

    void Start()
    {
        originPos = Vector3.zero;
        audioSource = GetComponent<AudioSource>();
    }

    // Update is called once per frame
    void Update()
    {
        GunFireRateCalc();
        TryFire();
        TryReload();
        TryFineSight();
    }

    //연사속도 재계산
    private void GunFireRateCalc()
    {
        if (currentFireRate > 0)
            currentFireRate -= Time.deltaTime;
    }

    //발사시도
    private void TryFire()
    {
        if (Input.GetButton("Fire1") && currentFireRate <= 0 && !isReload)
        {
            Fire();
        }
    }

    //발사전 계산
    private void Fire()
    {
        if (!isReload)
        {
            if (currentGun.currentBulletCount > 0)
                Shoot();
            else
            {
                CancelFineSight();
                StartCoroutine(ReloadCoroutine());
            }


        }
    }

    //발사 후 계산
    private void Shoot()
    {
        currentGun.currentBulletCount--;
        currentFireRate = currentGun.fireRate; // 연사 속도 재계산.
        PlaySE(currentGun.fire_Sound);
        currentGun.muzzleFlash.Play();
        Hit();
        StopAllCoroutines();
        StartCoroutine(RetroActionCoroutine());
    }

    private void Hit()
    {
        if (Physics.Raycast(theCam.transform.position, theCam.transform.forward, out hitInfo, currentGun.range))
        {
            GameObject clone = Instantiate(hit_effect_prefab, hitInfo.point, Quaternion.LookRotation(hitInfo.normal));
            Destroy(clone, 2f);
            //Debug.Log(hitInfo.transform.name);
        }
    }

    //재장전 시도
    private void TryReload()
    {
        if (Input.GetKeyDown(KeyCode.R) && !isReload && currentGun.currentBulletCount < currentGun.reloadBulletCount)
        {
            CancelFineSight();
            StartCoroutine(ReloadCoroutine());
        }
    }

    //재장전
    IEnumerator ReloadCoroutine()
    {
        if (currentGun.carryBulletCount > 0)
        {
            isReload = true;

            currentGun.anim.SetTrigger("Reload");


            currentGun.carryBulletCount += currentGun.currentBulletCount;
            currentGun.currentBulletCount = 0;

            yield return new WaitForSeconds(currentGun.reloadTiem);

            if (currentGun.carryBulletCount >= currentGun.reloadBulletCount)
            {
                currentGun.currentBulletCount = currentGun.reloadBulletCount;
                currentGun.carryBulletCount -= currentGun.reloadBulletCount;
            }
            else
            {
                currentGun.currentBulletCount = currentGun.carryBulletCount;
                currentGun.carryBulletCount = 0;
            }


            isReload = false;
        }
        else
        {
            Debug.Log("소유한 총알이 없습니다.");
        }
    }

    //정조준 시도
    private void TryFineSight()
    {
        if (Input.GetButtonDown("Fire2") && !isReload)
        {
            FineSight();
        }
    }

    //정조준 취소
    public void CancelFineSight()
    {
        if (isFineSightMode)
            FineSight();
    }

    //정조준 로직가동
    private void FineSight()
    {
        isFineSightMode = !isFineSightMode;
        currentGun.anim.SetBool("FineSightMode", isFineSightMode);

        if (isFineSightMode)
        {
            StopAllCoroutines();
            StartCoroutine(FineSightActivateCoroutine());
        }
        else
        {
            StopAllCoroutines();
            StartCoroutine(FineSightDeactivateCoroutine());
        }

    }

    //정조준 활성화
    IEnumerator FineSightActivateCoroutine()
    {
        while (currentGun.transform.localPosition != currentGun.fineSightOriginPos)
        {
            currentGun.transform.localPosition = Vector3.Lerp(currentGun.transform.localPosition, currentGun.fineSightOriginPos, 0.2f);
            yield return null;
        }
    }

    //정조준 비활성화
    IEnumerator FineSightDeactivateCoroutine()
    {
        while (currentGun.transform.localPosition != originPos)
        {
            currentGun.transform.localPosition = Vector3.Lerp(currentGun.transform.localPosition, originPos, 0.2f);
            yield return null;
        }
    }

    //총 반동
    IEnumerator RetroActionCoroutine()
    {
        Vector3 recoilBack = new Vector3(currentGun.retroActionForce, originPos.y, originPos.z);
        Vector3 retroActionRecoilBack = new Vector3(currentGun.retroActionFineSightForce, currentGun.fineSightOriginPos.y, currentGun.fineSightOriginPos.z);

        if (!isFineSightMode)
        {

            currentGun.transform.localPosition = originPos;

            // 반동 시작
            while (currentGun.transform.localPosition.x <= currentGun.retroActionForce - 0.02f)
            {
                currentGun.transform.localPosition = Vector3.Lerp(currentGun.transform.localPosition, recoilBack, 0.4f);
                yield return null;
            }

            // 원위치
            while (currentGun.transform.localPosition != originPos)
            {
                currentGun.transform.localPosition = Vector3.Lerp(currentGun.transform.localPosition, originPos, 0.1f);
                yield return null;
            }
        }
        else
        {
            currentGun.transform.localPosition = currentGun.fineSightOriginPos;

            // 반동 시작
            while (currentGun.transform.localPosition.x <= currentGun.retroActionFineSightForce - 0.02f)
            {
                currentGun.transform.localPosition = Vector3.Lerp(currentGun.transform.localPosition, retroActionRecoilBack, 0.4f);
                yield return null;
            }

            // 원위치
            while (currentGun.transform.localPosition != currentGun.fineSightOriginPos)
            {
                currentGun.transform.localPosition = Vector3.Lerp(currentGun.transform.localPosition, currentGun.fineSightOriginPos, 0.1f);
                yield return null;
            }
        }

    }

    //사운드 재생
    private void PlaySE(AudioClip _clip)
    {
        audioSource.clip = _clip;
        audioSource.Play();
    }

    public Gun GetGun()
    {
        return currentGun;
    }
}

 

이번시간에는 총알의 상태를 알려주는 HUD를 구현할 것이다

 

하이러키에 UI > Canvas 를 클릭하여 캔버스를 만들자

 

캔버스의 인스펙터

 

여기서 잠깐 Render Mode를 볼탠데 Screen Space - Overlay는 플레이어의 화면에 맞춰 동기화되는 것이고

Scereen Space - Camera는 카메라에 맞춰 동기화 되는 옵션

World Space는 우리가 씬에 배치하는 오브젝트 비슷하게 사용하는 것이다

 

그 뒤 빈 객체를 Canvas의 밑에 만들고 HUD라고 명명한다

그리고 HUD의 하위에 UI > Image 를 해서 이미지 객체를 만들고 BulletUIImage라고 명명한다

 

그뒤 Status_Bullet_Base라는 이미지를 케이디님이 올린 소스에서 찾아서

위에 체크한 부분들을 수정하고 BulletUIImage라고 위에 명명한 이미지 오브젝트에 넣어준다

BulletUIImage의 하위에 UI > Legcy > Text 를 눌러서 레거시 택스트를 만들고

위의 하위 러키처럼

CurrentBullet, ReloadBullet, CarryBullet 라고 3개를 만들어서 명명해준다

 

각 Bullet의 인스펙터

그뒤 위 3개처럼 인스펙터르 수정해서

왼쪽의 아래에 화살표 처럼 총알 UI와 Text를 배치해 준다

 

이제 HUD객체를 클릭한 상태에서 Add Component를 검색 Script를 검색 HUD라는 컴포넌트를 만들어주자

이렇게 만들은 컴포넌트는 우리가 위치를 지정하지 않았기 때문에 에셋폴더 최상위에 만들어 진다

 

이제 HUD스크립트를 작성하자

스크립트를 작성하기 전에 using UnityEngine.UI;를 선언해주자

Text컴포넌트를 다루기 위해 추가하는 것이다

9번줄에 GunController을 theGunController으로  받아와주고

10번 줄에 Gun을 currentGun으로 받아와준다

14번줄에 GameObject go_BulletHUD;로 우리가 이번에 만들은 HUD를 받아와주고

18번줄에 배열로 Text를 선언해서 text_Bullet로 선언한다

 

보이드 업데이트로 CheckBullet()가 항시 실행되게 해준다

 

여기서 중요한게 있는데 currentGun은 우리가 private로 공개수준을 높게 했기 때문에 외부에서 받아오지 못 한다

그래서 이렇게 GunController 스크립트로 들어가서 currentGun을 return해준다

공개수준을 꼭 public로 해주는 것을 잊지말고!

 

이제 다시 HUD로 돌아와서

28번은 배열의 첫번째로 들어가서 text를 선언해주고 carryBulletCountToString()를 작성해준다

이해하기 쉽게 말하자면

text_Bullet[0].text는 우리가 배열로 작성한 첫번째 text 객체에 접근하는 것이고

currentGun은 객체의 속성에 접근하는 것이고

carryBulletCount 는 총알의 숫자에 접근하는 것이고

ToString()는 text는 숫자는 활용하지 못하므로 숫자를 문자열로 바꿔주는 것이다

이걸 3번 반복해서

carryBulletCount 현재 총알

reloadBulletCount 리로드 되는 총알

currentBulletCount 현재 플레이어가 가진 모든 총알

을 선언해준다

 

이제 인스팩터로 돌아와서

 

이렇게 슬롯에 연결해주고

 

테스트해주면!

 

이렇게 총알이 사용하면서 바뀌는 것을 볼 수 있다

'FPS서바이벌 디펜스' 카테고리의 다른 글

8.Weapon Manager  (0) 2025.04.26
7.크로스헤어(조준점)  (0) 2025.04.23
5-3.명중, 피격 이팩트  (0) 2025.04.21
5-2.재장전, 정조준, 반동  (0) 2025.04.17
5.총 구현과 총구섬광  (0) 2025.04.15