11. 들어가기 전에
이 장에서는 2D 플레이어 캨릭터를 구현한다. 2D 플레이어 캐릭터를 구현하면서 2D 프로젝트의 간단한 특징, 2D 그래픽 소스를 사용하는 방법, 애니메이션 클립을 만드는 방법, 상황에 맞는 애니메이션을 재생하는 방법을 다룬다.
레트로의 유니티 게임 프로그래밍 에센스 | 이제민 - 교보문고
레트로의 유니티 게임 프로그래밍 에센스 | 이제민 - 교보문고
레트로의 유니티 게임 프로그래밍 에센스 | 독자분들로부터 수많은 찬사를 받았던 유니티 대표 도서 『소문난 명강의:레트로의 유니티 게임 프로그래밍 에센스』가 개정판으로 돌아왔습니다!
product.kyobobook.co.kr
11.1 유니티 2D 프로젝트 열기
5부 유니런 개발에서는 미리 준비한 2D 프로젝트를 사용한다. 씬과 코드를 제외한 모든 에셋이 준비된 상태에서 미완성 프로젝트를 완선해나감으로써 코딩과 기능 구현에 집중한다.
사용할 유니티 프로젝트 폴더는 예제 데이터 11장 폴더에 있는 Uni-Run 이다.
IJEMIN/Unity-Programming-Essence: 레트로의 유니티 게임 프로그래밍 에센스
GitHub - IJEMIN/Unity-Programming-Essence: 레트로의 유니티 게임 프로그래밍 에센스
레트로의 유니티 게임 프로그래밍 에센스. Contribute to IJEMIN/Unity-Programming-Essence development by creating an account on GitHub.
github.com
11.1.1 유니티 2D 프로젝트
유니티 2D 프로젝트와 3D 프로젝트는 유의미한 차이가 없다. 프로젝트 생성 이후 언제든지 현재 프로젝트 설정을 2D 와 3D 사이에서 변경할 수 있다.
2D 프로젝트의 주요 특징
이미지 파일을 스프라이트 타입으로 임포트한다.
기본 생성 카메라가 직교 모드를 사용한다.
라이팅 설정 중 일부가 비활성화된다.
씬 창이 2D 뷰로 보인다.
11.1.2 Uni-Run 프로젝트 열기
예제 데이터의 11장 폴더에 있는 Uni-Run 프로젝트 폴더를 연다.
1. 유니런 시작 프로젝트 열기
- 유니티 허브 실행 > Projects 화면에서 Open 클릭
- 예제 폴더 > 11 폴더 > Uni-Run 폴더 선택 > 열기
2. 새로운 씬 만들기
- ctrl+N 누르기 -> 새 씬 생성 창 표시됨
- Basic 2D 씬 템플릿 선택 > Create 클릭하여 새로운 씬 생성
- ctrl+S 누르기 -> 씬 저장 창이 표시됨
- 생성된 씬을 Main 이라는 이름으로 저장


11.2 시작 지점 만들기
가장 먼저 캐릭터가 배치될 시작 지점을 구성한다. 시작 지점에는 캐릭터가 서 있을 기본 발판과 낙사 판정 영역을 만든다.
11.2.1 시작 지점 발판
프로젝트에 미리 포함된 스프라이트 에셋을 사용해 시자가 지점의 발판을 만든다. 스프라이트는 2D 그래픽과 UI 를 그릴 때 사용하는 텍스처 에셋(이미지 파일)이다.
프로젝트 창에서 스프라이트를 하이어라키 창으로 드래그&드롭하면 해당 스프라이트를 사용하는 스프라이트 렌더러 컴포넌트가 추가된 게임 오브젝트가 자동 생성된다.
1. 시작 지점 발판 만들기
- 프로젝트 창에서 Sprites 폴더로 이동 > Platform_Long 스프라이트를 하이어라키 창으로 드래그&드롭
- 생성된 Platform_Long 게임 오브젝트의 이름을 Start Platform 으로 변경
- Start Platform 게임 오브젝트의 위치를 (0, -1, 0) 으로 변경
- Box Collider 2D 컴포넌트 추가

발판에 물리적인 표면을 추가하기 위해 박스 콜라이더 2D 컴포넌트를 사용했다. 컴포넌트 끝에 2D 가 붙는다는 사실에 주의한다.
11.2.2 데드존 만들기
플레이어 캐릭터가 점프하여 다음 발판에 착지하지 못하고 아래로 떨어지는 것을 감지하고, 플레이어 캐릭터가 죽게 하는 낙사 판정 영역(데드존) 을 만든다.
낙사 판정은 게임 화면의 하단 영역에 가로로 긴 트리거 콜라이더를 만들어 구현한다. 그렇게 데드존 게임 오브젝트를 만든 다음 Dead 태그를 할당한다.
Dead 태그는 플레이어 캐릭터를 죽게 하는 게임 오브젝트에 할당할 태그이며, 저자가 미리 프로젝트에 추가해놨다.
1. 데드존 만들기
- 빈 게임 오브젝트 생성
- 이름은 Deadzone, 태그는 Dead, 위치는 (0, -8, 0) 으로 변경
- Box Collider 2D 컴포넌트 추가
- Box Collider 2D 컴포넌트의 Is Trigger 체크, Size 는 (50, 2) 로 변경

