Tilemap (2) 

룰 타일로 타일맵 자동 연결하기

 

작성 기준 버전 :: 2019.1-2019.2

 

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

 

지난 섹션에서는 간단하게 타일맵을 만들고 사용하는 방법에 대해서 알아보았다. 이번 섹션에서는 룰 타일 기능을 이용한 동일 타일을 연달아 놓았을 때, 자동으로 연결되는 기능을 구현하는 방법을 알아보자.

 

타일의 자동 연결은 어떻게?

 

 

같은 타일을 놓으면 오른쪽 십자가처럼 자동으로 연결되기를 바랄 수 있다. 하지만 유니티 타일맵 기본 기능 만으로는 같은 타일을 옆에 놓아봤자 왼쪽 십자가처럼 끊어진 십자가만 놓아지고 오른쪽 십자가처럼 만들려면 직접 일일이 배치를 해야한다.

 

유니티 2D 엑스트라(Unity 2D Extra)

 

타일을 자동으로 연결해주는 기능은 기본 타일맵에서는 제공하지 않고, 유니티 테크놀러지에서 만들어서 깃허브에 올려둔 별도의 기능인 2D 엑스트라 임포트해서 사용해야 한다(링크에서 2D Extra를 다운로드 받으면 된다).

 

 

그리고 다운로드 받은 파일의 압축을 풀고 2d-extra-master 폴더를 프로젝트 뷰의 Assets 폴더 안에 넣어주면 된다.

 

룰 타일(Rule Tile)

 

유니티에서 같은 룰 타일을 근접 배치했을 때, 정해둔 규칙에 따라서 다른 스프라이트를 표시하도록 하는 것을 룰 타일(Rule Tile)이라고 부른다.

 

 

프로젝트 뷰에서 Create 버튼을 눌러보면 드롭다운 메뉴 최상단에 Tiles라는 새로운 항목이 생긴 것을 볼 수 있다. 이것은 방금 추가한 2D Extra 기능을 임포트하면서 생긴 것으로, Tiles 중에서 Rule Tile을 선택하면 새로운 룰 타일을 만들 수 있다.

 

  

룰 타일을 생성하고 선택해보면 위와 같은 화면이 표시된다.

 

 

룰 타일을 설정하는 순서로 먼저 디폴트 스프라이트와 디폴트 게임을 설정한다. 예제에서는 닫힌 타일을 기본 스프라이트로 정했고, 디폴트 게임 오브젝트로는 앞에서 만든 Wall 타일 팔레트를 설정했다.

 

  

아래의 Tiling Rule을 + 버튼을 클릭해서 추가하고 연결되어야 하는 경우의 수를 모두 정의해주면 된다. 약간은 반복성이 짙은 작업이지만, 모든 타일을 일일이 선택해서 작업하는 것보다는 한 번 타일 규칙을 설정하고 편하게 작업하는 것이다 좋다.

 

 

적절하게 타일링 규칙을 모두 설정했다면 설정한 룰 타일을 사용하고자 하는 타일 팔레트의 빈 칸에 드래그한다. 그러면 직접 만든 룰 타일이 팔레트에 포함된다.

 

 

그리고 씬 뷰에서 일반 타일과 룰 타일을 사용해서 타일을 그려보면 직접 정한 룰에 따라서 자동으로 타일이 연결되는 것을 확인할 수 있다.

 

 

 

타일링 규칙

 

타일 규칙

 

간단하게 룰 타일의 기능을 맛보았으니 이제 실제로 타일링 규칙들이 무엇을 의미하는지를 알아보자.

 

 

첫 번째 규칙은 초록색 화살표이다. 초록색 화살표는 해당 방향에 같은 룰 타일이 놓여져 있는 경우를 의미한다.

 

 

 

 

두 번째 규칙은 빨간색 엑스 표시이다. 이 표시는 해당 방향에 같을 룰 타일이 없는 경우를 의미한다.

룰 타일의 주요 규칙은 이 두 가지로 대부분 표현할 수 있다. 어느 방향에 같은 룰 타일이 있느냐 없느냐에 따라서 대부분의 경우를 구현할 수 있다.

 

방향규칙

 

하지만 모든 방향에 대한 경우를 일일이 구현하는 일은 굉장히 힘든 반복작업이기 때문에 룰 타일의 규칙에는 추가 방향 규칙 역시 제공한다.

 

Fixed

 

 

Fixed는 고정된 방향에만 적용하거나 방향에 상관이 없는 경우에 사용된다. 

 

 

대체로 모든 방향에 타일이 있는 경우나 십자형태로 배치된 가운데 블록처럼, 가운데 들어가는 타일에 주로 사용된다.

 

Rotated

 

 

Rotated는 중심을 기준으로 90도, 180도, 270도 회전시킨 방향에도 똑같이 적용하는 규칙이다.

 

  

주로 십자가 끝부분처럼 이미지가 회전되어 표현되어야 하는 경우에 사용된다.

 

MirrorX

 

 

MirrorX는 좌우 방향으로 대칭시켜주는 규칙이다. 왼쪽 타일과 오른쪽 타일이 같을 때 사용된다.

 

MirrorY

 

 

MirrorY는 상하 방향으로 대칭시켜주는 규칙이다.

 

 

위쪽 타일과 아래쪽 타일이 같아서 대칭 시켜줄 때 주로 사용된다.

 

MirrorXY

 

 

MirrorXY는 MirrorX 규칙과 MirrorY 규칙을 섞은 것으로 상하좌우 모두 대칭시켜주는 규칙이다.

 

  

모서리 타일에 주로 사용된다.

 

 

 

출력 규칙

 

싱글(Single)

 

 

타일이 얼마나 설치되든 같은 이미지만 사용하려고 할 때 사용된다. 너무 많이 사용되면 반복되는 타일이 많아짐으로써 배경이 단조롭게 보이는 단점이 있다.

 

랜덤(Random)

 

 

여러 타일을 섞어서 무작위로 출력하는 방법이다. Size를 늘려서 중간에 깨진 타일을 섞어서 출력한다던가 Shuffle을 Rotated로 넣어서 회전된 타일을 출력한다던가 하는 방식으로 사용할 수 있다. 타일맵에 무작위성을 줌으로써 타일맵의 단조로움을 줄여줄수 있다.

 

대신 무작위로 사용되는 타일은 일반 타일과 회전 타일 등과의 연속성을 잘 확인해야 한다. 일반 타일이 무작위 출력된 타일이나 회전된 타일과의 연결이 부자연스럽게 이어지면 플레이어들의 눈에 심하게 띄는 문제가 발생한다.

 

애니메이션(Animation)

 

 

여러 타일을 설정된 시간에 맞춰 순서대로 출력하여 애니메이션을 보여주는 타일의 설정이다. 주로 배경에 흐르는 폭포수 같은 효과를 줄 때 주로 사용된다.

 

 

사용시 주의점

 

2D 엑스트라는 아직 유니티 테크놀러지 측에서도 개선 중인 기술로 룰 타일을 사용할 때, 주의해야할 점이 있다.

 

 

이 문제는 심각한 이슈사항인데, 타일 팔레트에 룰 타일을 올려둔 채로 씬의 타일맵에 계속 그려보면서 타일링 룰을 수정하면 어느 시점부터 심각한 수준의 메모리 누수가 발생한다. 특히 유니티 엔진이 응답없음 상태가 되고 메모리 점유율이 계속 올라가는 상태라면, 유니티를 종료하고 다시 실행해도 실행하자마자 메모리 누수가 시작될 확률이 높다.

 

