이번엔 샷건을 구현해보자.
샷건은 다른 총들과 달리 생각해야할 점이 2가지가 있다.
1번째: 총 발사시, 쉘이 퍼져 나간다.
보통의 총들은 발사시에 한발씩 나가지만 샷건은 한번 발사시에 여러개의 쉘로 퍼져서 나간다
2번째: 장전 시 한발씩 장전이 된다.
보통의 총들은 장전시에 탄창을 교환하는 방식으로 한번에 장전되지만 샷건은 한발씩 장전되어야 한다
그럼 이제 샷건을 구현해보자.
1. 변수 설정
public class ShotGun : MainWeapon
{
private bool canReset = true; // 처음에만 총알 넣어주기 위해
private float nextFireTime; // 다음 발사 주기
public TextMeshProUGUI ammoTxt; // 탄약 UI 표시
private int shell = 10; // 발사되는 셸
private float spreadAngle = 30f; // 퍼지는각도
protected override void Awake()
{
base.Awake();
initializeAmmo = 50; // 총기 최대 탄약
maxLoadedAmmo = 5; // 장전될 수 있는 탄약
damage = 6; // 데미지
bulletRange = 5f; // 총알 발사 거리
fireRate = 1.26f; // 총알 발사 주기
recoilX = 0.5f; // 좌우 반동
recoilY = 10f; // 수직 반동
recoilRecoverySpeed = 5f; // 반동 회복 속도
reloadTime = 1.3f; // 장전 시간 //0.18f + 1.12f + 0.5f+2.12f
adsSpeed = 5; // 정조준 속도
adsFOV = 45; // 정조준시 CameraFOV
ResetAmmo(initializeAmmo); // 탄약 세팅
}
// 시작할 때, 탄약 세팅 함수
public void ResetAmmo(int _totalAmmo)
{
if (canReset)
{
loadedAmmo = maxLoadedAmmo;
remainAmmo = _totalAmmo - loadedAmmo;
canReset = false;
}
}
}
기존 라이플과 동일하게 설정을 해주고 추가적으로 쉘과 쉘이 퍼지는 각도 변수를 만들어 줬다.
탄약 세팅까지는 동일하다
2.슈팅과 장전
// 슈팅 함수
public override void Shoot(Transform _firePos)
{
if (Time.time >= nextFireTime)
{
nextFireTime = Time.time + fireRate;
base.Shoot(_firePos);
if (loadedAmmo <= 0)
{
Debug.Log("장전된 탄약 없음");
Reload();
canShoot = false; // 총알 없으면 슈팅 불가능
}
}
// 장전 중이면 장전 끝
if (isReloading && loadedAmmo > 0)
{
isReloading = false;
return;
}
}
// 장전 함수
public override void Reload()
{
if (isReloading)
{
Debug.Log("재장전중");
return;
}
if (loadedAmmo == maxLoadedAmmo)
{
Debug.Log("탄창 꽉참");
return;
}
if (remainAmmo <= 0)
{
Debug.Log("남은 탄약 없음");
return;
}
isReloading = true;
loadedAmmo++;
remainAmmo--;
canShoot = true;
isReloading = false;
}
처음에 말했던 샷건은 장전이 한발씩 되야 하기 때문에, 부모 클래스에서 받아오지 않고 재정의를 해줬다.
또한, 한발씩 장전이 되다 보니까 장전 중 적이 나타나면 장전을 그만두고 싸워야 하니 장전을 멈출 수 있는 로직도 만들어줬다. 장전 중 마우스 왼쪽키를 누르면 장전이 끊기게 설계했다.
3. 발사 함수
샷건의 가장 큰 특성은 가까이서 쏘면 굉장한 강한 데미지를, 멀리서 쏘면 굉장히 약한 데미지를 입히게 된다.
이유는, 샷건의 탄 퍼짐은 쉽게 말해 원뿔 모양의 형태로 총구에서 부터 점점 퍼져나가기 때문이다.
아래 모양을 보면 조금 더 생각하기 쉬울 것이다.
그럼 구현한 코드를 보여드리겠다.
public override void PlayerFireBullet()
{
for (int i = 0; i < shell; i++)
{
Vector3 spreadDirection = CalculateSpreadDirection(spreadAngle, cam.transform);
RaycastHit hit;
if (canShoot)
{
Debug.DrawRay(cam.transform.position, spreadDirection * bulletRange, Color.red, 1f);
if (Physics.Raycast(cam.transform.position, spreadDirection, out hit, bulletRange))
{
if ((canAttackMask.value & (1 << hit.transform.gameObject.layer)) == 0)
{
continue;
}
}
}
}
}
// 탄퍼짐 계산 함수
private Vector3 CalculateSpreadDirection(float _speadAngle, Transform _firePos)
{
// 균일한 원 내의 랜덤한 점 생성
float randomRadius = Random.Range(0f, 1f); // 원의 중심으로부터 거리
float randomAngle = Random.Range(0f, 2f * Mathf.PI); // 360도 사이의 무작위 각도
// 원뿔모양의 표면의 값을 점으로 변환
float x = Mathf.Cos(randomAngle) * randomRadius;
float y = Mathf.Sin(randomAngle) * randomRadius;
float z = Mathf.Sqrt(1f - randomRadius * randomRadius); // z좌표는 점이 단위 구의 표면 위에 위치
// 퍼짐 각도 적용
Vector3 spreadVector = new Vector3(x, y, z); // Vector3 값으로 변환
// 각도를 정규화
spreadVector = Vector3.Slerp(Vector3.forward, spreadVector, _speadAngle / 180f);
// 총알 나가는 곳을 기준으로 확산
return _firePos.rotation * spreadVector;
}
코드의 아래쪽 CalculateSpreadDirection 함수는 탄퍼짐을 계산해주는 함수이다.
1. 일단 원의 중심에서 랜덤한 거리( 0~1 )의 랜덤한 각도( 0 ~ 360도 ) 를 선언해주었다.
그림으로 말하자면 아래 사진 부분에 찍힐 점의 위치이다.
// 원의 중심으로부터 거리
float randomRadius = Random.Range(0f, 1f);
// 360도 사이의 무작위 각도
float randomAngle = Random.Range(0f, 2f * Mathf.PI);
2. 그 후에 원뿔 표면상의 점을 계산해준다.
쉽게 말하자면 위에서 해준 작업은 2차원의 원 위에 찍힐 점의 위치를 정해준 것이고
이번에 할 작업은 3차원으로 바꿔준다고 생각하면 쉽다.
float x,y는 원 위의 점을 극좌표계에서 직교좌표계로 변환하고
float z는 피타고라스의 정리 함수인 sqrt를 이용해 단위구 표면위의 z좌표를 계산한다
이렇게 하면 xyz는 단위구 표면 위의 한 점이 된다.
float x = Mathf.Cos(randomAngle) * randomRadius;
float y = Mathf.Sin(randomAngle) * randomRadius;
float z = Mathf.Sqrt(1f - randomRadius * randomRadius);
3. 이제 점의 위치를 구했으니 vector3로 변환하고 선형보간을 이용해 각도를 정규화 하면
지정된 퍼짐 각도 내에서 랜덤한 방향이 생성된다.
Vector3 spreadVector = new Vector3(x, y, z);
spreadVector = Vector3.Slerp(Vector3.forward, spreadVector, spreadAngle / 180f);
4. 마지막으로 총알이 발사될 위치에 곱해 리턴으로 넘겨주면 샷건의 탄퍼짐 계산이 끝난다.
return _firePos.rotation * spreadVector;
5. 계산된 값을 가져와 라이플처럼 대입만 해주면 샷건의 발사가 완성된다.
Vector3 spreadDirection = CalculateSpreadDirection(spreadAngle, cam.transform);
내가 구현한 샷건의 단점이라면 점이 찍히는 위치가 완전 랜덤이라 편차가 너무 크다는 점이다.
이부분은 레벨디자인 혹은 수정을 통해 보완해야할 것이다.
결과
'유니티 > 기능구현' 카테고리의 다른 글
[Unity] 유니티 C#) 2D 매트로베니아 미니맵과 전체맵 ( 유니티 설정편 ) (0) | 2024.12.19 |
---|---|
[Unity] 유니티 C#) FPS 주무기를 어떻게 구현할까? ( 저격총 ) (4) | 2024.10.27 |
[Unity] 유니티 C#) FPS 주무기를 어떻게 구현할까? ( 돌격소총 ) (0) | 2024.10.17 |
[Unity] 유니티 C#) FPS 주무기를 어떻게 구현할까? ( 주무기 클래스 ) (10) | 2024.10.17 |
[Unity] 유니티 C#) 투척무기 구현을 어떻게 할까? ( 던지기 ) (0) | 2024.08.11 |