11.3 캐릭터 스프라이트 편집
플레이어 캐릭터를 위한 2D 그래픽을 준비한다.
11.3.1 Multiple 스프라이트
프로젝트 창의 Sprites 폴더에는 여러 스프라이트 에셋이 준비되어 있다. 여기서 플레이어 캐릭터에 사용할 스프라이트는 Toko_Die, Toko_Jump, Toko_Run 이다.
각각의 스프라이트는 일본 유니티의 마스코트 캐릭터 중 하나인 토코가 죽는 모습, 점프하는 모습, 뛰는 모습을 담고 있다. 이들은 단일 이미지 파일에 캐릭터의 여러 모습을 합친 스프라이트 시트이다.
스프라이트 시트는 여러 이미지를 하나의 이미지 파일로 합친 것이다. 캐릭터가 연속적으로 움직이는 모습을 여러 이미지 파일로 만드는 것보다 이렇게 한 이미지 파일로 합쳐서 관리하는 것이 리소스 관리가 편하고 성능도 좋다.
유니티는 2D 프로젝트에서 이미지를 기본적으로 싱글(Single) 스프라이트 모드로 가져온다. 싱글 스프라이트에서 하나의 스프라이트 에셋은 하나의 스프라이트를 표혆ㄴ다. 스프라이트 에셋을 멀티플(Multiple) 스프라이트 모드로 변경하면 하나의 스프라이트 에셋을 여러 개의 개별 스프라이트로 잘라 사용할 수 있다. 따라서 우리가 사용할 캐릭터 스프라이트 시트 또한 멀티플 스프라이트로 사용해야 한다.
Die, Jump 스프라이트는 저자가 미리 멀티플 스프라이트로 변경해 개별 스프라이트로 잘라두었다. Run 스프라이트는 직접 잘라보면 된다.
1. Toko_Run 스프라이트 편집 창 열기
- 프로젝트 창의 Sprites 폴더에서 Toko_Run 스프라이트 선택
- 인스펙터 창에서 Sprite Mode 를 Multiple 로 변경
- Apply 버튼을 클릭해 적용 > Sprite Editor 버튼 클릭

2. Toko_Run 스프라이트 자르기
- 스프라이트 편집 창에서 Slice 버튼 클릭
- Type 은 Grid by Cell Size 로, Pixel Size 는 (64, 64) 로 변경
- Slice 클릭
- Apply 버튼을 클릭해 저장 > 스프라이트 편집 창 닫기

11.4 2D 캐릭터 게임 오브젝트 준비
구현할 플레이어 캐릭터는 다음과 같은 기능을 갖는다.
물리 상호작용
조작을 감지하고 점프
사망
애니메이션 재생과 제어
효과음 재생
11.4.1 게임 오브젝트 준비하기
플레이어 캐릭터가 될 게임 오브젝트를 만들고 필요한 컴포넌트들을 추가한다. 먼저 Toko_Run_0 스프라이트를 사용한 Player 게임 오브젝트를 만들고, Player 태그를 할당한다.
1. 플레이어 게임 오브젝트 생성
- 프로젝트 창에서 Toko_Run 스프라이트 펼치기 > Toko_Run_0 스프라이트를 하이어라키 창으로 드래그&드롭
- 생성된 Toko_Run_0 게임 오브젝트의 이름은 Player, 태그는 Player 로 변경
- Player 게임 오브젝트의 위치를 (-6, 2, 0) 으로 변경

2. 리지드바디 2D 컴포넌트 추가
- Player 게임 오브젝트에 Rigidbody 2D 컴포넌트 추가
- Rigidbody 2D 컴포넌트의 Collision Detection 을 Continuous 로 변경
- Rigidbody 2D 컴포넌트의 Constraints 펼치기 > Freeze Rotation 의 Z 체크

충돌 감지 방식을 이산(Discrete) 에서 연속(Continuous) 로 변경했다. 이산은 충돌 감지를 일정 시간 간격으로 끊어서 실행한다. 연속은 움직이기 이전 위치와 움직인 다음 위치 사이에서 예상되는 충돌까지 함께 감지한다. 그러므로 연속이 이산보다 충돌 감지가 상대적으로 정확하지만 성능을 더 요구한다.
Freeze Rotation Z 를 체크하여 캐릭터가 회전하는 상황까지 예방한다.
3. 써클 콜라이더 2D 컴포넌트 추가
- Player 게임 오브젝트에 Circle Collider 2D 추가
- Circle Collider 2D 컴포넌트의 Offset 을 (0, -0.57), Radius 를 0.2 로 변경

