반응형

 

유니티 엔진에 대한 본격적인 공부에 들어가기에 앞서 게임 엔진과 유니티 엔진에 대해서 알아봅시다.

반응형
반응형

Tutorial (9) 

UGUI 기초

 

작성 기준 버전 :: 2019.2

 

이번에는 유니티 엔진의 GUI에 대해서 알아보자.

 

[본 포스트의 내용은 유튜브 영상으로도 시청하실 수 있습니다]

 

게임에서 플레이어에게 게임 내의 정보를 전달하는 매개체를 유저 인터페이스, 줄여서 UI라고 부른다.

 

GUI
CUI

 

그리고 이 UI는 크게 문자로 보여주고 사용자 역시 문자를 입력해서 상호작용해야하는 Character User Interface, CUI와 이미지와 문자의 혼합된 형태로 보여지고 마우스를 이용해서 상호작용할 수 있는 Graphic User Interface, GUI로 나누어진다.

 

문자나 글자로만 상호작용하는 CUI는 컴퓨터의 성능이 모자라서 그래픽으로 UI를 보여주기 힘들던 옛날 게임에서나 볼 수 있는 방식의 UI다. 최근에 와서는 옛날 게임의 감성을 되살리고자 하는 게임에서 이 CUI 방식을 일부 차용하기도 한다.

 

하지만 컴퓨터의 성능이 충분히 올라온 지금은 대부분의 게임에서 GUI를 사용한다.

 

유니티 엔진의 GUI 시스템, UGUI

 

캔버스

 

유니티 엔진에서 사용되는 GUI 시스템을 유니티 GUI 줄여서 UGUI라고 부른다. 그럼 이제 에디터에서 UGUI의 기본부터 차근차근 살펴보자.

 

 

 

하이어라키 뷰에 우클릭 해보면 생성할 수 있는 게임 오브젝트의 종류를 볼 수 있는데 그 중에 UI 항목이 있다.

 

글자를 표현하는 Text, 그림을 표현하는 Image, 클릭할 수 있는 Button 등 UI로 사용할 수 있는 여러가지 형태의 게임 오브젝트들을 볼 수 있다.

 

 

우선 UI 게임 오브젝트 중에서 Canvas를 생성해보자. Canvas를 생성하면 씬 뷰에 하얀 선으로 직사각형이 표시되는 것을 볼 수 있다.

 

Canvas는 유니티 엔진에서 UI를 배치하기 위한 영억으로 모든 UI가 화면에 표시되기 위해서는 이 Canvas 컴포넌트가 부착된 게임 오브젝트의 자식 게임 오브젝트여야 한다.

 

 

Canvas의 설정은 용도에 따라서 여러가지가 있지만, 방금 생성한 Canvas처럼 Render Mode를 [Screen Space - Overlay]로 된 것을 기본적으로 많이 사용한다. 이 설정은 게임 해상도로 표현되는 스크린 스페이스에 UI를 그리는 설정이다.

 

이 설정에서 Canvas의 해상도는 게임의 해상도를 따른다.

 

 

게임 뷰를 보면 지금 해상도가 1920x1080으로 설정되어 있는 것을 볼 수 있는데 거기에 맞춰서 Canvas의 width와 height도 1920x1080인 것을 볼 수 있다. 게임 뷰의 해상도를 바꿔보면 Canvas의 해상도 역시 자동으로 바뀌는 것을 볼 수 있다.

 

[Screen Space - Overlay]는 일반적인 평면 UI에서 주로 사용되는 설정입니다.

 

Rect Transform 컴포넌트

 

일반 게임 오브젝트의 Transform
UI 게임 오브젝트의 Rect Transform

 

일반 게임 오브젝트와 UI 게임 오브젝트의 차이점으로는 일반 게임 오브젝트의 경우에는 Transform 컴포넌트로 씬 안에서의 위치를 표현하지만, UI 게임 오브젝트들은 Rect Transform 컴포넌트로 위치를 표현한다.

 

메인 메뉴 만들어 보기

 

간단하게 게임의 메인 메뉴 형태로 UI들을 만들어 보자.

 

2D 작업 모드

 

그 전에 씬 뷰에서 이렇게 원근감이 있는 상태로는 이동도 어렵고 UI 작업이 불편하기 때문에 키보드의 숫자 '2' 버튼을 눌러서 씬 뷰를 2D 모드로 만든다. 2D 모드는 UI 작업이나 2D 게임 작업을 위한 모드로 마우스 휠을 돌려서 확대/축소하고 휠 클릭으로 화면의 위치를 이동시킬 수 있다.

 

 

캔버스에 Button과 Text, Image를 이용해서 위의 이미지와 같이 UI를 구성해보자.

 

 

중간의 꾸미는 모양이 이미지는 이 그림을 다운로드 받아서 사용하면 된다.

 

늘 강조하던 내용이지만, 하이어라키 뷰에서 게임 오브젝트의 이름이 생성된 초기 이름 그대로이면 나중에 필요한 오브젝트를 찾기가 어려워지기 때문에 버튼 이름도 적절하게 바꿔주도록 한다.

 

참고로 유니티 엔진에서 어떤 UI가 더 위에 그려지느냐 하는 우선 순위는 하이어라키 뷰에서의 순서로 결정된다. 지금 하이어라키 뷰를 보면 "Background" Image가 다른 Text나 Button보다 하이어라키 뷰에서 상단에 있는 것을 볼 수 있다. 하지만 씬 뷰나 게임 뷰에서는 제일 뒤에 그려지고 있다.

 

이 "Background" 이미지를 조금씩 아래로 내려보면 다른 버튼과 텍스트를 하이어라키 뷰의 순서에 따라서 가리기 시작하는 것을 볼 수 있다.

 

이렇게 다른 UI 보다 앞에 나오길 바라는 UI는 하이어라키 뷰에서 아래로 옮기고, 뒤에 나오길 바라는 UI는 하이어라키 뷰에서 위로 옮겨서 UI의 순서를 조정할 수 있다.

 

이번에는 간단하게 방금 만든 메인 메뉴에 기능을 추가해보도록 하자. 먼저 MainMenu라는 이름으로 C# 스크립트를 생성한다.

 

public class MainMenu : MonoBehaviour
{
    // 새 게임 버튼을 눌렀을 때 버튼이 호출할 함수
    public void OnClickNewGame()
    {
        Debug.Log("새 게임");
    }

    // 불러오기 버튼을 눌렀을 때 버튼이 호출할 함수
    public void OnClickLoad()
    {
        Debug.Log("불러오기");
    }

    // 옵션 버튼을 눌렀을 때 버튼이 호출할 함수
    public void OnClickOption()
    {
        Debug.Log("옵션");
    }

    // 종료 버튼을 눌렀을 때 버튼이 호출할 함수
    public void OnClickQuit()
    {
#if UNITY_EDITOR // 에디터에서만 실행되는 코드
        UnityEditor.EditorApplication.isPlaying = false;    // 에디터의 플레이 모드를 중단
#else   // 빌드된 게임에서 실행되는 코드
        Application.Quit(); // 실행되고 있는 게임 프로그램을 종료
#endif
    }
}

 

코드를 저장하고 에디터로 돌아와서 "Main Menu Canvas"에 MainMenu 컴포넌트를 추가해준다.

 

 

그 다음에 각 버튼의 On Click 이벤트에 [+] 버튼을 누른 뒤, MainMenu 컴포넌트를 붙인 게임 오브젝트를 할당하고 각 버튼에 맞는 함수를 호출하도록 만든다.

 

그리고 플레이 버튼을 눌러 게임을 실행하고 각 버튼을 눌러보면 함수에 넣어둔 로그가 출력되고 마지막으로 종료 버튼을 누르면 플레이 상태가 종료되는 것을 알 수 있다.

 

이렇게 유니티에서 제공하는 UI 관련 컴포넌트들을 잘 응용하면 거의 모든 UI 기능들을 구현할 수 있다.

반응형
반응형

Tutorial (8) 

스크립트 작업 기초

 

작성 기준 버전 :: 2019.2

 

[본 튜토리얼의 내용을 유튜브 영상을 통해서 확인하실 수도 있습니다]

 

이번 섹션에서는 스크립트 작업으로 기초적인 커스텀 컴포넌트를 만드는 법을 배워보자.

 

본격적인 섹션 진행에 앞서 게임 오브젝트와 컴포넌트에 관련된 지식이 필요하다면 이 포스트를 참고해보자.

 

또한 이번 섹션을 진행하기 위해서는 C# 프로그래밍에 대한 기초적인 지식을 필요로 한다.

 

커스텀 컴포넌트 생성

 

[그림 1]

 

우선 커스텀 컴포넌트를 만들기 위해서 C# 스크립트를 하나 생성해보자. 프로젝트 뷰에 우클릭하여 [Create > C# Script] 항목을 선택한다.

 

 

그렇게하면 NewBehaviourScript라는 이름으로 C# 스크립트 파일이 하나 생성된다.

 

 

바로 엔터 키를 누르지 말고 파일의 이름을 ScriptingTest로 변경하고 엔터 키를 누르도록 하자. C# 스크립트 파일은 제일 처음 이름이 정해질 때, 스크립트 파일 내부의 클래스 이름이 정해지며, 스크립트 파일의 이름과 클래스의 이름이 일치하는 것을 권장하기 때문에 클래스의 이름을 처음에 제대로 정하는 것이 나중에 수정하는 것보다 좋다. 특히 나중에 파일의 이름을 바꾸면 내부의 클래스의 이름도 수동으로 바꿔야하므로 굉장히 번거롭다.

 

 

그리고 생성된 스크립트 파일을 더블클릭하면 비주얼 스튜디오가 열립니다.

 

모노비헤이비어 클래스 상속

 

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

public class ScriptingTest : MonoBehaviour
{
    // Start is called before the first frame update
    void Start()
    {
        
    }

    // Update is called once per frame
    void Update()
    {
        
    }
}

 

최초로 생성된 기본 코드는 위와 같다. 먼저 생성된 ScriptingTest 클래스가 모노비헤이비어(MonoBehaviour) 클래스를 상속받고 있는 것을 볼 수 있다. 이 유니티로 게임을 제작할 때 사용되는 C# 클래스는 이 모노비헤이비어를 상속받는 클래스과 상속받지 않는 클래스로 크게 나누어진다.

 

 

모노비헤이비어 상속 여부에 따른 차이는, 모노비헤이비어를 상속받지 않은 클래스는 게임 오브젝트에 컴포넌트로써 부착되지 못한다는 것에 있다. 때문에 컴포넌트로써 게임 오브젝트에 부착되어서 씬 내부에 존재해야하는 클래스는 모노비헤이비어를 상속받는게 필수이고, 씬에 컴포넌트로 배치되지 않고 코드 내부에서 개념적으로만 존재할 클래스는 모노비헤이비어를 상속받지 않아야 한다.

 