그렇기 때문에 타일맵과 룰 타일을 안정적으로 사용하고 싶다면, 룰 타일의 타일링 룰을 먼저 작성한 뒤 팔레트를 만들어서 룰 타일을 올리고 간단하게 씬에서 테스트한 뒤, 문제가 없다면 그대로 사용하고, 만약 타일링 룰을 수정해야 한다면, 현재 타일 팔레트를 삭제하고 타일링 룰을 수정한 뒤 다시 타일 팔레트를 만들 것을 권장한다.

 


 

Tilemap (1) - 2D 게임의 기본 타일맵!

Tilemap (2) - 룰 타일로 타일맵 자동 연결하기

Tilemap (3) - 타일맵에 콜라이더 추가하기

 

[유니티 어필리에이트 프로그램]

아래의 링크를 통해 에셋을 구매하시거나 유니티를 구독하시면 수익의 일부가 베르에게 수수료로 지급되어 채널의 운영에 도움이 됩니다.

 

에셋스토어

여러분의 작업에 필요한 베스트 에셋을 찾아보세요. 유니티 에셋스토어가 2D, 3D 모델, SDK, 템플릿, 툴 등 여러분의 콘텐츠 제작에 날개를 달아줄 다양한 에셋을 제공합니다.

assetstore.unity.com

 

Easy 2D, 3D, VR, & AR software for cross-platform development of games and mobile apps. - Unity Store

Have a 2D, 3D, VR, or AR project that needs cross-platform functionality? We can help. Take a look at the easy-to-use Unity Plus real-time dev platform!

store.unity.com

 

Create 2D & 3D Experiences With Unity's Game Engine | Unity Pro - Unity Store

Unity Pro software is a real-time 3D platform for teams who want to design cross-platform, 2D, 3D, VR, AR & mobile experiences with a full suite of advanced tools.

store.unity.com

[투네이션]

 

-

 

toon.at

[Patreon]

 

WER's GAME DEVELOP CHANNEL님이 Game making class videos 창작 중 | Patreon

WER's GAME DEVELOP CHANNEL의 후원자가 되어보세요. 아티스트와 크리에이터를 위한 세계 최대의 멤버십 플랫폼에서 멤버십 전용 콘텐츠와 체험을 즐길 수 있습니다.

www.patreon.com

[디스코드 채널]

 

Join the 베르의 게임 개발 채널 Discord Server!

Check out the 베르의 게임 개발 채널 community on Discord - hang out with 399 other members and enjoy free voice and text chat.

discord.com

 

반응형

Tilemap (1)

2D 게임의 기본 타일맵!

 

작성 기준 버전 :: 2019.1 - 2019.2

 

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

 

  

예전에 RPG메이커를 사용했을 때는 맵이 타일(Tile) 방식으로 이루어져 있어서 타일 팔레트를 가져와서 마치 그림 그리듯이 원하는 타일을 칠해주는 방식으로 맵을 만들 수 있었다.

 

유니티 엔진을 처음 배웠던 5 버전에서는 이러한 기능이 없어서 2D 맵 오브젝트를 직접 배치하거나 타일맵 기능을 직접 구현해야만 했었다. 하지만 유니티 2017.2 버전에서부터 타일맵 기능이 유니티에도 추가되었다. 맵오브젝트를 배치할 필요없이 그림 그리듯이 맵을 만들 수 있다는 것은 굉장한 장점이다. 

 

 

 

타일맵의 타일을 하나 추가해서 아래쪽 이미지와는 조금 다를 수 있다.

  

샘플용 간단한 타일맵 이미지를 다운받은 뒤, 타일맵의 사용법에 대해서 알아보자. 구글에서 적당한 리소스를 다운받아서 사용해도 좋다.

 

 

타일 팔레트(Tile Palette) 만들기

 

타일맵을 씬에 배치하기 이전에 먼저 타일맵을 색칠할 수 있는 타일의 종류를 모아둔 것을 만들어야 하는데 이것을 유니티 엔진에서는 타일 팔레트(Tile Palette)라고 부른다. 이 타일 팔레트를 만드는 방법에 대해서 배워보자.

 

타일 팔레트가 될 이미지 임포트 & 세팅

 

먼저 타일 팔레트에 들어갈 이미지를 임포트하고 타일맵에 사용하기 좋게 세팅해야 한다.

 

 

다운로드 받은 타일맵 이미지를 프로젝트에 임포트한다. 처음으로 임포트한 이미지는 대부분 설정이 위와 같을 것이다. 타일맵 이미지의 임포트 세팅을 적절하게 설정해주어야 한다. 

 

우선 Pixels Per Unit은 이미지의 픽셀을 몇 개를 단위로 유니티 엔진의 공간 상에서 1단위(유닛, Unit)로 표현할 것인가에 대한 설정이다. 유니티에서의 1단위는 보통 1m를 의미한다. 즉, 몇 개의 픽셀을 1m로 볼 것인가를 의미하는 셈이다. 타일맵 이미지를 열어서 확인 해보면 알겠지만 타일맵의 이미지는 한 칸당 128픽셀로 이루어져 있다. 한 칸을 1미터로 볼 것이기 때문에 Pixel Per Unit에 128을 입력해주자.

 

그 다음 설정은 Sprite Mode 값이다. 이 설정의 기본 값은 Single인데 이미지 한 장을 한 장의 스프라이트(Sprite)로 본다는 의미이다. 타일맵 이미지를 보면 알 수 있겠지만 타일맵 이미지는 한 장이 하나의 타일이 아니라 여러 장의 타일이 그려져 있는 것을 알 수 있다. 이 때문에 한 장의 이미지가 하나의 스프라이트가 아닌 그려진 타일의 수만큼의 스프라이트를 가져야한다는 것을 알 수 있다. 

 

 

Multiple로 설정해주자. 그 다음에는 이 이미지가 어떻게 나누어져야 하는지를 정하기 위해서 스프라이트 에디터(Sprite Editor) 버튼을 클릭한다.

 

 

 

그러면 스프라이트 에디터 창이 열리는데 기본적으로 타일맵 이미지가 하나의 스프라이트로 이루어지고 있음을 확인할 수 있다.

 

 

이미지를 여러 개의 스프라이트로 나누기 위해서 스프라이트 에디터 창의 상단 메뉴 중에서 슬라이스(Slice)를 선택하고 자동(Autometic)으로 되어 있는 타입을 Grid by Cell Count로 바꾼다.

 

 

 

그리고 컬럼(Column)과 로우(Row)를 각각 5와 3으로 변경하고 슬라이스 버튼을 누른다.

 

 

 

그렇게 하면 하나의 스프라이트로 되어 있던 타일맵 이미지가 여러 개의 스프라이트로 나누어지는 것을 볼 수 있다. 상단 바의 Apply 버튼을 누르고 적용한다.

 

 

 

프로젝트 뷰에서도 이 모습을 확인할 수 있다.

 

타일 팔레트 만들기

 

 

타일 팔레트는 상단 메뉴바에서 [Window>2D>Tile Palette] 항목을 선택한다.

 

 

그렇게 하면 위의 이미지와 같이 타일 팔레트 뷰가 열린다. 여기서 Create New Palette 버튼을 누르고

 

 

팔레트의 이름을 정하고 Create 버튼을 누른 뒤 저장하면 빈 타일 팔레트가 생성된다.

 

 