써클 콜라이더 2D 컴포넌트가 Player 게임 오브젝트의 하체 일부만 차지할 수 있도록 오프셋과 반지름을 조정하여 배치함.
박스 콜라이더 2D 대신 써클 콜라이더 2D 를 사용한 이유는 Player 게임 오브젝트가 점프 후 각진 모서리에 안착했을 때 부드럽게 모서리를 타고 올라가도록 만들기 위함.
11.4.2 오디오 소스
오디오 소스 컴포넌트는 게임 오브젝트에 소리를 낼 수 있는 능력을 부여한다. Player 게임 오브젝트가 점프 소리를 내도록 오디오 소스 컴포넌트를 추가한다.
1. 오디오 소스 컴포넌트 추가
- Player 게임 오브젝트에 Audio Source 컴포넌트 추가
- Audio Source 컴포넌트에서 AudioClip 필드 옆의 선택 버튼 > 선택 창에서 jump 오디오 클립 더블 클릭
- Audio Source 컴포넌트의 Play On Awake 체크 해제

게임 오브젝트에 오디오 소스 컴포넌트를 추가하고, 미리 준비된 jump 오디오 클립을 해당 오디오 소스 컴포넌트가 재생할 오디오 클립으로 할당했다.
여기서 오디오 소스 컴포넌트는 소리를 재생하는 부품이지, 소리를 담은 파일이 아니라는 점에 주의한다. 소리를 담은 파일은 오디오 클립이다.
오디오 소스 컴포넌트에서 체크 해제한 Play On Awake 는 오디오 소스 컴포넌트가 활성화 되었을 때 최초 1회 오디오를 자동 재생하는 옵션이다. 해당 설정이 활성화되어 있으면 게임 시작과 동시에 점프 소리가 1회 무조건 재생되므로 해제했다.
11.5 캐릭터 애니메이션 준비하기
플레이어 캐릭터가 사용할 애니메이션을 만들고, 플레이어 게임 오브젝트에 애니메이션을 재생할 수 있는 컴포넌트를 추가한다.
1. 애니메이션 저장용 폴더 만들기
- 프로젝트 창에서 + > Folder 클릭
- 생성된 폴더 이름을 Animations 로 변경

11.5.1 애니메이션 만들기
여러 장의 스프라이트 이미지를 사용해 캐릭터의 애니메이션을 만든다. 유니티 에디터에서 애니메이션을 제작하려면 애니메이션 창을 열어야 한다.
1. 애니메이션 창 열기
- Window > Animation > Animation 클릭
- 열린 애니메이션 창을 적절한 곳에 드래그하여 배치

11.5.1.1 Run 애니메이션 클립 만들기
현재 애니메이션 창에는 Player 에 대한 애니메이팅을 하기 위해 애니메이터와 애니메이션 클립 만들기 라는 메시지와 Create 버튼이 출력된다. 이러한 메시지가 출력되지 않으면 하이어라키 창에서 Player 게임 오브젝트를 선택하면 출력된다.

1. Run 애니메이션 클립 만들기
- 애니메이션 창에서 Create 클릭 -> 애니메이션 클립 저장 창이 열림
- 새로운 애니메이션 클립을 Run 이라는 이름으로 Assets 폴더 내부의 Animations 폴더에 저장

2. Run 애니메이션 클립 구성하기
- 프로젝트 창에서 Sprites 폴더로 이동
- Toko_Run 스프라이트 펼치기 > Shift+클릭으로 Toko_Run_0~Toko_Run_7 까지 모두 선택
- 애니메이션 창의 타임라인으로 드래그&드롭

3. 샘플 레이트 변경
- 애니메이션 창에서 Samples 를 16으로 변경

11.5.1.2 Jump 애니메이션 클립 만들기
이번에는 점프하는 모습을 담은 Jump 애니메이션 클립을 만든다.
1. Jump 애니메이션 클립 만들기
- 애니메이션 창에서 Run 클릭 > Create New Clip... 클릭
- 새로운 애니메이션 클립을 Jump 라는 이름으로 Animations 폴더에 저장


2. Jump 애니메이션 클립 구성하기
- 프로젝트 창의 Sprites 폴더에서 Toko_Jump 스프라이트 펼치기 > Shift+클릭으로 Toko_Jump_0 부터 Toko_Jump_4 까지 모두 선택
- 애니메이션 창의 타임라인으로 드래그&드롭
- 애니메이션 창에서 Samples 를 6으로 변경

11.5.1.3 Die 애니메이션 클립 만들기
Die 애니메이션 클립도 Run, Jump 와 같은 방법으로 만든다.
위와 같은 방식이라 사진 생략
1. Die 애니메이션 클립 만들기
- 애니메이션 창에서 Jump 클릭 > Create New Clip... 클릭
- 새로운 애니메이션 클립을 Die 라는 이름으로 Animations 폴더에 저장
2. Die 애니메이션 클립 구성하기
- Sprites 폴더에서 Toko_Die 스프라이트 모두 선택
- 애니메이션 창 타임라인으로 드래그&드롭
- Samples 를 6으로 변경
3. Die 애니메이션 클립의 루프 설정 해제
- 프로젝트 창에서 Animations 폴더로 이동 > Die 애니메이션 클립 선택
- 인스펙터 창에서 Loop Time 체크 해제(죽는 모션은 반복하면 안 되니까)