모노비헤이비어의 라이프 사이클

 

 

 

 

모노비헤이비어를 상속받아서 게임 오브젝트에 부착되어 동작하는 스크립트를 잘 활용하려면 모노비헤이비어의 라이프 사이클에 대해서 잘 알아두는 것이 좋다. 모노비헤이비어를 상속받는 컴포넌트는 생성되어 게임 오브젝트에 부착되는 순간부터 위의 이미지와 같은 과정을 거친다.

 

그리고 위의 모노비헤이비어 상속 파트에서 본 코드 블럭을 보면 Start() 함수와 Update() 함수가 구현되어 있는 것을 볼 수 있다. 이와 같이 거치는 과정의 이름으로 함수를 만들어두면 해당 과정을 거칠 때, 그 함수가 실행되는 구조이다.

 

그럼 각 과정이 언제 호출되는지 어떻게 구현하면 되는지에 대해서 하나씩 알아보자.

 

Awake

 

private void Awake()
{
    Debug.Log("Awake");   
}

 

Awake 과정은 스크립트 인스턴스가 로딩될 때 단 한 번 호출되는 함수이다. 컴포넌트에 대한 초기화가 필요한 경우에 사용된다. 참고로 모노비헤이비어를 상속받는 클래스는 생성자 대신에 Awake() 함수를 구현해서 사용해야 한다.

 

OnEnable

 

private void OnEnable()
{
    Debug.Log("OnEnable");   
}

 

OnEnable 과정은 모노비헤이비어를 상속받은 컴포넌트가 부착된 게임 오브젝트가 활성화될 때마다 호출되는 함수이다.

 

 

에디터의 씬에서 게임 오브젝트를 선택하면 인스펙터 뷰에서 선택한 게임 오브젝트에 대한 정보를 볼 수 있는데, 이 중에 게임 오브젝트 이름 앞에 체크박스가 있다. 이 체크박스를 클릭해보면 체크박스 상태에 따라서 게임 오브젝트가 활성화되었다 비활성화되었다하는 것을 볼 수 있다. 이렇게 게임 오브젝트가 활성화될 때마다 OnEnable() 콜백 함수가 호출되는 것이다. 참고로 게임 오브젝트가 비활성화된 상태에서는 해당 게임 오브젝트에 부착된 모든 컴포넌트가 동작을 멈춘다.

 

Start

 

private void Start()
{
    Debug.Log("Start");   
}

 

Start 과정은 Update 과정이 실행되기 직전에 단 한 번 호출된다. 모노비헤이비어의 라이프 사이클 중에 단 한 번 호출된다는 점이 Awake와 같지만 Start는 게임 오브젝트가 활성화된 경우에만 호출된다는 차이점이 있다.

 

Update

 

private int i = 5;
private void Update()
{
    i--;
    if(i >= 0)
    {
        Debug.Log("Update :: " + i);
    }
    else
    {
        Destroy(gameObject);
    }
}

 

Update 과정은 모노비헤이비어가 활성화된 상태에서 매 프레임마다 호출된다. 대부분의 게임의 동작 처리는 이 Update() 함수에서 수행되는 경우가 많다. 다만, 이 Update() 함수는 프레임마다 호출되기 때문에 프레임 드랍이 발생하는 경우에는 호출 횟수가 줄어든다. 프레임과 상관 없이 코드가 작동하기 원한다면 FixedUpdate() 함수를 사용해야 한다.

 

Update() 함수는 OnEnable() 함수를 설명하면서 이야기했듯이 게임 오브젝트가 비활성화된 상태에서는 동작하지 않는다.

 

LateUpdate

 

private void LateUpdate()
{
    Debug.Log("LateUpdate");   
}

 

LateUpdate는 단어 그대로 늦은 업데이트로 Update() 함수가 실행된 직후에 실행되는 업데이트 함수이다. Update() 함수에서 게임 로직을 처리한 직후에 처리하고 싶은 로직이 있다면 이곳에서 처리하면 된다.

 

FixedUpdate

 

private void FixedUpdate()
{
    Debug.Log("FixedUpdate");   
}

 

FixedUpdate는 매 프레임마다 호출되는 Update와 달리 지정된 시간마다 호출되는 업데이트 함수이다. 때문에 프레임이 들쭉날쭉한 상황에서도 일정한 시간마다 호출된다. 주로 호출 시간에 따라서 결과가 달라지면 안되는 물리적인 계산에 사용된다.

 

OnDisable

 

private void OnDisable()
{
    Debug.Log("OnDisable");   
}

 

OnDisable 과정은 모노비헤이비어가 비활성화되는 경우에 사용된다. 그리고 오브젝트가 삭제되는 경우에도 호출된다.

 

OnDestroy

 

private void OnDestroy()
{
    Debug.Log("OnDisable");   
}

 

OnDestory 과정은 모노비헤이비어가 제거될 때 호출된다.

 

 

위의 코드를 모두 ScriptingTest 클래스에 작성하고 플레이시켜보면 위의 이미지와 같은 순서로 로그가 발생하는 것을 볼 수 있다.

 

 

 

 

 

변수

 

우리가 게임을 만들면서 사용될 값, 공격력, 방어력, 공격속도, 이동속도, HP 등의 데이터나 정보를 담아둘 것을 변수라고 부른다. 유니티 엔진에서 스크립트를 작성하는 C#은 담고자하는 값의 종류에 따라서 변수의 종류가 나누어진다. 그럼 이 변수의 종류에 대해서 알아보도록 하자.

 

정수(int)

 

int i = 10;

 

첫 번째 변수 유형은 정수형이다. 정수형 변수 int는 0과 양의 정수, 음의 정수를 담기 위한 변수로, -2,147,483,648부터 2,147,483,647까지 담을 수 있다. 

 

남아있는 라이프의 갯수, 현재 생산된 인구 수 등의 정수로 딱 떨어지는 곳에서 사용될 수 있다.

 

실수(float)

 

float f = 3.14159f;

 

두 번째 변수 유형은 실수형이다. 실수형 변수 float은 소수를 담기 위한 변수로 일반적으로 소수점 다섯 번째자리 0.00001까지 정확도를 표현할 수 있다.

 

주로 1.2초 같은 시간이나 20.25%와 같은 확률 등을 표현할 때, 주로 사용된다.

 

문자열(string)

 

string str = "hello";

 

세 번째 변수 유형은 문자열입니다. 문자열 변수 string은 말그대로 문자들의 집합인 문자열을 담는 변수이다.

 

주로 캐릭터나 아이템의 이름, 설명, 게임에서 사용되는 대사 자막 등의 데이터를 담는데 사용된다.

 

논리값(bool)

 

bool isMoveable = true;

 

네 번째 변수 유형은 논리값이다. 논리값 변수 bool은 참(true) 혹은 거짓(false)의 상태를 가지는 변수로 주로 조건을 처리할 때 사용된다.

 

이 외에도 각 종류의 변수를 묶음 단위로 취급하는 배열 등이 있고, 일반 C# 클래스나 모노비헤이비어를 상속받은 클래스 역시 변수가 될 수 있다.

 

 

함수

 

함수는 게임 기능을 수행하기 위한 작업을 하나의 블록으로 묶은 것을 의미한다. 모노비헤이비어의 라이프 사이클에 대해서 설명하면서 본 Awake, OnEnable, Start, Update, OnDisable, OnDestroy 역시 함수이다. 일반적으로 함수는 하나의 기능 단위로 작성되는 경우가 많다.

 

int attackDamage = 10;

public bool Attack(Monster monster)
{
    monster.hp -= attackDamage;
    return monster.hp <= 0;
}

 

위의 예시 코드는 몬스터를 공격해서 체력을 공격력만큼 깎고, 몬스터의 체력이 0 이하가 되면 true를 반환하도록 코드가 작성되어 있다. 이렇게 하면 Attack() 함수를 호출하여 몬스터의 체력을 깎고 공격한 몬스터가 죽었는가에 따라서 여러가지 처리를 할 수 있게 된다.

 

 

공개 수준 결정

 

개발자는 코드를 작성하면서 변수나 함수에 대해서 공개 수준을 결정할 수 있다.

 

public int i;

protected float f;

private string str;
 
public void Function1() { }
 
protected void Function2() { }
 
private void Function3() { }

 

변수와 함수의 공개 수준은 앞에 표시된 public, protected, private 키워드를 통해서 결정된다. 이러한 공개 수준은 일반적인 C# 프로그래밍에서와 같이 public은 클래스 외부에서 접근이 가능하고 protected는 해당 클래스를 상속받은 클래스에서만 접근이 가능하다. 그리고 private는 해당 클래스의 내부에서만 사용 가능하다.

 

public class ScriptingTest : MonoBehaviour
{
    public int attackDamage = 10;
}

 

그리고 유니티 엔진만의 특징으로는 모노비헤이비어 클래스를 상속받은 클래스에서 public으로 설정된 변수는 에디터의 인스펙터 뷰에서 바로 보고 수정할 수 있다는 장점이 있다.

 

 

이러한 방식의 장점은 매번 게임의 수치가 바뀔 때마다 프로그래머가 코드를 수정하고 새로 빌드 과정을 거칠 필요없이 게임 디자이너가 에디터에서 즉석으로 값을 바꿀 수 있다는 것이다.

 

하지만 인스펙터 뷰에서 보이게 하고자 하는 모든 변수를 public으로 설정하면 코드 내부에서 어떤 클래스에서던지 접근이 가능해진다. 이런 경우를 방지하고자 protected나 private로 설정한 채로 인스펙터 뷰에 공개하고 싶을 수도 있다.

 

[SerializeField]
private int attackDamage = 10;

 

그럴 때는 SerializeField라는 어트리뷰트를 해당 변수 앞에 명시해주면 private나 protected로 둔 상태로도 인스펙터 뷰에 변수를 공개할 수 있다.

 

[HideInInspector]
public int attackDamage = 10;

 

그와 반대로 변수를 public으로 둔 상태로 인스펙터 뷰에 공개하고 싶지 않다면 HideInInspector 어트리뷰트를 붙여주면 된다.

 

모노비헤이비어 클래스를 상속받아서 만들어진 컴포넌트는 클래스를 기반으로 변수를 어떻게 구성하고 함수를 어떻게 구현하느냐에 따라서 그 컴포넌트의 기능과 역할이 정해진다. 