그 다음 타일로 만들고자 하는 스프라이트를 타일 팔레트로 드래그 하고 저장하면 타일맵 에셋들이 프로젝트 뷰에 생성되는 것을 볼 수 있다.

 

 

 

그리고 타일 팔레트에도 추가한 타일틀이 보여지는 것을 확인할 수 있다. 여기서 이 원하는 타일을 선택하고 씬에 배치된 타일맵에 타일을 그리면 된다.

 

 

 

타일맵 생성

 

 

우선 타일맵을 생성하기 위해서는 Create>2D>Tilemap을 선택하면 된다. 이 생성 과정의 경우에는 하이어라키(Heirarchy) 뷰에서 우클릭을 하거나 하이어라키 뷰 상단의 Create 메뉴 버튼, 혹은 상단 메뉴바의 GameObject 메뉴를 선택해서도 똑같이 만들 수 있다.

 

 

타일맵을 생성하면 위의 이미지와 같이 게임씬에 타일맵을 그리기 쉽게 격자선을 그려주는 그리드(Grid) 컴포넌트가 부착된 게임 오브젝트가 생성된다.

 

 

그리고 자식 게임 오브젝트로는 타일맵 컴포넌트(Tilemap Component)와 타일맵 렌더러 컴포넌트(Tilemap Renderer Component)가 부착된 게임 오브젝트가 생성된다. 앞서 만든 타일 팔레트에서 타일을 선택해서 이 타일맵 게임 오브젝트에 타일을 그려서 게임 맵을 만들면 된다. 그리드 아래에 여러 타일맵 게임 오브젝트를 넣어서 여러 층의 타일맵을 겹쳐서 맵을 만들 수도 있다.

 

타일맵 그려보기

 

 

 

타일맵이 만들어졌으면 타일 팔레트에서 타일을 선택해서 맵을 그려보자. 클릭하고 드래그하면 맵이 손쉽게 그려진다. 그리고 시프트(Shift) 키를 누르고 클릭 & 드래그하면 그려진 타일들이 지워진다.

 

 

 

추가로 [ ] 대괄호 키를 누르면 타일을 회전시킬 수 있다.

 


 

Tilemap (1) - 2D 게임의 기본 타일맵!

Tilemap (2) - 룰 타일로 타일맵 자동 연결하기

Tilemap (3) - 타일맵에 콜라이더 추가하기 

 

[유니티 어필리에이트 프로그램]

아래의 링크를 통해 에셋을 구매하시거나 유니티를 구독하시면 수익의 일부가 베르에게 수수료로 지급되어 채널의 운영에 도움이 됩니다.

 

에셋스토어

여러분의 작업에 필요한 베스트 에셋을 찾아보세요. 유니티 에셋스토어가 2D, 3D 모델, SDK, 템플릿, 툴 등 여러분의 콘텐츠 제작에 날개를 달아줄 다양한 에셋을 제공합니다.

assetstore.unity.com

 

Easy 2D, 3D, VR, & AR software for cross-platform development of games and mobile apps. - Unity Store

Have a 2D, 3D, VR, or AR project that needs cross-platform functionality? We can help. Take a look at the easy-to-use Unity Plus real-time dev platform!

store.unity.com

 

Create 2D & 3D Experiences With Unity's Game Engine | Unity Pro - Unity Store

Unity Pro software is a real-time 3D platform for teams who want to design cross-platform, 2D, 3D, VR, AR & mobile experiences with a full suite of advanced tools.

store.unity.com

[투네이션]

 

-

 

toon.at

[Patreon]

 

WER's GAME DEVELOP CHANNEL님이 Game making class videos 창작 중 | Patreon

WER's GAME DEVELOP CHANNEL의 후원자가 되어보세요. 아티스트와 크리에이터를 위한 세계 최대의 멤버십 플랫폼에서 멤버십 전용 콘텐츠와 체험을 즐길 수 있습니다.

www.patreon.com

[디스코드 채널]

 

Join the 베르의 게임 개발 채널 Discord Server!

Check out the 베르의 게임 개발 채널 community on Discord - hang out with 399 other members and enjoy free voice and text chat.

discord.com

 

반응형

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)

 

[유니티 어필리에이트 프로그램]

아래의 링크를 통해 에셋을 구매하시거나 유니티를 구독하시면 수익의 일부가 베르에게 수수료로 지급되어 채널의 운영에 도움이 됩니다.

 

에셋스토어

여러분의 작업에 필요한 베스트 에셋을 찾아보세요. 유니티 에셋스토어가 2D, 3D 모델, SDK, 템플릿, 툴 등 여러분의 콘텐츠 제작에 날개를 달아줄 다양한 에셋을 제공합니다.

assetstore.unity.com

 

Easy 2D, 3D, VR, & AR software for cross-platform development of games and mobile apps. - Unity Store

Have a 2D, 3D, VR, or AR project that needs cross-platform functionality? We can help. Take a look at the easy-to-use Unity Plus real-time dev platform!

store.unity.com

 

Create 2D & 3D Experiences With Unity's Game Engine | Unity Pro - Unity Store

Unity Pro software is a real-time 3D platform for teams who want to design cross-platform, 2D, 3D, VR, AR & mobile experiences with a full suite of advanced tools.

store.unity.com

[투네이션]

 

-

 

toon.at

[Patreon]

 

WER's GAME DEVELOP CHANNEL님이 Game making class videos 창작 중 | Patreon

WER's GAME DEVELOP CHANNEL의 후원자가 되어보세요. 아티스트와 크리에이터를 위한 세계 최대의 멤버십 플랫폼에서 멤버십 전용 콘텐츠와 체험을 즐길 수 있습니다.

www.patreon.com

[디스코드 채널]

 

Join the 베르의 게임 개발 채널 Discord Server!

Check out the 베르의 게임 개발 채널 community on Discord - hang out with 399 other members and enjoy free voice and text chat.

discord.com

 

반응형

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)

 

[유니티 어필리에이트 프로그램]

아래의 링크를 통해 에셋을 구매하시거나 유니티를 구독하시면 수익의 일부가 베르에게 수수료로 지급되어 채널의 운영에 도움이 됩니다.

 

에셋스토어

여러분의 작업에 필요한 베스트 에셋을 찾아보세요. 유니티 에셋스토어가 2D, 3D 모델, SDK, 템플릿, 툴 등 여러분의 콘텐츠 제작에 날개를 달아줄 다양한 에셋을 제공합니다.

assetstore.unity.com

 

Easy 2D, 3D, VR, & AR software for cross-platform development of games and mobile apps. - Unity Store

Have a 2D, 3D, VR, or AR project that needs cross-platform functionality? We can help. Take a look at the easy-to-use Unity Plus real-time dev platform!

store.unity.com

 

Create 2D & 3D Experiences With Unity's Game Engine | Unity Pro - Unity Store

Unity Pro software is a real-time 3D platform for teams who want to design cross-platform, 2D, 3D, VR, AR & mobile experiences with a full suite of advanced tools.

store.unity.com

[투네이션]

 

-

 

toon.at

[Patreon]

 

WER's GAME DEVELOP CHANNEL님이 Game making class videos 창작 중 | Patreon

WER's GAME DEVELOP CHANNEL의 후원자가 되어보세요. 아티스트와 크리에이터를 위한 세계 최대의 멤버십 플랫폼에서 멤버십 전용 콘텐츠와 체험을 즐길 수 있습니다.

www.patreon.com

[디스코드 채널]

 