11.5.2 유한 상태 머신
애니메이션 클립은 준비되었지만 아직 어떤 애니메이션 클립을 어떤 상황에서 어떻게 재생할지 결정하지 않았다. 상황에 맞는 애니메이션을 재생하려면 애니메이터 컨트롤러가 필요하다.
애니메이터 컨트롤러는 상황에 따라 어떤 애니메이션 클립을 재생해야 하는지 저장한 지도이며, 유한 상태 머신(Finite State Machine, FSM) 모델을 사용한다.
유한 상태 머신은 유한한 수의 상태가 존재하며, 한 번에 한 상태만 '현재 상태' 가 되도록 프로그램을 설계하는 모델이다. 유한 상태 머신에서는 어떤 상태에서 다른 상태로 전이하여 현재 상태를 전환할 수 있다.
11.5.3 애니메이터 컨트롤러와 애니메이터
애니메이터 컨트롤러는 유한 상태 머신을 사용해 재생할 애니메이션을 결정하는 상태도를 표현하는 에셋이다. 애니메이터는 애니메이터 컨트롤러를 참고하여 게임 오브젝트에 애니메이션을 적용하는 컴포넌트이다.
새로운 애니메이터 컨트롤러는 프로젝트 창에서 Create > Animator Controller 로 생성할 수 있다.
애니메이터 컨트롤러는 에셋이며, 애니메이터는 컴포넌트라는 점에 주의한다.
이전에 Run 애니메이션 클립을 만들 때 Player 애니메이터 컨트롤러가 자동 생성되었고, Player 게임 오브젝트에 Player 애니메이터 컨트롤러 에셋을 사용한 애니메이터 컴포넌트가 자동 추가되었다. 따라서 유니런 프로젝트에서는 우리가 직접 애니메이터 컨트롤러를 생성할 필요가 없다.
프로젝트 창의 Animations 폴더에서 Player 애니메이터 컨트롤러, Player 게임 오브젝트에서 애니메이터 컴포넌트를 찾을 수 있다.

1. 애니메이터 창 열기
- 애니메이션 창의 우측 상단 점 세개 버튼 > Close Tab 클릭
- 유니티 상단 메뉴에서 Window > Animation > Animator 클릭
- 애니메이터 창을 적절한 위치로 드래그하여 배치

애니메이터 창은 현재 선택된 게임 오브젝트의 애니메이터 컴포넌트가 사용 중인 애니메이터 컨트롤러의 상태도를 표시한다.
11.5.4 Player 애니메이터 컨트롤러의 상태
현재 애니메이터 창에 표시된 상태도는 Player 애니메이터 컨트롤러의 상태이다. 만약 Player 애니메이터 컨트롤러에 대한 상태도가 표시되지 않는다면 하이어라키 창에서 Player 게임 오브젝트를 선택하면 표시된다.

Player 애니메이터 컨트롤러에는 Run, Jump, Die, Entry, Exit, Any State 상태가 존재한다.
Run: Run 애니메이션 클립을 재생하는 상태
Jump: Jump 애니메이션 클립을 재생하는 상태
Die: Die 애니메이션 클립을 재생하는 상태
기본 포함되는 특수 상태
Entry: 현재 상태가 진입하는 입구
Exit: 상태 머신의 동작이 종료되는 출구
Any State: 현재 상태가 무엇이든 상관없이 특정 상태로 즉시 전이하게 허용하는 상태
Entry 상태는 애니메이터 동작이 시작되는 진입점이다. Entry 상태와 전이로 연결된 상태는 가장 먼저 현재 상태로 활성화되는 기본 상태(Default State) 가 된다. 현재는 Run 상태가 기본 상태로 지정되어 있는데 기본 상태는 주황색으로 하이라이트된다.
어떤 상태를 기본 상태로 변경하려면 해당 상태에서 마우스 오른쪽 클릭 > Set as Layer Default State 를 클릭한다. 우리는 Run 상태를 기본 상태 그대로 사용한다. 만약 어떠한 이유로 기본 상태가 Run 상태가 아니라면 Run 상태를 기본 상태로 변경하도록 한다.
Exit 상태는 애니메이터의 동작이 끝나는 지점이다. 현재 상태가 이곳으로 전이하면 애니메이터 동작이 완전히 종료된다.
Any State 상태는 현재 상태가 무엇이든 상관없이 특정 상태로의 즉시 전이를 가능하게 만든다. 피격 애니메이션이나 사망 애니메이션처럼 현재 상태와 상관없이 즉시 재생해야 하는 애니메이션에 자주 사용된다.
예를 들어 Any State 상태에서 Die 상태로 전이를 연결했다고 가정하자. 현재 상태가 Run, Jump, Die 중 어떠한 것이라도 상관없이 Any State -> Die 전이 조건만 만족하면 즉시 현재 상태가 Die 상태로 전이한다.
11.5.5 전이 구성하기
이제 Player 애니메이터 컨트롤러의 상태들을 전이로 연결한다.
플레이어 캐릭터는 특별한 사건이 일어나지 않는 한 Run 상태를 현재 상태로 유지한다. 그렇게 계속 Run 상태를 현재 상태로 재생하는 도중 플레이어 캐릭터가 점프르르 시작하면 현재 상태가 Jump 상태로 변화해야 한다.
그리고 플렝이어 캐릭터가 점프 후 발판에 착지하면 Jump 상태에서 Run 상태로 변화해야 한다. 따라서 Run 상태와 Jump 상태는 전이를 양방향으로 연결해야 한다.
플레이어 캐릭터가 낙하하거나 장애물에 부딪쳐 죽으면 현재 상태가 무엇이든 상관없이 현재 상태가 무조건 Die 상태로 변화해야 한다. 따라서 Any State 상태에서 Die 상태로 전이를 연결해야 한다.
1. 전이 구성하기
- Run 상태에서 마우스 오른쪽 클릭 > Make Transition 클릭
- 전이 화살표를 Jump 상태에 연결(전이 화살표를 끌어다 Jump 상태에 클릭)
- Jump 상태에서 마우스 오른쪽 클릭 > Make Transition 클릭
- 전이 화살표를 Run 상태에 연결
- Any State 상태에서 마우스 오른쪽 클릭 > Make Transition 클릭
- 전이 화살표를 Die 상태에 연결