반응형
  1. 료용 2020.01.26 23:22 신고

    베르님 글보고 맨날 이상한닉으로 질문하다가 결국 티스토리 가입했습니다 ㅋㅋㅋ

    기본적인설명을 심플하게 잘쓰셔놧네요

    • wergia 2020.01.27 03:48 신고

      고정으로 들어오시게되었군요!
      감사합니다 료옹님! 언제나 제가 다시봐도 이해하기 쉽게 쓰려고 노력중입니다 ㅎㅎ
      근데 다시 옛날 글보니까 오타도 많고 그렇더라구요 나중에 수정을 좀 해야겠어요

    • wergia 2020.01.27 03:49 신고

      아 료용님이구나 ㅎㅎ
      새벽에 잠이 안와서 반쯤 깬 상태로 보는거라 닉네임을 잘못봤네요

  2. 료용 2020.02.03 02:18 신고

    좋은글들 잘보고있습니다.

    동방프로젝트게임을 친구가하는걸 보고있는데 전에했던플레이를 다시 볼수있는 기능이있던데

    베르님 혹시 리플레이영상 만드실수있으신가요? 저는 도저히 감도안오더라고요 어떻게시작해야될지도...

    • wergia 2020.02.03 03:50 신고

      리플레이 기능을 만드는걸 이야기 하시는 건가요?
      이 부분은 나중에 포스트로 한번 써봐야겠네요.
      다만, 우선 먼저 말씀 드리자면 리플레이 기능은 여러방법으로 만들 수 있는데, 첫
      번째는 게임 내의 오브젝트의 위치와 컴포넌트가 가지는 값을 모두 기록하고 있다가 그 기록을 따라 생성된 리플레이 오브젝트들이 따라가게 만드는 방법이 있고,
      두번째 방법은 사용자의 입력을 기록하여, 그 기록대로 움직이게 하는 방법,
      세번째 방법은 카메라로 바라보는 장면을 렌더텍스쳐로 모조리 따서 영상으로 만들어서 실시간으로 저장하는 방법 정도가 있겠네요.
      가장 최적의 방법은 두번째 방안이 되겠습니다. 자세한 설명은 포스트에서 뵙죠.

  3. 료용 2020.02.03 23:00 신고

    답변고맙습니다. 근데 탄까지 쏘는 그런게임인데 그렇다면 카메라자체를 저장하는 세번째방법이 맞지않나요? 두번째방법에서 적이쏘는 탄막같은것을 담아낼수있나요??

    • wergia 2020.02.04 09:22 신고

      간단하게 말해서 사용자의 입력을 기록한다고 했는데, 음.. 좀 더 정확하게 말하면 이벤트를 기록하는 겁니다. 사용자나 적의 비행체가 움직이는 이벤트, 탄을 발사하는 이벤트, 데미지를 입는 이벤트 등을 기록하는 겁니다.
      탄막 같은것도 발사되는 이벤트랑 탄에서 또 다른 탄이 생성되는 이벤트를 기록하면 됩니다.
      카메라 자체를 저장하는 건 사실 그렇게 추천하지는 않습니다. 게임 프레임을 로직이랑 실제 렌더링 돌리는데 30프레임 60프레임 방어하기도 바쁜데 실시간으로 렌더링하는걸 영상으로 저장하는 것도 생각보다 무거운 작업입니다.

  4. 료용 2020.02.07 17:09 신고

    그렇게해서 결국 했던걸 다시 행동하게 한다는거죠? 근데 왜이렇게 어려워보이죠 ㅋㅋㅋㅋㅋㅋㅋㅋ

    • wergia 2020.02.08 12:55 신고

      처음에 설계를 잘하고 들어가야되는 부분이기는 합니다. 애초에 리플레이를 생각안하고 짠 코드에 리플레이 기능을 추가하려면 크게 고생하게 되더라구요. 난이도로 치자면 카메라를 그대로 녹화하는게 제일 쉽기는 합니다. 다만, 게임 성능 문제도 있고 그냥 영상으로 녹화된 리플레이는 배틀그라운드처럼 막 게임 속을 자유롭게 이동하면서 리플레이를 감상하는게 불가능해지죠. 근데 고사양의 게임이 아니고, 게임 리플레이 속을 자유롭게 돌아다닐 필요가 없다면 카메라를 그대로 녹화하는 방법도 나쁘지는 않습니다.

반응형

Explorer 2D Game Kit 분석 (4) 

게임플레이 요소 (1)


작성 기준 버전 :: 2019.1.4f1


그림 1

 

본격적으로 게임플레이와 관련된 요소들에 대해서 분석해볼 차례이다.


그림 2

 

기본적인 하이어라키 뷰(Hierarchy View) 상태는 스타트 씬과 비슷하지만 게임이 진행되는 씬이기 때문에 플레이어와 연관된 에셋인 Player Assets 파트와 레벨을 구성하고 있는 Level Assets 파트가 추가되어 있는 것을 알 수 있다.


시스템(System)


시스템으로 분류된 게임 오브젝트는 씬 컨트롤러(Scene Controller), 트랜지션 스타트(Transition Start), 트랜지션 데스티네이션(Transition Destination), VFX 컨트롤러(VFX Controller), 백그라운드 뮤직 플레이어(Background Music Player), 피직스 헬퍼(Physics Helper)가 있다.


이 중에서 씬 컨트롤러와 그리고 백그라운드 뮤직 플레이어는 지난 섹션에서 다루었으니 넘어가도록 하고, 지난 섹션에서 다루기는 했으나 트랜지션 포인트(Transition Point) 컴포넌트를 부착하고 있는 트랜지션 스타트 게임 오브젝트는 약간의 차이가 있으나 가볍게 다루고 넘어가도록 한다.


트랜지션 스타트 게임 오브젝트(Transition Start Game Object)


그림 3

 

지난 섹션에서 다루었다시피 트랜지션 스타트 게임 오브젝트는 트랜지션 포인트 컴포넌트가 부착되어 있으며 플레이어의 캐릭터가 콜라이더에 닿으면 플레이어를 다른 씬으로 보내는 역할을 한다.


다만, 스타트 씬에서의 트랜지션 포인트와 다른 점은, 스타트 씬에서는 콜라이더에 닿은 캐릭터가 존재하지 않기 때문에 외부에서 트랜지션 포인트를 호출해서 씬을 전환하는 방식을 사용했다면, 이제부터는 본래의 방식에 맞게 캐릭터가 콜라이더에 닿으면 다른 씬을 보내도록 구성되어 있다는 점이다.


그림 4

 

실제로 씬에서 트랜지션 스타트 게임 오브젝트를 선택해서 보면 발판에서 캐릭터가 뛰어내리면 콜라이더에 닿을 수 있게 배치되어 있는 것을 확인할 수 있다.


그리고 원래의 스타트 씬에서는 Trasitioning Game Object 프로퍼티가 비어있었는데 지금은 Ellen이라는 게임 오브젝트가 할당되어 있는 것을 알 수 있다. 이 게임 오브젝트는 씬에 배치 되어있는 플레이어의 캐릭터로, 다른 물체나 몬스터 등의 다른 캐릭터가 아닌 플레이어의 캐릭터만 닿았을 때, 씬을 이동시키기 위해서 할당해둔 것이다.


void OnTriggerEnter2D (Collider2D other)

{

    if (other.gameObject == transitioningGameObject)

    {

        m_TransitioningGameObjectPresent = true;


        if (ScreenFader.IsFading || SceneController.Transitioning)

            return;


        if (transitionWhen == TransitionWhen.OnTriggerEnter)

            TransitionInternal ();

    }

}


트리거 설정된 콜라이더에 충돌이 발생했을 때 호출되는 OnTriggerEnter2D 콜백 함수를 보면 확실히 알 수 있다. 매개변수로 넘어온 충돌체의 게임 오브젝트와 미리 할당해둔 transitioningGameObject와 비교하여 같을 경우에만, 다른 씬으로 이동시키는 구조이다.

이렇게 하지 않으면 트랜지션 포인트의 콜라이더에 플레이어의 캐릭터가 아닌 총알이나 몬스터가 닿기만 해도 씬이 이동되는 상황을 보게 될 것이다.


유니티 콘텐츠 팀에서 선택한 방법에도 약간의 단점이 있다. 그것은 트랜지션 스타트에 미리 캐릭터를 할당해두는 방식이기 때문에 나중에 캐릭터를 선택할 수 있는 기능을 넣게 된다면, 다른 캐릭터로 시작하면 그 캐릭터는 이 콜라이더에 닿아도 다른 씬으로 이동하지 못할 수도 있다.


이것은 간단하게 만들어진 예시이기 때문에 발생한 문제로, 플레이어 캐릭터에 태그나 레이어를 설정하고, 태그나 레이어로 비교해서 통과시키는 방법으로 해결할 수 있다.



트랜지션 데스티네이션 게임 오브젝트(Transition Destination Game Object)


그림 5

 

트랜지션 데스티네이션 게임 오브젝트는 씬 트랜지션 데스티네이션 컴포넌트(Scene Transition Destination Component)와 캐릭터 스테이트 세터 컴포넌트(Character State Setter Component)로 구성되어 있다. 이 게임 오브젝트는 플레이어의 캐릭터가 씬 이동을 할 때 목적지 역할을 한다. 첫 번째 게임 플레이 씬인 Zone1에 두 개가 배치되어 있는데, 처음 게임이 시작되었을 때 배치되는 위치인 트랜지션 데스티네이션 스타트(Transition Destination Start)와 두 번째 게임 씬인 Zone2로부터 넘어왔을 때의 도착 지점인 트랜지션 데스티네이션 프롬 Zone2(Transition Destination From Zone2)가 그것이다.


씬 트랜지션 데스티네이션 컴포넌트(Scene Transition Destination Component)


public class SceneTransitionDestination : MonoBehaviour

{

    public enum DestinationTag

    {

        A, B, C, D, E, F, G,

    }


    public DestinationTag destinationTag;

    [Tooltip("This is the gameobject that has transitioned.  For example, the player.")]

    public GameObject transitioningGameObject;

    public UnityEvent OnReachDestination;

}

 

씬 트랜지션 데스티네이션 컴포넌트에는 사실상 큰 기능 자체는 존재하지 않고, 씬 컨트롤러(Scene Controller)에서 씬이 전환된 직후에 씬 안에 존재하는 씬 트랜지션 데스티네이션 컴포넌트가 부착된 모든 게임 오브젝트를 가지고 와서 데스티네이션 태그(Destination Tag)를 비교해 일치하는 트랜지션 데스티네이션 게임 오브젝트의 위치에 플레이어의 캐릭터를 이동시키기 위한 표지판 역할을 한다.

캐릭터 스테이트 세터 컴포넌트(Character State Setter Component)


캐릭터 스테이트 세터 컴포넌트는 씬 트랜지션 테스티네이션 컴포넌트와 함께 트랜지션 데스티네이션 게임 오브젝트에 부착된 컴포넌트로, 씬 트랜지션 데스티네이션 컴포넌트가 도착 위치를 지정하는 역할을 한다면 캐릭터 스테이트 세터 컴포넌트는 씬에 도착한 직후의 캐릭터의 상태를 설정하는 역할을 한다.


