캐릭터의 대표적인 이동 방법
팩맨이나 소코반 형식의 게임 (창고지기라고도 부르는 것으로 알고 있다. 이름만으로는 몰라도 게임을 보면 누구나 한번 쯤은 본 적이 있을 것이다.) 은 키보드의 방향키를 이용하여 캐릭터를 조종하는데, 방향키를 누르고 있으면 캐릭터가 지속적으로 움직이게 할 수도 있지만, 방향키 한번에 캐릭터가 한 칸씩 움직이게 만들고자 할 수도 있을 것이다.
오브젝트 이동에 대해 아래와 같은 예시를 많이 봤을 것이다.
private void Update() { float horizontal = Input.GetAxis("Horizontal"); float vertical = Input.GetAxis("Vertical"); transform.Translate(Vector3.right * Time.smoothDeltaTime * horizontal, Space.World); transform.Translate(Vector3.up * Time.smoothDeltaTime * vertical, Space.World); }
캐릭터가 잘 움직이는 것을 볼 수 있을 것이다. 문제는 위와 같이 transform.Translate를 사용할 경우 캐릭터가 누르고 있을 때 쭉 이동하는데, 정수 단위가 아닌 소수 단위로 좌표 이동을 하게 될 것이다.
만약 아래 방향키를 눌렀을 때 (0, 0)에서 (0, -1)로 이동하게 만들고 싶다면 어떻게 하면 될까?
Input 클래스의 GetKey
방향키를 누르는 동작은 크게 키보드를 누르기 시작할 때, 누르고 있을 때, 손을 뗐을 때로 나뉘어 진다. Input 클래스에는 위의 동작을 인식하는 방법이 있다.
Input.GetKey(KeyCode.A); Input.GetKeyDown(KeyCode.A); Input.GetKeyUp(KeyCode.A);
Input.GetKey
괄호 안의 키 또는 버튼을 누르고 있는 동안의 상태이다. 키보드의 키를 꾹 누르고 있는 동안에 미사일을 발사하거나 캐릭터가 쭉 움직이는 동작을 만들 때 사용할 수 있다.
Input.GetKeyUp
키보드에서 괄호 안의 키나 버튼에서 손을 뗐을 때를 감지한다.
Input.GetKeyDown
키보드에서 괄호 안의 키나 버튼이 물리적인 힘에 의해 내려갔을 때, 즉 버튼을 누른 순간을 감지한다. 이 때 GetKey은 누르고 있는 동안 지속적으로 동작을 하지만, GetKeyDown은 누른 순간의 특정 기능을 한 번만 처리하게 된다.
Input.GetKeyDown을 이용해 캐릭터 좌표 이동 구현하기
데스크탑의 경우 키보드를 이용해 캐릭터를 조종하므로 GetKeyDown을 사용할 것이다.
먼저 두 코드를 보도록 하자.
GameManager.cs의 Update 함수. 방향키를 누를 때 플레이어 캐릭터의 이동을 처리한다.
void Update() { if (Input.GetKeyDown(KeyCode.UpArrow)) { GetPlayer.posy++; GetPlayer.move("up"); } else if (Input.GetKeyDown(KeyCode.DownArrow)) { GetPlayer.posy--; GetPlayer.move("down"); } else if (Input.GetKeyDown(KeyCode.LeftArrow)) { GetPlayer.posx--; GetPlayer.move("left"); } else if (Input.GetKeyDown(KeyCode.RightArrow)) { GetPlayer.posx++; GetPlayer.move("right"); } }
player.cs의 move 함수. GameManager.cs의 Update에서 호출하여 캐릭터를 움직이게 만든다.
public void move(string direction) { this.gameObject.transform.position = new Vector3(posx, posy, 0); }
위의 코드의 Update 함수에서 GetPlayer는 플레이어 캐릭터 오브젝트에 부착된 player 스크립트를 가져온 것이다. player 스크립트는 플레이어 캐릭터의 현재 좌표 정보와 좌표 이동을 위한 move 함수가 구현되어 있다. (여기서 direction은 다른 기능을 처리하기 위한 파라미터로, 이동에 영향을 주지 않으므로 신경쓰지 않아도 된다.)
Input.GetKeyDown으로 특정 방향키를 눌렀을 때 캐릭터의 x 또는 y 좌표가 바뀌게 되며, 해당 변경 좌표를 player 스크립트에 저장하고 move 함수에서 캐릭터 좌표를 바꿔준다.
쉽게 말해, Input.GetKeyDown을 이용해 버튼을 처음 눌렀을 때를 감지하여, 캐릭터의 좌표를 바꾸고, transform.position으로 캐릭터의 위치를 바꿔주면 된다.
위와 같은 방법으로 이동을 구현하면 캐릭터가 순간 이동하는 것처럼 움직이게 되는데, Input.GetKey와 Vector3.Lerp 함수를 이용하면 아래와 같이 캐릭터가 부드럽게 움직이게 만들 수도 있다.
player.cs의 move함수를 변형.
// Lerp를 이용한 이동 public void moveWay() { this.gameObject.transform.position = Vector3.Lerp(this.gameObject.transform.position, targetPosition, 0.5f); } // Lerp는 소수점에서 이동이 멈출 수 있으므로 강제로 int 좌표로 캐릭터 좌표를 바꿔준다 public void resetPosition() { this.gameObject.transform.position = targetPosition; posx = (int)targetPosition.x; posy = (int)targetPosition.y; }
GameManager.cs의 Update 함수 변경.
// Update is called once per frame void Update() { //Lerp 기능을 적용하지 않은 캐릭터 이동 //if (Input.GetKeyDown(KeyCode.UpArrow)) //{ // GetPlayer.posy++; // //GetPlayer.move("up"); // GetPlayer.move("up"); //} //else if (Input.GetKeyDown(KeyCode.DownArrow)) //{ // GetPlayer.posy--; // //GetPlayer.move("down"); // GetPlayer.move("down"); //} //else if (Input.GetKeyDown(KeyCode.LeftArrow)) //{ // GetPlayer.posx--; // //GetPlayer.move("left"); // GetPlayer.move("left"); //} //else if (Input.GetKeyDown(KeyCode.RightArrow)) //{ // GetPlayer.posx++; // //GetPlayer.move("right"); // GetPlayer.move("right"); //} if (Input.GetKeyDown(KeyCode.UpArrow)) GetPlayer.blockMove("up"); if (Input.GetKey(KeyCode.UpArrow)) { GetPlayer.targetPosition = new Vector3(GetPlayer.posx, GetPlayer.posy + 1); GetPlayer.moveWay(); } if (Input.GetKeyUp(KeyCode.UpArrow)) { GetPlayer.resetPosition(); } if (Input.GetKeyDown(KeyCode.DownArrow)) GetPlayer.blockMove("down"); if (Input.GetKey(KeyCode.DownArrow)) { GetPlayer.targetPosition = new Vector3(GetPlayer.posx, GetPlayer.posy - 1); GetPlayer.moveWay(); } if (Input.GetKeyUp(KeyCode.DownArrow)) { GetPlayer.resetPosition(); } if (Input.GetKeyDown(KeyCode.LeftArrow)) GetPlayer.blockMove("left"); if (Input.GetKey(KeyCode.LeftArrow)) { GetPlayer.targetPosition = new Vector3(GetPlayer.posx - 1, GetPlayer.posy); GetPlayer.moveWay(); } if (Input.GetKeyUp(KeyCode.LeftArrow)) { GetPlayer.resetPosition(); } if (Input.GetKeyDown(KeyCode.RightArrow)) GetPlayer.blockMove("right"); if (Input.GetKey(KeyCode.RightArrow)) { GetPlayer.targetPosition = new Vector3(GetPlayer.posx + 1, GetPlayer.posy); GetPlayer.moveWay(); } if (Input.GetKeyUp(KeyCode.RightArrow)) { GetPlayer.resetPosition(); } }
(GetKeyDown의 기능은 게임 화면의 블록을 움직이는 기능이므로 신경쓰지 않아도 된다.)
Lerp 메서드에 대해선 기회가 생긴다면 정리하겠지만, 쉽게 말해 현재 위치에서 목적지까지의 거리를 계산하여 적절한 가속도로 이동하는 것이다. 가속을 적용하기 위해선 Update 함수에서 현재 위치를 계속 계산하여 Lerp를 새로 적용해야 하는데, GetKeyDown을 사용하면 현재 위치를 한번만 체크하고 Lerp를 계산하게 되므로 가속에 따른 이동이 적용되지 않는다. (이동한다고 해도 현재 위치에서 목적지까지 아주 조금 움직이고 끝날 것이다.)
GetKey를 이용하게 되면 아주 짧게 누른다고 해도 컴퓨터는 0.1초라도 누르는 동작으로 취급하여 현재 위치를 조금이나마 갱신할 수 있게 된다. 다만 매우 짧게 누르면 역시 목적지에 도달하기 전에 멈춰버리기 때문에 부드럽게 움직이는 것처럼 보일 수 있지만 목적지에 완전히 도착하지는 못한다. 따라서 키보드에서 손을 뗐을 때 resetPosition으로 목적지까지 캐릭터를 강제 이동하게 만든다.
위의 방법 역시 방향키를 너무 빠르게 한꺼번에 누르면 캐릭터가 조금 이상하게 움직이는데, 움직이는 도중에 다른 방향키의 이동을 막아버리는 등의 방식으로 제어할 수는 있다. (귀찮아서 안함)
위의 방법보다 좋은 해결책은 얼마든지 있으니 참고용으로만 사용하고 자신만의 기발한 캐릭터 좌표 이동을 구현해도록 하자.
혹시 참고를 하고 싶다면 아래의 링크에서 전체 코드를 확인할 수 있다. (가끔씩 업데이트 되어 내용이 달라질 수 있지만 아마 기본 틀은 유지되지 않을까 싶다.)
댓글
댓글 쓰기