실수로 전이를 잘못 연결한 경우 애니메이터 창에서 해당 전이 화살표를 클릭하고 Delete 키로 삭제하고 다시 만들면 된다.
이제 전이가 언제 발동될지 결정하는 조건을 구성한다. 전이의 조건을 애니메이터 컨트롤러의 파라미터를 사용해서 구성한다.
2. 파라미터 추가
- 애니메이터 창에서 Parameters 탭 클릭
- + 버튼 클릭 > Bool 클릭 > 생성된 파라미터 이름을 Grounded 로 변경
- + 버튼 클릭 > Trigger 클릭 > 생성된 파라미터 이름을 Die 로 변경

파라미터는 전이의 조건으로 사용할 수 있는 수치이다. 파라미터로 가능한 타입에는 실수(float), 정수(int), 불리언(bool), 트리거(trigger) 가 있다.
우리는 bool 타입인 Grounded 파라미터를 플레이어 캐릭터가 발판에 닿았을 때는 true, 닿지 않았을 때는 false 로 지정할 것이다. 그리고 Grounded 파라미터를 기준으로 Run 상태와 Jump 상태 중 어떤 상태를 현재 상태로 재생할 것인지 결정한다.
Grounded 파라미터와 달리 Die 파라미터는 트리거 타입으로 설정했다. 트리거 타입의 파라미터는 다른 타입과 달리 특정 값을 할당할 수 없다. 트리거 타입의 파라미터는 트리거를 셋 또는 발동하여 방아쇠를 당기는 방식으로 사용한다. 트리거 타입은 셋하는 순간 즉시 true 가 되고 곧바로 false 가 되도록 구현되어 있다.
지속적으로 어떤 수치를 비교하는 것이 아니라 어떤 사건이 발생했을 때 전이가 일어나도록 조건을 구성할 때는 트리거 파라미터를 쓰는 것이 좋습니다.
이제 추가한 파라미터들을 전이의 조건으로 사용해본다. 그리고 전이의 다른 여러 설정값을 적절한 값으로 변경해본다.
!! 여기서 Has Exit Time, Transition Duration 개념을 설명한다(궁금하면 보도록..)
애니메이션 전환 - Unity 매뉴얼
애니메이션 전환(Animation transitions)을 사용하면 상태 머신이 하나의 애니메이션 상태에서 다른 애니메이션 상태로 전환하거나 혼합될 수 있습니다. 전환은 상태 간의 혼합이 가져야 하는 기간뿐
docs.unity3d.com
3. Run -> Jump 전이 설정하기
- 애니메이터 창에서 Run -> Jump 전이(화살표) 클릭
- 인스펙터 창에서 Has Exit Time 체크 해제
- Settings 탭 펼치기 > Transition Duration 을 0으로 변경
- 조건에 Grounded 추가(Conditions 의 + 버튼 클릭)
- Grounded 의 조건값을 false 로 변경

이것으로 Run -> Jump 전이의 조건을 Grounded 가 false 인 경우, 즉 발이 발판에 닿지 않을 때로 설정했다. 그리고 Has Exit Time 을 해제하고 전환 지속 시간의 값을 0으로 변경했다.
4. Jump -> Run 전이 설정하기
- 애니메이터 창에서 Jump -> Run 전이 클릭
- 인스펙터 창에서 Has Exit Time 체크 해제
- Settings 탭 펼치기 > Transition Duration 을 0으로 변경
- 조건에 Grounded 추가(Conditions 의 + 버튼 클릭)
- Grounded 의 조건값을 true 로 변경

5. Any State -> Die 전이 설정하기
- 애니메이터 창에서 Any State -> Die 전이 클릭
- 인스펙터 창에서 Settings 탭 펼치기 > Transition Duration 을 0으로 변경
- 조건에 Die 추가(Conditions 의 + 버튼 클릭 > 추가된 파라미터를 Die 로 변경)