기본적으로 공개되어 있는 프로퍼티는 Set Character Velocity, Set Character Facing Contents, Set State, Set Parameter가 있으며 Set Character Velocity는 씬에 진입했을 때의 캐릭터의 속도를 설정할 수 있고, Set Character Facing Contents는 캐릭터가 바라볼 방향, Set State는 캐릭터의 애니메이션, Set Parameter는 캐릭터 애니메이터의 매개변수 값을 설정하는 옵션이다.


그림 6

 

캐릭터 스테이트 세터 컴포넌트에서 눈여겨 볼 점은 [그림 5]에서와 같이 프로퍼티가 선택되지 않았을 때는 해당 프로퍼티에 연관된 옵션이 보이지 않다가 프로퍼티 값이 true로 설정되면 [그림 6]과 같이 프로퍼티와 연관된 옵션이 보이도록 에디터가 커스터마이징되어 있다는 점이다. 프로젝트 뷰에서 CharacterStateSetterEditor를 검색해서 CharacterStateSetterEditor.cs 파일을 확인해보면 어떤 식으로 프로퍼티 값에 따라서 보여줄 옵션을 설정할 수 있는지 배울 수 있다.


이렇게 유니티 에디터에서 필요하거나 사용되는 옵션만 보여주는 것 만으로도 에디터에서 작업하는 디자이너 개발자의 작업 효율을 크게 상승시킬 수 있다.


[Header("Character Velocity")]

public bool setCharacterVelocity;

public Vector2 characterVelocity;


[Header("Character Facing")]

public bool setCharacterFacing;

public bool faceLeft;


public Animator animator;


[Header("Character Animation State")]

public bool setState;

public string animatorStateName;


[Header("Character Animation Parameter")]

public bool setParameters;

public ParameterSetter[] parameterSetters;


그림 7

 

여기에 더불어 Header 어트리뷰트를 사용하면 [그림 7]과 같이 프로퍼티의 분류를 훨씬 명확하게 인지하도록 만들 수 있다.



VFX 컨트롤러 게임 오브젝트(VFX Controller Game Object)


그림 8

 

VFX 컨트롤러 게임 오브젝트는 VFX 컨트롤러 컴포넌트(VFX Controller Component)가 부착되어 있다. 이 게임 오브젝트의 목적은 캐릭터가 점프하거나 달릴 때 먼지가 일너나는 들의 이팩트를 관리하는 것이다.


VFX 컨트롤러 컴포넌트(VFX Controller Component)


VFX 컨트롤러 컴포넌트는 이펙트를 관리하는 역할의 컴포넌트이다. 이것은 다른 시스템 컴포넌트와 마찬가지로 싱글톤 패턴으로 만들어졌으며, 같은 이펙트 게임 오브젝트들이 계속해서 생성되고 파괴됨으로써 발생하는 성능 저하를 막기 위해서 오브젝트 풀링 기법을 사용하고 있다. 오브젝트 풀링 기법은 기초적인 최적화 기법으로 오브젝트가 생성/파괴될 때 발생하는 오버헤드를 막기 위해서 오브젝트를 재사용하는 기법을 말한다.


그림 9

 

VFX 컨트롤러 컴포넌트에서는 게임에서 사용될 VFX 프리팹들을 리스트에 담아서 관리하는데, 만약 같은 방식으로 생성되어야 하는 VFX지만 상황에 따라서 다른 VFX를 생성해야 경우, 예를 들어 캐릭터가 달릴 때 풀 바닥에서는 풀이 날리는 VFX가 발생해야 하지만, 흙 바닥에서 달릴 때는 흙 먼지가 날리는 VFX가 생성되도록 하기 위해서, Vfx Override로 특별한 경우를 정의하고 있다.



피직스 헬퍼 게임 오브젝트(Physics Helper Game Object)


 

피직스 헬퍼 게임 오브젝트는 피직스 헬퍼 컴포넌트(Physics Helper Component)를 가진 게임 오브젝트로서 게임 내에서 불리적인 처리에 도움을 주는 역할을 한다.


피직스 헬퍼 컴포넌트(Physics Helper Component)


피직스 헬퍼 컴포넌트는 게임이 플레이되는 상황에서 어디서든지 호출될 수 있기 때문에 싱글톤 패턴으로 작성되어 있으며, 캐릭터가 밟고 서는 플랫폼이나 타일맵에 관련된 물리적인 처리를 담당하고 있다.


사실 피직스 헬퍼 같은 컴포넌트는 개발자가 편의에 따라서 작성하기 나름인 스크립트이다. 게임마다 필요한 물리 연산이나 처리는 모두 다르기 마련이라, 필요한 물리 작업에 따라서 피직스 헬퍼의 내용은 천차만별로 달라질 수 있다. 다만, 이렇게 자주 사용되는 기능을 분류 별로 묶어서 어디서든지 호출할 수 있게 헬퍼 클래스로 만드는 작업은 관련된 기능을 이리저리 흩뜨려 놓는 것보다 확실히 더 나은 효율적인 작업을 보장한다.




Explorer 2D Game Kit 분석 (1) - 개요

Explorer 2D Game Kit 분석 (2) - Start 씬 해부하기 (1)

Explorer 2D Game Kit 분석 (3) - Start 씬 해부하기 (2)

Explorer 2D Game Kit 분석 (4) - 게임플레이 요소 (1)


반응형
반응형

Explorer 2D Game Kit 분석 (3) 

Start 씬 해부하기 (2)


작성 기준 버전 :: 2019.1.4f1


Start 씬


 

지난 섹션에 이어서 Start 씬에 대한 분석을 이어나가보자.



UI


 

하이어라키 뷰(Hierarchy View)에서 UI로 분류된 게임 오브젝트는 스크린 페이더(Screen Fader), 이벤트 시스템(Event System), 스타트 메뉴 캔버스(Start Menu Canvas), 옵션 캔버스 마스터(Option Canvas Master)가 있다. 분류에서도 알 수 있듯이 모두 UI와 관련이 있는 게임 오브젝트들이다.


스크린 페이더 게임 오브젝트(Screen Fader Game Object)


 

스크린 페이더 게임 오브젝트에는 스크린 페이더 컴포넌트(Screen Fader Component)가 부착되어 있고 자식 게임 오브젝트로 블랙 페이더(Black Fader), 게임 오버 캔버스(Game Over Canvas), 로딩 캔버스(Loading Canvas)를 가진다.


스크린 페이더 컴포넌트(Screen Fader Component)


public class ScreenFader : MonoBehaviour

 

스크린 페이더 컴포넌트는 씬을 이동할 때, 화면을 페이드 인(fade in), 페이드 아웃(fade out) 시키는 역할을 하는 컴포넌트이다.


public static ScreenFader Instance

{

    get

    {

        if (s_Instance != null)

            return s_Instance;


        s_Instance = FindObjectOfType<ScreenFader> ();


        if (s_Instance != null)

            return s_Instance;


        Create ();


        return s_Instance;

    }

}


public static void Create ()

{

    ScreenFader controllerPrefab = Resources.Load<ScreenFader> ("ScreenFader");

    s_Instance = Instantiate (controllerPrefab);

}


void Awake ()

{

    if (Instance != this)

    {

        Destroy (gameObject);

        return;

    }

        

    DontDestroyOnLoad (gameObject);

}

 

스크린 페이더는 씬을 불러오거나 캐릭터의 위치를 이동시킬 때, 항상 존재해야 되는 컴포넌트이기 때문에 역시 싱글톤으로 구현되어 있다.


 

위 코드에는 약간의 문제점이 있는데 만약 존재하는 스크린 페이더가 없으면 새 스크린 페이더를 생성하는 Create() 함수를 보면 Resources 폴더에서 스크린 페이더 프리팹을 로드해서 인스턴스화하게 되어있지만, 프로젝트 뷰를 보면 스크린 페이더 프리팹은 Resources 폴더가 아닌 곳에 존재한다. 그렇기 때문에 만약 씬에 스크린 페이더가 없는데, 호출하면 스크린 페이더가 제대로 생성되지 않고 오류가 발생하게 된다. 이 문제를 해결하기 위해서는 SceneControl 폴더에 Resources 폴더를 만들고 ScreenFader 프리팹을 거기로 옮겨주면 문제는 해결된다.


기능


public enum FadeType

{

    Black, Loading, GameOver,

}


public static IEnumerator FadeSceneIn ()

public static IEnumerator FadeSceneOut (FadeType fadeType = FadeType.Black)

protected IEnumerator Fade(float finalAlpha, CanvasGroup canvasGroup)


스크린 페이더 컴포넌트에서는 화면이 페이드 아웃/인 되는 경우를 같은 씬 내에서 텔레포트하는 Black, 씬과 씬 사이를 이동하는 Loading, 플레이어의 캐릭터가 죽어서 리스폰되는 GameOver, 이렇게 세 가지로 나누어서 정의하고 있다.


그리고 주요 기능을 하는 함수 3가지를 가진다. 화면이 점차 밝아지면서 씬으로 들어가는 효과를 주는 FadeSceneIn(), 화면이 점차 어두워지면서 씬에서 빠져나오는 효과를 주는 FadeSceneOut(), 그리고 Fade() 함수는 FadeSceneIn() 함수와 FadeSceneOut() 함수 양쪽에서 호출되는 내부 함수로 화면을 밝게 하거나 어둡게하는 효과를 처리한다.


이런 식으로 씬 로드를 처리할 때, 별도의 로딩 씬을 만들지 않고, UI로 덮어씌우는 방식을 커튼식 로딩 UI로 분류할 수 있다. 다만 유니티 콘텐츠 팀에서는 로딩 UI와 씬 로딩 기능을 합치지 않고, 씬을 로딩하는 씬 컨트롤러와 UI를 덮어씌우는 스크린 페이더로 분리시켜두었다. 이렇게 함으로써 스크린 페이더를 활용할 때 씬을 이동하는 경우 뿐만 아니라 씬 내부에서 텔레포트를 할 때도 스크린 페이더를 사용할 수 있게 활용도를 높일 수 있었다.


자식 오브젝트들(Child Objects)


 

스크린 페이더의 기능에 대해서 알아보았으니 이제 스크린 페이더 게임 오브젝트의 자식 게임 오브젝트들에 대해서 확인해보자. 스크린 페이더의 자식 게임 오브젝트는 블랙 페이더(Black Fader), 게임 오버 캔버스(Game Over Canvas), 로딩 캔버스(Loading Canvas), 이렇게 3개이며, 앞선 스크린 페이더 컴포넌트 분석에서 봤듯이 블랙 페이더는 같은 씬 내에서 텔레포트로 이동할 때 보여질 UI, 게임 오버 캔버스는 캐릭터가 죽어서 리스폰 될 때 보여질 UI, 로딩 캔버스는 다른 씬으로 이동할 때 보여질 UI이다. 각 자식 오브젝트들은 UI를 구성할 이미지들을 자식 오브젝트로 가지며 각각의 화면 구성은 아래와 같다.


블랙 페이더게임 오버 캔버스로딩 캔버스

 

