Chapter 2-4. 무기 : 총알 HUD
카테고리: Unity Lesson 3
태그: Unity Game Engine
인프런에 있는 케이디님의 [유니티 3D] 실전! 생존게임 만들기 - Advanced 강의를 듣고 정리한 필기입니다. 😀
🌜 강의 들으러 가기 Click
Chapter 2. 무기
총알의 상태를 알 수 있는 HUD
🚖 총알 HUD 만들기
캔버스
총알 HUD 를 그릴 종이 마련
- 캔버스 추가 Create - UI - Canvas
- Canvas의 Render Mode는 Screen Space -Overlay 로 해준다.
    - 캔버스는 언제나 게임 화면 위에 덮어 씌워 그려지게 된다.
 
###
이미지
왼쪽 하단에 현재 총알 상태를 나타낼 배경 이미지
- 캔버스 위에 이미지 UI 를 추가해준다.
    - 이미지 UI 는 Sprite 만 추가할 수 있다.
 
- 이미지 UI에 추가할 이미지 파일 설정
    - Texture Type 을 Sprite 로 설정
- 이미지 크기 맞추기
        - 예를 들어 Sprite 이미지 파일의 너비 높이가가 182 x 38 이라면 182 를 커버할 수 있도록 Max Size를 256으로 맞춰주는게 좋다.
            - Max Size가 굳이 더 클 필요도 없으며 182 보다 작게 설정한다면 이미지가 잘린다.
 
 
- 예를 들어 Sprite 이미지 파일의 너비 높이가가 182 x 38 이라면 182 를 커버할 수 있도록 Max Size를 256으로 맞춰주는게 좋다.
            
 
- 이미지 UI에 Sprite 이미지 파일을 Source Image에 할당
    - Rect Transform 조정
        - Sprite 이미지 파일의 크기에 맞게 너비와 높이를 똑같이 맞춰주는게 좋겠다.
            - 예를 들어 Sprite 이미지 파일의 너비 높이가가 182 x 38 이라면 Rect Transform 의 Width 와 Height 도 182, 38 로 해준다.
 
- PosX, PosY, PosZ 로 위치를 조정한다.
 
- Sprite 이미지 파일의 크기에 맞게 너비와 높이를 똑같이 맞춰주는게 좋겠다.
            
 
- Rect Transform 조정
        


텍스트
총알 정보를 알려주는 3 개의 텍스트를 추가
- 모두 이미지의 자식으로 추가해주었고 알맞는 위치에 배치했다.
- 폰트 사이즈와 정렬 등등 설정해줌.
- RayCast 체크 해제
    - 이 텍스트가 어떤 충돌을 감지할 필요가 없기 때문에
 


- 총알 UI 전체를 대표하는 빈 오브젝트 HUD- 이미지 BulletUIImage- 텍스트 CurrentBullet
- 텍스트 ReloadBullet
- 텍스트 CarryBullet
 
- 텍스트 
 
- 이미지 
- 현재 탄창 안에 있는 총알의 개수를 표시할 텍스트 CurrentBullet
- 최대 재장전 개수를 표시할 텍스트 ReloadBullet
- 현재 소유하고 있는 총알의 총 개수를 표시할 텍스트 CarryBullet
🚖 총알 개수 UI 업데이트
📜GunController.cs
    public Gun GetGun()
    {
        return currentGun;
    }