Join the 베르의 게임 개발 채널 Discord Server!

Check out the 베르의 게임 개발 채널 community on Discord - hang out with 399 other members and enjoy free voice and text chat.

discord.com

 

반응형

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)

 

[유니티 어필리에이트 프로그램]

아래의 링크를 통해 에셋을 구매하시거나 유니티를 구독하시면 수익의 일부가 베르에게 수수료로 지급되어 채널의 운영에 도움이 됩니다.

 

에셋스토어

여러분의 작업에 필요한 베스트 에셋을 찾아보세요. 유니티 에셋스토어가 2D, 3D 모델, SDK, 템플릿, 툴 등 여러분의 콘텐츠 제작에 날개를 달아줄 다양한 에셋을 제공합니다.

assetstore.unity.com

 

Easy 2D, 3D, VR, & AR software for cross-platform development of games and mobile apps. - Unity Store

Have a 2D, 3D, VR, or AR project that needs cross-platform functionality? We can help. Take a look at the easy-to-use Unity Plus real-time dev platform!

store.unity.com

 

Create 2D & 3D Experiences With Unity's Game Engine | Unity Pro - Unity Store

Unity Pro software is a real-time 3D platform for teams who want to design cross-platform, 2D, 3D, VR, AR & mobile experiences with a full suite of advanced tools.

store.unity.com

[투네이션]

 

-

 

toon.at

[Patreon]

 

WER's GAME DEVELOP CHANNEL님이 Game making class videos 창작 중 | Patreon

WER's GAME DEVELOP CHANNEL의 후원자가 되어보세요. 아티스트와 크리에이터를 위한 세계 최대의 멤버십 플랫폼에서 멤버십 전용 콘텐츠와 체험을 즐길 수 있습니다.

www.patreon.com

[디스코드 채널]

 

Join the 베르의 게임 개발 채널 Discord Server!

Check out the 베르의 게임 개발 채널 community on Discord - hang out with 399 other members and enjoy free voice and text chat.

discord.com

 

반응형

Prefab 

게임 오브젝트를 에셋화 하기

 

작성 기준 버전 :: 2019.1.4f1

 

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

 

유니티에서 게임 오브젝트는 씬에 배치될 수 있는 오브젝트를 의미한다. 이 게임 오브젝트에 어떤 컴포넌트가 붙는가에 따라서 그 게임 오브젝트의 역할이 결정되는데, 씬에 하나만 배치되는 오브젝트는 컴포넌트를 직접 부착해서 배치할 수는 있지만 똑같은 오브젝트를 많이 배치해야 되는 경우에 매번 배치할 때마다 필요한 컴포넌트를 부착하는 작업을 해야한다면 이것은 매우 비효율적인 작업이 된다. 

 

일일이 게임 오브젝트를 생성한 다음 컴포넌트를 붙이는 비효율에서 벗어나기 위해서 제일 처음 만들어진 게임 오브젝트를 복사해서 배치할 수도 있는데, 이것은 또 다른 비효율적인 작업에 봉착하게 된다. 만약 이렇게 붙여넣은 오브젝트들의 크기를 전부 2배로 키워야 한다면? 그럼 붙여넣은 게임 오브젝트들을 일일이 찾아서 스케일 값을 바꿔주어야 한다. 이것 역시 심각하게 비효율적인 작업이다.

 

이러한 예시 이외에 어떤 게임 오브젝트를 게임이 진행하는 도중에 생성해서 배치해야 된다면, 코드 상에서 빈 게임 오브젝트를 생성하고, 거기에 필요한 컴포넌트를 붙여서 일일이 초기화해서 배치를 하는 것 역시 비효율적이다.

 

 

비효율적인 작업들 1 : 새 오브젝트마다 손수 컴포넌트 붙이고 설정하기

 

앞서 제시한 예시들을 하나씩 따라가보자.

 

 

우리는 이제 씬에 이른바 "Elegance Black Box"라고 명명된 검은색의 Black Box 컴포넌트가 부착된 상자를 여러 개 배치하려고 한다. 이 멋진 검은 상자를 만들기 위해서 우리는 다음과 같은 작업을 해야한다.

 

 

먼저 새 상자를 만든다.

 

 

새 상자의 이름을 "Elegance Black Box"로 변경한다.

 

 

그 다음 머티리얼에 검은 색 머티리얼을 넣고 Black Box 컴포넌트를 붙여준다(사실 따로 순서를 진행할 수도 있지만 그만큼 번거롭고 지루해지기 때문에 그냥 합쳤다).

 

자 총 4단계의 과정을 거쳤다. 이 작업을 만들고자 하는 "Elegance Black Box"의 갯수만큼 반복하면 된다. 고작 4단계인데 이렇게 번거롭다. 만약 더 복잡한 구조의 게임 오브젝트라면 어떻겠는가?

 

 

비효율적인 작업들 2 : 배치된 게임 오브젝트 복제하기

 

 

유니티 엔진에서는 복제하고자 하는 게임 오브젝트를 선택하고 우클릭하여 [Duplicate] 항목을 선택하거나 [Ctrl + D] 단축키를 눌러서 복제할 수 있다.

 

 

와! 일일이 새 게임 오브젝트를 만들고 컴포넌트를 붙이는 작업을 하지 않아도 된다! 혁명적인가? 분명 여기까지는 혁명적이다. 하지만 원수같은 기획자들이 "Elegance Black Box"를 좀 더 우아하게 강조하기 위해서 크기를 25% 키우자고 주장했다.

 

 

그나마 예시에서는 갯수가 적고 하이어라키 뷰에서 오브젝트가 흩어져 있지 않아서 모두 선택해서 빠르게 해결했다. 하지만 하이어라키 뷰에서 다른 게임 오브젝트 밑에 숨어있다거나 흩어져있다면 일일이 찾아서 수정해야 한다. 물론 하이어라키 뷰의 검색 기능을 이용하면 훌륭하게 해결할 수 있을 지도 모른다. 그러나 이런 검색 작업 역시 비효율적인것은 사실이다.

 

 

비효율적인 작업들 3 : 코드에서 동적으로 생성하기

 

이번에는 동료 디자이너가 "멋진 검은 상자가 게임 중에 동적으로 생성되면 좋겠는데!"라고 말했다. 

 

public static BlackBox CreateNewBlackBox()

{

    var newBox = GameObject.CreatePrimitive(PrimitiveType.Cube).AddComponent<BlackBox>();

    newBox.name = "Elegance Black Box";

    newBox.GetComponent<Renderer>().material = Resources.Load<Material>("M_Black");

    return newBox;

}

 

거기에 당신은 위와 같이 블랙 박스를 생성하는 코드를 만들어 냈다. 이러면 끝난 것일까? 아니다. 디자이너가 블랙 박스에 대해서 수정사항을 가지고 올 때마다 당신은 코드를 수정해야 한다. 거기에 컴파일 시간은 덤이다! 그리고 지금은 간단한 오브젝트라 코드가 몇 줄 되지 않지만 복잡한 오브젝트면 코드의 양이 늘어나고 거기에 더불어 버그의 확률도 함께 상승한다.

 

 

 

우리의 구세주 프리팹

 

이러한 모든 문제를 해결하기 위해서 있는 것이 바로 프리팹이다. 프리팹은 게임 오브젝트와 거기에 붙여진 컴포넌트와 그 프로퍼티들을 에셋의 형태로 저장하는 것이다.

 

프리팹 만들기

 

 

 

