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
- 이미지 BulletUIImage
- 현재 탄창 안에 있는 총알의 개수를 표시할 텍스트 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 코드를 작성하면 그 곳에서 관리 될 것이다.
- 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();
}
}
'C# > UNITY_FPS 3D 서바이벌게임' 카테고리의 다른 글
Chapter 2-6. 무기 : Weapon Manager (0) | 2024.03.11 |
---|---|
Chapter 2-5. 무기 : 크로스헤어 (0) | 2024.03.11 |
Chapter 2-3. 무기 : 총 피격 구현 (0) | 2024.03.11 |
Chapter 2-2. 무기 : 총 구현 2️⃣ (재장전, 정조준, 반동) (0) | 2024.03.11 |
Chapter 2-1. 무기 : 총 구현 1️⃣ (애니메이션, 파티클, 오디오) (0) | 2024.03.11 |