총기 구현
1. GunHolder 생성
Player 자식으로 GunHolder 빈오브젝트, 그 자식으로 Gun 프리팹 생성
Gun 프리팹 자식으로 Amature 빈 오브젝트, 그 자식으로 Gun 빈 오브젝트 생성, 총알 발사 ParticleSystem 추가
* ParticleSystem은 무료 에셋을 다운 받아서 사용하자!
Stop Action 은 None으로 해서 Particle 재생 후, Destroy 되지 않도록 해줌
ParticleSystem이 총구에서 실행 되도록 위치 조정
2. 스크립트 작성
Gun
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Gun : MonoBehaviour
{
[field: SerializeField] public Vector3 OriginPos;
[field: SerializeField] public Vector3 FineSightOriginPos;
[field : SerializeField] public string GunName { get; private set; } // 총의 이름
[field: SerializeField] public float Range { get; private set; } // 총의 사정 거리
[field: SerializeField] public float FireRate { get; private set; } // 연사 속도 (한발과 한발간의 시간 텀)
[field: SerializeField] public float ReloadTime { get; private set; } // 재장전 속도
[field: SerializeField] public int Damage { get; private set; } // 총의 공격력
[field: SerializeField] public float RetroActionForce { get; private set; } // 반동 세기
[field: SerializeField] public float RetroActionFineSightForce { get; private set; } // 정조준 반동 세기
[field: SerializeField] public float Accuracy { get; private set; } // 정확도
[field: SerializeField] public float AccuracyFineSight { get; private set; } // 정조준 정확도
[field: SerializeField] public int ReloadBulletCount { get; private set; } // 총의 재장전 개수
public int CurrentBulletCount; // 현재 탄창에 남아있는 총알의 개수
[field: SerializeField] public int MaxBulletCount { get; private set; } // 총알 최대 소유 개수
public int CarryBulletCount; // 현재 소유하고 있는 총알의 총 개수
[field: SerializeField] public Animator Anim { get; private set; }
[field: SerializeField] public ParticleSystem MuzzleFlash { get; private set; } // 화염구 이펙트
[field: SerializeField] public GameObject HitEffectPrefab { get; private set; } // 총알 피격 이펙트
[field: SerializeField] public AudioClip Fire_Sound { get; private set; } // 총 발사 소리 오디오 클립
}
GunController
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using Cinemachine;
public class GunController : MonoBehaviour
{
[Header("Gun")]
public Gun CurrentGun;
[Header("Camera")]
[SerializeField] private CinemachineVirtualCamera _virtualCamera;
public bool IsFindSightMode { get; private set; }
private CinemachinePOV _pov;
private AudioSource _audioSource; // 발사 소리 재생기
private RaycastHit _hitInfo; // 총알의 충돌 정보
private float _currentFireRate;
private bool _isReload = false;
private float _originFOV;
private void Awake()
{
_audioSource = GetComponent<AudioSource>();
_pov = _virtualCamera.GetCinemachineComponent<CinemachinePOV>();
}
private void Start()
{
IsFindSightMode = false;
}
void Update()
{
GunFireRateCalc();
TryFire();
TryReload();
TryFineSight();
}
private void GunFireRateCalc()
{
if (_currentFireRate > 0)
_currentFireRate -= Time.deltaTime;
}
private void TryFire()
{
if (Input.GetButton("Fire1") && _currentFireRate <= 0)
{
Fire();
}
}
private void Fire()
{
if (!_isReload)
{
if (CurrentGun.CurrentBulletCount > 0)
Shoot();
else
{
CancelFineSight();
StartCoroutine(COReload());
}
}
}
private void Shoot()
{
CurrentGun.CurrentBulletCount--;
_currentFireRate = CurrentGun.FireRate;
PlaySE(CurrentGun.Fire_Sound);
CurrentGun.MuzzleFlash.Play();
CurrentGun.Anim.SetTrigger("Fire");
// 피격 처리
Hit();
// 총기 반동 코루틴 실행
StopAllCoroutines();
StartCoroutine(CORetroAction());
Debug.Log("총알 발사");
}
private void TryReload()
{
if (Input.GetKeyDown(KeyCode.R) && !_isReload && CurrentGun.CurrentBulletCount < CurrentGun.ReloadBulletCount)
{
CancelFineSight();
StartCoroutine(COReload());
}
}
private void Hit()
{
Vector3 randomRange = new Vector3(Random.Range(-CurrentGun.Accuracy, CurrentGun.Accuracy), Random.Range(-CurrentGun.Accuracy, CurrentGun.Accuracy), 0);
Vector3 randomRangeFineSight = new Vector3(Random.Range(-CurrentGun.AccuracyFineSight, CurrentGun.AccuracyFineSight), Random.Range(-CurrentGun.AccuracyFineSight, CurrentGun.AccuracyFineSight), 0);
if (!IsFindSightMode) // 정조준이 아닌 상태
{
Debug.DrawRay(Camera.main.transform.position, (Camera.main.transform.forward + randomRange) * CurrentGun.Range, Color.blue, 0.3f);
if (Physics.Raycast(Camera.main.transform.position, Camera.main.transform.forward + randomRange, out _hitInfo, CurrentGun.Range)) // 카메라 월드좌표
{
GameObject clone = Instantiate(CurrentGun.HitEffectPrefab, _hitInfo.point, Quaternion.LookRotation(_hitInfo.normal));
Debug.Log(_hitInfo.transform.name);
}
}
else // 정조준 상태
{
Debug.DrawRay(Camera.main.transform.position, (Camera.main.transform.forward + randomRangeFineSight) * CurrentGun.Range, Color.blue, 0.3f);
if (Physics.Raycast(Camera.main.transform.position, Camera.main.transform.forward + randomRangeFineSight, out _hitInfo, CurrentGun.Range)) // 카메라 월드좌표
{
GameObject clone = Instantiate(CurrentGun.HitEffectPrefab, _hitInfo.point, Quaternion.LookRotation(_hitInfo.normal));
Debug.Log(_hitInfo.transform.name);
}
}
}
private void TryFineSight()
{
if (Input.GetButtonDown("Fire2") && !_isReload)
{
FineSight();
}
}
private void FineSight()
{
IsFindSightMode = !IsFindSightMode;
CurrentGun.Anim.SetBool("FineSightMode", IsFindSightMode);
if (IsFindSightMode)
{
StopAllCoroutines();
StartCoroutine(COFineSightActivate());
}
else
{
StopAllCoroutines();
StartCoroutine(COFineSightDeActivate());
}
}
private void CancelFineSight()
{
if (IsFindSightMode)
FineSight();
}
IEnumerator COFineSightActivate()
{
_originFOV = _virtualCamera.m_Lens.FieldOfView;
while (CurrentGun.transform.localPosition != CurrentGun.FineSightOriginPos)
{
CurrentGun.transform.localPosition = Vector3.Lerp(CurrentGun.transform.localPosition, CurrentGun.FineSightOriginPos, 0.2f);
_virtualCamera.m_Lens.FieldOfView = 30f;
yield return null;
}
}
IEnumerator COFineSightDeActivate()
{
while (CurrentGun.transform.localPosition != CurrentGun.OriginPos)
{
CurrentGun.transform.localPosition = Vector3.Lerp(CurrentGun.transform.localPosition, CurrentGun.OriginPos, 0.2f);
_virtualCamera.m_Lens.FieldOfView = _originFOV;
yield return null;
}
}
IEnumerator COReload()
{
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("소유한 총알이 없습니다.");
}
}
IEnumerator CORetroAction()
{
Vector3 recoilBack = new Vector3(CurrentGun.OriginPos.x, CurrentGun.OriginPos.y, CurrentGun.OriginPos.z - CurrentGun.RetroActionForce); // 정조준 x
Vector3 retroActionRecoilBack = new Vector3(CurrentGun.FineSightOriginPos.x, CurrentGun.FineSightOriginPos.y, CurrentGun.FineSightOriginPos.z - CurrentGun.RetroActionFineSightForce); // 정조준
if (!IsFindSightMode) // 정조준이 아닌 상태
{
CurrentGun.transform.localPosition = CurrentGun.OriginPos;
// 반동 시작
while (CurrentGun.transform.localPosition.z >= CurrentGun.OriginPos.z - CurrentGun.RetroActionForce + 0.02f)
{
CurrentGun.transform.localPosition = Vector3.Lerp(CurrentGun.transform.localPosition, recoilBack, 0.4f);
_pov.m_VerticalAxis.Value += -CurrentGun.RetroActionForce;
yield return null;
}
// 원위치
while (CurrentGun.transform.localPosition != CurrentGun.OriginPos)
{
CurrentGun.transform.localPosition = Vector3.Lerp(CurrentGun.transform.localPosition, CurrentGun.OriginPos, 0.1f);
yield return null;
}
}
else // 정조준 상태
{
CurrentGun.transform.localPosition = CurrentGun.FineSightOriginPos;
// 반동 시작
while (CurrentGun.transform.localPosition.z >= CurrentGun.FineSightOriginPos.z - CurrentGun.RetroActionFineSightForce + 0.02f)
{
CurrentGun.transform.localPosition = Vector3.Lerp(CurrentGun.transform.localPosition, retroActionRecoilBack, 0.4f);
_pov.m_VerticalAxis.Value += -CurrentGun.RetroActionFineSightForce;
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();
}
}
3. Gun 설정
각 값 설정, Gun이 카메라에 보이도록 Position 설정
OriginPos : 기본 총구 위치와 동일하게 (위 Gun Position과 동일)
FindSightOriginPos : 정조준 시, 위치로 설정
4. GunHolder 설정
Audio Source 컴포넌트 추가, Virtual Camera 사용한다면 추가
CurrentGun, HandGun1 추가
5. HitEffect 설정
HitEffectPrefab이 실행 후, 파괴되도록 Stop Action을 Destroy로 설정
* ParticleSystem은 무료 에셋을 다운 받아서 사용하자!
6. Animator 설정
idle, walk, run, reload, fire, findSightMode 애니메이션 설정
* idle, walk, run, reload, findSightMode 는 Position을 직접 설정해서 애니메이션을 만듦
'유니티' 카테고리의 다른 글
3D 총기 구현 (2) (1) | 2024.01.05 |
---|---|
Rotation 값 변경 (1) | 2024.01.03 |
매니저 관리 (0) | 2023.12.29 |
DontDestroyOnLoad (0) | 2023.12.28 |
씬 이동 (0) | 2023.12.28 |