프리팹을 만드는 방법은 아주 간단하다. 하이어라키 뷰에서 프리팹으로 만들고자 하는 게임 오브젝트를 선택해서 프로젝트 뷰로 끌어다 놓기만 하면 된다. 프리팹이 된 게임 오브젝트는 앞의 아이콘이 무채색 육면체에서 파란 육면체로 바뀐다.

 

배치된 게임 오브젝트 한꺼번에 변경하기

 

이번에도 아까 전처럼 씬에 배치된 모든 블랙 박스의 크기를 변경하고 싶을 수 있다. 프리팹으로는 이런 작업이 아주 간단하다.

 

 

프로젝트 뷰에서 원본 프리팹을 선택하고 프리팹의 크기를 변경해주면 씬에 배치된 모든 프리팹 인스턴스의 크기가 함께 변경된다. 하지만 이 방법은 씬에 배치된 각각의 인스턴스의 프로퍼티가 수정된 상태라면 적용되지 않으니 주의해야 한다.

 

프리팹 인스턴스에서 편집

 

 

위 예시에서는 프리팹 원본에서 수정된 것을 프리팹 인스턴스로 적용되는 내용이었다. 반대로 씬에 배치된 프리팹 인스턴스를 수정하고 이것을 원본 프리팹에 적용할 수도 있다. 씬에 배치된 프리팹 인스턴스 게임 오브젝트를 선택하면 일반 게임 오브젝트와는 다르게 인스펙터 뷰의 게임 오브젝트의 이름 아래에 Prefab : Open, Select, Override 버튼을 볼 수 있다.

 

 

여기서 Open 버튼을 선택하면 선택된 프리팹의 원본만을 수정할 수 있는 전용 씬으로 이동된다. 여기서는 프로젝트 뷰에서는 보이지 않는 프리팹 원본의 깊은 자식 오브젝트까지 열어서 수정할 수 있게 된다. 또한 수정된 프리팹의 내용은 자동으로 저장되며 하이어라키 뷰의 프리팹 아이콘 옆의 < 버튼을 클릭하면 다시 원래 씬으로 돌아올 수 있다.

 

원본 프리팹을 더블 클릭하거나 씬에 배치된 프리팹 인스턴스 옆의 > 버튼을 클릭해도 프리팹 수정 씬으로 들어올 수 있다.

 

 

Select 버튼을 클릭하면 프로젝트 뷰의 원본 프리팹이 바로 선택된다.

 

 

Override 버튼은 만약 프리팹 인스턴스에 원본 인스턴스와 달라진 점이 있다면 내용이 나타난다. 여기서 Revert All을 선택하면 프리팹 인스턴스의 변경 사항이 초기화되고 프리팹 원본 값으로 돌아간다. Apply All을 선택하면 프리팹 인스턴스의 수정 사항이 반대로 프리팹 원본에 덮어 씌워진다.

 

프리팹 인스턴스화

 

프로젝트 뷰에 존재하는 프리팹 원본은 에셋 상태로 이 상태 그대로는 게임 씬에서 보거나 사용할 수 없다. 이것을 게임 씬에 배치하고 사용할 수 있게 생성하는 과정을 인스턴스화라고 한다. 프리팹 인스턴스화는 게임 오브젝트의 Instantiate() 함수를 이용해서 할 수 있다.

 

프로젝트 뷰에 있는 게임 오브젝트는 크게 두 가지 방법으로 가져올 수 있다.

 

Resources 폴더에서 가져오기

 

 

첫 번째 방법은 Resources 폴더에서 가져오는 것이다. 이 방법은 어느 경로이든 무관하게 가져오고자 하는 프리팹이 프로젝트 뷰에서 Resources 폴더 안에 들어있기만 하면 된다. 단, Resources 폴더에 들어있는 파일들은 게임이 실행되면 무조건 메모리에 적재되기 때문에 메모리 이슈를 일으키고 싶지 않다면, 필요한 에셋만을 Resources 폴더에 넣어둘 것을 권장한다.

 

public static BlackBox CreateNewBlackBox()

{

    var boxPrefab = Resources.Load<BlackBox>("Elegance Black Box");

    return Instantiate(boxPrefab);

}

 

그 다음 Resources.Load() 함수로 Resources 폴더 안의 프리팹을 가져와서 Instantiate() 함수로 씬에 생성할 수 있다.

 

씬에 배치된 게임 오브젝트의 컴포넌트의 프로퍼티로 참조하기

 

public class BoxSpawn : MonoBehaviour

{

    [SerializeField]

    private GameObject boxPrefab;

 

    private void Start()

    {

        Instantiate(boxPrefab);

    }

}

 

두 번째 방법은 씬에 배치된 게임 오브젝트에 부착된 컴포넌트의 프로퍼티로 프리팹 원본을 참조하고 있다가 생성하는 방법이다.

 

 

씬에 Box Spawner 게임 오브젝트를 만들고 위에서 작성한 Box Spawn 컴포넌트를 부착하고 Box Prefab 프로퍼티에 프리팹을 할당해주면 된다. 그러면 게임이 시작되면 박스 스포너가 블랙 박스를 생성하는 것을 확인할 수 있다.

 

기타

 

위에서 제시한 방법 이외에도 에셋 번들에서 가져와서 생성하는 방법 등 다른 기능과 연계된 심화 방법들이 존재한다.

 

 

프리팹의 장점

 

게임 오브젝트가 프리팹화됨으로써 얻을 수 있는 장점은 굉장히 많다. 첫 번째는 재사용이 굉장히 편하다는 점이고, 씬에 흩어져서 배치된 프리팹의 인스턴스들을 한꺼번에 수정하기도 쉽다. 그리고 프로그래머가 컴포넌트만 제대로 만들어준다면, 게임 디자이너들이 프로그래머에게 요청하지 않고도 손쉽게 게임 요소들을 수정할 수 있다는 점이 제일 큰 장점이다.

 

[유니티 어필리에이트 프로그램]

아래의 링크를 통해 에셋을 구매하시거나 유니티를 구독하시면 수익의 일부가 베르에게 수수료로 지급되어 채널의 운영에 도움이 됩니다.

 

에셋스토어

여러분의 작업에 필요한 베스트 에셋을 찾아보세요. 유니티 에셋스토어가 2D, 3D 모델, SDK, 템플릿, 툴 등 여러분의 콘텐츠 제작에 날개를 달아줄 다양한 에셋을 제공합니다.

assetstore.unity.com

 

Easy 2D, 3D, VR, & AR software for cross-platform development of games and mobile apps. - Unity Store

Have a 2D, 3D, VR, or AR project that needs cross-platform functionality? We can help. Take a look at the easy-to-use Unity Plus real-time dev platform!

store.unity.com

 

Create 2D & 3D Experiences With Unity's Game Engine | Unity Pro - Unity Store

Unity Pro software is a real-time 3D platform for teams who want to design cross-platform, 2D, 3D, VR, AR & mobile experiences with a full suite of advanced tools.

store.unity.com

[투네이션]

 

-

 

toon.at

[Patreon]

 

WER's GAME DEVELOP CHANNEL님이 Game making class videos 창작 중 | Patreon

WER's GAME DEVELOP CHANNEL의 후원자가 되어보세요. 아티스트와 크리에이터를 위한 세계 최대의 멤버십 플랫폼에서 멤버십 전용 콘텐츠와 체험을 즐길 수 있습니다.

www.patreon.com

[디스코드 채널]

 

