(유니티3D) FPS 총기 반동 구현하기 1
유니티를 처음 만졌을때부터 저는 FPS 시점을 굉장히 좋아했습니다.
직접 만든 3D 공간을 직접 돌아다니는 기분을 맛 볼 수 있었고 무엇보다 캐릭터의 모습을 보지 않아도 됐기 때문이죠.
연습삼아 여러 게임을 만들때마다 에셋스토어에서 무료 캐릭터를 다운받기도 귀찮으니 자연스레 캐릭터는 캡슐이 되고 밖에서 보면 이 모습이 영 마음에 들지 않았습니다.
하지만 FPS 시점은 자신의 모습이 보이지 않아도 됩니다! 이건 다시 말해 만약 싱글 FPS 게임을 만든다고 할 때 자신 캐릭터를 만들어야 하는 과정을 무시해도 된다는 된다는 소리입니다! (물론 온라인이면 상대방이 나를 볼테니 무시 할 수 없겠지만요.)
그리고 FPS 게임이라고 하면 게임 좀 해본 사람들이라면 현 상태에서 위화감이 느껴질 수도 있습니다.
왠지 화면 우측 하단에 뭔가 총이나 무기를 들고 있어야만 할 것 같은 느낌이 무럭무럭 솟아나죠.
그래서 총을 달아주고 뭔가를 발사하는 단계까지는 많이들 해보셨을겁니다.
하지만 이 이상의 기능을 구현한 사람은 그리 보지 못했습니다. 물론 제가 본 것 기준이라 그런 것일 수도 있겠지만 의외로 인터넷에도 총기 반동 관련해서 검색해보면 그리 많이 보이지는 않습니다.
그래서 한 번 직접 구현해보고 싶어져서 해봤습니다.
이 글은 단 하나, 총기 모델링만을 에셋 스토어에서 무료로 받아 사용하고 있으며 그외의 외부 리소스는 일체 사용하고 있지 않습니다.
그래서 사운드, 애니메이션, 탄흔 전부 없으며 총기반동 연출도 요즘 게임에 비하면 조잡할 뿐더러 방식 또한 효율적이지 않을 수 있습니다.
간단하게 프로그래머가 총을 좀 더 그럴듯 하게 만들고 싶을 때 보면 좋을 것 같습니다.
그리고 방식은 히트스캔(레이캐스트) 방식을 사용했습니다.
먼저 결과 화면부터 보시겠습니다.
gif 프레임을 낮게 찍었더니 반동이 그다지 느껴지지 않는게 아쉽군요.
이상하게 생긴 맵은 쉐이더 공부한다고 이것저것 만들어서 그렇습니다.
아무튼 여기서 볼 수 있는 것은
1. 벌어지는 에임
2. 벌어지는 에임에 맞춰 낮아지는 명중률
3. 반동 연출을 위한 총기가 밀리는 효과
4. 반동으로 인해 위로 솟구치는 카메라
정도가 되겠네요.
하지만 총기 반동을 적용시키기 전에 기본 FPS 세팅이 필요합니다.
캐릭터가 있고, 1인칭 시점에, 마우스로 시선을 움직이고, 에임이 있습니다.
이 글이 생각보다 길어져서 2개로 나누었습니다. 이 글에서는 그 기본 세팅에 대해서 설명하며 총기 반동은 두 번째 글에서 작성토록 하겠습니다.
기본세팅
FPS니까 캐릭터는 캡슐로 써도 됩니다. 어차피 유저는 안 보이니까요. 그리고 오른손잡이니까 오른쪽에 총을 달아줍니다.
총구에는 기즈모를 달아둡니다. 총알이 나갈 위치입니다.
그리고 대충 눈 높이에 카메라를 달아주는데 여기서 하나 중요한게 있습니다.
일반적으로 하나의 카메라를 사용하고 위와같이 세팅하면
이런식으로 총이 어딘가에 파묻히게 됩니다. 우리가 원하는건 이런 형태가 아니기 때문에 다른 방법을 쓸 필요가 있습니다.
여러 방법이 있을 수 있겠지만 가장 간단하고 자연스러운 것은 카메라를 2개 쓰는 것이 아닐까 생각합니다.
이런식으로 메인카메라와 별개로 카메라를 하나 만들어서 동일한 위치에 배치시켜줍니다.
aim에 대해서는 아래서 설명하겠습니다.
이후 카메라 설정을 좀 바꿔줍니다.
Main <-> UI
메인 카메라는 Culling Mask 하나만 바꿉니다. 메인카메라에서도 총을 그릴 필요는 없기 때문에 총에 레이어를 하나 달아준 다음 그 레이어만 제외시킵니다. 저는 이미 마련되어있는 UI 레이어를 할당해주었습니다.
그리고 UI 카메라는 Clear Flags를 Depth only로 바꾼다음 Culling Mask에 메인카메라에서 제외시킨 레이어를 설정합니다.
Depth only인 카메라는 Culling Mask에 설정되지 않은 레이어는 아예 그리지 않습니다. 그리고 위 이미지에서 맨 아래 있는 Depth만 따져서 그려집니다.
고로 기본 Depth가 -1이므로 +1 해주어 0으로 설정해줍니다. 그러면 총의 위치는 그대로이지만 유저의 시점에선 총이 파묻히지 않게 됩니다.
파묻히지 않는 총
다음은 에임을 설정해보겠습니다.
FPS 장르는 대부분 화면 중앙을 알려주는 에임이라고 불리는 UI가 있습니다.
이건 화면 중앙임을 알려주기만 하면 되기 때문에 형태, 색, 크기는 개발자가 임의로 고정시켜도 되고 유저에게 선택권을 쥐어주게 해도 됩니다.
여기서는 이후 반동시 에임이 벌어지는 것을 보다 확실하기 보기 위해 십자 형태로 했습니다.
그리고 위에 Hierarchy 이미지에서도 알 수 있듯 카메라에 직접 박아넣었지만 이 방법보다는 UGUI를 활용하는 편이 좋다고 생각합니다. 다만 저는 반동의 영향을 받을 때 레이캐스트 목표 지점을 좀 더 직관적으로 하려고 이렇게 했습니다.
어떤 방법을 사용하든 이후 반동 반영 방식은 거의 동일합니다.
이렇게 큐브 4개를 써도 되고 UGUI에서 이미지 4개를 써도 됩니다.
그리고 이후 코딩의 간결화를 원한다면 한 가지 작업을 더 해주어야 합니다.
바로 피봇 설정인데 위에가 부모이고 아래가 자식입니다. 아래가 실제 큐브 정보를 담고 있습니다.
여기서 부모는 무엇을 하는가? 회전을 합니다.
이런식으로 4개의 에임을 설정해두면 자식의 정보는 완전 동일하게 사용할 수 있으며 부모만 돌리면 됩니다.
그리고 이것은 이후 반동으로 에임이 벌어질때 로컬 좌표로 자식만 동일하게 위로 이동시키면 알아서 부모의 영향을 받아 4방향으로 벌어지게 됩니다.
이는 UGUI로 했을 때도 동일할 것이라 생각합니다.
이러면 에임이 벌어지는 코딩이 for문 하나로 줄어듭니다. 어디까지나 코딩의 간결화를 위한 과정이기에 위 과정이 더 귀찮다면 생략해도 됩니다.
다음은 카메라 회전입니다.
아마 저처럼 FPS 시점을 자주 사용하신다면 이미 알고 계시리라 생각합니다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | using UnityEngine; public class FPSMouseCtrl : MonoBehaviour{ public Transform mainCamera; public Transform uiCamera; float cameraY = 0; void Update() { MouseLook(); } void MouseLook() { transform.Rotate(Vector3.up * Input.GetAxisRaw("Mouse X") * 100.0f * Time.deltaTime); //좌우는 몸이 통째로 돌아간다. *100.0f는 감도 조절 cameraY -= Input.GetAxisRaw("Mouse Y") * 100.0f * Time.deltaTime; //상하는 카메라만 움직인다. if (cameraY < -90) //위로 쳐다볼때 뒤로 넘어가지 않도록 한다. cameraY = -90; else if (90 < cameraY) //아래로 쳐다볼때 뒤로 넘어가지 않도록 한다. cameraY = 90; mainCamera.localRotation = Quaternion.Euler(cameraY, 0, 0); //메인카메라와 ui카메라에 같이 적용시킨다. uiCamera.localRotation = Quaternion.Euler(cameraY, 0, 0); } } | cs |
간단하게 마우스를 통해 화면을 둘러보는 코드입니다.
이미 마우스 입력 값이 기본 Input으로 "Mouse X"와 "Mouse Y"가 준비되어있기 때문에 그대로 사용하면 됩니다.
FPS에서 좌우를 보는 것은 몸을 통째로 움직이는 것이기에 캡슐에 바로 Rotate 함수로 회전시켜주면 되며 상하는 캡슐이 돌아가면 안되므로 카메라만 움직여줍니다.
이 때 카메라는 한 바퀴 돌게 되면 안 되므로 각도에 제한을 걸어줍니다.
각도 적용은 localEulerAngles을 사용해서 적용해도 됩니다.
기본 설정의 마지막입니다. 시선을 돌릴 수 있고 총이 있고 에임이 있으니 이젠 쏴야겠죠.
그리고 FPS 하면 히트스캔 방식이고요. 요즘에는 탄도학을 적용하는 FPS가 늘어서 히트스캔이 아닌 게임들도 많습니다만 여기선 히트스캔으로 가겠습니다.
그리고 히트스캔을 위한 기능으로 유니티에선 레이캐스트를 지원하고 있습니다.
보통 일반 현대 총기류를 다루는 FPS 게임들은 총알의 궤적을 그리는 경우가 많지 않습니다.
대부분 발사 순간의 파열 연출과 탄착 연출, 그리고 탄흔으로 총이 히트스캔으로 박힌 것을 표현합니다.
하지만 전 그런 리소스가 없기때문에 궤적을 그려서 경로와 목적지를 그렸습니다.
Line Renderer 컴포넌트를 활용했습니다.
Line Renderer 컴포넌트가 들어간 오브젝트를 발사 직후에만 잠깐 켜지도록 하고 총구에 있는 기즈모로부터 레이캐스트가 찍힌 지점까지를 연결 시키면 이와 같이 그려집니다.
그리고 총 하면 역시 자동소총이 일반적이지 않을까 합니다. 그래서 총에는 기본적으로 연사 기능을 달아주었습니다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 | using System.Collections; using System.Collections.Generic; using UnityEngine; public class Shoot : MonoBehaviour { public Transform GIZMO; //총구 좌표 public LineRenderer BEAM; //궤적 public int setDelay = 5; //발사 간격 FixedUpdate이므로 setDelay / 50 초마다 발사 int delay = 0; //발사하고 흐른 시간 // Use this for initialization void Awake () { BEAM.startWidth = 0.05f; BEAM.endWidth = 0.001f; //궤적 두께 설정 초기화 } // Update is called once per frame void FixedUpdate () { //발사 간격의 등간격을 위해 FixedUpdate 내에서 진행 RaycastHit hit; if (Input.GetMouseButton(0)) { if (delay <= 0) //딜레이가 걸려있지 않을 때만 발사 { delay = setDelay; //발사했다면 딜레이가 생긴다. if (Physics.Raycast(transform.position, transform.forward, out hit, 1000.0f)) { BEAM.SetPosition(1, hit.point); //레이가 맞았다면 맞은 지점까지를 궤적으로 그린다. } else { BEAM.SetPosition(1, transform.position + transform.forward * 1000.0f)); //레이가 맞지 않았다면 에임이 있는 거리에 가상으로 끝점을 그린다. } StartCoroutine(ShootBeam()); //궤적을 그리는 코루틴 작동. } } if (0 < delay) //딜레이가 있다면 매 프레임 줄인다. delay--; } IEnumerator ShootBeam() { BEAM.SetPosition(0, GIZMO.position); //궤적의 시작점을 현재 기즈모의 위치로 한다. BEAM.gameObject.SetActive(true); //궤적을 활성화 yield return SingletoneValue.Instance.fixedDelay; //3프레임 지속 후 궤적을 끈다. yield return SingletoneValue.Instance.fixedDelay; yield return SingletoneValue.Instance.fixedDelay; BEAM.gameObject.SetActive(false); } } | cs |
등간격으로 연사를 할 수 있으며 궤적을 그리는 코드입니다.
SingletoneValue.Instance.fixedDelay 이 부분은 싱글톤에서 new WaitForFixedUpdate()를 미리 해둔 것입니다. 사소하지만 가비지를 줄이기 위해 해뒀습니다.
연사의 등간격을 위해 FixedUpdate 내에서 코딩했습니다.
좀 더 자연스러움을 더하고자 한다면 그냥 Update에서 하거나 랜덤성을 따로 주어 완전 등간격에선 벗어나는 편이 좋을지도 모르겠습니다만 거기까진 고려하지 않았습니다.
그리고 여기서 하나 재밌는 것을 볼 수 있는데
왼쪽: 씬뷰 오른쪽: 게임뷰
씬뷰에서 보면 궤적도 뒤집힌 것처럼 보이고 총알이 아예 거꾸로 발사된 것 같이 보입니다.
하지만 게임뷰에선 아무런 문제가 없죠
궤적은 총구에서부터 시작되지만 레이는 카메라로부터 쏘아져서 생기는 증상입니다.
일종의 게임적 허용 부분이긴 한데 유저 시점에선 멀쩡해 보이는 FPS에선 딱히 신경 쓰지 않아도 됩니다.
여담이지만 TPS 장르에선 위 증상이 게임마다 다릅니다.
대표적으로 사이퍼즈는 위와 동일하게 궤적은 총에서부터 그려지지만 레이는 카메라에서 쏩니다. 그래서 특정 상황에선 카인 같은 캐릭터의 총알이 뒤로 나가는 것을 볼 수 있습니다.
하지만 배틀그라운드의 경우엔 총알도 총구에서부터 나갑니다. 배틀그라운드는 비록 히트스캔 방식이 아닙니다만 어떤 느낌인지는 아시리라 생각됩니다.
사이퍼즈가 잘못되었다는 것도 아닙니다. 현실성을 강조한 총게임도 아니고 유저에게는 오히려 에임만 신경쓰면 되기에 더 편할 수도 있습니다.
이러한 부분은 개발자의 판단에 따라 응용하면 좋을 것 같습니다.
여기까지 가장 기초적인 히트스캔 방식의 FPS의 겉모습은 모두 완성되었습니다.
움직이는 것은 따로 적지 않았지만 마우스로 시선을 돌리고, 중앙에 에임이 있으며, 마우스를 클릭하고 있으면 에임의 중앙으로 연사를 합니다. 씬 뷰에서는 벽에 붙으면 총이 벽을 뚫고 가지만 유저의 시점에서는 아무런 문제가 없습니다. 이걸 좀 더 개량하면 발사 지점에 물리력을 줄 수도 있고 리소스들을 활용하여 더 멋진 연출 또한 가능합니다.
하지만 이 글은 반동에 중점을 두었기에 여기서는 언급하지 않겠습니다.
다음글은 여기서 만든 것을 토대로 반동을 구현해보겠습니다.
'정리' 카테고리의 다른 글
(유니티3D) php와 mysql로 서버 DB 이용하기 1 (0) | 2018.02.21 |
---|---|
(유니티3D) FPS 총기 반동 구현하기 2 (0) | 2018.01.24 |
(유니티 2D) UGUI Canvas 설정 (0) | 2017.12.28 |
(유니티 2D) 카메라 사이즈와 Pixels Per Unit (1) | 2017.12.28 |
(유니티3D)CharacterController 횡스크롤 캐릭터 (0) | 2017.12.17 |