(유니티3D) FPS 총기 반동 구현하기 2
작성에 앞서 여기서 적은대로 따라하시면 굉장히 투박합니다.
랜덤 값을 주지도 않고 FixedUpdate로 완전 등간격으로 이루어지며 애니메이션 없이 단순 Transform 값의 변경과 복귀 과정으로만 이루져있기 때문에 참고로만 봐주시면 감사하겠습니다.
이전 글에 이어서 이번엔 총기에 반동을 주도록 하겠습니다.
이전 글에서 2개의 코드를 보여드렸었는데 그 코드들에서 몇 개를 추가해야 합니다.
그리고 반동 영향 값에 비례해서 카메라에도 영향을 줘야합니다. 때문에 둘이 공유하는 값이 하나 있어야 하는데 저는 싱글톤으로 두 코드에서 같이 활용할 수 있도록 했습니다. 한 쪽 코드에 public으로 만들어 두고 이용해도 상관 없습니다.
이번 글에선
1. 벌어지는 에임
2. 벌어지는 에임에 맞춰 낮아지는 명중률
3. 반동 연출을 위한 총기가 밀리는 효과
4. 반동으로 인해 위로 솟구치는 카메라
를 적어두었는데 이번에 구현해보겠습니다.
1. 벌어지는 에임은 이전 글에서 적은 피봇 방식을 적용했다는 가정 하에 진행하겠습니다.
부모 회전시키고 피봇을 삼았기 때문에 에임 큐브가 들어있는 자식들의 로컬 Transform은 모두 동일합니다.
그러니 코드에선 이 자식들만을 가져와서 동일하게 이동을 시켜주면 알아서 각자의 방향으로 벌어지는 효과가 나타나게 됩니다.
1 2 3 4 | foreach (GameObject aim in aims) //에임을 발사할때마다 0.1씩 벌어지게 한다. { aim.transform.Translate(Vector3.up * 0.1f); } | cs |
저같은 경우엔 기준이 위에 있는 에임 큐브이므로 Vector3.up 방향으로 이동시켜 주었습니다.
0.1f는 적당히 감으로 집어넣었습니다. 이러면 발사 할 때마다 0.1f 씩 벌어지게 됩니다.
하지만 이것으로 끝이면 에임이 돌아오지 않기 때문에 되돌려주기도 해야합니다.
되돌려주는 코루틴을 따로 작성하고
1 2 3 4 | foreach (GameObject aim in aims) { aim.transform.localPosition = Vector3.Lerp(aim.transform.localPosition, new Vector3 (0, 0.275f, 15.0f), Time.deltaTime * 3.0f); //에임의 위치도 Lerp로 천천히 되돌린다. } | cs |
거기엔 매 프레임 되돌리는 코드를 작성합니다.
new Vector3 (0, 0.275f, 15.0f) 는 에임의 초기 로컬 좌표입니다.
여기서는 Lerp를 썼습니다. 벌어질 수록 더 큰 값으로 줄어들어 어느정도 연사할때까지는 에임이 점점 더 벌어지다 벌어지는 속도와 줄어드는 속도가 일치하면 더이상 벌어지지 않기 때문입니다.
그런데 이러면 한 발만 쏴도 완전히 돌아오기까지 걸리는 시간이 꽤 오래 걸리는 단점이 있습니다.
크게 신경 쓰이는 정도는 아니지만 정확한 명중률이 중요한 게임이라면 MoveTowards를 사용하는 것도 좋습니다.
반동이 일정량 이상이면 Lerp를 적용하고 이하면 MoveTowards로 되돌리게 해도 되고 아예 MoveTowards로 하되 반동에 최대값을 설정해 두면 무한히 에임이 벌어지는 것을 방지 할 수 있습니다.
2. 에임은 벌어졌지만 레이캐스트는 여전히 카메라 중앙을 향하고 있기에 실제 명중률에는 아무런 영향이 없습니다. 에임이 벌어지는 것과 비례하게 다른 곳을 쏘게 해봅시다.
반동과 비례하게 랜덤값을 가로로 한 번, 세로로 한 번 주어서 레이를 쏘게하면 간단합니다만 이러면 랜덤 범위가 사각형을 그리게 됩니다.
우리가 원하는 모양은 사각형이 아닌 원형이기 때문에 이 방법은 바람직하지 않습니다.
직접 원형의 랜덤값을 뽑는 식을 만들어도 되겠지만 다행히 유니티는 원형에서 랜덤값을 뽑는 함수가 마련되어 있습니다.
구에서도 뽑을 수 있고 구의 표면에서 뽑을 수도 있지만 굳이 구까지는 필요하지 않으므로 원형을 이용합니다.
Random.insideUnitCircle 이것을 이용하면 반지름이 1인 원 내의 무작위한 좌표를 Vector2의 형태로 리턴합니다.
1 2 3 4 5 6 7 8 9 10 11 | Vector2 reboundRay = Random.insideUnitCircle * SingletoneValue.Instance.rebound * 0.033f; //반동이 있다면 반동만큼의 크기를 가진 원의 범위 내에서 랜덤값을 가진다. if (Physics.Raycast(transform.position, transform.TransformDirection(new Vector3(reboundRay.x, reboundRay.y, 15)) , out hit, 1000.0f)) //랜덤값의 방향으로 레이를 쏜다. 15는 에임까지의 거리 { BEAM.SetPosition(1, hit.point); //레이가 맞았다면 맞은 지점까지를 궤적으로 그린다. } else { BEAM.SetPosition(1, transform.position + transform.TransformDirection(new Vector3(reboundRay.x, reboundRay.y, 15))); //레이가 맞지 않았다면 에임이 있는 거리에 가상으로 끝점을 그린다. } StartCoroutine(ShootBeam()); //발사 코루틴 작동. | cs |
이걸 이용하여 랜덤한 좌표를 얻어내고 그 방향으로 레이를 쏘도록 합니다.
SingletoneValue.Instance.rebound는 위에서 말한 반동 값입니다. 발사 코루틴이 작동 될때마다 값이 증가하며 에임과 동일하게 Lerp로 실시간으로 줄어듭니다.
만약 반동이 없다면 0이 들어갈테고 그럼 나머지 값에 상관없이 (0, 0)이 나오게 되어 첫 발은 정 중앙으로 가게 됩니다.
맞지 않았을 때도 마찬가지로 바꿔줍니다.
이러면 벌어진 에임 내에서 랜덤하게 궤적이 그려지게 됩니다.
3. 이건 사실 애니메이션으로 하는게 훨씬 더 자연스럽지 않을까 생각은 듭니다만 실제로 해본적도 없고 애니메이션을 만들고싶지도 않기에 위와 거의 동일한 방법으로 하였습니다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | Vector3 gunDefaultPos; //반동의 영향을 받지 않은 기본 위치 void Awake () { gunDefaultPos = gunModel.transform.localPosition; //기본 위치 초기화 } //발사 코루틴 gunModel.transform.localPosition = gunDefaultPos; //이전 반동의 영향을 받고 있어도 총기 반동 연출을 다시 시작한다. gunModel.transform.Translate(Vector3.forward * 0.3f); //반동으로 0.3만큼 뒤로 밀린다. //반동 초기화 과정 코루틴에서 gunModel.transform.localPosition = Vector3.Lerp(gunModel.transform.localPosition, gunDefaultPos, Time.deltaTime * 3.0f); //총기의 위치를 Lerp로 천천히 되돌린다. if (Vector3.Distance(gunModel.transform.localPosition, gunDefaultPos) < 0.001f) //총이 거의 제자리로 돌아왔다면 { gunModel.transform.localPosition = gunDefaultPos; isRebound = false; //모든 반동의 영향을 초기화 시키고 코루틴의 종료를 알리고 코루틴을 종료한다. } | cs |
관련 부분만 잘라놓은 코드입니다.
만약 총도 에임처럼 부모 피봇을 이용한다면 기본 위치를 따로 저장해 둘 필요 없이 Vector3.zero로 보내도 됩니다만 이건 그리 번거로운 과정도 아니고 이런식으로 해도 된다는 것을 보여드리고 싶어서 이렇게 했습니다.
그리고 에임과 달리 발사때마다 위치를 초기화 시켜주는 이유는 총기 자체의 반동은 굳이 가중 될 필요가 없을거라고 생각해서 그렇습니다.
4. 마지막으로 카메라가 솟구치게 해보겠습니다.
이 부분에 대해서는 개발자가 미리 어느정도 결정해두고 가야할 사항들이 있습니다.
- 반동으로 올라간 만큼 다시 내려오게 할 것인지, 아니면 그대로 놔둘 것인지
- 반동을 동일하게 줄 것인지, 랜덤하게 줄 것인지
- 반동을 위로만 줄 것인지, 좌우로도 줄 것인지
- 시선이 더이상 올라가지 않는 최대치 상태에서도 더 카메라가 상승하게 할 것인지
FPS의 카메라는 시선임과 동시에 몸이 향하는 방향과 일치하기 때문에 카메라에 직접 영향을 주는 부분은 신중하게 결정해야 합니다.
이 코드의 경우엔 전부 앞쪽에 해당되며 마지막은 해당되지 않습니다.
발사 관련 코드에서는 SingletoneValue.Instance.rebound 의 값을 위와 비슷하게 세팅합니다.
발사하면 증가하며 시간이 흐를수록 Lerp를 통해 줄어듭니다.
그리고 이 값은 카메라에서 이용합니다.
이전 글에서 만든 마우스 움직임에 따라 시선이 움직이는 코드를 보면 마지막에 카메라 각도를 세팅하는 부분이 있습니다.
1 2 | mainCamera.localRotation = Quaternion.Euler((cameraY - SingletoneValue.Instance.rebound < -90 ? -90 : cameraY - SingletoneValue.Instance.rebound), 0, 0); uiCamera.localRotation = Quaternion.Euler((cameraY - SingletoneValue.Instance.rebound < -90 ? -90 : cameraY - SingletoneValue.Instance.rebound), 0, 0); | cs |
그 부분을 이렇게 바꿔주면 됩니다.
삼항식으로 만약 반동까지 더해져서 카메라가 뒤로 넘어가려 한다면 -90의 값이 들어가도록 하고 그렇지 않다면 더해진 값이 들어가도록 해줍니다.
이제 총기 반동과 관련하여 기본적인 것들을 전부 적용했습니다.
전체 코딩은 접어 두겠습니다.
이제 보다 FPS 게임의 모습 같아졌습니다.
총을 쏘면 총이 뒤로 밀리고 에임이 벌어지고 그에 맞춰 탄착 지점도 랜덤하게 결정됩니다. 그리고 반동으로 카메라도 올라갑니다.
비록 Translate와 Lerp를 주로 사용하고 모두 등간격이라 딱딱하면서 부자연스러운 모습을 보입니다만 이 부분은 개발자에 따라 조금만 다듬으면 될 문제 같습니다.
'정리' 카테고리의 다른 글
(유니티3D) php와 mysql로 서버 DB 이용하기 2 (코드) (0) | 2018.02.21 |
---|---|
(유니티3D) php와 mysql로 서버 DB 이용하기 1 (0) | 2018.02.21 |
(유니티3D) FPS 총기 반동 구현하기 1 (1) | 2018.01.24 |
(유니티 2D) UGUI Canvas 설정 (0) | 2017.12.28 |
(유니티 2D) 카메라 사이즈와 Pixels Per Unit (1) | 2017.12.28 |