Join the 베르의 게임 개발 채널 Discord Server!

Check out the 베르의 게임 개발 채널 community on Discord - hang out with 399 other members and enjoy free voice and text chat.

discord.com

 

반응형

Programming 

로딩 씬(Loading Scene) 구현하기(커튼 방식)

 

작성 기준 버전 :: 2019.1.4f1

 

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

 

게임의 장르와 배경들의 종류는 많고도 많지만 그 어떤 종류의 게임이던간에 아주 가벼운 게임이 아닌 이상 반드시 등장하는 장면이 있다. 그 장면은 바로 로딩 씬이다. 다들 로딩 씬이 등장하면 언제쯤 지나가려나 하며 로딩 바에 마우스를 올리고 정말로 바가 채워지고 있는지 확인해본 경험이 있을 것이다. 게임을 처음으로 접했던 어린 시절에는 이런 로딩 씬이 왜 필요한지도 몰랐고 그냥 재미있는 게임을 할 시간을 잡아먹는 나쁜 녀석이라는 생각만 가득했다.

 

 

하지만 게임 개발을 시작한 이후로 이 로딩 씬만큼 중요한 씬이 또 없다는 것을 깨달을 수 있었다. 로딩 씬의 역할은 단지 시간만 잡아먹는 것이 아니라 게임의 씬이 전환될 때 다음 씬에서 사용될 리소스들을 물리적인 저장소에서 읽어와서 메모리에 올리는 등의 게임을 하기 위한 준비를 하는 작업이다.

 

만약에 게임에 로딩 장면이 존재하지 않는다면 어떻게 될까? 아마 플레이어는 다음 씬으로 넘어가는 동안 가만히 게임이 멈춘 화면을 보고 있거나 까만 화면을 보고 있어야 한다. 그런 일이 발생한다면 로딩이 얼마나 진행되었는지 알 수 없고 이 게임이 로딩 중인지 정지한 것인지 구분할 수도 없어서 너무 답답할 것이다. 그렇기 때문에 씬이 전환될 때에는 로딩 씬을 만들어서 플레이어에게 로딩이 얼마나 진행되었는지 알려주면서 플레이어가 지루하지 않게 게임 게임 팁이나 게임 스토리등을 보여주는 것이다.

 

 

개념

 

 

 

이전에는 로딩하는 씬을 전용으로 만들어서 로딩하는 방법을 소개했었다. 이번에는 그 방법과는 다르게 마치 무대에서 잠시 커튼을 내리고 무대를 교체하는 것과 비슷하게 로딩 바를 보여주는 UI를 전면에 씌운 뒤 로딩하는 방법을 구현해본다.

 

 

구현하기

 

앞에서는 로딩 씬의 필요성에 대해서 이야기했다면 이제는 실제로 로딩 씬을 유니티에서 구현하는 방법을 알아보자. 

 

 

코드 작성하기

 

씬 화면을 가리고 로딩작업을 진행하는 SceneLoader 클래스를 생성하고 다음의 순서로 코드를 작성한다.

 

using System.Collections;
using UnityEngine;
using UnityEngine.SceneManagement;
using UnityEngine.UI;

 

SceneLoader는 기존 씬을 UI로 가리고 다른 씬을 불러오는 등의 작업을 해야하기 때문에 UnityEngine.SceneManagement와 UnityEngine.UI 네임스페이스를 using 선언해준다.

 

public class SceneLoader : MonoBehaviour
{
    protected static SceneLoader instance;
    public static SceneLoader Instance
    {
        get
        {
            if(instance == null)
            {
                var obj = FindObjectOfType<SceneLoader>();
                if(obj != null)
                {
                    instance = obj;
                }
                else
                {
                    instance = Create();
                }
            }
            return instance;
        }

        private set
        {
            instance = value;
        }
    }

    [SerializeField]
    private CanvasGroup sceneLoaderCanvasGroup;
    [SerializeField]
    private Image progressBar;

    private string loadSceneName;

    public static SceneLoader Create()
    {
        var SceneLoaderPrefab = Resources.Load<SceneLoader>("SceneLoader");
        return Instantiate(SceneLoaderPrefab);
    }

    private void Awake()
    {
        if (Instance != this)
        {
            Destroy(gameObject);
            return;
        }

        DontDestroyOnLoad(gameObject);
    }
}

 

SceneLoader의 기본적인 구성은 위와 같다. 이 클래스는 싱글톤 패턴으로 작성되어서 어디서든지 호출할 수 있도록 만들어졌으며, 전체 패널의 투명도를 조절하는 방식으로 씬 로딩 UI를 페이드 인 아웃을 시켜서 자연스럽게 등장시키기 위해서 캔버스 그룹을 사용한다. 그리고 진행도를 표현하기 위해서 이미지를 멤버 변수로 가진다. 

 

public void LoadScene(string sceneName)
{
    gameObject.SetActive(true);
    SceneManager.sceneLoaded += LoadSceneEnd;
    loadSceneName = sceneName;
    StartCoroutine(Load(sceneName));
}

private IEnumerator Load(string sceneName)
{
    progressBar.fillAmount = 0f;
    yield return StartCoroutine(Fade(true));

    AsyncOperation op = SceneManager.LoadSceneAsync(sceneName);
    op.allowSceneActivation = false;

    float timer = 0.0f;
    while (!op.isDone)
    {
        yield return null;
        timer += Time.unscaledDeltaTime;

        if (op.progress < 0.9f)
        {
            progressBar.fillAmount = Mathf.Lerp(progressBar.fillAmount, op.progress, timer);
            if (progressBar.fillAmount >= op.progress)
            {
                timer = 0f;
            }
        }
        else
        {
            progressBar.fillAmount = Mathf.Lerp(progressBar.fillAmount, 1f, timer);

            if (progressBar.fillAmount == 1.0f)
            {
                op.allowSceneActivation = true;
                yield break;
            }
        }
    }
}

private void LoadSceneEnd(Scene scene, LoadSceneMode loadSceneMode)
{
    if(scene.name == loadSceneName)
    {
        StartCoroutine(Fade(false));
        SceneManager.sceneLoaded -= LoadSceneEnd;
    }
}

private IEnumerator Fade(bool isFadeIn)
{
    float timer = 0f;

    while(timer <= 1f)
    {
        yield return null;
        timer += Time.unscaledDeltaTime * 2f;
        sceneLoaderCanvasGroup.alpha = Mathf.Lerp(isFadeIn ? 0 : 1, isFadeIn ? 1 : 0, timer);
    }

    if(!isFadeIn)
    {
        gameObject.SetActive(false);
    }
}

 

커튼 방식으로 로딩 UI를 구현하는 핵심 코드는 위와 같다. 씬을 로딩하는 로직 자체는 로딩 씬 교체 방식과 같으며 여기에 추가적으로 씬을 로딩하기 직전에 로딩 UI를 페이드 인하는 부분과 로딩이 완전히 끝난 지점에서 페이드 아웃되는 코드가 추가되었다. 그리고 씬 로딩 완료가 완전히 완료된 시점을 확인하기 위해서 SceneManager의 SceneLoaded 콜백에 함수를 추가해주는 코드 역시 추가되었다.

 

코드를 모두 작성했다면 저장하고 에디터로 돌아간다.

 

 

 

로딩 UI 구성

 

 

 