각자 구성하고 있는 이미지들은 다르지만, 이 자식 오브젝트들은 UI를 그릴 각각의 캔버스를 각자 가지며 자식 이미지들을 한꺼번에 컨트롤할 캔버스 그룹을 컴포넌트를 가진다.


여러 UI들을 하나의 캔버스 밑에 두지 않는지 의아해할 수도 있다. 하지만 모든 UI를 하나의 캔버스 아래에 두면 UI의 덩어리가 커져서 관리가 힘들어질 뿐만 아니라 유니티에서는 UI를 그릴 때, 캔버스 안의 UI 요소가 하나라도 변경되면 해당 UI 요소가 속한 캔버스의 모든 UI가 다시 그려지기 때문에 성능 면의 문제가 발생할 수 있다. 그렇기 때문에 UI를 구성할 때는 적절한 기능 단위로 UI를 묶어서 캔버스를 구성하는 것이 좋다.


그리고 캔버스 그룹은 UI 게임 오브젝트 하위에 속하는 자식 UI들을 한꺼번에 통제해야할 때 유용하게 사용된다. 여기서는 캔버스 아래에 있는 여러 이미지 들의 알파 값을 한꺼번에 조절해서 UI를 투명하게 하거나 그 반대의 작업을 하고자 사용되었다.



스타트 메뉴 캔버스(Start Menu Canvas)


 

스타트 메뉴 캔버스는 Start 씬에 제일 전면에 기본적으로 깔려 있는 UI 캔버스이다.


 

스타트 메뉴 캔버스에는 플레이어 인풋 컴포넌트(Player Input Component), 스타트 UI 컴포넌트(Start UI Component), 메뉴 액티비티 컨트롤러 컴포넌트(Menu Activity Controller Component)가 부착되어 있으며, UI 요소 들로는 배경 화면과 UI를 구분 짓기 위한 백그라운드 틴트 이미지와 메뉴를 구성하는 제목, 메뉴판 등의 이미지 그리고 메뉴 기능을 동작시키는 버튼을 가진다.


이 중에서 플레이어 인풋 컴포넌트는 이 씬에서 처리하는 작업이 없고 단지 옵션에서 플레이어에게 키를 알려주기 위해서 존재하기 때문에 게임 씬에서 분석하기로 하고 지금은 넘어가도록 한다. 그리고 메뉴 액티비티 컨트롤러 역시 사실상 하는 기능이 없는 상태이기 때문에 여기서는 넘긴다.


스타트 UI 컴포넌트(Start UI Component)


public class StartUI : MonoBehaviour 

{

    public void Quit()

    {

#if UNITY_EDITOR

        EditorApplication.isPlaying = false;

#else

Application.Quit();

#endif

    }

}

 

스타트 UI 컴포넌트 역시 크게 하는 일은 없다. UI 중에 EXIT GAME 버튼이 눌렸을 때 호출될 이벤트만 구현되어 있다. 여기서 볼만한 점은 유니티 에디터에서 실행되었을 때는 에디터의 isPlaying을 false로 만들어서 플레이를 중지시키고 빌드된 상황에서는 어플리케이션을 종료하도록 UNITY_EDITOR 심볼을 통해서 정의되어 있다는 점이다. 이런 식의 조건부 컴파일 방법은 정해진 심볼에 따라 특히 유니티에서는 빌드하고자 하는 플랫폼이나 운영체제에 따라 실행될 코드를 분리할 수 있다는 점이다.


조건부 컴파일에도 역시 단점과 주의해야할 점이 분명이 있다. 비주얼 스튜디오 기준으로 활성화되지 않은 심볼의 코드는 회색으로 표시되며 활성화되지 않는다. 그 때문에 인텔리센스 역시 동작하지 않으며, 이 구간에서는 자동완성을 지원하지 않는다. 때문에 신텍스 에러가 발생하지 않도록 주의해야 하며, 한 조건부 코드에 로직 변경이 발생했을 때, 다른 조건부 코드에도 까먹지 말고 변경된 로직을 적용해주어야 한다. 


조건부 컴파일을 사용하면 세심하게 관리해야할 코드가 늘어난다. 수정사항이 발생했을 때 활성화된 코드와 비활성화된 코드를 제대로 바꿔주지 않으면 에러가 발생하고 작업 시간과 빌드 시간이 배로 늘어날 것이다. 그렇기 때문에 가능하다면 플랫폼에 특화된 코드보다는 모든 플랫폼에서 동작하는 코드를 작성하고 불가피한 경우에만 조건부 컴파일로 코드를 나눌 것을 권장한다.


버튼의 사용법


남은 스타트 메뉴 캔버스의 요소들은 대부분 기본적인 것으로 별달리 언급할 요소가 못되지만, 시작 메뉴의 버튼들은 이야기해 볼 만한 것이 있다.



보통 유니티의 UI에서 버튼과 상호작용할 때 생기는 효과를 사용할 때는 기본적으로 색깔만 바뀌는 컬러 틴트(Color Tint)를 사용하거나 조금 더 특별한 방식으로 효과를 주고 싶을 때는 스프라이트를 교체하는 스프라이트 스왑(Sprite Swap) 기능을 주로 사용한다.


 

Start 씬에서 플레이를 실행하고 각 버튼에 마우스를 올려보면 작은 삼각형이 회전하는 연출이 보일 것이다. 이것은 컬러 틴트나 스프라이드 스왑만으로는 불가능한 연출이다.

 

스타트 메뉴 캔버스에 속한 버튼을 선택해보면 그 이유를 알 수 있는데 트랜지션(Transition)을 컬러 틴트나 스프라이트 스왑이 아닌 애니메이션(Animation)으로 설정되어 있고 별도의 애니메이터 컨트롤러가 붙어있는 것을 볼 수 있다.


 

각 상황마다 버튼이 실행할 애니메이션을 만들어서 이미지가 바뀌거나 색이 바뀌는 것보다 더욱 다양한 연출을 할 수 있다.



옵션 캔버스 마스터(Option Canvas Master)


 

옵션 캔버스 마스터는 게임의 설정을 조절하기 위한 UI들을 모아둔 캔버스로 자식 게임 오브젝트로 음향을 설정하기 위한 오디오 캔버스와 게임 플레이 조작을 위한 컨트롤 캔버스를 가지고 있다. 다만 컨트롤 캔버스의 경우, 키 변경 기능을 구현해두지 않았기 때문에 게임에서 사용하는 키를 보여주는 기능만 있다. 그리고 옵션 캔버스 마스터 자체는 캔버스 분리 이 외에는 평범하게 만들어졌기 때문에 특별하게 언급할 부분이 없다.


Scene Assets


 

스타트 씬에서 씬 에셋으로 분류해둔 게임 오브젝트들은 카메라, 포스트 프로세싱, 라이트, 그리고 씬을 꾸미는 배경 게임 오브젝트들이다.


원근감 연출


사실 씬 에셋 파트에서는 크게 조명할 부분은 없지만, 볼만한 부분은 원근감 연출에 있다.


 

보통의 2D 게임에서는 투영 방식(Projection)을 직교법(Orthographic)으로 설정해서 원근감이 사라지게 만드는 경우가 많다. 


 

하지만 게임 키트에서는 원근법(Perspective)으로 설정하여 카메라와의 거리에 따라서 오브젝트의 크기게 달라보이게 만들었다.


 

그리고 원근감을 연출하기 위한 두 번째 장치로 Start Screen Sprite Offsetter 라는 컴포넌트를 만들어서 마우스의 움직임을 감지하고 오프셋 수치에 따라서 배경에 배치된 오브젝트들이 다르게 움직이게 만들어져 있다.


 

잠시 화면을 가리는 스타트 메뉴를 비활성화 시키고 플레이 버튼을 눌러서 게임을 실행시킨 뒤, 마우스를 움직여보면 배경이 마우스의 움직임에 따라서 반응하여 더욱 원근감을 강하게 느낄 수 있도록 만들어주는 것을 볼 수 있다.


이것으로 스타트 씬에 대한 분석은 끝났고 이 다음부터는 게임 플레이와 관련된 부분을 분석해보자.




Explorer 2D Game Kit 분석 (1) - 개요

Explorer 2D Game Kit 분석 (2) - Start 씬 해부하기 (1)

Explorer 2D Game Kit 분석 (3) - Start 씬 해부하기 (2)

Explorer 2D Game Kit 분석 (4) - 게임플레이 요소 (1)


반응형
반응형

Explorer 2D Game Kit 분석 (2) 

Start 씬 해부하기 (1)


작성 기준 버전 :: 2019.1.4f1


Start 씬


 

2D 게임 키트에서 제일 처음으로 분석해볼 것은 바로 Start 씬이다. Start 씬은 게임 키트에서 게임이 시작되는 메인 메뉴를 구성하고 있다. 이 씬에서 하이어라키 뷰에 게임 오브젝트들은 어떻게 배치되어 있는지, UI는 어떻게 구성하고 있는지, 그리고 스크립트들은 어떻게 짜여 있는지를 알아볼 것이다.



하이어라키 뷰(Hierarchy view)


 

하이어라키 뷰에서는 시작 씬에 배치된 게임 오브젝트들을 리스트 형식으로 모두 한 번에 볼 수 있다. 실제로 게임을 만들면서 씬에 게임 오브젝트들을 배치하다보면 제대로 된 정리가 이루어지지 않고 난장판이 되는 경우가 다반사이다. 그에 반해 유니티 콘텐츠 팀에서 제작한 2D 게임 키트의 경우에는 위의 이미지와 같이 깔끔하게 하이어라키 뷰를 정리해두었다.


 

우선 빈 게임 오브젝트를 이용하여 경계선을 지어서 분류별로 게임 시스템과 관련된 게임 오브젝트, UI 게임 오브젝트, 해당 씬에서만 사용되는 게임 오브젝트 등으로 구분해두었다. 이렇게 해둠으로써 어디에 어떤 게임 오브젝트가 있는지 일일이 찾을 필요없이 카테고리별로 빠르게 찾을 수 있게 된다. 시작 씬 이외에도 게임을 플레이하는 씬인 Zone1~5 씬이 있는데 대부분의 씬에서도 약간의 차이점은 있지만 이러한 분류를 따르고 있다. 


개발자 별로 자신에게 적절하거나, 팀원과 상의한 후 팀의 씬 정리 규칙을 세우고 그 규칙에 따라 게임 오브젝트를 배치한다면 한결 보기 좋게 씬을 관리할 수 있다. 



System


하이어라키 뷰에서 시스템으로 분류된 게임 오브젝트는 씬 컨트롤러(Scene Controller), 트랜지션 스타트(Transition Start), 백그라운드 뮤직 플레이어(Background Music Player)가 있다. 시스템 쪽으로 분류해둔 게임 오브젝트들은 게임 시스템과 관련된 오브젝트이며, 대부분 모든 씬에서 존재해야되는 오브젝트들이 많다.