11.6 PlayerController 스크립트
이제 Player 게임 오브젝트를 조종하는 PlayerController 스크립트를 작성한다. 유니런 프로젝트에는 사용할 스크립트들이 미리 포함되어 있다. 단, 내부 구현은 비어 있는 미완성이기 때문에 책을 진행하면서 직접 완성한다.
PlayerController 스크립트는 사용자 입력을 감지하여 점프하고, 충돌을 감지하여 장애물에 닿았을 때 사망 처리를 실행한다. 그리고 상황에 알맞은 효과음과 애니메이션을 재생한다.
1. PlayerController 스크립트 추가
- 프로젝트 창의 Scripts 폴더로 이동 > PlayerController 스크립트를 하이어라키 창의 Player 로 드래그&드롭
- 인스펙터 창에서 PlayerController 스크립트를 더블 클릭하여 열기

using UnityEngine;
// PlayerController는 플레이어 캐릭터로서 Player 게임 오브젝트를 제어한다.
public class PlayerController : MonoBehaviour {
public AudioClip deathClip; // 사망시 재생할 오디오 클립
public float jumpForce = 700f; // 점프 힘
private int jumpCount = 0; // 누적 점프 횟수
private bool isGrounded = false; // 바닥에 닿았는지 나타냄
private bool isDead = false; // 사망 상태
private Rigidbody2D playerRigidbody; // 사용할 리지드바디 컴포넌트
private Animator animator; // 사용할 애니메이터 컴포넌트
private AudioSource playerAudio; // 사용할 오디오 소스 컴포넌트
private void Start() {
// 초기화
}
private void Update() {
// 사용자 입력을 감지하고 점프하는 처리
}
private void Die() {
// 사망 처리
}
private void OnTriggerEnter2D(Collider2D other) {
// 트리거 콜라이더를 가진 장애물과의 충돌을 감지
}
private void OnCollisionEnter2D(Collision2D collision) {
// 바닥에 닿았음을 감지하는 처리
}
private void OnCollisionExit2D(Collision2D collision) {
// 바닥에서 벗어났음을 감지하는 처리
}
}
필요한 변수들은 미리 선언되어 있다. 메서드들은 선언만 되어 있고 처리가 구현되어 있지 않다. 직접 완성해야 한다.
11.6.1 PlayerController 의 변수
PlayerController 의 변수들을 살펴본다.
가장 먼저 사망 시 재생할 오디오 클립을 할당할 AudioClip 타입의 변수 deathClip, 점프에 사용할 힘인 float 타입의 변수 jumpForce 가 선언되어 있다.
public AudioClip deathClip; // 사망시 재생할 오디오 클립
public float jumpForce = 700f; // 점프 힘
그다음 플레이어 캐릭터의 상태를 나타내는 변수들이 있다. jumpCount 는 플레이어 캐릭터가 점프한 횟수를 나타낸다. 플레이어 캐릭터가 바닥에 닿을 때마다 매번 0으로 리셋된다. 이단 점프나 삼단 점프 등을 구현할 때 캐릭터가 현재 몇 회까지 점프했는지 검사하기 위해 사용한다.
isGrounded 는 플레이어 캐릭터가 바닥에 닿아있는지 나타낸다. 기본값은 false
isDead 는 플레이어 캐릭터의 사망 상태를 표현한다. 기본값은 false. 이를 사용하면 죽은 상태에서 다시 죽는 비논리적인 상황을 막을 수 있다.
private int jumpCount = 0; // 누적 점프 횟수
private bool isGrounded = false; // 바닥에 닿았는지 나타냄
private bool isDead = false; // 사망 상태
마지막으로 player 게임 오브젝트의 컴포넌트들을 할당할 변수들이 선언되어 있다.
private Rigidbody2D playerRigidbody; // 사용할 리지드바디 컴포넌트
private Animator animator; // 사용할 애니메이터 컴포넌트
private AudioSource playerAudio; // 사용할 오디오 소스 컴포넌트
11.6.2 Start() 메서드
1. Start() 메서드 완성하기
private void Start() {
// 초기화
// 게임 오브젝트로부터 사용할 컴포넌트들을 가져와 변수에 할당
playerRigidbody = GetComponent<Rigidbody2D>();
animator = GetComponent<Animator>();
playerAudio = GetComponent<AudioSource>();
}
11.6.3 Update() 메서드
1. Update() 메서드 완성하기
private void Update() {
// 사용자 입력을 감지하고 점프하는 처리
if (isDead)
{
// 사망시 처리를 더 이상 진행하지 않고 종료
return;
}
// 마우스 왼쪽 버튼을 눌렀으며 && 최대 점프 횟수(2) 에 도달하지 않았다면
if (Input.GetMouseButtonDown(0) && jumpCount < 2)
{
// 점프 횟수 증가
jumpCount++;
/* 점프 직전에 속도를 순간적으로 제로(0,0) 로 변경
플랫포머 게임에서 점프를 구현할 때는
직전 속도에 영향을 받지 않도록
순간적으로 속도를 제로로 만드는 것이 일반적*/
playerRigidbody.velocity = Vector2.zero;
// 리지드바디에 위쪽으로 힘 주기
playerRigidbody.AddForce(new Vector2(0, jumpForce));
// 오디오ㅗ 소스 재생
playerAudio.Play();
}
else if (Input.GetMouseButtonUp(0) && playerRigidbody.velocity.y > 0)
{
// 마우스 왼쪽 버튼에서 손을 떼는 순간 && 속도의 y 값이 양수라면(위로 상승 중)
// 현재 속도를 절반으로 변경
playerRigidbody.velocity = playerRigidbody.velocity * 0.5f;
}
// 애니메이터의 Grounded 파라미터를 isGrounded 값으로 갱신
animator.SetBool("Grounded", isGrounded);
}
Player 애니메이터 컨트롤러에서 Run -> Jump 전이의 조건은 Grounded 파라미터가 false 인 경우, Jump -> Run 전이의 조건은 Grounded 파라미터가 true 인 경우로 구성했다. 따라서 animator.SetBool("Grounded", isGrounded) 에 의해 Run 과 Jump 애니메이션이 상황에 맞춰 재생된다.
11.6.4 Die() 메서드
이어서 플레이어 캐릭터가 사망 시 실행할 Die() 메서드를 작성한다. Die() 메서드는 플레이어 캐릭터의 죽음을 구현한다. 사망 애니메이션을 재생하고, 플레이어 캐릭터의 현재 상태를 사망 상태로 변경한다.
1. Die() 메서드 완성하기
private void Die() {
// 사망 처리
// 애니메이터의 Die 트리거 파라미터를 셋
animator.SetTrigger("Die");
// 오디오 소스에 할당된 오디오 클립을 deathClip 으로 변경
playerAudio.clip = deathClip;
// 사망 효과음 재생
playerAudio.Play();
// 속도를 제로로 변경
playerRigidbody.velocity = Vector2.zero;
// 사망 상태를 true 로 변경
isDead = true;
}
11.6.5 OnTriggerEnter2D() 메서드
완성한 Die() 메서드를 언제 실행할지 결정해야 한다. 플레이어 캐릭터를 사망하게 할 낙사 영역과 장애물 게임 오브젝트에 Dead 라는 태그를 할당하고, 트리거 콜라이더를 추가할 것이다.
따라서 트리거 충돌을 감지하는 OnTriggerEnter2D() 메서드에서 Dead 태그를 가진 콜라이더와 닿았는지 검사하고 Die() 메서드를 실행하면 된다.
그런데 2D 콜라이더를 사용하는 경우 OnTriggerEnter() 의 2D 버전인 OnTriggerEnter2D() 메서드를 사용해야 한다.
1. OnTriggerEnter2D() 완성하기
private void OnTriggerEnter2D(Collider2D other) {
// 트리거 콜라이더를 가진 장애물과의 충돌을 감지
if (other.tag == "Dead" && !isDead)
{
// 충돌한 상대방의 태그가 Dead 이며 아직 사망하지 않았다면 Die() 실행
Die();
}
}
11.6.6 OnCollisionEnter2D() 와 OnCollisionExit2D()
Update() 메서드에서 jumpCount 와 isGrounded 를 사용했지만 isGrounded 값을 결정하는 부분과 jumpCound 를 리셋하는 부분을 아직 구현하지 않았다. 마지막으로 isGrounded 값을 결정하고 jumpCount 를 리셋하는 부분을 구현한다.
isGrounded 는 플레이어 캐릭터가 바닥에 닿은 동안 true, 바닥과 떨어져 있는 동안 false 가 된다. 이것을 일반 콜라이더와 닿는 순간에는 isGrounded 를 true 로, 일반 콜라이더와 떨어지는 순간에는 isGrounded 를 false 로 변경하는 방법으로 구현한다. 같은 방식으로 플레이어 캐릭터가 바닥에 닿는 순간 jumpCount 를 0으로 리셋한다.
1. OnCollisionEnter2D() 와 OnCollisionExit2D() 완성하기
private void OnCollisionEnter2D(Collision2D collision) {
// 바닥에 닿았음을 감지하는 처리
// 어떤 콜라이더와 닿았으며, 충돌 표면이 위쪽을 보고 있으면
if (collision.contacts[0].normal.y > 0.7f)
{
// isGrounded 를 true 로 변경하고, 누적 점프 횟수를 0으로 리셋
isGrounded = true;
jumpCount = 0;
}
}
private void OnCollisionExit2D(Collision2D collision) {
// 바닥에서 벗어났음을 감지하는 처리
// 어떤 콜라이더에서 떼어진 경우 isGrounded 를 false 로 변경
isGrounded = false;
}
노말벡터의 방향을 검사하는 행위에 주목한다.
OnCollision 계열의 충돌 이벤트 메서드는 여러 충돌 정보를 담는 Collision 타입의 데이터를 입력받는다. Collision 타입은 충돌 지점들의 정보를 담는 ContactPoint 타입의 데이터를 contacts 라는 배열 변수로 제공한다. 따라서 contacts 배ㅐ열의 길이는 충돌 지점의 개수와 일치한다.
collision.contacts[0] 은 두 물체 사이의 여러 충돌 지점 중에서 첫 번째 충돌 지점의 정보를 가져온다. ContactPoint 와 ContactPoint2D 타입은 충돌 지점에서 충돌 표면의 방향(노말벡터) 을 알려주는 변수인 normal 을 제공한다.
어떤 표면의 노말벡터의 y 값이 1.0 인 경우 해당 표면의 방향은 위쪽이다. 반대로 y 값이 0인 경우 해당 표면의 방향은 완전히 오른쪽이나 왼쪽이다. y 값이 -1.0 인 경우 해당 표면은 아래를 향한다.
11.6.7 PlayerController 스크립트 전체 코드
using UnityEngine;
// PlayerController는 플레이어 캐릭터로서 Player 게임 오브젝트를 제어한다.
public class PlayerController : MonoBehaviour {
public AudioClip deathClip; // 사망시 재생할 오디오 클립
public float jumpForce = 700f; // 점프 힘
private int jumpCount = 0; // 누적 점프 횟수
private bool isGrounded = false; // 바닥에 닿았는지 나타냄
private bool isDead = false; // 사망 상태
private Rigidbody2D playerRigidbody; // 사용할 리지드바디 컴포넌트
private Animator animator; // 사용할 애니메이터 컴포넌트
private AudioSource playerAudio; // 사용할 오디오 소스 컴포넌트
private void Start() {
// 초기화
// 게임 오브젝트로부터 사용할 컴포넌트들을 가져와 변수에 할당
playerRigidbody = GetComponent<Rigidbody2D>();
animator = GetComponent<Animator>();
playerAudio = GetComponent<AudioSource>();
}
private void Update() {
// 사용자 입력을 감지하고 점프하는 처리
if (isDead)
{
// 사망시 처리를 더 이상 진행하지 않고 종료
return;
}
// 마우스 왼쪽 버튼을 눌렀으며 && 최대 점프 횟수(2) 에 도달하지 않았다면
if (Input.GetMouseButtonDown(0) && jumpCount < 2)
{
// 점프 횟수 증가
jumpCount++;
// 점프 직전에 속도를 순간적으로 제로(0,0) 로 변경
playerRigidbody.velocity = Vector2.zero;
// 리지드바디에 위쪽으로 힘 주기
playerRigidbody.AddForce(new Vector2(0, jumpForce));
// 오디오ㅗ 소스 재생
playerAudio.Play();
}
else if (Input.GetMouseButtonUp(0) && playerRigidbody.velocity.y > 0)
{
// 마우스 왼쪽 버튼에서 손을 떼는 순간 && 속도의 y 값이 양수라면(위로 상승 중)
// 현재 속도를 절반으로 변경
playerRigidbody.velocity = playerRigidbody.velocity * 0.5f;
}
// 애니메이터의 Grounded 파라미터를 isGrounded 값으로 갱신
animator.SetBool("Grounded", isGrounded);
}
private void Die() {
// 사망 처리
// 애니메이터의 Die 트리거 파라미터를 셋
animator.SetTrigger("Die");
// 오디오 소스에 할당된 오디오 클립을 deathClip 으로 변경
playerAudio.clip = deathClip;
// 사망 효과음 재생
playerAudio.Play();
// 속도를 제로로 변경
playerRigidbody.velocity = Vector2.zero;
// 사망 상태를 true 로 변경
isDead = true;
}
private void OnTriggerEnter2D(Collider2D other) {
// 트리거 콜라이더를 가진 장애물과의 충돌을 감지
if (other.tag == "Dead" && !isDead)
{
// 충돌한 상대방의 태그가 Dead 이며 아직 사망하지 않았다면 Die() 실행
Die();
}
}
private void OnCollisionEnter2D(Collision2D collision) {
// 바닥에 닿았음을 감지하는 처리
// 어떤 콜라이더와 닿았으며, 충돌 표면이 위쪽을 보고 있으면
if (collision.contacts[0].normal.y > 0.7f)
{
// isGrounded 를 true 로 변경하고, 누적 점프 횟수를 0으로 리셋
isGrounded = true;
jumpCount = 0;
}
}
private void OnCollisionExit2D(Collision2D collision) {
// 바닥에서 벗어났음을 감지하는 처리
// 어떤 콜라이더에서 떼어진 경우 isGrounded 를 false 로 변경
isGrounded = false;
}
}
11.6.8 PlayerController 컴포넌트 설정하기
이제 Player 게임 오브젝트에 추가한 PlayerController 컴포넌트의 필드를 설정한다.
private 으로 선언된 컴포넌트 관련 변수 playerRigidbody, animator, playerAudio 는 GetComponent() 메서드로 참조를 가져왔기 때문에 인스펙터 창에서 별도로 설정할 필요가 없다.
따라서 public 으로 선언한 AudioClip 타입의 변수 deathClip 만 설정하면 된다.
1. deathClip 에 오디오 할당
- 하이어라키 창에서 Player 게임 오브젝트 선택
- 인스펙터 창에서 PlayerController 컴포넌트의 Death Clip 필드 옆의 선택 버튼 클릭
- 선택 창에서 die 오디오 클립을 더블 클릭하여 Death Clip 필드에 할당

11.7 마치며
Player 게임 오브젝트를 구현하면서 2D 프로젝트에서 스프라이트를 다루는 방법, 애니메이터를 제어하여 어떤 애니메이션을 재생할지 결정하는 방법을 배웠다. 다음 장에서는 움직이는 발판과 장애물을 구현한다.