로딩 UI를 구성하기 위해서 SceneLoader라는 이름으로 캔버스(Canvas)를 하나 만들고 거기에 캔버스 그룹과 아까 만든 Scene Loader 컴포넌트를 부착한다. 그리고 Scene Loader Canvas Group 프로퍼티에 방금 추가한 캔버스 그룹 컴포넌트를 할당해준다.

 

 

그 다음엔 화면 전체를 덮는 이미지 하나를 Background라는 이름으로 추가한다. 이 이미지는 화면 전체를 가림으로써 씬이 교체되는 것을 플레이어의 시선에서 가려주고 마우스 입력을 방지하는 용도로 사용된다. 여기에는 검은 이미지 이외에도 플레이어의 시선을 끌만한 컨셉아트나 배경 이미지를 넣어줄 수 있다.

 

 

 

그리고 씬 로딩 진행도를 보여줄 이미지를 Progress Bar라는 이름으로 추가하고 위의 이미지와 같이 설정한다.

 

 

Progress Bar 이미지 설정이 끝났다면 SceneLoader 게임 오브젝트를 선택한 뒤, 위의 이미지와 같이 SceneLoader의 액티브를 끄고, Canvas Group의 Alpha 값을 0으로 설정한다. 그리고 Progress Bar 이미지를 Scene Loader 컴포넌트의 Progress Bar 프로퍼티에 할당해준다.

 

 

마지막으로 프로젝트 뷰에 Resources 폴더를 만들고 방금 만든 SceneLoader 게임 오브젝트를 드래그해서 프리팹으로 만들어주고 게임 오브젝트를 씬에서 삭제한다.

 

 

테스트 세팅하기

 

 

위의 과정을 모두 마쳤다면 두 개의 씬을 새로 만들고 씬이 전환되었음을 확인하기 위해서 각 씬에 다른 모양의 게임 오브젝트를 추가해준다.

 

using UnityEngine;

public class SceneLoadTester : MonoBehaviour
{
    private void Update()
    {
        if (Input.GetKeyDown(KeyCode.Space))
        {
            SceneLoader.Instance.LoadScene("Scene2");
        }
    }
}

 

 

그리고 SceneLoader의 기능을 테스트하기 위해 스페이스바를 누르면 SceneLoader를 호출하는 코드를 작성하고 Scene1에 게임 오브젝트를 생성해서 스크립트를 넣어준다.

 

 

상단의 [File > Build Settings] 메뉴를 선택하거나 [Ctrl + Shift + B]를 눌러서 빌드 세팅 창을 연 다음 만든 씬들을 빌드될 씬 목록에 넣어준다.

 

 

 

테스트

 

 

그런 후에 첫 번째 씬에서 플레이 버튼을 눌러 게임을 실행하고 스페이스바를 누르면 첫 번째 씬에서 자연스럽게 로딩 UI가 나타난 후에 아래쪽 로딩 바가 자연스럽게 차오르고 두 번째 씬으로 넘어가는 것을 확인할 수 있다.

 

위의 예시에서는 씬의 로딩 진행도 만을 이용해서 진행 정도를 체크했지만, 유니티에서는 다음 씬에서 사용될 애셋 번들을 불러오는 것 또한 로딩에 포함될 수 있고, 만약 네트워크 게임을 제작한다면 네트워크 동기화 정도도 포함될 수 있다.

 

 

여담으로 일부 게임 제작자의 경우에는 로딩 시간이 너무 짧아서 로딩 시간동안 보여주고자 하는 팁이나 스토리 등이 너무 빠르게 스쳐지나간다고 생각하는 경우에는 일부러 로딩 속도를 늦추거나 페이크 로딩 시간을 넣어서 로딩 시간을 일부러 길게 만드는 경우도 있다.

 


 

이렇게 로딩 UI를 무대의 커튼처럼 전면에 깔아서 로딩하는 방법 이외에도 아예 로딩씬을 불러온 뒤에 다음 씬을 로딩하는 방법도 있으며 해당 방법은 링크에서 확인할 수 있다.

 

[유니티 어필리에이트 프로그램]

아래의 링크를 통해 에셋을 구매하시거나 유니티를 구독하시면 수익의 일부가 베르에게 수수료로 지급되어 채널의 운영에 도움이 됩니다.

 

에셋스토어

여러분의 작업에 필요한 베스트 에셋을 찾아보세요. 유니티 에셋스토어가 2D, 3D 모델, SDK, 템플릿, 툴 등 여러분의 콘텐츠 제작에 날개를 달아줄 다양한 에셋을 제공합니다.

assetstore.unity.com

 

Easy 2D, 3D, VR, & AR software for cross-platform development of games and mobile apps. - Unity Store

Have a 2D, 3D, VR, or AR project that needs cross-platform functionality? We can help. Take a look at the easy-to-use Unity Plus real-time dev platform!

store.unity.com

 

Create 2D & 3D Experiences With Unity's Game Engine | Unity Pro - Unity Store

Unity Pro software is a real-time 3D platform for teams who want to design cross-platform, 2D, 3D, VR, AR & mobile experiences with a full suite of advanced tools.

store.unity.com

[투네이션]

 

-

 

toon.at

[Patreon]

 

WER's GAME DEVELOP CHANNEL님이 Game making class videos 창작 중 | Patreon

WER's GAME DEVELOP CHANNEL의 후원자가 되어보세요. 아티스트와 크리에이터를 위한 세계 최대의 멤버십 플랫폼에서 멤버십 전용 콘텐츠와 체험을 즐길 수 있습니다.

www.patreon.com

[디스코드 채널]

 

Join the 베르의 게임 개발 채널 Discord Server!

Check out the 베르의 게임 개발 채널 community on Discord - hang out with 399 other members and enjoy free voice and text chat.

discord.com

 

반응형

DontDestroyOnLoad 

파괴하지 않을 게임 오브젝트 만들기

 

작성 기준 버전 :: 2019.1.4f1

 

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

 

유니티에서는 씬(Scene, 장면) 단위로 게임이 플레이될 공간이나 장소 등을 구현하며, 한 씬에서 다른 씬으로 넘어갈 때는, 기존 씬이 언로드되면서 기존 씬에 있던 게임 오브젝트(Game Object)는 모두 파괴된다. 아래의 예시를 보자.

 

 

위 이미지는 Destroy Object라는 이름의 게임 오브젝트 다섯 개를 배치한 Test Scene의 캡처 화면이다. 플레이가 시작되면 아무것도 없는 Other Scene으로 넘어가게 설계되어 있다.

 

 

설계대로 플레이 버튼을 누르면 Test Scene에서 Other Scene으로 이동하며 씬에 배치되어 있는 다섯 개의 Destroy Object가 사라지는 것을 볼 수 있다.

 

 

 

DontDestoryOnLoad 사용법

 

위의 예시에서 볼 수 있듯이 유니티에서는 새로운 씬을 불러오면 이전 씬에 남아있던 게임 오브젝트들은 모두 사라진다. 하지만 개발자의 의도나 설계에 따라서 몇몇 게임 오브젝트들은 다른 씬으로 넘어갈 때 파괴되지 않도록 할 필요성이 생길 수도 있다.

 

public class DontDestoryObject : MonoBehaviour
{
    private void Awake()
    {
        DontDestroyOnLoad(gameObject);
    }
}

 

씬 로드시 파괴하지 않을 오브젝트로 만들려면 바로 위의 코드처럼 DontDestroyOnLoad()함수를 호출해서 매개변수로 자신의 게임 오브젝트를 전달하면 된다.

 

   