씬 컨트롤러 게임 오브젝트(Scene Controller Game Object)


 

씬 컨트롤러 게임 오브젝트에는 씬 컨트롤러 컴포넌트와 씬 컨트롤러 래퍼 컴포넌트가 부착되어 있다. 우선 씬 컨트롤러 컴포넌트는 다른 씬을 불러오는 씬 로드 기능을 관리하고 있으며 씬 컨트롤러 래퍼 컨트롤러 컴포넌트는 씬 컨트롤러 컴포넌트를 감싸는 역할을 한다(이 감싸는 역할이란 무엇인가는 잠시 후에 설명하도록 하겠다).


씬 컨트롤러 컴포넌트(Scene Controller Component)


public class SceneController : MonoBehaviour

 

씬 컨트롤러 컴포넌트는 앞서 이야기 했듯이 다른 씬을 불러오는 씬 로드 관리를 담당하는 컴포넌트이다. 


protected static SceneController instance;


public static SceneController Instance

{

    get

    {

        if (instance != null)

            return instance;


        instance = FindObjectOfType<SceneController>();


        if (instance != null)

            return instance;


        Create ();


        return instance;

    }

}


public static SceneController Create ()

{

    GameObject sceneControllerGameObject = new GameObject("SceneController");

    instance = sceneControllerGameObject.AddComponent<SceneController>();


    return instance;

}


void Awake()

{

    if (Instance != this)

    {

        Destroy(gameObject);

        return;

    }


    DontDestroyOnLoad(gameObject);


    m_PlayerInput = FindObjectOfType<PlayerInput>();


    if (initialSceneTransitionDestination != null)

    {

        SetEnteringGameObjectLocation(initialSceneTransitionDestination);

        ScreenFader.SetAlpha(1f);

        StartCoroutine(ScreenFader.FadeSceneIn());

        initialSceneTransitionDestination.OnReachDestination.Invoke();

    }

    else

    {

        m_CurrentZoneScene = SceneManager.GetActiveScene();

        m_ZoneRestartDestinationTag = SceneTransitionDestination.DestinationTag.A;

    }

}


씬 컨트롤러 컴포넌트에서 제일 먼저 살펴볼 부분은 이것이다. 씬을 불러오는 기능은 모든 씬에서 존재하며 다른 씬을 불러올 수 있어야 하기 때문에 싱글톤 패턴DontDestoryOnLoad가 적용되어 있다.


protected IEnumerator Transition(string newSceneName, bool resetInputValues, SceneTransitionDestination.DestinationTag destinationTag, TransitionPoint.TransitionType transitionType = TransitionPoint.TransitionType.DifferentZone)

{

    m_Transitioning = true;

    PersistentDataManager.SaveAllData();


    if (m_PlayerInput == null)

        m_PlayerInput = FindObjectOfType<PlayerInput>();

    m_PlayerInput.ReleaseControl(resetInputValues);

    yield return StartCoroutine(ScreenFader.FadeSceneOut(ScreenFader.FadeType.Loading));

    PersistentDataManager.ClearPersisters();

    yield return SceneManager.LoadSceneAsync(newSceneName);

    m_PlayerInput = FindObjectOfType<PlayerInput>();

    m_PlayerInput.ReleaseControl(resetInputValues);

    PersistentDataManager.LoadAllData();

    SceneTransitionDestination entrance = GetDestination(destinationTag);

    SetEnteringGameObjectLocation(entrance);

    SetupNewScene(transitionType, entrance);

    if(entrance != null)

        entrance.OnReachDestination.Invoke();

    yield return StartCoroutine(ScreenFader.FadeSceneIn());

    m_PlayerInput.GainControl();


    m_Transitioning = false;

}

 

씬 컨트롤러 컴포넌트의 가장 중심 기능인 씬 로드 기능은 Transition() 코루틴 함수에 정의되어 있다. 그 외의 함수들은 씬을 다시 시작하는 함수, 목표 지점 태그로  이동할 위치를 가져오는 함수 등 부가적인 기능을 구현하고 있다.


씬 컨트롤러 래퍼 컴포넌트(Scene Controller Wrapper Component)


public class SceneControllerWrapper : MonoBehaviour

{

    public void RestartZone (bool resetHealth)

    {

        SceneController.RestartZone (resetHealth);

    }


    public void TransitionToScene (TransitionPoint transitionPoint)

    {

        SceneController.TransitionToScene (transitionPoint);

    }


    public void RestartZoneWithDelay(float delay)

    {

        SceneController.RestartZoneWithDelay (delay, false);

    }


    public void RestartZoneWithDelayAndHealthReset (float delay)

    {

        SceneController.RestartZoneWithDelay (delay, true);

    }

}


씬 컨트롤러 래퍼 컴포넌트는 씬 컨트롤러 컴포넌트를 감싸는 컴포넌트로 호출 방향을 구분하기 위해서 만들어졌다.


 

호출 방향의 구분의 개념은 위의 이미지와 같다. 위의 이미지에서 볼 수 있듯이 씬에 배치된 게임 오브젝트의 이벤트로 호출될 때는 씬 컨트롤러 래퍼 컴포넌트를 통해서 호출되도록 만들고, 스크립트 내부에서 호출될때는 씬 컨트롤러를 직접 호출하게 설계되어 있다. 굳이 이렇게 나누어서 설계를 할 필요가 있는가 싶겠지만, 이렇게 씬 쪽에서 호출되는 방향과 내부 스크립트에서 호출되는 방향을 구분함으로써 문제가 발생했을 때, 어느 쪽 호출에서 문제가 발생했는지 빠르게 발견할 수 있다는 장점이 있다.



트랜지션 포인트 게임 오브젝트(Transition Point Game Object)


 

트랜지션 포인트 게임 오브젝트는 박스 콜라이더(Box Collider)와 트랜지션 포인트(Transition Poiont) 컴포넌트를 가진 게임 오브젝트로 실제로는 박스 콜라이더에 접촉한 플레이어를 다른 씬으로 전송하는 역할을 담당하는 게임 오브젝트이다.


단, 현재 씬은 플레이어의 캐릭터가 존재하지 않는 메인 메뉴 씬이기 때문에, 플레이어 캐릭터 오브젝트가 박스 콜라이더에 충돌하는 상황은 존재하지 않을 것이다.


트랜지션 포인트 컴포넌트(Transition Point Component)


[RequireComponent(typeof(Collider2D))]

public class TransitionPoint : MonoBehaviour


트랜지션 포인트 컴포넌트는 해당 컴포넌트가 부착된 게임 오브젝트가 소유한 콜라이더 2D(Collider2D)에 접촉한 플레이어 캐릭터를 다른 지역으로 보내는 역할을 한다. 그렇기 때문에 RequireComnent 어트리뷰트를 이용해서 트랜지션 포인트 컴포넌트가 부착되는 게임 오브젝트에는 반드시 Collider2D 컴포넌트가 부착되어 있어야 함을 정의하고 있다.


public enum TransitionType

{

    DifferentZone, DifferentNonGameplayScene, SameScene,

}


public enum TransitionWhen

{

    ExternalCall, InteractPressed, OnTriggerEnter,

}

 

그리고 트랜지션 포인트 클래스 내부에는 Transition Type과 Transition When이라는 열거형 두 가지가 정의되어 있다. Transition Type은 트랜지션 포인트가 어떤 종류의 씬으로 전환되는지를 의미한다. DifferentZone 타입은 다른 게임 플레이 씬으로 이동하는 것을 의미한다. 스타트 씬에 있는 트랜지션 포인트 역시 DifferentZone으로 설정되어 있는 것을 볼 수 있는데, 메인 메뉴 씬을 기준으로 시작 게임 씬 역시 "다른 게임 플레이 씬"이기 때문에 DifferentZone으로 설정되는 것이 맞다. DifferentNonGameplayScene 타입은 다른 씬이지만, 게임 플레이 씬은 아닌 경우이다. 예를 들자면 게임 플레이 씬에서 다시 메인 메뉴 씬으로 돌아오는 경우이다. SameScene은 같은 씬의 다른 지점으로 이동할 때를 의미한다.


TransitionWhen 열거형은 어느 시점에 전송을 시작할 것인가에 대한 것인데, ExternalCall은 외부에서 호출이 있을 경우를 의미한다. 앞에서 메인 메뉴에서는 플레이어 캐릭터 오브젝트가 없기 때문에 박스 콜라이더에 충돌하는 상황이 벌어지지 않을 것이라고 말했다. 그렇기 때문에 위의 이미지에서 스타트 씬의 트랜지션 포인트 게임 오브젝트에 부착된 트랜지션 포인트 컴포넌트의 Transition When의 값이 ExternalCall으로 설정되어 있는 것을 볼 수 있다. 즉, 콜라이더의 충돌을 이용하지 않는 경우라면 ExternalCall을 사용하는 것이다. InteractPressed는 플레이어 캐릭터가 트랜지션 포인트에 접촉한 상태에서 상호작용 키를 눌렀을 때를 의미한다. OnTriggerEnter 타입은 캐릭터가 트랜지션 포인트의 콜라이더에 접촉하는 순간에 바로 전송을 시작한다.


protected void TransitionInternal ()

{

    if (requiresInventoryCheck)

    {

        if(!inventoryCheck.CheckInventory (inventoryController))

            return;

    }

        

    if (transitionType == TransitionType.SameScene)

    {

        GameObjectTeleporter.Teleport (transitioningGameObject, destinationTransform.transform);

    }

    else

    {

        SceneController.TransitionToScene (this);

    }

}


트랜지션 포인트 컴포넌트에서 다른 씬으로 이동시키는 주요 기능은 TransitionInternal() 함수에서 처리하고 있으며, 여기에서 다른 씬을 로드하는 기능을 담당하는 씬 컨트롤러를 호출한다. 그리고 때에 따라서 트랜지션 타입이 SameSceme이라면 이동시키고자 하는 게임 오브젝트(예를 들어 플레이어)를 같은 씬 내의 목표 위치로 이동시키는 기능 역시 함께 담당한다.


프리팹화


트랜지션 포인트 게임 오브젝트는 파란색 육면체 아이콘을 보면 프리팹화되어 있는 것을 볼 수 있다. 이렇게 함으로써 플레이되고 있는 씬이나 캐릭터의 위치를 이동시키기 위해서 트랜지션 포인트를 일일이 만들 필요없이 트랜지션 포인트 프리팹을 원하는 위치에 가져다 놓고 프로퍼티만 설정하면 언제든 위치 이동 장치를 만들 수 있는 것으로 재활용성을 극대화했음을 알 수 있다.


씬 이름 활용법


 

이 트랜지션 포인트의 구현법 중에 가장 유용하다고 평할만한 포인트는 바로 씬 이름을 다루는 부분이다. 보통 다른 씬을 호출할 때, 씬 이름을 문자열로 호출하거나 빌드 세팅에 등록된 씬 인덱스를 이용해서 호출하는 경우가 많은데 이런 방법들은 몇 가지 문제점을 내포하고 있다. 


