[프로젝트] 유니티로 탄막슈팅게임 만들기 01
유니티에서 오브젝트들은 다음과 같이 만들었다.
Player 와 CameraPoint는 빈 오브젝트이며 이 두 오브젝트에 캐릭터와 카메라의 스크립트를 넣었다.
GameManager 오브젝트는 현재 사용하지 않는다. 이후 게임 스크립트의 전체적인 관리를 하게 될 스크립트를 넣을 것이다.
1-1. 캐릭터 움직임(Soft) 및 카메라 시점 구현 초기버전
C# 스크립트 작성을 통해 다음과 같은 움직임을 구현하였다.
W, A, S, D 키를 사용하여 캐릭터 오브젝트에 힘을 가하여 부드럽게 움직일 수 있고,
Left / Right Arrow를 사용하여 카메라 시점을 회전할 수 있도록 구현했다.
당연하게도 이러한 키 세팅은 사용하기 어렵다. (NieR:Automata™에서는 초기 키설정이 이러하며, 뉴비들은 이 세팅에 영혼까지 털린다)
위와 같이 3인칭 시점의 게임에서는 FINAL FANTASY XIV 정도의 세팅이 가장 편하다.
#1. W, A, S, D를 사용해 전, 후, 좌, 우로 움직인다.
#2. 카메라 시점은 마우스 좌 또는 우클릭 후 드래그하여 조절한다.
#3. 마우스 휠로 줌인 줌아웃을 할 수 있다.
#4. 캐릭터는 카메라 로컬 시점에 따른 전, 후, 좌, 우의 움직임을 갖는다.
위 사항 중 #2 부터 #4까지 구현하는 것을 목표로 한다.
추가 사항으로 마우스 좌클릭을 통해서는 캐릭터가 움직이는 방향에 영향을 주지 않고 '시점만' 변경할 수 있도록 구현하는 것도 엑스트라 과제이다.
현재까지의 소스코드
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 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 | using System.Collections; using System.Collections.Generic; using UnityEngine; public class Player : MonoBehaviour { public float moveSpeedF; public float maxSpeed; public float returnSpeedF; void Start () { //시작위치 지정 (보이지 않는 오브젝트 StartPoint로 지정) moveSpeedF = 10; maxSpeed = 10; returnSpeedF = 10; } void Update () { // 시프트를 누를 경우 maxSpeed와 moveSpeedF를 크게 한다. if (Input.GetKeyDown(KeyCode.LeftShift)) { moveSpeedF = 15; maxSpeed = 20; } if (Input.GetKeyUp(KeyCode.LeftShift)) { moveSpeedF = 10; maxSpeed = 10; } // Shift키를 떼었을 때 속도 저하 if(maxSpeed < GetComponent<Rigidbody>().velocity.z || (-1)*maxSpeed > GetComponent<Rigidbody>().velocity.z) { if (GetComponent<Rigidbody>().velocity.z < (-maxSpeed)) GetComponent<Rigidbody>().AddForce(Vector3.forward * returnSpeedF); if (GetComponent<Rigidbody>().velocity.z > (maxSpeed)) GetComponent<Rigidbody>().AddForce(Vector3.back * returnSpeedF); } if (Input.GetKey(KeyCode.LeftShift) == false && GetComponent<Rigidbody>().velocity.z - maxSpeed < 0.5 && GetComponent<Rigidbody>().velocity.z - maxSpeed > -0.5 && (Input.GetKey(KeyCode.W) == true || Input.GetKey(KeyCode.S) == true)) { if (GetComponent<Rigidbody>().velocity.z > 0) GetComponent<Rigidbody>().velocity = new Vector3(GetComponent<Rigidbody>().velocity.x, GetComponent<Rigidbody>().velocity.y, maxSpeed); if (GetComponent<Rigidbody>().velocity.z < 0) GetComponent<Rigidbody>().velocity = new Vector3(GetComponent<Rigidbody>().velocity.x, GetComponent<Rigidbody>().velocity.y, (-1) * maxSpeed); } Debug.Log(GetComponent<Rigidbody>().velocity.z); //wasd로 이동, 누르지 않을 때는 속도가 0으로 되돌아감 if (Input.GetKey(KeyCode.W)) { if(GetComponent<Rigidbody>().velocity.z<maxSpeed) GetComponent<Rigidbody>().AddForce(Vector3.forward * moveSpeedF); } if (Input.GetKey(KeyCode.A)) { if (GetComponent<Rigidbody>().velocity.x > (-1) * maxSpeed) GetComponent<Rigidbody>().AddForce(Vector3.left * moveSpeedF); } if (Input.GetKey(KeyCode.S)) { if (GetComponent<Rigidbody>().velocity.z > (-1) * maxSpeed) GetComponent<Rigidbody>().AddForce(Vector3.back * moveSpeedF); } if (Input.GetKey(KeyCode.D)) { if (GetComponent<Rigidbody>().velocity.x < maxSpeed) GetComponent<Rigidbody>().AddForce(Vector3.right * moveSpeedF); } // w,s 키를 누르지 않을 경우 속도 0 if ((!(Input.GetKey(KeyCode.W)) && !Input.GetKey(KeyCode.S))&&(GetComponent<Rigidbody>().velocity.z!=0)) { if (GetComponent<Rigidbody>().velocity.z < 0.5 && GetComponent<Rigidbody>().velocity.z > -0.5) { GetComponent<Rigidbody>().velocity = new Vector3(GetComponent<Rigidbody>().velocity.x, GetComponent<Rigidbody>().velocity.y, 0); } if (GetComponent<Rigidbody>().velocity.z < 0) GetComponent<Rigidbody>().AddForce(Vector3.forward * returnSpeedF); if (GetComponent<Rigidbody>().velocity.z > 0) GetComponent<Rigidbody>().AddForce(Vector3.back * returnSpeedF); } // a,d 키를 누르지 않을 경우, 좌우 속도는 0이 되도록 힘을 가한다. if ((!(Input.GetKey(KeyCode.A)) && !Input.GetKey(KeyCode.D)) && (GetComponent<Rigidbody>().velocity.x != 0)) { if (GetComponent<Rigidbody>().velocity.x < 0.5 && GetComponent<Rigidbody>().velocity.x > -0.5) { GetComponent<Rigidbody>().velocity = new Vector3(0, GetComponent<Rigidbody>().velocity.y, GetComponent<Rigidbody>().velocity.z); } if (GetComponent<Rigidbody>().velocity.x < 0) GetComponent<Rigidbody>().AddForce(Vector3.right * returnSpeedF); if (GetComponent<Rigidbody>().velocity.x > 0) GetComponent<Rigidbody>().AddForce(Vector3.left * returnSpeedF); } } } | cs |
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 | using System.Collections; using System.Collections.Generic; using UnityEngine; public class Camera : MonoBehaviour { GameObject player; float dx, dy, dz; float rotateSpeed; void Start () { player = GameObject.Find("Player"); //dx =, dy=, dz =; //rx =, ry=, rz =; rotateSpeed = 70; } void Update () { transform.position = player.transform.position; if (Input.GetKey(KeyCode.LeftArrow)) { transform.Rotate(Vector3.up * Time.deltaTime * rotateSpeed); } if (Input.GetKey(KeyCode.RightArrow)) { transform.Rotate(Vector3.down * Time.deltaTime * rotateSpeed); } //transform.position = new Vector3(player.transform.position.x + dx, player.transform.position.y + dy, player.transform.position.z + dz); } } | cs |
1-2. 캐릭터 움직임(Soft) 및 카메라 시점 구현 개선 버전
위에서 목표한 네가지 모두 구현을 완료하였다.
#1. W, A, S, D를 사용해 전, 후, 좌, 우로 움직인다.
#2. 카메라 시점은 마우스 좌 또는 우클릭 후 드래그하여 조절한다.
#3. 마우스 휠로 줌인 줌아웃을 할 수 있다.
#4. 캐릭터는 카메라 로컬 시점에 따른 전, 후, 좌, 우의 움직임을 갖는다.
카메라의 시점 변경은 우클릭 드래그로 하였다.
이외에 더 추가한 기능으로는
#5. 좌 Shift키를 사용하여 캐릭터의 속도 리미트를 높게 한다.
#6. 시프트에서 손을 놓았을 경우, 그리고 WASD 중 아무것도 누르지 않을 경우 감속할 수 있도록 한다.
#7. 카메라가 상하 90도 이상을 넘어가면 화면이 혼란해지므로 ±최대각을 적용하였다. (현재는 75도)
여기서 몇가지의 문제가 더 발생한다.
위 움짤에서 확인할 수 있듯이 '낙하'를 하는 경우에도 속도 리밋이 걸려 낙하에도 방해를 받는다. 조건을 x방향과 z방향에서만 받아주었는데도 문제가 해결되지 않아 난항.
-> 나중에 이유를 알았는데, 캐릭터 Rigidbody의 Drag 항목에 0이 아닌 값이 들어있어서 자체적으로 감속되는 힘이 작용하고 있기 때문이었다.
그리고 정말 세세한 조정이지만 좌우로 낮은 속도가 남아있는 동시에 앞으로 달리고 있다면 좌우 속도는 0이 될 수 있도록 감속을 주어야한다. 이건 선택사항이며 역시 난항.
유니티 프로그램 내에서 '마찰'을 조정하면 되겠으나 이는 코드로서 작성하지 않고있다는 이유로 기각.
지금까지 난항을 겪었던 이유는 캐릭터에 '힘을 주어' 움직이고 있기 때문이며 이 때문에 속도 조정에 있어 그 수치를 맞추는 것이 매우 힘들다. 코드를 수정할 수록 완성도가 높아지고 있는 건 사실이나 여기에 매우 스트레스를 받아 새로 하나의 스크립트를 작성하게 되었다.
원래 사용하던 코드는 'PlayerSoftMove.cp'로 바꾸었으며
새로 작성하게 될 코드는 'PlayerHardMove.cp'이다.
파일 이름과 같게 캐릭터의 부드러운 움직임을 포기한 코드를 작성한다.
~현재까지의 C# 스크립트 코드 두개~
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 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 | using System.Collections; using System.Collections.Generic; using UnityEngine; public class PlayerSoftMove : MonoBehaviour { public float moveSpeedF; public float maxSpeed; public float returnSpeedF; float currentSpeed; float breakSpeedF; Vector3 tmpPosition, tmpLocalPosition; GameObject cameraPoint; private Rigidbody rigid; public float JumpPower; private bool IsJumping; void Start() { //시작위치 지정 (보이지 않는 오브젝트 StartPoint로 지정) returnSpeedF = 10; cameraPoint = GameObject.Find("CameraPoint"); tmpPosition = transform.position; tmpLocalPosition = transform.localPosition; JumpPower = 10; } void Update() { // 점프 //Jump(); if (Input.GetKeyDown(KeyCode.Space)) { Debug.Log("점프 가능 !"); IsJumping = true; GetComponent<Rigidbody>().AddForce(Vector3.up * JumpPower, ForceMode.VelocityChange); } // 캐릭터 y축 = 카메라 y축 transform.rotation = cameraPoint.transform.rotation; transform.eulerAngles = new Vector3(0, transform.eulerAngles.y, 0); // MaxSpeed와 가속도 초기화 moveSpeedF = 50; maxSpeed = 10; breakSpeedF = moveSpeedF * 10; // 현재 속도 측정 currentSpeed = Mathf.Sqrt(Mathf.Pow(GetComponent<Rigidbody>().velocity.z,2)+ Mathf.Pow(GetComponent<Rigidbody>().velocity.x, 2)); // 시프트를 누를 때 maxSpeed와 moveSpeedF를 크게 한다. if (Input.GetKey(KeyCode.LeftShift)) { moveSpeedF = 50; maxSpeed = 20; breakSpeedF = moveSpeedF * 10; } // ws를 누르지 않을 경우 속도를 줄인다 if (!(Input.GetKey(KeyCode.W)) && !(Input.GetKey(KeyCode.S))) { LowerSpeedFrontBack(); } // da를 누르지 않을 경우 속도를 줄인다 if (!(Input.GetKey(KeyCode.D)) && !(Input.GetKey(KeyCode.A))) { LowerSpeedRightLeft(); } //현재 스피드가 최대 스피드보다 클 경우 속도를 낮춘다. if (currentSpeed > maxSpeed) { LowerSpeed(); } //wasd로 이동 if (Input.GetKey(KeyCode.W)) { GetComponent<Rigidbody>().AddRelativeForce(Vector3.forward * moveSpeedF); } if (Input.GetKey(KeyCode.A)) { GetComponent<Rigidbody>().AddRelativeForce(Vector3.left * moveSpeedF); } if (Input.GetKey(KeyCode.S)) { GetComponent<Rigidbody>().AddRelativeForce(Vector3.back * moveSpeedF); } if (Input.GetKey(KeyCode.D)) { GetComponent<Rigidbody>().AddRelativeForce(Vector3.right * moveSpeedF); } // 속도 조정 중 방향 확인에 쓰일 임시 변수 tmpPosition = transform.position; tmpLocalPosition = transform.localPosition; } private void OnCollisionEnter(Collision collision) { //바닥에 닿으면 if (collision.gameObject.CompareTag("Ground")) { //점프가 가능한 상태로 만듦 IsJumping = false; } } //각종 함수 void Jump() { //스페이스 키를 누르면 점프 if (Input.GetKeyDown(KeyCode.Space)) { //바닥에 있으면 점프를 실행 if (!IsJumping) { Debug.Log("점프 가능 !"); IsJumping = true; GetComponent<Rigidbody>().AddForce(Vector3.up * JumpPower, ForceMode.VelocityChange); } //공중에 떠있는 상태이면 점프하지 못하도록 리턴 else { Debug.Log("점프 불가능 !"); return; } } } //속도 초과 시 조정 void LowerSpeed() { Vector3 forceTo = tmpPosition - transform.position; GetComponent<Rigidbody>().AddForce(forceTo*breakSpeedF); } void LowerSpeedFrontBack() { //Vector3 forceTo = ; //GetComponent<Rigidbody>().AddForce(Vector3.forward * forceTo * breakSpeedF); } void LowerSpeedRightLeft() { //float forceTo = tmpLocalPosition.x - transform.localPosition.x; //GetComponent<Rigidbody>().AddRelativeForce(Vector3.left * forceTo * breakSpeedF); } } | cs |
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 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 | using System.Collections; using System.Collections.Generic; using UnityEngine; public class CameraPoint : MonoBehaviour { GameObject player; GameObject camera; float rotateSpeed; float currentDistance; float forDistance; float distanceMin; float distanceMax; float mouseX, mouseXtmp, mouseXdelta; float mouseY, mouseYtmp, mouseYdelta; float cameraMaxDegree, cameraMinDegree; void Start () { player = GameObject.Find("Player"); camera = GameObject.Find("Camera"); forDistance = 12; distanceMin = 6; distanceMax = 24; rotateSpeed = 0.3f; cameraMaxDegree = 75; cameraMinDegree = 285; } void Update () { // 카메라 포인트의 위치는 플레이어와 같도록 transform.position = player.transform.position; // 마우스 스크롤로 줌 제어(성공) MouseScrollZoom(); // 마우스 우클릭 드래그로 방향 제어(성공) MouseRightDrag(); // 좌우화살표 키로 방향 제어(삭제 예정) /* if (Input.GetKey(KeyCode.LeftArrow)) { transform.Rotate(Vector3.up * Time.deltaTime * rotateSpeed); } if (Input.GetKey(KeyCode.RightArrow)) { transform.Rotate(Vector3.down * Time.deltaTime * rotateSpeed); } */ } // 함수들 // 마우스 스크롤로 줌 제어 void MouseScrollZoom() { Vector3 cameraDistanceVector = camera.transform.position - transform.position; if (Input.mouseScrollDelta.y == 1 && forDistance > distanceMin) forDistance -= Input.mouseScrollDelta.y * 0.5f; if (Input.mouseScrollDelta.y == -1 && forDistance < distanceMax) forDistance -= Input.mouseScrollDelta.y * 0.5f; camera.transform.position = SetPosition(transform.position, cameraDistanceVector, forDistance); } // 마우스 우클릭 드래그로 방향 제어 (써놓고도 맞는질 모르겠다) void MouseRightDrag() { if (Input.GetMouseButton(1)) { mouseX = Input.mousePosition.x; mouseY = Input.mousePosition.y; if(mouseXtmp==0 && mouseYtmp == 0) { mouseXtmp = mouseX; mouseYtmp = mouseY; } mouseXdelta = mouseX - mouseXtmp; mouseYdelta = mouseY - mouseYtmp; mouseXtmp = mouseX; mouseYtmp = mouseY; transform.Rotate(Vector3.up * mouseXdelta * rotateSpeed); transform.Rotate(Vector3.left * mouseYdelta * rotateSpeed); transform.localEulerAngles = new Vector3(transform.localEulerAngles.x, transform.localEulerAngles.y, 0); if (transform.localEulerAngles.x > cameraMaxDegree && transform.localEulerAngles.x<=90) { transform.localEulerAngles = new Vector3(cameraMaxDegree, transform.localEulerAngles.y, transform.localEulerAngles.z); } if (transform.localEulerAngles.x < cameraMinDegree && transform.localEulerAngles.x>270) { transform.localEulerAngles = new Vector3(cameraMinDegree, transform.localEulerAngles.y, transform.localEulerAngles.z); } } else { mouseXtmp = 0; mouseYtmp = 0; } } // 두 인수(Vector3)를 넣어 두 위치 사이의 거리를 반환 float Distance(Vector3 a, Vector3 b) { return Mathf.Sqrt(Mathf.Pow(a.x-b.x,2) + Mathf.Pow(a.y-b.y,2) + Mathf.Pow(a.z-b.z,2)); } // 기준점과의 거리를 새로운 거리설정에 맞게 최적화(줌인/아웃에서 사용) Vector3 SetPosition(Vector3 point, Vector3 input, float localForDistance) { float cameraDistanceValue = Distance(new Vector3(0, 0, 0), input); float multi = localForDistance / cameraDistanceValue; Vector3 newDistanceVector = new Vector3(input.x * localForDistance / cameraDistanceValue, input.y * localForDistance / cameraDistanceValue, input.z * localForDistance / cameraDistanceValue); Vector3 newPosition = point + newDistanceVector; //Debug.Log(cameraDistanceValue+" "+localForDistance); return newPosition; } } | cs |
2-1. 캐릭터 움직임 코드 재작성(Hard ver)
현재의 카메라에서 개선해야 할 점은
# 줌인, 줌아웃을 할 때 부드럽게 움직이면 좋겠다
# 스크롤을 매우 빠르게 올리고 내릴 경우 줌인/아웃이 작동하지 못한다
이 정도이며 옵션사항정도로 생각하므로 어느정도 안정화는 끝났다고 생각한다.
따라서 캐릭터의 무빙 코드만을 재작성하자.
새로 작성할 코드는 캐릭터 오브젝트에 힘을 가하는 것이 아니라 직접 포지션에 조작을 가한다.
앞뒤 전후로만 움직이면 코드 작성에 있어서는 매우 편할 것이나, 문제는 카메라가 보는 방향이 캐릭터가 보는 방향이며 그 방향에 따른 움직임을 해주어야 한다는 것이다. 그렇게 제일 먼저 작성된 코드는 W키를 누를 때, 앞으로 진행하는 코드이다.
| if (Input.GetKey(KeyCode.W)) { transform.localPosition = new Vector3(transform.localPosition.x + Mathf.Sin(transform.eulerAngles.y / 360 * 2 * Mathf.PI) * maxSpeed, transform.localPosition.y, transform.localPosition.z + Mathf.Cos(transform.eulerAngles.y / 360 * 2 * Mathf.PI) * maxSpeed); } | cs |
같은 방법으로 A, S, D도 작성해준다.
포지션의 변화는 캐릭터가 바라보는 방향(y축의 회전정도)에 따라 삼각함수를 사용하여 방향을 결정할 수 있도록 했다.