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개를 만들어서 명명해준다
그뒤 위 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 |