우선 씬 인덱스를 사용하는 방법은 등록된 씬의 순서가 변경되면 의도하지 않은 다른 씬이 호출되는 문제가 쉽게 발생한다.


그리고 일반적인 문자열을 사용하는 방식은 사용자의 오타 문제가 있을 수 있고, 특히 코드 난독화를 사용할 때, 상수 타입의 고정된 문자열을 코드에서 직접 사용한다면, 코드 난독화가 상수 문자열로 코드에 들어있는 씬 이름을 암호화해서 원하는 씬을 불러오지 못하는 경우도 발생할 수 있다.


그렇다면 2D 게임 키트에서는 어떻게 씬 이름을 다루어서 이런 문제를 해결했는지 살펴보자.


[SceneName]

public string newSceneName;


트랜지션 포인트 스크립트에는 해당 포인트가 플레이어를 어떤 씬으로 보낼지에 대한 변수인 new Scene Name 변수가 선언되어 있다.


 

일반적인 공개된 문자열이라면 인스펙터 뷰에서 위의 이미지와 같이 보여야할 것이다.

 

하지만 인스펙터 뷰에서 트랜지션 포인트 컴포넌트의 New Scene Name 프로퍼티를 보면 일반적인 string과는 다르게 팝업 선택 필드 방식으로 빌드 세팅에 등록된 씬 이름들을 선택할 수 있게 되어 있다. 이런 식으로 빌드 세팅에 등록된 이름을 선택하는 방식이면 등록된 씬의 순서가 변경될 때의 문제나 오타 문제, 씬 이름이 암호화될 문제 역시 발생하지 않는다.


[SceneName]


보통의 공개된 문자열과 다른 부분은 바로 이 SceneName 어트리뷰트가 붙어있다는 점이다. 바로 이 SceneName 어트리뷰트를 사용해서 이 어트리뷰트가 붙은 string은 인스펙터 창에서 인풋 필드(Input Field) 대신에 등록된 씬 이름이 드롭다운 형식으로 표현되게 만든 것이다.


public class SceneNameAttribute : PropertyAttribute

{}


SceneName 어트리뷰트를 [F12] 키로 따라가보면 씬 네임 어트리뷰트는 정의만 되어있고 내부에는 아무 것도 없다.


[CustomPropertyDrawer(typeof(SceneNameAttribute))]

public class SceneNameDrawer : PropertyDrawer


씬 네임 어트리뷰트의 실제 기능을 구현하는 코드는 씬 네임 드로워(Scene Name Drawer)에 있다. 씬 네임 드로워는 씬 네임 어트리뷰트가 부착된 프로퍼티를 인스펙터 뷰에서 어떻게 보여줄 것인가를 정의한다.


int m_SceneIndex = -1;

GUIContent[] m_SceneNames;

readonly string[] k_ScenePathSplitters = { "/", ".unity" };


public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)

void Setup(SerializedProperty property)

 

씬 네임 드로워는 3개의 멤버 변수와 2개의 함수로 이루어져있는데, m_SceneIndex는 현재 인스펙터 뷰에서 선택한 인덱스를 m_SceneNames는 팝업 선택 필드에서 보여줄 씬 이름들을 담는다. 그리고 k_ScenePathSplitters는 위의 빌드 세팅 이미지에서 볼 수 있듯이 [2D Game Kit/Scene/씬이름]으로 나타나는 씬 경로를 [ / ]로 쪼개고 씬 이름만 가져오기 위해서 정의된 것이다.


public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)

{

    if (EditorBuildSettings.scenes.Length == 0) return;

    if (m_SceneIndex == -1)

        Setup(property);


    int oldIndex = m_SceneIndex;

    m_SceneIndex = EditorGUI.Popup(position, label, m_SceneIndex, m_SceneNames);


    if (oldIndex != m_SceneIndex)

        property.stringValue = m_SceneNames[m_SceneIndex].text;

}

 

OnGUI() 함수는 에디터의 GUI가 그려질 때 호출되는 함수로, 씬 네임 드로워에서는 씬 네임 어트리뷰트가 부착된 프로퍼티의 GUI를 개발자가 정의한 대로 인스펙터 뷰에 그려주는 역할을 한다.


void Setup(SerializedProperty property)

{

    EditorBuildSettingsScene[] scenes = EditorBuildSettings.scenes;

    m_SceneNames = new GUIContent[scenes.Length];


    for (int i = 0; i < m_SceneNames.Length; i++)

    {

        string path = scenes[i].path;

        string[] splitPath = path.Split(k_ScenePathSplitters, StringSplitOptions.RemoveEmptyEntries);


        string sceneName = "";

        if (splitPath.Length > 0)

            sceneName = splitPath[splitPath.Length - 1];

        else

            sceneName = "(Deleted Scene)";

        m_SceneNames[i] = new GUIContent(sceneName);

    }


    if (m_SceneNames.Length == 0)

        m_SceneNames = new[] { new GUIContent("[No Scenes In Build Settings]") };


    if (!string.IsNullOrEmpty(property.stringValue))

    {

        bool sceneNameFound = false;

        for (int i = 0; i < m_SceneNames.Length; i++)

        {

            if (m_SceneNames[i].text == property.stringValue)

            {

                m_SceneIndex = i;

                sceneNameFound = true;

                break;

            }

        }

        if (!sceneNameFound)

            m_SceneIndex = 0;

    }

    else m_SceneIndex = 0;


    property.stringValue = m_SceneNames[m_SceneIndex].text;

}


Setup() 함수는 OnGUI() 함수에서 m_SceneIndex가 -1일 때, 즉 프로퍼티가 선택되지 않았을 때, 호출된다. Setup() 함수는 EditorBuildSettings에서 빌드 세팅에 등록된 씬의 목록을 가져와 OnGUI() 함수에서 팝업 선택 필드에서 그릴 수 있는 GUIContent로 가공하는 역할을 한다. 이런 과정을 통해서 빌드 세팅에 등록된 씬의 이름을 가져와서 팝업 선택 필드에 넣어주는 것이다.


이 이야기는 분석을 진행하면서 계속 말하겠지만, 이런 씬 네임 어트리뷰트와 씬 네임 드로워를 정의함으로써 프로그래머는 약간의 귀찮음을 감수하고 추후에 발생할 수 있는 버그와 문제 등을 예방할 수 있고 더 나아가서 에디터에서 주 작업을 진행할 디자이너의 편의와 작업 효율을 향상시킬 수 있게 된다.



백그라운드 뮤직 플레이어 게임 오브젝트(Background Music Player Game Object)


 

백그라운드 뮤직 플레이어에는 백그라운드 뮤직 플레이어 컴포넌트(Backgroung Music Player Component)가 부작되어 있다. 이 컴포넌트는 이름 그대로 게임에서 흘러나오는 배경 음악을 관리한다.


백그라운드 뮤직 플레이어 컴포넌트(Background Music Player Component)


public class BackgroundMusicPlayer : MonoBehaviour


이 컴포넌트는 배경 음악을 관리하는 컴포넌트로 배경 음악 역시 모든 씬에서 흘러나와야 하기 때문에 싱글톤 패턴으로 작성되어 있다. 다만 이번 예제인 2D 게임 키트에서는 배경 음악의 변경이 거의 없기 때문에 크게 언급할 부분은 없다. 다만, 직접 내부 코드나 오디오 믹서를 사용하는 부분은 참고해 볼만하다.




Explorer 2D Game Kit 분석 (1) - 개요

Explorer 2D Game Kit 분석 (2) - Start 씬 해부하기 (1)

Explorer 2D Game Kit 분석 (3) - Start 씬 해부하기 (2)

Explorer 2D Game Kit 분석 (4) - 게임플레이 요소 (1)


반응형
반응형

Explorer 2D Game Kit 분석 (1) 

개요




유니티 콘텐츠 팀에서 제작한 Explorer 2D 게임 키트(2D Game Kit)가 지난 2018년 2월에 공개되었다. 2D 게임 키트는 아티스트나 디자이너 등의 개발자들이 코드를 직접 작성하지 않고도 아트와 게임플레이 요소 등을 손쉽게 활용해서 게임 레벨을 만들어볼 수 있게 구성된 에셋 컬렉션이다.


이 에셋 컬렉션에는 환경을 꾸밀 수 있는 여러 가지 스프라이트 에셋과 움직이는 플랫폼, 밀어서 옮길 수 있는 상자, 스위치, 석문을 열 수 있는 마법 열쇠 와 몬스터 등 플랫폼 게임에서 사용되는 요소들을 담고 있다.


해당 키트는 유니티의 에셋 스토에서 다운로드 받을 수 있다.



분석의 목적과 방향


유니티 콘텐츠 팀에서 제작한 2D 게임 키트는 아티스트와 디자이너 등의 개발자들이 코드 작성 작업을 제외하고 게임플레이를 쉽게 구성할 수 있게 하는 것이 목적이다. 이를 반대로 해석하면 프로그래머가 어떤 식으로 작업을 해주면 아티스트와 디자이너들이 더욱 손쉽게 게임 레벨을 구성할 수 있게 툴과 시스템을 만들 수 있는지를 배울 수 있다는 의미와 같다.


2D 게임 키트의 분석의 진행은 다음과 같이 이루어질 것이다. 


게임 키드에 포함된 씬을 하나씩 살펴보면서 어떻게 게임 오브젝트들이 배치되어 있는지, UI 구성은 어떻게 되어 있는지, 디자이너 등의 개발자가 레벨 구성을 쉽게 하도록 도와주기 위해서 어떤 식으로 코드가 구성 되었는지 등을 살펴보게 될 것이다.



Explorer 2D 게임 키트 에셋 다운로드 및 세팅

 

 

우선 게임 키트를 에셋을 설치하기 위해 빈 프로젝트를 생성한다.


 

에셋 스토어에서 2D Game Kit를 검색해서 다운로드받은 뒤 임포트 작업을 진행한다.


 

에셋 임포트가 끝나면 위의 이미지처럼 2D 게임 키트의 에셋들이 추가된다.



Start 씬을 열어보면 위와 같은 게임화면이 게임 뷰에 나타난다. 이로써 게임 키트에 대한 분석을 하기 위한 준비가 끝났다.



참고


Explorer 2D Game Kit 분석은 2019.1 버전을 사용할 것을 권장한다. 2019.2 버전의 유니티를 사용할 경우, 움직이는 플랫폼 발판이나 밀어서 이동 가능한 상자 오브젝트가 제대로 작동하지 않을 가능성이 높다.




Explorer 2D Game Kit 분석 (1) - 개요

Explorer 2D Game Kit 분석 (2) - Start 씬 해부하기 (1)

Explorer 2D Game Kit 분석 (3) - Start 씬 해부하기 (2)

