개요
사소한거 하나하나까지 다 짚고 넘어가는 개발 일지는 너무 사치였습니다.
시간이 많을때나 가능할 행동을 프로젝트 3개 병행하면서 하기엔 너무 빡세죠.
심지어 프로젝트 3개중 2개는 블로그를 적을 예정입니다.
실제로 이정도의 State를 적었음에도 다 옮겨담지 못하는 이유가 있습니다.
하나하나 다 적기엔 너무 빡세거든요. 그래서 대충 어떠한 구조인지만 적었습니다.
오늘은 기획이 조금 바뀌었고 개발을 다시 시작하겠습니다.
기획 변경?
매주 금요일 게임엔진응용 시간, 저희는 선생님에게 게임 진행 상황을 보고 해야합니다.
일단 제가 너무 진전이 더뎠고 기획도 너무 과했어요.
저도 알고 있었고 선생님은 당연히 알고 계셨겠죠.
무기를 4개가 아닌 1가지, 총만 냅두기로 했습니다.
할일이 줄긴 했습니다. 이제 해야할건 총 구현하고 적 구현하고 맵만 만들면 끝이죠.
애니메이션 수정
일단 맨손 기반의 애니메이션을 전부 총을 들고 움직이도록 수정했습니다.
애니메이터의 레이어를 사용해서 어떠한 상황이던 총을 들고 있도록 했습니다.
총 모델은 인터넷에서 가져왔는데 잘 어울리는지는 모르겠네요.
사실 그래픽 스타일은 맵 분위기가 가장 중요하기 때문에..
총 쏘기
총쏘는 방식엔 크게 두가지가 있습니다.
히트스캔과 투사체 방식인데요. 둘 다 장단점이 있지만 전 히트스캔을 사용할겁니다.
왜냐하면 히트스캔이 플레이 하는 입장에서도 쉽고 만드는 입장에서도 쉽거든요.
공격 했을때 카메라에서부터 forward 방향에 적이 있는지만 판단해주면 됩니다.
쉽죠?
캐싱을 안하니 코드가 좀 길긴 한데... 뭐 어떻습니까.
트레일도 추가하니 조금 총같아졌군요. 와!
타격감
다른 게임들과 달리 FPS 총게임은 타격감을 살리기 매우 힘듧니다.
1인칭이기 때문에 타격감의 국룰인 카메라 흔들기를 사용하면 게임 플레이를 방해합니다.
그렇기 때문에 타격감을 살리려면
총을 쐈을때 총소리가 나고 (SFX)
+ 총을 쐈으니 화약이 터지는 이펙트가 있어야 하고 (VFX)
+ 반동이 발생해야 하고 (카메라 움직임)
+ 적이 맞았다면 적이 맞는 이펙트가 있어야 하고 (VFX)
+ 적이 맞는 소리가 있어야하고 (SFX)
+ 적이 맞았으니 그에 맞는 반응이 나와야 합니다. (애니메이션)
이걸 다 챙겨야 타격감이 나올까 말까 고민합니다.
일단 VFX나 SFX나 애니메이션 전부 지금 하기엔 조금 이르니 반동만 빠르게 해보도록 하죠.
반동
반동은 쉽습니다.
쏠때마다 카메라의 XYZ 축을 랜덤으로 회전시키고 천천히 다시 기본으로 돌아오면 되죠.
그러면 일단 회전할 값을 저장할 변수와 현재 회전을 저장할 변수가 필요합니다.
private Vector3 _currentRot;
private Vector3 _targetRot;
그리고 발사 할때마다 회전을 하기 위해 회전 시키는 함수를 적어줍니다.
private void Recoil()
{
_targetRot += new Vector3(_recoilX,
Random.Range(-_recoilY, _recoilY),
Random.Range(-_recoilZ, _recoilZ));
}
Y,Z 축과 다르게 X축만 양수를 사용하는 이유는 총쏘는데 총이 내려가면 이상하니까 그런겁니다.
아무튼 이걸 이제 적용시켜주면 끝입니다.
private void Update()
{
_targetRot = Vector3.Lerp(_targetRot, Vector3.zero, _returnSpeed * Time.deltaTime);
_currentRot = Vector3.Slerp(_currentRot, _targetRot, _snappiness * Time.deltaTime);
_headTrm.localRotation = Quaternion.Euler(_currentRot);
}
선형보간은 신이 맞습니다.
값을 대충 세팅 한 다음에 사용해보면 대충 이렇습니다.
이러면 반동은 끝입니다.
연사
지금 총은 연사가 없습니다.
왜냐하면 입력을 받을때 입력이 들어온 것만 받기 때문입니다.
연사를 구현하기 위해선 공격키가 때졌는지 판단할 필요성도 있습니다.
public void OnAttack(InputAction.CallbackContext context)
{
if (context.performed)
OnAttackStartEvent?.Invoke();
if (context.canceled)
OnAttackEndEvent?.Invoke();
}
Action을 하나 더 추가해서 입력이 들어올때, 입력이 끝날때를 감지해줍시다.
그리고선 기존의 총 쏘기 구현부를 Fire() 함수로 따로 옮겨주고,
private void Fire()
{
RaycastHit hit;
Recoil();
Physics.Raycast(Camera.main.transform.position, Camera.main.transform.forward, out hit, Camera.main.farClipPlane, _whatIsObstacle | _whatIsEnemy);
BulletTrail trail = PoolManager.Instance.Pop(PoolingType.VFX_BulletTrail) as BulletTrail;
trail.DrawTrail(_firePos.position, _visualCam.transform.forward * _visualCam.farClipPlane, 0.03f);
if (hit.collider != null)
{
if (hit.collider.TryGetComponent(out Health health))
{
health.GetDamage();
}
}
}
코루틴을 사용해 간단하게 연사를 구현해줍니다.
private IEnumerator FireCoroutine()
{
var ws = new WaitForSeconds(_fireDelay);
while(true)
{
Fire();
yield return ws;
}
}
그리고 코루틴을 저장해서 제때 켰다가 꺼주면 됩니다.
private void HandleOnAttackStartEvent()
{
_fireCoroutine = StartCoroutine(FireCoroutine());
}
private void HandleOnAttackEndEvent()
{
StopCoroutine(_fireCoroutine);
}
하지만 이렇게 되면 꾹 눌러서 연사를 하지 않고 그냥 오토마우스 같은 것을 사용해서 엄청나게 빠르게 클릭하면 딜레이고 뭐고 그냥 엄청나게 빠른 총이 돼버립니다.
그렇기 때문에 마지막으로 연사가 끝난 시간을 저장한 뒤
_lastAttackTime = Time.time;
코루틴에서 WaitUntil을 사용해서 잠시 제동을 걸어줍시다.
yield return new WaitUntil(() => _lastAttackTime + _fireDelay > Time.time);
이러면 연사 끝입니다.
마치는 말
이제 좀 총같아졌습니다.
글을 쓰면서 시각 자료가 너무 없다고 생각해서 이번엔 시각자료를 조금 늘렸습니다.
보기는 좀 괜찮나요?
'개발 > UNNAMED' 카테고리의 다른 글
UNNAMED | 네번째 프로젝트 # 6 : 버튼과 기획 변경 (1) | 2024.06.10 |
---|---|
UNNAMED | 네번째 프로젝트 # 5 : 적 FSM (0) | 2024.06.08 |
UNNAMED | 네번째 프로젝트 # 3 : Player FSM (0) | 2024.06.01 |
UNNAMED | 네번째 프로젝트 # 2 : FPS Player Movement (0) | 2024.06.01 |
UNNAMED | 네번째 프로젝트 # 1 : 사전 준비 및 New Input System (0) | 2024.05.27 |