📜HUD.cs에서 📜GunController.cs의 currentGun이 필요한데 private 변수라서 이렇게 currentGun을 리턴해주는 함수를 추가했다.
📜HUD.cs
총알 UI 전체를 대표하는 빈 오브젝트
HUD에 붙여준다.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class HUD : MonoBehaviour
{
    // 총알 정보를 얻기 위해 📜Gun.cs, 📜GunController.cs 가 필요
    [SerializeField]
    private GunController theGunController;
    private Gun currentGun;
    // 총알 텍스트 UI들을 담았던 이미지 UI를 할당할 것이다. 필요할 때 HUD를 호출하고 필요 없을 땐 비활성화 할 것이다.
    [SerializeField]
    private GameObject go_BulletHUD;
    // 총알 개수를 텍스트 UI에 반영
    [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 UnityEngine.UI 필요
- currentGun- 📜GunController.cs (theGunController)의 📜Gun.cs타입 멤버 변수currentGun을 가져와 할당할 것이다.
- 📜Gun.cs 에서 현재 탄창안에 있는 총알 수, 재장전 총알 수, 현재 소유하고 있는 총알 수 변수를 가지고 있기 때문에 필요한 작업이다.
        - currentBulletCount
- reloadBulletCount
- carryBulletCount
 
- 나중에 📜WeaponManager.cs 코드를 작성하면 그 곳에서 관리 될 것이다.
 
- 📜GunController.cs (
- go_BulletHUD- 무기를 바꾸면 그 총에 맞는 총알 HUD로 바뀌어야 한다. 현재 들고 있는 총을 해제하면 총알 HUD를 비활성화 해야 하고 총을 새로 들게 되면 HUD를 활성화 새로 해야 하므로 필요
- 총알 텍스트 UI들과 함께 배경 이미지를 담당하는 BulletUIImage이미지가 할당 될 것임
- HUD를 활성화 해야한느지 비활성화 해야하는지에 대한 신호는 📜WeaponManager.cs 로부터 받게 될 것이다.
 
- text_Bullet- 3 가지 텍스트 UI 배열
- using UnityEngine.UI 필요
 
- 매 프레임마다 CheckBullet() 을 실행하여 총알 HUD 텍스트를 업데이트 한다.

- theGunController에 📜GunController.cs 가 붙어있는- Holder할당
- go_BulletHUD에 총알 텍스트 UI들의 부모인- BulletUIImage이미지 할당
- text_Bullet배열에 3 개의 텍스트 UI를 각각 원소로 할당

총을 발사할 때마다 총알 텍스트 UI가 업데이트 되는 것을 확인할 수 있다.
🚖 여기까지 스크립트 정리
📜GunController.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class GunController : MonoBehaviour
{
    [SerializeField]
    private Gun currentGun; // 현재 들고 있는 총. 📜Gun.cs 가 할당 됨.
    private float currentFireRate; // 이 값이 0 보다 큰 동안에는 총알이 발사 되지 않는다. 초기값은 연사 속도인 📜Gun.cs의 fireRate 
    private bool isReload = false;  // 재장전 중인지. 
    [HideInInspector]
    public bool isFineSightMode = false; // 정조준 중인지.
    [SerializeField]
    private Vector3 originPos;  // 원래 총의 위치(정조준 해제하면 나중에 돌아와야 하니까)
    private AudioSource audioSource;  // 발사 소리 재생기
    private RaycastHit hitInfo;  // 총알의 충돌 정보
    [SerializeField]
    private Camera theCam;  // 카메라 시점에서 정 중앙에 발사할 거라서
    [SerializeField]
    private GameObject hitEffectPrefab;
    void Start()
    {
        originPos = Vector3.zero;
        audioSource = GetComponent<AudioSource>();
    }
    void Update()
    {
        GunFireRateCalc();
        TryFire();
        TryReload();
        TryFineSight();
    }
    private void GunFireRateCalc()
    {
        if (currentFireRate > 0)
            currentFireRate -= Time.deltaTime;  // 즉, 1 초에 1 씩 감소시킨다.
    }
    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()
    {
        // 카메라 월드 좌표!! (localPosition이 아님)
        if (Physics.Raycast(theCam.transform.position, theCam.transform.forward, out hitInfo, currentGun.range))
        {
            GameObject clone = Instantiate(hitEffectPrefab, hitInfo.point, Quaternion.LookRotation(hitInfo.normal));
            Destroy(clone, 2f);
        }
    }
    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.reloadTime);  // 재장전 애니메이션이 다 재생될 동안 대기
            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.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class HUD : MonoBehaviour
{
    // 총알 정보를 얻기 위해 📜Gun.cs, 📜GunController.cs 가 필요
    [SerializeField]
    private GunController theGunController;
    private Gun currentGun;
    // 총알 텍스트 UI들을 담았던 이미지 UI를 할당할 것이다. 필요할 때 HUD를 호출하고 필요 없을 땐 비활성화 할 것이다.
    [SerializeField]
    private GameObject go_BulletHUD;
    // 총알 개수를 텍스트 UI에 반영
    [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();
    }
}
🌜 개인 공부 기록용 블로그입니다. 오류나 틀린 부분이 있을 경우 
언제든지 댓글 혹은 메일로 지적해주시면 감사하겠습니다! 😄
 
      
    
댓글 남기기