Explorer 2D Game Kit 분석 (4) - 게임플레이 요소 (1)


반응형
반응형

Tutorial (7)

 

애니메이션

 

작성 기준 버전 :: 2018.3.2f1

 

[튜토리얼의 내용을 유튜브 영상을 통해서도 확인하실 수 있습니다.]

 

게임에서 캐릭터가 어떠한 동작도 하지 않고 가만히 멈춘 채로 플레이어가 입력하는 대로 움직이가만 한다면 그 게임은 과연 어떤 느낌일까? 아마 그것은 굉장히 기괴한 모습이거나, 게임이 완성되지 못한 느낌일 것이다. 이렇듯이 애니메이션은 게임에 생동감을 불어넣는 중요한 요소이다.

 

메카님(Mecanim)

 

유니티에서 지원하는 애니메이션 시스템을 유니티 측에서는 메카님이라고 이름을 붙였다. 이 메카님 시스템은 기본적인 애니메이션 기능은 물론 애니메이션 레이어, 애니메이션 블렌드, 애니메이션 리타게팅 등의 다양한 기능을 제공한다. 애니메이션과 관련된 고급 기능들은 이후에 다른 섹션을 통해서 알아보도록 하고 이번 섹션에는 메카님 시스템의 기초적인 애니메이션 기능을 알아보도록 하자.

 

 

애니메이션 클립과 애니메이터 컨트롤러(Animation Clip & Animator Controller)

 

유니티의 애니메이션 시스템은 해당 오브젝트가 어떻게 움직여야 하는지에 대한 정보들이 포함된 애니메이션 클립과 플로우 차트와 같은 방식으로 애니메이션 클립들을 구조화하여 현재 어떤 클립이 재생되어야 하고 언제 애니메이션이 변경되어야 하는지 등을 추적하는 상태머신 형태의 애니메이터 컨트롤러로 이루어진다.

 

애니메이터 컨트롤러와 애니메이션 클립의 아이콘 형태는 다음과 같다.

 

 

애니메이션 클립을 만드는 방법은 크게 두 가지가 있다. 첫 번째는 3ds Max나 Maya 같은 외부의 프로그램으로 애니메이션을 만들어서 임포트 하는 것이고, 다른 하나는 유니티에서 직접 애니메이션 키를 잡아서 클립을 만드는 것이다.

 

3ds Max 같은 외부 프로그램에서 애니메이션을 만들어서 임포트하는 방식은 3D 모델링의 애니메이션을 만들고자 할 때 주로 사용하며 유니티 엔진에서 직접 키를 잡아서 애니메이션 클립을 만드는 것은 비교적 간단한 애니메이션이나 UI 애니메이션을 만들고자 할 때 사용하는 빈도가 높다.

 

 

외부에서 만든 애니메이션 임포트하기

 

Practice Animation.zip
다운로드

 

우선은 외부에서 만들어진 애니메이션을 임포트하는 방법을 배워보자. 연습용 애니메이션은 위의 첨부파일을 다운로드 받으면 된다. 다운 받은 압축파일의 압축을 해제하면 BoxMan@Run.FBX, BoxMan@Stand.FBX, BoxMan@Walk.FBX, BoxMan@Attack.FBX 네 개의 FBX 파일이 나올 것이다. 일반적으로 외부의 3D 모델링 프로그램을 통해 만들어진 애니메이션들은 FBX 확장자를 가진 것을 사용한다.

 

 

이렇게 받은 네 개의 파일을 유니티 엔진의 프로젝트 뷰로 드래그&드롭 한다. 그렇게 하면 네 개의 파일이 우리의 프로젝트에 포함되는 것을 확인할 수 있다.

 

 

임포트된 FBX 파일은 프로젝트 뷰에서 파란 직육면체에 종이가 붙은 아이콘으로 표현되며, 그 앞의 작은 삼각형을 클릭해서 접힌 부분을 열면 그 아래에 애니메이션 클립이 포함되어 있는 것을 확인할 수 있다.

 

 

이 FBX 파일을 선택한 상태로 인스펙터 창에서 애니메이션의 이름이나 길이, 반복 여부 등을 수정할 수 있다.

 

 

 

 

 

유니티 엔진에서 직접 애니메이션 클립 만들기

 

 

유니티 엔진에서 직접 애니메이션 클립을 만드는 방법을 배우기 위해서는 우선 씬에 애니메이션 클립을 만들 게임오브젝트 하나를 생성한다.

 

 

그리고 생성된 오브젝트를 선택한다. 

 

 

그 다음에 상단 메뉴바에서 Window>Animation>Animation을 선택하거나 Ctrl+6 단축키를 누르면 애니메이션을 수정할 수 있는 애니메이션 패널이 열린다.

 

 

아직 큐브에는 다른 애니메이션이 없기 때문에 애니메이션 클립을 생성하게 도와주는 Create 버튼만 보인다. 유니티 엔진에서 애니메이션 클립을 직접 만들기 위해서는 이 버튼을 사용하면 된다. 이 버튼을 클릭한다.

 

 

 

 

그러면 애니메이션 클립 생성 및 저장을 위한 대화상자가 뜨는데 CubeRotating.anim 이라는 이름으로 애니메이션 클립을 하나 생성하자.

 

 

애니메이션 클립이 생성되면 애니메이션 창에 타임라인이 표시된다.

 

  

이제 큐브가 회전하는 애니메이션을 추가하기 위해서 Add Property 버튼을 누르고 Transform 항목의 Rotation의 + 버튼을 눌러준다.

 

 

로테이션 프로퍼티가 추가되면 타임라인의 1초 지점을 클릭하고 Rotation.y 값을 360으로 설정한다.

 

 

그 다음 애니메이션 창의 재생 버튼을 눌러보면 화면에 배치된 큐브가 회전하는 것을 확인할 수 있다.

 

유니티 엔진에서 직접 만들어지는 애니메이션 클립은 대부분 이런 과정을 통해서 만들어지며 일반적으로 간단한 애니메이션이나 UI 애니메이션을 만들 때 사용되는 경우가 많다.

 

 

 

 

 

애니메이터 컨트롤러

 

애니메이션 클립에 대해서 설명했으니 이제 애니메이터 컨트롤러에 대해서 이야기할 차례이다. 앞서 이야기 했듯이 애니메이션 클립이 한 동작에 대한 애니메이션이라면 애니메이터 컨트롤러는 여러 애니메이션 클립을 모아서 오브젝트가 어느 시점에 어떤 애니메이션을 어떻게 재생할지 결정하는 역할을 한다.

 

  

애니메이터의 기본적인 구성요소는 스테이트(State), 트랜지션(Transition), 파라미터(Parameter) 이렇게 세 가지이다.

 

 

스테이트(State)

 

 

스테이트는 일반적으로 애니메이터에서 애니메이션 클립을 담고 있는 하나의 상태로, 지금 어느 애니메이션 클립이 재생되어야 하는가를 표현한다.

 

 

스테이트 중 하나를 클릭하여 선택하면 인스펙터 창에서 현재 선택된 스테이트의 정보를 확인하고 수정할 수 있다. 스테이트의 이름을 바꿀 수 있는 것은 물론이고 Motion 프로퍼티에는 현재 스테이트가 재생할 애니메이션 클립을 설정할 수 있고 Speed 프로퍼티를 통해서 애니메이션이 재생될 속도 역시 설정할 수 있다.

 

또한 위 예시 이미지에서 배치된 스테이트들은 애니메이터의 가장 기본적인 스테이트들로 스테이트당 하나의 애니메이션 클립을 담는다. 애니메이션 클립 하나를 담는 스테이트 이외에도 파라미터 값에 따라서 여러 애니메이션을 블랜딩해주는 블랜드 트리나, 여러 스테이트들을 담을 수 있는 서브-스테이트 머신이 있다. 추가적인 내용은 다른 파트에서 다루도록 하겠다.

 

특수한 스테이트

 

예시로 보여진 애니메이터 컨트롤러의 그래프를 보면 일반적인 노드는 회색의 사각형으로 표시되고 있는데, 그 외에 특별한 형태의 노드를 볼 수 있다. 

 

 

첫 번째는 엔트리(Entry)다. 엔트리는 애니메이션이 처음 시작될 때의 진입점을 의미한다.

 

 

이 엔트리에서 제일 처음으로 연결된 노드는 주황색으로 표시되며, 게임오브젝트가 활성화되어 애니메이션이 시작되면 이 주황색으로 표시된 애니메이션부터 재생이 시작된다.

 

 

노드를 기본 스테이트로 만들기 위해서는 기본 스테이트로 만들고자 하는 노드를 우클릭하고 [Set as Layer Default State] 항목을 선택하면 된다.

 

 

두 번째는 모든 스테이트(Any State) 노드이다. 이 노드는 애니메이터가 어떤 애니메이션을 재생하고 있는 상태이던 간에 트랜지션의 조건이 만족되면 무조건 다음 스테이트로 넘어가서 애니메이션을 재생하게 만든다.

 

예를 들어 캐릭터가 걷는 중이든, 가만히 서있는 중이든, 아니면 포션을 마시는 중이었든, 큰 데미지를 입어서 죽으면, 바로 Die 스테이트로 넘어가도록 하는 것이다.

 

 

마지막으로 엑시트(Exit) 노드이다. 엑시트 노드는 애니메이터의 흐름이 한 번 끝났음을 의미하고, 엑시트 노드를 통과하면 엔트리 노드로부터 다시 애니메이터의 흐름이 다시 시작된다.

 

 

파라미터(Parameter)

 

 

애니메이터의 파라미터는 한 애니메이션에서 다른 애니메이션으로 전환되는 조건이 되는 변수의 역할을 한다. Parameters에서 + 버튼을 누르면 추가할 파라미터의 종류를 선택할 수 있다. 파라미터의 종류로는 Float, Int, Bool, Trigger가 있으며 Float는 소수점을 나타낼 수 있는 실수, Int는 정수, Bool는 참/거짓을 표현하는 논리 변수이며, Trigger는 Set되면 True가 되고 해당 Trigger가 걸린 트랜지션을 통과하면 자동으로 False로 바뀌는 타입이다.

 

 

트랜지션(Transition)

 

 

트랜지션은 애니메이터에서 스테이트와 스테이트 사이를 이어주는 것이다. 스테이트 사이를 이어줄 때, 애니메이션이 어느 방향으로 흘러갈지 방향을 정할 수 있다. 그 외에도 트랜지션을 선택하면 인스펙터 창을 통해서 선택된 트랜지션이 실행될 조건이나, 한 스테이트에서 다른 스테이트로 넘어갈 때 애니메이션을 어떻게 블랜딩해줄 것인지 등을 설정할 수 있다.

 


 

이 외의 애니메이션과 관련된 포스트는 애니메이션 카테고리에서 확인할 수 있다.

 

 

반응형

+ Recent posts