이렇게 파괴하지 않을 오브젝트로 만들어진 게임 오브젝트는 게임 플레이가 시작되고 함수가 호출되면 위의 이미지처럼 Test Scene에서 DontDestroyOnLoad 영역으로 옮겨진다.

 

 

만들어진 스크립트를 Dont Destroy Game Object라는 이름의 구체 게임 오브젝트에 붙인 다음 플레이 버튼을 눌러보면 Test Scene에서 Other Scene으로 넘어가면서 Destroy Object는 전부 사라지지만 Dont Destory Game Object는 남아있는 것을 확인할 수 있다.

 

 

Don't Destroy On Load 게임 오브젝트 파괴하기

 

Don't Destory On Load로 설정된 게임 오브젝트는 그럼 파괴할 수 없는 것인가? 라고 생각할 수도 있다. 하지만 제일 앞의 단어가 Can't가 아니라 Don't 임을 명심하자. 파괴할 수 없는 것이 아니라 파괴하지 않는 것이다.

 

Destroy(gameObject);

 

Don't Destory On Load로 설정된 게임 오브젝트는 Destroy() 함수를 이용하면 손쉽게 다시 파괴할 수 있다.

 

 

설계시 주의 사항

 

Don't Destory On Load 게임 오브젝트를 사용한 설계를 할 때는 주의할 점이 있다.

 

 

위의 이미지와 같이 Don't Destory On Load가 적용된 게임 오브젝트가 들어있는 씬과 다른 씬을 여러 번 왔다 갔다 하는 방식의 구조를 생각해보자. 

 

Test Scene에 진입하면 파괴하지 않는 게임 오브젝트가 Don't Destroy On Load 영역으로 이동되면서 씬을 이동해도 파괴되지 않게 될 것이다. 그 다음 Other Scene으로 이동했다가 다시 Test Scene으로 돌아오면 어떻게 될까? 파괴하지 않는 게임 오브젝트는 Don't Destroy On Load 영역으로 이동했으니 더 이상 Test Scene에 남아있지 않을까?

 

아니다. 씬을 새로 불러올 때는 해당 씬의 초기 상태로 불러오기 때문에 파괴하지 않는 게임 오브젝트는 새 것이 생성되어 있으며 이것은 그대로 다시 Don't Destroy On Load 영역으로 옮겨진다. 즉, 똑같은 파괴하지 않는 게임 오브젝트가 2개가 되어버리는 것이다.

 

 

그대로 두 씬을 왕복하면 같은 게임 오브젝트가 계속해서 누적되는 문제가 발생한다. 이 문제는 그대로 불필요한 메모리 사용을 증가시킬 것이고, 뿐만 아니라 예측할 수 없는 작동 문제 역시 일으킬 수 있다. 

 

 

해결 방법 1 : 중복 검사 후 파괴하기

 

public class DontDestoryObject : MonoBehaviour
{
    private void Awake()
    {
        var obj = FindObjectsOfType<DontDestoryObject>();
        if (obj.Length == 1)
        {
            DontDestroyOnLoad(gameObject);
        }
        else
        {
            Destroy(gameObject);
        }
    }
}

 

해당 게임 오브젝트를 파괴하지 않는 게임 오브젝트로 설정하는 이유가 게임 내에서 단 하나만 존재하는 오브젝트를 만들고자 하는 경우라면 가장 간단하고 손쉬운 해결 방법으로는 게임 오브젝트가 생성된 직후에 실행되는 Awake() 함수에서 현재 씬에 있는 같은 오브젝트가 몇 개인지 검사를 한 뒤 2개 이상이라면 생성된 오브젝트를 파괴하는 방법이다.

 

 

 

해결 방법 2 : 초기화 씬 구현하기

 

 

두 번째 방법으로는 파괴하지 않는 게임 오브젝트를 생성하는 전용 씬을 만들고 그 씬에서 파괴하지 않는 게임 오브젝트를 생성한 뒤 다른 씬만 오가는 방식이다. 이 방법 게임 내에서 단 하나만 존재하는 오브젝트를 만들고자 하는 경우에 유용한 방법이다.

 

 

해결 방법 3 : 라이프 사이클 관리하기

 

파괴하지 않는 게임 오브젝트는 위의 예시에서 볼 수 있듯이 씬을 이동한다고 해서 파괴되지 않는다. 그렇기 때문에 제대로 관리하지 않는다면 생성되는 족족 Don't Destroy On Load 영역에 게임 오브젝트가 쌓일 것이다. 

 

위의 두 가지 방법은 게임 내에서 해당 오브젝트를 단 하나만 만들고자 하는 경우에 유용한 방법이다. 만약 해당 오브젝트가 파괴되지 않아야 하지만 여러 개의 오브젝트를 만드는 것을 허용하는 구조라면 위의 방법들은 적절하지 않다.

 

또한 앞선 예시들은 모두 파괴하지 않을 게임 오브젝트를 정적으로 씬에 배치를 해서 씬이 불러와질 때마다 생성되게 만드는 구조를 채용했다. 하지만 이와 반대로 같은 종류의 파괴되지 않을 게임 오브젝트를 여러 개 생성하는 것을 허용하려고 한다면 정적인 씬 배치 방식보다는 프리팹으로 관리하며 원하는 시점에 생성하는 것이 좋다.

 

 

이러한 경우에 더욱 주의해야할 점은 파괴하지 않는 게임 오브젝트들이 동적으로 여러 개 생성되어 작동하고 있으며, 이후에도 계속해서 생성된다는 점이다. 때문에 파괴하지 않은 게임 오브젝트들에 대해서 제대로 추적/관리해야 하며 해당 오브젝트의 사용이 끝나면 파괴하는 식으로 라이프 사이클(Life Cycle) 관리가 필요하다.

 

[유니티 어필리에이트 프로그램]

아래의 링크를 통해 에셋을 구매하시거나 유니티를 구독하시면 수익의 일부가 베르에게 수수료로 지급되어 채널의 운영에 도움이 됩니다.

 

에셋스토어

여러분의 작업에 필요한 베스트 에셋을 찾아보세요. 유니티 에셋스토어가 2D, 3D 모델, SDK, 템플릿, 툴 등 여러분의 콘텐츠 제작에 날개를 달아줄 다양한 에셋을 제공합니다.

assetstore.unity.com

 

Easy 2D, 3D, VR, & AR software for cross-platform development of games and mobile apps. - Unity Store

Have a 2D, 3D, VR, or AR project that needs cross-platform functionality? We can help. Take a look at the easy-to-use Unity Plus real-time dev platform!

store.unity.com

 

Create 2D & 3D Experiences With Unity's Game Engine | Unity Pro - Unity Store

Unity Pro software is a real-time 3D platform for teams who want to design cross-platform, 2D, 3D, VR, AR & mobile experiences with a full suite of advanced tools.

store.unity.com

[투네이션]

 

-

 

toon.at

[Patreon]

 

WER's GAME DEVELOP CHANNEL님이 Game making class videos 창작 중 | Patreon

WER's GAME DEVELOP CHANNEL의 후원자가 되어보세요. 아티스트와 크리에이터를 위한 세계 최대의 멤버십 플랫폼에서 멤버십 전용 콘텐츠와 체험을 즐길 수 있습니다.

www.patreon.com

[디스코드 채널]

 

Join the 베르의 게임 개발 채널 Discord Server!

Check out the 베르의 게임 개발 채널 community on Discord - hang out with 399 other members and enjoy free voice and text chat.

discord.com

 

반응형

+ Recent posts