[C# / Unity 를 이용한 게임 만들기] 고양이의 구름등반 1탄!

Patrick!·2023년 5월 28일
0
post-thumbnail

게임다운 게임을 만들기 위해서는 !

하나의 객체마다 숨을 불어넣은 듯한 디테일을 살리는 것이 중요하다. (살아있는 느낌이 있도록!)
게임은 플레이 말고도 다양한 재미를 선사해야 한다. (플레이를 하더라도 재밌는 관점은 따로있다!)

1. 게임을 기획해보자.

게임을 기획한다는 것은 언제나 가슴설래는 일이다.(이 순간이 너무 재밌고 즐겁다)
고양이가 구름을 발판 삼아 깃발이 있는 정상까지 올라가는 게임을 만들어보려 한다.

책에서는 다음과 같이 게임의 규칙을 명명했다.

  1. 플레이어는 구름을 밟고 올라가면서 목표지점에 도달해야 한다.
  2. 플레이어가 구름에 올라가면 거기에 맞춰 화면도 위로 올라간다.
  3. 플레이어의 움직임에 따라 애니메이션 효과가 작동해야 한다.
  4. 플레이어가 정상에 도착하면 클리어 씬으로 전환되고 클리어 씬을 탭하면 다시 게임 씬으로 돌아가도록 구현하겠습니다.

물리동작(Physics -> Rigidbody, Collider), 애니메이션(Mecanim), 카메라 스크립트 전환, 씬 전환 등을 이번 시간에 알아보도록 할거다.

2. 기획한 것을 하나하나 만들어보자!

우선적으로 이번 게임은 Y축으로 상승진행한다. 그렇기에 이는 핸드폰을 세로모드로 진행해야 하기에 기존과는 달리 세로모드로 게임을 제작한다.

그렇기에 빌드환경이 모바일 환경이라면 Portrait 환경으로 진행해야 한다는 것을 잊지말자.
(Landscape 환경으로 진행하면 개판이 된다!)

이제 게임의 주체들을 생성하고 실행을 시켜보자.
하지만 생각처럼 쉽게 될 일은 아니었다 실행과 동시에 오브젝트들은 움직이지 않고 고정된 상태로 게임이 진행된다...

이번 게임에는 '중력'과 '충돌판정'이라는 중요한 요소가 필요하다.
하지만 지난 시간에 배운 충돌판정 영역을 생성하는 방법으로 진행하면 문제가 되기에 Unity에서 제공하는 중력과 충돌판정을 이용해보자!

3. 계획한 것을 이루기 위해서는 새로운 기술을 배워야한다.

현재까지 만든 게임에 심각한 문제
1. 고양이는 중력의 영향을 받지만 구름은 중력의 영향을 받지 않아야 한다.
2. 구름과 고양이의 충돌판정이 있어야한다.
3. 플레이어의 움직임에 따라 애니메이션 효과가 작동해야 한다.


첫 번째, 문제를 해결해보자

1번 게임 규칙에 따라 1. 플레이어는 구름을 밟고 올라가면서 목표지점에 도달해야 한다.
-> 고양이는 점프로 공중에서 다시 떨어져야 한다. (중력)
-> 고양이는 구름을 밟고 있어야 한다. (충돌판정)

이 둘을 만족시킬 수 있는 Unity 기능으로 Physics 가 있다.
주로 무대 위를 플레이어가 자유롭게 이동하는 액션 게임이나 복잡한 충돌 판정이 필요한 슈팅 게임에 이상적으로 사용된다.
Physics는 Rigidbody component(중력)와 Collider component(충돌판정)가 존재한다.

A. Rigidbody component(중력)

흔히 '힘 계산' -> 물체에 작용하는 중력이나 마찰 등에 사용되는 기능이다.

이 기능은 현실에 작용되는 '중력'을 구현하기 적합한 Unity 기능이다.
Rigidbody를 적용할 객체에 [add component] -> Physics 2D -> Rigidbody를 클릭한다.

위의 사진처럼 간단하게 Rigidbody를 추가할 수 있다.

추가하고 나면 해당 컴포넌트가 객체의 Inspector창에 출력된다. Unity Rigidbody Document
(자세한 기능 목록은 공식문서를 참고)

이 게임을 제작하는데 있어 가장 중요한 기능은 [body Type] 과 [Constraints] 이다.
[body Type] = 중력의 영향 결정하는 객체로 설정하는 항목

[Constraints] = x, y축의 움직임 or z축의 회전에 대한 제약사항을 설정하는 항목

  • 플레이어(고양이)는 중력의 영향을 받아야 하기에 -> Dynamic
  • 오브젝트(구름)은 중력의 영향을 받지 않아야 하기에 -> Kinematic

하지만 여기서 플레이어(고양이)는 추가적으로 [Constraints]의 설정을 진행해야 한다.
이에 대한 설명은 Collider를 다루면서 진행하도록 하겠다!


두 번째 문제를 해결해보자.

Collider component(충돌판정)이 필요했던 이유는 구름(오브젝트)를 밟고 있는 플레이어(고양이)를 만들어야하기 때문이다.

B. Collider component (충돌판정)

이전 시간에는 간단하게 피타고라스 정의를 이용한 원형 충돌판정에 대한 부분을 다루었다.
하지만 여기서는 Collider를 이용해 간단하게 만들 수 있다. (굳이 코드를 작성할 필요가 없었던 것이다...)

Collider를 적용할 객체에 [add component] -> Physics 2D -> Circle Collider를 클릭한다.

위의 사진처럼 다양한 모양의 Collider를 추가할 수 있다.

추가한 Circle Collider는 위의 사진과 같이 Material 과 위치, 크기등을 설정할 수 있다.

여기서 조금 더 디테일하게 플레이어의 크기에 맞추기 위해 Box Collider까지 추가해보도록 하자.

왜 두개를 사용하는 것인가?
처음 내가 만들때도 이러한 의문을 가지기 십상이었다. 간단하게 만들어서 충돌판정을 만들면 될일이지 않은가?
=> 충돌판정은 현실에서 일어나는 것처럼 매우 사실적이다.
(원모양의 구멍에 커다란 세모를 통과시키면 안되는 것처럼 당연하다는 것이다.)

그렇기에 개발을 진행할 때도 플레이어의 바닥면은 둥근 편이 유용하게 작용한다.
경사가 심하지 않는 언덕을 넘어갈 때 작은 턱을 올라갈 때 처럼 단순한 충돌판정에도 걸리는 경우보다는 유연하게 작동할 수 있도록 하는 개념이라고 보면 된다.

그렇기에 Box Collider 와 Circle Collider를 이용해서 다음과 같이 플레이어(고양이)의 충돌판정의 범위를 설정했다.
(지난 시간보다 매우 살아있는 느낌을 주는 편이며, 매우 흥미롭다.)

구름(오브젝트)같은 경우 Box Collider만을 이용해서 간단하게 만들었다.

Rigidbody와 Collider를 적용한 게임의 실행된 모습을 보면 다음과 같이 구름 위에 고양이가 있는 모습대로 구현됬다!

여기서 Collider를 실용적으로 활용하기 위해서는 2D의 원리를 알고 있어야 한다.
X, Y, Z축이 원래 다 존재한다. 하지만 실용적으로 사용되는 부분은 X축과 Y축만 해당한다.

그럼 Z축은 언제 사용되는 것인가? => 플레이어의 회전 방지에 해당한다.

플레이어의 Collider 모양이 반 캡슐인 상태에서 발밑이 원형이므로 외부에서 힘들 조금만 받아도 쉽게 넘어진다.
그렇기에 넘어지지 않도록 Freeze Rotation 항목을 설정해야 한다.
=> 이는 Collider 가 아닌 Rigidbody의 Constraints에서 설정할 수 있다.

Cat 의 Rigidbody -> Constraints 의 Freeze Rotation Z 축을 클릭하면 회면의 안쪽으로 플레이어가 회전하지 않게 된다. (2D 게임을 제작할 때, 확인해야할 매우 중요한 부분이라고 생각되었다.)

이제 플레이어를 조작할 코드를 살펴보도록 하자.

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class PlayerController : MonoBehaviour
{
    Rigidbody2D rigi2D;
    // 점프하는 힘
    float jumpForce = 680.0f;
    // 움직이는 힘
    float walkForce = 30.0f;
    // 움직이는 힘의 최대값
    float maxWalkSpeed = 2.0f;

    void Start()
    {
        this.rigi2D = GetComponent<Rigidbody2D>();
    }

    void Update()
    {
        if (Input.GetKeyDown(KeyCode.Space)) this.rigi2D.AddForce(transform.up * this.jumpForce);

        // 좌우이동
        int key = 0;
        if (Input.GetKey(KeyCode.RightArrow)) key = 1;
        if (Input.GetKey(KeyCode.LeftArrow)) key = -1;

        // 플레이어 속도 계산
        float speedx = Mathf.Abs(this.rigi2D.velocity.x);

        // 스피드 제한
        if (speedx < this.maxWalkSpeed) this.rigi2D.AddForce(transform.right * key * this.walkForce);

        // 움직이는 방향에 따라 반전
        if (key != 0) transform.localScale = new Vector3(key, 1, 1);
    }
}

위의 코드에서 주목할 것은 바로 AddForce()이다.
프레임마다 AddForce 메서드를 사용해 힘을 계속해서 가하면 플레이어는 점점 가속하게 된다.
이는 엑셀을 밟으면 차가 가속하는 원리롸 같다고 보면 된다. (하지만 여기서는 속도를 제어할 필요가 있다.)

그렇기에 제어할 부분으로 maxWalkSpeed를 넘어가면 이를 제어한다.

if (speedx < this.maxWalkSpeed) this.rigi2D.AddForce(transform.right * key * this.walkForce);

그리고 캐릭터를 방향키로 움직이다보면 캐릭터가 문워크를 하는 상황이 발생한다.... (이게 아닌데!)
입력한 방향키에 따라 바라보는 시선을 동일하게 하기 위해서 다음과 같은 코드를 추가했다.

if (key != 0) transform.localScale = new Vector3(key, 1, 1);

다시 본론으로 돌아와서!

세 번째 문제를 해결해보자.

Physics를 적용한 것 만으로는 고양이의 움직임을 자연스럽게 만들 수 없다...
자연스러움을 추가하기 위해서는 '애니메이션'이 필수적이다!

앞서 만든 조작 스크립트의 입력에 따른 애니메이션을 구현하기 위해서 '스프라이트 애니메이션'을 사용한다.

스프라이트 애니메이션
스프라이트 애니메이션을 만들려면 게임을 실행할 때 스크립트에서 일러스트를 한 컷씩 교체하거나 애니메이션 작성과 전환을 일관되게 할 수있는 '메카님' 구조를 사용해야합니다.

메카님
애니메이션을 작성하고 실행할 때 유니티 에디터에서 일관되게 조작할 수 있는 기능입니다.

게임을 설계할 때 스프라이트 애니메이션을 작성해 각 애니메이션의 교체 시기를 지정할 수 있습니다.
게임을 플레이할 때 메카님이 오브젝트 상태를 판단하고 자동으로 애니메이션을 바꿔 재생할 수 있습니다.

메카님을 사용하려면 그전에 스프라이트, 애니메이션 클립, 애니메니터 컨트롤러, Animator 컴포넌트의 관계를 알아야합니다.

스프라이트: 움직임을 정의한 파일의 개념 ('걷기 애니메이션의 모든 이미지파일')
애니메이션 클립: 스프라이트를 정리한 파일들의 개념 ('걷기 애니메이션', '점프 애니메이션' 등이 해당)
애니메이터 컨트롤러: 애니메이션 클립을 정리한 개념 ('어느시점에 어느 애니메이션 클립을 재생할지 지정')
Animator 컴포넌트: 애니메이션하려는 오브젝트에 있는 Animator컴포넌트에 애니메이터 컨트롤러를 설정하면 애니메이터 컨트롤러에서 정의한 애니메이션을 재생할 수 있다.

이제 애니메이션을 적용할 대상!(고양이)을 클릭하고 진행해봅시다!

[Window] -> [Animation] -> [Animation] 를 진행하여 Animation 창을 켜봅시다!

처음 Animation 창이 켜지면 아무것도 진행할 수 없기 때문에

[Create]를 클릭하여 파일명을 만들 애니메이션의 이름으로 작성하고 저장합니다.

저장했다면 아래와 같이 [Add Property] 가 생성되면서 애니메이션을 만들 수 있는 준비상태가 됩니다.

이제 [Add Property]를 눌러 [Sprite Renderer] 를 선택해서 [Sprite]를 선택합니다.
(보면 다양한 요소들이 존재하지만 차차 알아가도록 하자...)

이전에는 존재하지 않던 Sprite가 생성된 모습을 확인할 수 있습니다. 이제 본격적인 작업에 들어간다.

A : 애니메이션이 시작하는 지점 및 애니메이션의 초기 상태
B : 애니메이션이 끝나는 지점 및 애니메이션이 초기 상태로 돌아가는 기점
C : 애니메이션이 존재하는 표시
D : C에 존재하는 애니메이션을 출력
E : [F] 를 이용해 생성된 [B] 지점
F : [Add Keyframe] 의 기능으로 애니메이션 전체 길이를 정하는 기능입니다.
(끝나는 지점을 선택하고 [F]를 클릭하면 해당 지점이 애니메이션의 끝나는 지점으로 설정됩니다.)

위 사진에서 나타난 것처럼 애니메이션을 일정한 간격으로 드래그 합니다.
이때 중요한 것은 꼭! 일정한 간격을 유지해야 한다는 것 입니다. (이는 만드는 애니메이션에 따라 달라집니다.)
걷는다는 개념을 애니메이션으로 만들 때, 사람이 걷는 것과 같은 개념으로 걷게 만들 수 있도록 일정한 간격을 유지하는 것입니다.

그리고 여기서 중요한 개념은 [F]의 [Add Keyframe]기능입니다.
애니메이션은 하나의 동작에 대해 시작과 끝이 존재하는 개념이며 이 개념이 성립하려면 끝에서 시작점으로 돌아가는 동작이 필요합니다. -> 이 동작을 가능하게 해주는 기능이 바로 [Add Keyframe] 입니다.

애니메이션 창이 켜지면 처음에 1초를 간격으로 생성되어 있을 겁니다. 마지막 부분([B])를 삭제하고 애니매이션을 채워 넣은 후에 [F]를 진행하면 하나의 애니메이션을 완성할 수 있습니다.

위의 애니메이션을 만들었다면 이를 PlayerController에 적용시켜 속도를 설정하자!

public class PlayerController : MonoBehaviour {
	
    Animator animator;
    
    void Start() {
    	this.animator = GetComponent<Anumator>();
    }
    
    void Update() {
    	// 속도 설정
    	this.animator.speed = speedx / 2.0f;
    }
}

위에 작성된 코드를 PlayerController에 추가하면 애니메이션 재생 속도가 플레이어 이동속도에 비례하도록 작성되었습니다.

이제 적용된 애니메이션을 확인해보면! 아래와 같이 방향키로 움직일 때, 자연스럽게 움직이는 것처럼 표출됩니다!


이번에 배운 개념

  1. Physics (Rigidbody component(중력) / Collider component(충돌판정))
  2. Animator (애니메이션)

이제 모든 문제를 해결했으니 게임을 완성시켜보자.
설명이 길어지다 보니 게임을 완성시킬지 의문이 든다 ㅎㅎ.... 2편에서 !


출처 : 그림으로 이해하고 만들면서 익히는 유니티 교과서[개정5판] - 기타무라 마나미

profile
C++와 Unreal Engine / C#과 Unity / Katalon Studio를 통한 자동화 테스트 등을 하루하루 공부한 기록

0개의 댓글