개발단에 가입하여 베르의 게임 개발 유튜브를 후원해주세요! 

 

베르의 게임 개발 유튜브

안녕하세요! 여러분들과 함께 게임 개발을 공부하는 베르입니다! 게임 개발에 도움이 되는 강좌들을 올리는 채널입니다! [투네이션 후원] https://toon.at/donate/637735212761460238 [유니티 어필리에이트

www.youtube.com

 

안녕하세요! 여러분들과 함께 게임 개발을 공부하는 베르입니다!

이번에는 유니티의 기본 개념인 게임 오브젝트와 컴포넌트에 대해서 알아봅시다!

 

사용 엔진 버전 : 2021.3

 

타임라인

0:00 인트로

0:10 게임 오브젝트

0:51 컴포넌트

3:29 아웃트로

스크립트

인트로

안녕하세요. 여러분들과 함께 게임 개발을 공부하는 베르입니다.

이번 영상에서는 유니티의 게임 오브젝트와 컴포넌트에 대해서 알아보도록 하겠습니다.

게임 오브젝트

게임 오브젝트는 유니티 엔진에서 가장 중요한 개념이며 씬에 배치되는 가장 기본 단위의 오브젝트입니다.

한마디로, 캐릭터나 바닥에 떨어진 아이템, 배경으로 배치된 건물이나 소품, 폭발하면서 발생하는 이펙트, 빛을 밝히는 광원, 모든 장면을 찍는 카메라까지 씬 안에 배치되는 모든 것은 게임 오브젝트입니다.

그리고 이 게임 오브젝트에 어떤 컴포넌트가 추가되느냐에 따라서 게임 속에서 그 게임 오브젝트의 역할이 결정되는 구조입니다.

한마디로 게임 오브젝트는 기능을 구현하는 컴포넌트들을 담는 하나의 컨테이너 역할을 하는 것입니다.

게임 오브젝트는 기본적으로 이름, 태그, 레이어를 가지고 있는데 이것을 이용해서 나중에 원하는 오브젝트를 찾는 등의 처리를 할 수 있습니다.

컴포넌트

컴포넌트는 게임 오브젝트에 붙일 수 있는 다양한 기능을 가진 구성요소입니다.

앞서 이야기했듯이 비어있는 게임 오브젝트에 어떤 컴포넌트를 붙이느냐에 따라서 그 게임 오브젝트의 역할이 달라집니다.

간단한 예시를 들기 위해 빈 게임 오브젝트를 하나 만들어 보겠습니다.

하이어라키 뷰에 우클릭 한 뒤 Create Empty 메뉴를 선택합니다.

이것은 비어있는 게임 오브젝트를 만든다는 의미입니다.

새로 생성된 게임 오브젝트를 보면 트랜스폼 컴포넌트가 붙어있는 것을 볼 수 있습니다.

비어있는 게임 오브젝트를 만들겠다고 했는데 왜 트랜스폼 컴포넌트가 붙어있는지 의문이 들 수도 있습니다.

유니티 엔진에서 트랜스폼 컴포넌트는 모든 게임 오브젝트에 기본 컴포넌트로 반드시 부착되어 있도록 되어 있으며 게임 오브젝트로부터 제거할 수 없게 되어 있습니다.

트랜스폼 컴포넌트는 이 게임 오브젝트가 씬의 어느 위치에 어떻게 회전되어서 어떤 크기로 배치되어 있는지를 결정합니다.

그럼 게임 오브젝트에 컴포넌트를 추가하는 방법을 배우기 위해서 방금 만든 빈 게임 오브젝트에 카메라 컴포넌트를 부착해서 새로운 카메라로 만들어보겠습니다.

컴포넌트를 추가할 게임 오브젝트를 선택하고 인스펙터창에서 Add Component 버튼을 누르면 게임 오브젝트에 추가할 수 있는 컴포넌트들의 목록이 보입니다.

이 중에서 게임 오브젝트에 추가할 컴포넌트를 찾아서 선택하면 됩니다.

하지만 이 상태로는 카메라 컴포넌트를 빠르게 찾기 어렵기 때문에, 검색창에 camera를 검색하여 선택합니다.

그러면 비어있던 게임 오브젝트 자리에 카메라 모양이 생기며 게임 오브젝트가 카메라가 된 것을 확인할 수 있습니다.

이렇게 유니티에서 제공하는 기본적인 컴포넌트 이외에도 개발자가 직접 컴포넌트를 만들어서 게임 오브젝트에 붙일 수도 있습니다.

대부분 실제 게임 기능을 하는 시스템들은 이런 커스텀 컴포넌트로 만들어지고 게임 오브젝트에 추가됩니다.

커스텀 컴포넌트를 만드는 방법은 프로젝트 뷰에서 우클릭한 뒤 Create > C# Script 항목을 선택하는 것입니다.

그러면 New BehaviourScript라는 이름으로 C# 스크립트 파일이 생성됩니다.

이렇게 만든 컴포넌트는 보통 용도에 맞게 이름을 지어줍니다.

커스텀 기능이 만들어지는 예시를 위해서 만든 스크립트를 더블 클릭해서 스크립트 에디터를 엽니다.

그리고 업데이트 함수에서 스페이스 바를 누르면 간단한 로그를 출력하도록 코드를 작성하겠습니다.

코드를 저장하고 에디터로 돌아갑니다.

이렇게 생성한 컴포넌트를 다른 컴포넌트와 마찬가지로 게임 오브젝트를 선택하고 Add Component 버튼을 누른 뒤 추가할 컴포넌트의 이름을 검색해서 게임 오브젝트에 붙일 수 있습니다.

그리고 게임을 플레이하고 스페이스 바를 누르면 작성한 코드대로 로그가 출력됩니다.

이런 간단한 예시를 확장해서 앞으로 여러분들이 게임에 필요한 기능들을 만들어 나가는 방식으로 개발을 하게 되는 겁니다.

아웃트로

이번 영상에서는 유니티의 게임 오브젝트와 컴포넌트에 대해서 알아보았습니다.

이 강좌는 시청자 여러분들의 시청과 후원으로 제작되었습니다.

이상 베르의 게임 개발 유튜브였습니다. 감사합니다.

 

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

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

 

에셋스토어

여러분의 작업에 필요한 베스트 에셋을 찾아보세요. 유니티 에셋스토어가 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

 

반응형

개발단에 가입하여 베르의 게임 개발 유튜브를 후원해주세요!

 

베르의 게임 개발 유튜브

안녕하세요! 여러분들과 함께 게임 개발을 공부하는 베르입니다! 게임 개발에 도움이 되는 강좌들을 올리는 채널입니다! [투네이션 후원] https://toon.at/donate/637735212761460238 [유니티 어필리에이트

www.youtube.com

 

안녕하세요! 여러분들과 함께 게임 개발을 공부하는 베르입니다!

이번에는 유니티 2020에서 추가된 Properties 기능에 대해서 알아봅시다.

 

사용 엔진 버전 : 2020.3

 

타임라인

0:00 인트로

0:08 인스펙터에서 여러 개의 오브젝트를 컨테이너에 담기

0:26 1. 일일이 집어넣기

0:43 2. 인스펙터 잠그기

1:13 3. Properties 이용하기

1:45 아웃트로

스크립트

인트로

안녕하세요. 여러분들과 함께 게임 개발을 공부하는 베르입니다.

이번에는 유니티 에디터의 Properties 기능에 대해서 알아보겠습니다.

인스펙터에서 여러 개의 오브젝트를 컨테이너에 담기

유니티에서는 에디터의 인스펙터에서 오브젝트를 담을 컨테이너를 이렇게 스크립트에서 리스트나 배열로 만들 수 있습니다.

스크립트에서 만든 리스트나 배열은 에디터에서 이렇게 보여지죠.

그리고 이 컨테이너에 씬 안에 있는 오브젝트를 끌어넣을 수 있습니다.

1. 일일이 집어넣기

그런데 한꺼번에 여러 개의 오브젝트를 넣기 위해서 여러 오브젝트를 선택하면 컴포넌트가 붙어있는 오브젝트의 선택이 풀리면서 인스펙터에서 사라집니다.

그래서 인스펙터를 잠그는 방법을 몰랐을 때는 오브젝트를 하나하나 선택해서 일일이 집어넣을 수 밖에 없었죠.

2. 인스펙터 잠그기

하지만 인스펙터 뷰 우측 상단에 있는 자물쇠 모양 버튼을 클릭하면 인스펙터가 잠기면서 지금 선택한 오브젝트로 인스펙터가 고정됩니다.

그러면 다른 오브젝트를 선택해도 인스펙터가 바뀌지 않고 여러 오브젝트를 한꺼번에 컨테이너에 넣을 수 있게 되죠.

하지만 인스펙터 잠금을 푸는 것을 깜빡하면 다른 오브젝트를 변경하기 위해 선택해도 인스펙터가 변경되지 않아서 헤매게 되는 실수가 발생합니다.

그래서 작업이 끝나면 인스펙터 잠금을 일일이 풀어줘야 한다는 단점도 있습니다.

3. Properties

이 모든 단점을 해결하기 위한 기능이 바로 Properties입니다.

이 기능은 2020 버전에 추가된 기능으로 게임 오브젝트에 우클릭하고 제일 아래의 [Properties...] 항목을 선택하면 Properties 창이 열리며 인스펙터에서 보이는 것과 동일한 내용을 볼 수 있게 됩니다.

여기서는 다른 오브젝트를 선택해도 Properties 창의 내용이 바뀌지 않습니다.

그래서 여러 오브젝트를 선택해서 컨테이너로 한 번에 넣어줄 수 있습니다.

그리고 작업이 끝나면 어떤 잠금을 푼다던가 하는 일에 신경쓰지 않고 그냥 Properties 창을 꺼버리면 됩니다.

아웃트로

이번 영상에서는 유니티 에디터의 Properties 기능에 대해서 알아보았습니다.

이 강좌는 시청자 여러분들의 시청과 후원으로 제작되었습니다.

이상 베르의 게임 개발 유튜브였습니다. 감사합니다.

 

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

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

 

에셋스토어

여러분의 작업에 필요한 베스트 에셋을 찾아보세요. 유니티 에셋스토어가 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 

스크립트로 게임 오브젝트 생성하고 파괴하기

 

작성 기준 버전 :: 2019.2

 

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

 

 

씬에 새로운 게임 오브젝트를 생성하는 제일 기본적인 방법은 하이어라키 뷰에 우클릭해서 생성하고자 하는 게임 오브젝트를 선택하는 것이었다. 하지만 이렇게 에디터의 하이어라키 뷰에서 생성한 게임 오브젝트는 다른 컴포넌트나 스크립트의 개입 없이는 파괴되지 않기 때문에, 정적인 오브젝트이다.

 

반대로 스크립트의 개입으로 게임 플레이 도중에 생성되고 파괴되는 것을 동적으로 오브젝트를 생성하거나 파괴한다고 한다.

 

그럼 이제부터 이렇게 스크립트를 이용해서 게임 플레이 도중에 게임 오브젝트를 생성하고 파괴하는 방법을 알아보도록 하자.

 

게임 오브젝트 생성하기

 

public class GameObjectCreator : MonoBehaviour

{

    void Start()

    {

        new GameObject();

    }

}

 

게임 오브젝트를 생성하기 위해서는 위의 예시 코드와 같이 new GameObject()라는 코드를 작성하면 된다.

 

이 코드는 C#에서는 GameObject라는 클래스의 객체를 메모리에 생성하는 코드이지만, 유니티 엔진에서는 GameObject 클래스의 객체를 메모리에 생성하면서 씬에 실제 게임 오브젝트 역시 생성하는 기능을 한다.

 

 

이대로 코드를 저장하고 에디터로 돌아가서 하이어라키 뷰에서 게임 오브젝트를 하나 생성하고, 방금 만든 GameObjectCreator 컴포넌트를 부착해준 뒤, 플레이 버튼을 보면 "New Game Object"라는 이름으로 게임이 시작되는 시점에는 씬에 없던 새로운 게임 오브젝트가 동적으로 생성된 것을 볼 수 있다.

 

하지만 이렇게 생성된 게임 오브젝트는 말 그대로 '빈' 게임 오브젝트이다. 게임에서 아무것도 없는 빈 게임 오브젝트가 할 수 있는 역할은 아마 없을 것이다. 빈 게임 오브젝트에 새로운 역할을 부여해주려면 새로운 컴포넌트를 부착해야 한다.

 

그러니 앞 단계에 이어서 게임 오브젝트에 스크립트로 컴포넌트를 붙이는 방법을 알아보자.

 

프로젝트 뷰에 우클릭해서 TestComponent라는 이름으로 C# 스크립트를 생성한다. 그리고 GameObjectCreator 스크립트 파일에서 아래와 같이:

 

public class GameObjectCreator : MonoBehaviour

{

    void Start()

    {

        var newObj = new GameObject().AddComponent<TestComponent>();        newObj.name = "Test Component Game Object";

    }

}

 

.AddComponent 함수를 사용하면 비어있는 게임 오브젝트에 스크립트로 방금 만든 TestComponent 컴포넌트를 부착할 수 있다. 

 

그리고 .name 프로퍼티를 이용하면 게임 오브젝트의 이름을 바꿀 수 있다. 이렇게 생성한 게임 오브젝트의 이름을 적절하게 바꿔주면 나중에 에디터에서 작업할 때 찾고자 하는 게임 오브젝트를 빠르게 찾을 수 있게 된다.

 

 

코드를 저장하고 에디터로 돌아가서 실행해보면 게임이 플레이되면 아까처럼 새로운 게임 오브젝트가 생성되는데 새 게임 오브젝트의 이름이 "New Game Object"가 아닌 "Test Component Game Object"인 것을 볼 수 있다. 그리고 이 "Test Component Game Object"를 하이어라키 뷰에서 클릭해보면 이렇게 TestComponent가 부착되어 있는 것을 볼 수 있다.

 

게임 오브젝트 파괴하기

 

게임 오브젝트를 생성하기만 하고 생성된 게임 오브젝트를 적절하게 제거하지 않으면 게임이 사용하는 메모리가 점점 늘어나면서 게임이 점점 느려지고 마지막엔 결국 게임이 멈춰버릴 수도 있다.

 

그럼 이제 생성된 게임 오브젝트를 스크립트로 파괴하는 방법을 알아보자.

 

이번에는 TestComponent 스크립트 파일을 연다.

 

public class TestComponent : MonoBehaviour

{

    void Start()

    {

        Destroy(gameObject);

    }

 

    private void OnDestroy()    {        Debug.Log(name + "가 파괴됨!");

 

    }

}

 

Start 함수에서 Destory 함수를 호출하고 매개변수로 파괴하고자 하는 게임 오브젝트를 넣어주면 된다. 우리는 TestComponent가 부착된 게임 오브젝트가 파괴되기를 원하기 때문에 gameObject 프로퍼티를 넣어준다.

 

그리고 OnDestroy 함수를 만들고 게임 오브젝트가 파괴될 때, 파괴되는 게임 오브젝트의 이름과 함께 파괴되었다는 로그를 남겨주는 코드를 작성한다.

 

 

코드를 저장하고 에디터로 돌아가서 플레이해보면 게임이 시작되자마자 Test Component Game Object가 파괴되었다는 로그만 남기고 사라져버린다.

 

Destroy(gameObject, 5f);

 

게임 오브젝트가 생성되자마자 바로 파괴되는 것이 아니라 일정한 시간이 지난 뒤에 파괴되는 것을 원한다면, Destroy 함수의 두 번째 매개변수에 실수형 매개변수를 넣어주면 된다.

 

유니티 스크립트 작업의 기반이 되는 C# 프로그래밍에는 함수 오버로딩이라고 하는 기능을 제공하는데 이것은 똑같은 이름의 함수라도 입력하는 매개변수에 따라서 비슷하지만 다른 동작을 하도록 구현할 수 있도록 만들어준다.

 

gameObject만 매개변수로 넣었을 때의 Destory 함수는 입력받은 게임 오브젝트를 바로 파괴하지만, 두 번째 매개변수로 실수형 숫자를 같이 넣어주면, 넣어준 숫자만큼의 시간이 지난 후에 입력받은 gameObject를 파괴하는 식으로 조금 다르게 동작한다.

 

참고로 두 번째 매개변수의 시간은 초 단위로 동작한다. 그러니까 이 TestComponent가 부착된 게임 오브젝트는 생성되고나서 5초가 지나면 파괴된다.

 

코드를 저장하고 에디터로 돌아가서 플레이 버튼을 눌러보면 게임 오브젝트가 생성되고 약 5초가 지난 뒤 파괴되는 것을 확인할 수 있다.

 

이런 식으로 스크립트로 게임 오브젝트를 생성하고 원하는 시점에 파괴되게 만들 수 있다.

 

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

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

 

에셋스토어

여러분의 작업에 필요한 베스트 에셋을 찾아보세요. 유니티 에셋스토어가 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

 

반응형

Transform 

게임 오브젝트의 공간 정보

 

작성 기준 버전 :: 2019.2

 

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

 

이번 섹션에서는 게임 오브젝트의 공간 정보를 관리하는 트랜스폼 컴포넌트에 대해서 알아보자.

 

본 내용에 앞서 벡터좌표계, 게임 오브젝트와 컴포넌트에 관한 지식이 필요하다면 링크된 글들을 읽어보면 도움이 된다.

 

트랜스폼 컴포넌트(Transform Component)

 

 

[그림 1]

 

우선 트랜스폼 컴포넌트는 게임 오브젝트에 필수로 부착되는 컴포넌트로, 인스펙터 뷰에서 보면 [그림 1]과 같이 Vector3 형식의 포지션(Position), 로테이션(Rotation), 스케일(Scale) 프로퍼티를 사용자에게 공개하고 있다.

 

 

프로퍼티의 이름에 맞게 포지션 프로퍼티는 게임 오브젝트의 위치 정보를 수정할 수 있다.

 

 

로테이션 프로퍼티는 회전 정보를 가지고 이를 수정할 수 있다.

 

 

스케일 프로퍼티는 크기 정보에 관여한다.

 

이렇게 인스펙터 뷰에서 보이는 트랜스폼 컴포넌트로 씬 안에 있는 게임 오브젝트의 위치를 옮기거나, 회전시키고, 그 크기를 바꿀 수 있다. 하지만 인스펙터 뷰에서 트랜스폼 컴포넌트의 내용을 변경하는 것은 게임 중에는 불가능한 일로 고정된 건물이나 물건같은 오브젝트에나 사용할 수 있는 방법이다.

 

플레이어, 몬스터와 같은 캐릭터, 총알, 화살 같은 투사체, 말, 자동차 같은 탈 것처럼 게임 안에서 플레이어의 조작이나 AI의 조작을 따라서 움직일 게임 오브젝트들은 스크립트를 이용해서 이동시켜야 한다.

 

 

스크립트로 트랜스폼 컴포넌트 다루기

 

트랜스폼 컴포넌트 접근하기

 

public class TransformController : MonoBehaviour

{

    void Start()

    {

        Transform myTransformComponent = transform;

    }

}

 

커스텀 컴포넌트가 부착된 게임 오브젝트의 트랜스폼 컴포넌트를 가져오기 위해서는 모노비헤이비어(MonoBehaviour) 클래스를 통해서 상속받은 transform 프로퍼티를 호출하면 된다.

 

 

transform 프로퍼티를 어디서 상속받는지 궁금할 수도 있다. 그럴 때는 트랜스폼 컨트롤러 클래스가 상속받는 모노비헤이비어 클래스를 클릭하고 F12키를 눌러서 모노비헤이비어 클래스 파일로 이동한 다음, 같은 과정을 컴포넌트(Component) 클래스가 나올 때까지 반복하면 된다. 그러면 컴포넌트 클래스에 정의된 transform 프로퍼티를 확인할 수 있다.

 

위치 이동시키기

 

position으로 직접 이동시키기

 

그럼 제일 먼저 트랜스폼 컴포넌트를 이용해서 게임 오브젝트를 이동시켜보자.

 

public void MovePosition(Vector3 newPosition)

{

    transform.position = newPosition;

}

 

게임 오브젝트의 위치 정보를 다루는 포지션 프로퍼티에 접근하기 위해서는 위의 예시 코드와 같이 transform.position을 이용하면 된다.

 

float timer = 0f;

void Update()

{

    timer += Time.deltaTime;

    MovePosition(new Vector3(0f, Mathf.Cos(timer), 0f));

}

 

방금 만든 Update() 함수에서 MovePosition() 함수를 호출한다. 게임 오브젝트의 위치를 코사인 그래프에 따라서 위 아래로 움직이도록 만들어진 코드이다.

 

 

이 코드를 게임 오브젝트에 부착하고 에디터에서 플레이 시켜보면 코사인 그래프의 높이에 따라 게임 오브젝트가 위 아래로 천천히 움직이는 것을 볼 수 있다.

 

Translate() 함수로 이동시키기

 

위에서 position으로 이동시키기는 말그대로 트랜스폼 컴포넌트의 position 프로퍼티에 직접 위치를 넣어서 이동시키는 방법이다.

 

float timer = 0f;

void Update()

{

    timer += Time.deltaTime;

    MovePositionUseTranslate(new Vector3(0f, Mathf.Cos(timer), 0f));

}

 

public void MovePositionUseTranslate(Vector3 moveDirection)

{

    transform.Translate(moveDirection);

}

 

Translate() 함수는 position 프로퍼티에 직접 위치를 집어넣어서 이동시키는 것과는 달리 게임 오브젝트가 이동하고자 하는 방향과 속력인 벡터를 매개변수로 받아 그 벡터의 방향과 길이만큼 게임 오브젝트를 이동시키는 함수이다.

 

 

위 코드를 저장하고 플레이해보면 position을 이용한 오브젝트 이동에서는 1 ~ -1 사이에서만 움직이던 것과는 달리 Translate() 함수를 이용한 이동에서는 훨씬 큰 폭으로 움직이는 것을 볼 수 있다. 이것은 이동 방향 벡터가 코사인 그래프를 따라서 바뀌는 동안에 0보다 값이 커지면 위로, 0보다 작아지면 아래로 움직이기 때문이다.

 

position 이동과 Translate() 이동의 비교

 

public class TranslateMover : TransformController

{

    void Update()

    {

        MovePositionUseTranslate(new Vector3(0f, 0.1f, 0f));

    }

}

 

public class PositionMover :

 TransformController

{

    void Update()

    {

              MovePosition(new Vector3(0f, 0.1f, 0f));

    }

}

 

두 이동 방식을 비교하기 위해서 TransformController를 상속받는 두 클래스를 만들어보았다. PositionMover 클래스는 매 프레임 MovePosition() 함수를 호출해서 (0, 0.1, 0) 벡터를 넣어주고, TranslateMover 클래스는 매 프레임 MovePositionUseTranslate() 함수를 호출해서 역시 같은 벡터를 넣어주고 있다.

 

 

에디터로 돌아가서 게임 오브젝트 두 개를 만들고 이 두 컴포넌트를 각각 붙여주고 플레이하면 TranslateMover 컴포넌트를 붙인 게임 오브젝트만 저 멀리 올라가버리는 것을 볼 수 있다. 하지만 PositionMover 컴포넌트를 붙인 게임 오브젝트는 시작되는 순간에 (0, 0.1, 0) 좌표로만 이동한 다음에 그대로 움직이지 않는 것을 보면, 두 방법의 차이를 이해할 수 있다.

 

 

 

 

 

회전시키기

 

rotation으로 회전시키기

 

void Start()

{

    transform.rotation = new Quaternion();

}

 

게임 오브젝트를 회전시키기 위해서는 transform.rotation 프로퍼티를 사용하면 된다. 다만, 인스펙터 뷰에서 공개된 Rotation 프로퍼티가 Vector3 형식인 것과 달리 스크립트에서는 쿼터니언(Quaternion) 구조체를 사용한다.

 

Quaternion rotation = new Quaternion();

 

rotation.w

rotation.x

rotation.y

rotation.z

 

쿼터니언 구조체는 벡터와는 다른 사원수라는 체계를 사용해서 오브젝트의 회전을 표현한다. 이 사원수라는 체계는 상당히 난해한 체계이기 때문에 유니티의 공식 문서에서는 사원수에 대한 지식을 충분히 가지고 있지 않다면 쿼터니언을 직접 수정하지 않도록 권장하고 있다.

 

public void RotateRotation(Vector3 newRotation)

{

    transform.rotation = Quaternion.Euler(newRotation);

}

 

public void RotateRotation(Vector3 newRotation)

{

    transform.Rotate(newRotation);

}

 

그럼 사원수를 제대로 알지 못하면 게임 오브젝트를 회전시키지 못하게 되는가? 그렇지는 않다. 인스펙터 뷰에서처럼 3차원 벡터를 이용해서 회전을 다루는 방법을 오일러 각 체계(Euler angle system)라고 부른다. 오일러 각 체계 이용하면 xyz 각 축을 기준으로 오브젝트가 얼마나 회전한 상태인지 직관적으로 알 수 있다. 그래서 쿼터니언 구조체에는 이 오일러 각 체계의 회전을 사원수 체계의 회전으로 전환해주는 Euler() 함수가 포함되어 있다. 이 함수를 이용하면 Vector3로 표현된 각을 Quaternion으로 변환할 수 있다.

 

그리고 회전 역시 이동과 마찬가지로 rotation 프로퍼티를 직접 수정하는 방법과 Rotate() 함수를 사용하는 방법 두 가지가 있다. 그리고 그 차이점 역시 이동시키기에서의 position 직접 이동과 Translate() 함수를 이용한 이동과 비슷하다.

 

float timer = 0f;

void Update()

{

    timer += Time.deltaTime;

    RotateRotation(new Vector3(0f, ((Mathf.Cos(timer) + 1f) * 0.5f) * 360f, 0f));

}

 

 

RotateRotation() 함수를 업데이트에서 호출하도록 코드를 작성하고 플레이시켜보면 게임 오브젝트가 회전하는 것을 볼 수 있다.

 

transform.forward로 바라보는 방향 정하기

 

float timer = 0f;

void Update()

{

    timer += Time.deltaTime;

    ForwardControl(new Vector3(Mathf.Cos(timer), 0f, Mathf.Sin(timer)));

}

 

public void ForwardControl(Vector3 newForward)

{

    transform.forward = newForward;

}

 

게임 오브젝트를 회전시키는 다른 방법으로는 transform.forward 프로퍼티를 이용하면 게임 오브젝트의 forward, 즉 정면을 설정해서 특정한 방향을 바라보게 할 수 있다. 프로퍼티로 가져올 수 있는 방향으로는 forward, up, right가 있다. 

 

LookAt() 함수로 원하는 위치를 바라보게 하기

 

float timer = 0f;

void Update()

{

    timer += Time.deltaTime;

    ForwardControl(new Vector3(Mathf.Cos(timer), 0f, Mathf.Sin(timer)));

}

 

public void LookObject(Vector3 pos)

{

    transform.LookAt(pos);

}

 

transform 컴포넌트에 있는 LookAt() 함수를 사용하면 원하는 지점을 바라보게 할 수 있다. LookAt() 함수의 매개변수로 Vector3 뿐만 아니라 다른 게임 오브젝트의 트랜스폼 컴포넌트를 넣어서 다른 게임 오브젝트를 따라가며 바라보게 할 수도 있다.

 

 

크기 조절하기

 

float timer = 0f;

void Update()

{

    timer += Time.deltaTime;

    float scale = Mathf.Cos(timer) + 2f;

    Scaling(new Vector3(scale, scale, scale));

}

 

public void Scaling(Vector3 scale)

{

    transform.localScale = scale;

}

 

게임 오브젝트의 크기 조절은 transform.localScale 프로퍼티를 통해서 할 수 있다.

 

 

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

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

 

에셋스토어

여러분의 작업에 필요한 베스트 에셋을 찾아보세요. 유니티 에셋스토어가 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

 

반응형
  1. 감사합니다 2020.02.25 22:27

    글 잘보고 있습니다. 감사합니다!!

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 어트리뷰트를 붙여주면 된다.

 

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

 

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

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

 

에셋스토어

여러분의 작업에 필요한 베스트 에셋을 찾아보세요. 유니티 에셋스토어가 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

 

반응형
  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 신고

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

Programming 

오브젝트 풀링 기법

 

작성 기준 버전 :: 2019.2

 

프로그래밍에서 오브젝트를 생성하거나 파괴하는 작업은 꽤나 무거운 작업으로 분류된다. 오브젝트 생성은 메모리를 새로 할당하고 리소스를 로드하는 등의 초기화하는 과정으로, 오브젝트 파괴는 파괴 이후에 발생하는 가비지 컬렉팅으로 인한 프레임 드랍이 발생할 수 있다.

 

이러한 문제를 해결하기 위해서 사용되는 기법이 바로 오브젝트 풀링(Object pooling)이다.

 

오브젝트 풀링의 개념

 

오브젝트 풀링의 개념은 아주 간단하다. 

 

 

먼저 풀링을 할 오브젝트를 담을 오브젝트 풀을 구성한다.

 

 

그 다음 풀링할 오브젝트를 생성해서 오브젝트 풀에서 그 오브젝트들을 관리하게 만든다.

 

 

외부에서 해당 오브젝트가 필요하면 오브젝트 풀에서 꺼내간다.

 

 

오브젝트 풀에서 꺼낸 오브젝트의 사용이 끝나면 오브젝트를 풀에 돌려준다.

 

 

오브젝트 풀에서 오브젝트를 가져오려고 할 때, 모든 오브젝트가 이미 사용중이라면 새로운 오브젝트를 생성해서 꺼내준다.

 

이것이 바로 오브젝트 풀의 개념이다. 게임에 필요한 오브젝트를 미리 생성해서 필요할 때마다 꺼내쓰고 사용이 끝나면 오브젝트 풀에 돌려주는 것이다. 이렇게 함으로써, 오브젝트가 필요할 때마다 생성하고 다 쓰면 파괴하는 것이 아니라, 게임이 시작될 때, 필요한 만큼의 오브젝트만 생성하고, 모자라면 추가로 생성하고, 게임이 끝나면 파괴하는 방식으로 오브젝트의 생성/파괴 횟수를 줄일 수 있게 된다.

 

오브젝트 풀링 기법은 특히 메모리가 부족하고 CPU의 성능이 낮은 디바이스의 사양이 낮았던 예전에 주로 사용되었던 기법이지만, 현재에 와서도 게임의 최적화를 위해서 많이 사용되며, 모바일 게임이 많이 제작되면서 PC에 비해서 모자란 디바이스 성능으로 인해서 재조명 받는 기법이다.

 

보통 자주 생성되었다가 파괴되어야 하는 총알이나, 캐릭터가 뛸 때 발생하는 먼지 이펙트 같은 곳에 많이 사용되는 기법이다.

 

 

 

유니티 엔진에서의 구현

 

기본 세팅

 

기본적인 세팅은 다음과 같다.

 

 

씬 한 가운데 캐릭터의 역할을 할 초록색 박스를 배치했다.

 

public class Shooter : MonoBehaviour
{
    [SerializeField]
    private GameObject bulletPrefab;

    private Camera mainCam;

    void Start()
    {
        mainCam = Camera.main;
    }

    void Update()
    {
        if(Input.GetMouseButton(0))
        {
            RaycastHit hitResult;
            if(Physics.Raycast(mainCam.ScreenPointToRay(Input.mousePosition), out hitResult))
            {
                var bullet = Instantiate(bulletPrefab).GetComponent<Bullet>();
                var direction = new Vector3(hitResult.point.x, transform.position.y, hitResult.point.z) - transform.position;
                bullet.transform.position = direction.normalized;
                bullet.Shoot(direction.normalized);
            }
        }
    }
}

 

그리고 박스 게임 오브젝트에는 마우스 클릭 방향으로 총알 게임 오브젝트를 생성해서 발사하는 Shooter 컴포넌트가 부착되어 있다.

 

 

public class Bullet : MonoBehaviour
{
    private Vector3 direction;
    public void Shoot(Vector3 direction)
    {
        this.direction = direction;
        Destroy(gameObject, 5f);
    }

    void Update()
    {
        transform.Translate(direction);   
    }
}

 

그리고 지정된 방향으로 5초 간 날아가다 자동으로 소멸하는 총알 프리팹 역시 만들어두었다.

 

 

이것을 테스트 해보면 마우스를 클릭하면 엄청난 양의 총알 오브젝트가 새로 생성되고 시간이 지나면 모두 파괴되는 것을 볼 수 있다.

 

 

이 상황을 프로파일러를 통해서 확인해보면 총알을 생성하는 Instantiate와 파괴하는 Destroy가 어느 정도 성능을 잡아먹고 있는 것을 확인할 수 있다.

 

오브젝트 풀 구현

 

public class ObjectPool : MonoBehaviour
{
    public static ObjectPool Instance;

    [SerializeField]
    private GameObject poolingObjectPrefab;

    Queue<Bullet> poolingObjectQueue = new Queue<Bullet>();

    private void Awake()
    {
        Instance = this;

        Initialize(10);
    }

    private void Initialize(int initCount)
    {
        for(int i = 0; i < initCount; i++)
        {
            poolingObjectQueue.Enqueue(CreateNewObject());
        }
    }

    private Bullet CreateNewObject()
    {
        var newObj = Instantiate(poolingObjectPrefab).GetComponent<Bullet>();
        newObj.gameObject.SetActive(false);
        newObj.transform.SetParent(transform);
        return newObj;
    }

    public static Bullet GetObject()
    {
        if(Instance.poolingObjectQueue.Count > 0)
        {
            var obj = Instance.poolingObjectQueue.Dequeue();
            obj.transform.SetParent(null);
            obj.gameObject.SetActive(true);
            return obj;
        }
        else
        {
            var newObj = Instance.CreateNewObject();
            newObj.gameObject.SetActive(true);
            newObj.transform.SetParent(null);
            return newObj;
        }
    }

    public static void ReturnObject(Bullet obj)
    {
        obj.gameObject.SetActive(false);
        obj.transform.SetParent(Instance.transform);
        Instance.poolingObjectQueue.Enqueue(obj);
    }
}

 

오브젝트 풀링 기법을 구현하는 기초적인 코드의 전체는 위의 코드와 같다. 

 

public static ObjectPool Instance;

private void Awake()
{
    Instance = this;

    Initialize(10);
}

 

이 코드를 하나씩 살펴보자. 우선 오브젝트 풀의 경우 오브젝트를 생성하는 어떤 코드에서든 접근이 가능해야하는 경우가 많기 때문에 싱글톤 패턴으로 구현되는 경우가 많다. 

 

[SerializeField]
private GameObject poolingObjectPrefab;

 

그리고 생성해야할 오브젝트의 프리팹을 가지고 있다. 프리팹을 가지고 있어야 하는 이유는 처음 생성해둔 오브젝트를 모두 꺼내주고 나서, 꺼내준 오브젝트를 미처 돌려받기 전에, 오브젝트를 요청받았을 때, 새로운 오브젝트를 생성해서 꺼내주기 위한 것이다.

 

Queue<Bullet> poolingObjectQueue = new Queue<Bullet>();

 

큐(Queue)를 이용해서 생성된 총알 오브젝트를 순서대로 관리한다. 만약 오브젝트 풀에서 오브젝트를 꺼내주는 순서와 돌려받는 순서를 제대로 관리하지 못하면, 하나의 오브젝트를 여러 곳에 빌려주는 등의 문제가 발생할 수 있다.

 

private void Awake()
{
    Instance = this;

    Initialize(10);
}

 

게임 오브젝트가 생성되면 제일 먼저 실행되는 Awake() 함수에서는 싱글톤 패턴에서 사용되는 오브젝트 풀 인스턴스에 자신을 할당하는 일과 오브젝트 풀을 초기화하는 Initialize() 함수를 호출한다. 

 

private void Initialize(int initCount)
{
    for(int i = 0; i < initCount; i++)
    {
        poolingObjectQueue.Enqueue(CreateNewObject());
    }
}

 

Initialze() 함수에서는 매개변수로 받은 초기화 갯수 값에 따라서 CreateNewObject() 함수에서 만들어진 새로운 총알 오브젝트를 pooling Object Queue에 넣어준다. 이렇게 게임이 시작하기 전에 사용될 게임 오브젝트를 미리 적절한 갯수를 만들어 줌으로써 게임 플레이 도중에 발생할 과부하를 게임 준비 과정인 로딩으로 돌릴 수 있다. 모두 잘 알겠지만, 플레이어는 아주 조금 더 긴 로딩은 당장 용납하지만, 플레이 도중에 발생하는 렉은 용납하지 못한다.

 

private Bullet CreateNewObject()
{
    var newObj = Instantiate(poolingObjectPrefab).GetComponent<Bullet>();
    newObj.gameObject.SetActive(false);
    newObj.transform.SetParent(transform);
    return newObj;
}

 

CreateNewObject() 함수는 poolingObjectPrefab으로부터 새 게임 오브젝트를 만든 뒤 비활성화해서 반환하는 역할을 한다. 이렇게 곧바로 비활성화하는 이유는 아직 오브젝트 풀이 꺼내주지 않은 오브젝트는 비활성화된 것으로써 플레이어의 눈에 띄지 않아야하기 때문이다.

 

public static Bullet GetObject()
{
    if(Instance.poolingObjectQueue.Count > 0)
    {
        var obj = Instance.poolingObjectQueue.Dequeue();
        obj.transform.SetParent(null);
        obj.gameObject.SetActive(true);
        return obj;
    }
    else
    {
        var newObj = Instance.CreateNewObject();
        newObj.gameObject.SetActive(true);
        newObj.transform.SetParent(null);
        return newObj;
    }
}

 

GetObject() 함수는 오브젝트 풀이 가지고 있는 게임 오브젝트를 요청한 자에게 꺼내주는 역할을 한다. 단, 모든 오브젝트를 꺼내서 빌려줘서 poolingObjectQueue에 빌려줄 오브젝트가 없는 상태라면 CreateNewObject() 함수를 호출해서 새로운 오브젝트를 생성해서 빌려준다.

 

public static void ReturnObject(Bullet obj)
{
    obj.gameObject.SetActive(false);
    obj.transform.SetParent(Instance.transform);
    Instance.poolingObjectQueue.Enqueue(obj);
}

 

ReturnObject() 함수는 빌려준 오브젝트를 돌려받는 함수이다. 돌려받은 오브젝트를 비활성화한 뒤 정리하는 일을 처리한다.

 

무엇이든지 빌려줬으면 돌려받아야 한다. 이 원칙은 프로그래밍에서도 매우 중요한 원칙으로 사용한 오브젝트를 제대로 돌려받지 않으면 GetObject() 함수를 호출할 때, 계속해서 새로운 오브젝트를 생성하고 사라지지 않은 오브젝트들이 씬에 쌓이게 된다. 이것이 바로 메모리 누수이다.

 

 

코드 작성이 끝나면 씬에 새 게임 오브젝트를 배치하고 오브젝트 풀 컴포넌트를 부착하고 pooling Object Prefab 프로퍼티에 풀링해야할 게임 오브젝트의 프리팹을 할당해주면 된다.

 

 

 

그 다음에는 총알 오브젝트를 생성하는 부분과 파괴하는 부분을 수정해야 한다.

 

public class Shooter : MonoBehaviour
{
    void Update()
    {
        if(Input.GetMouseButton(0))
        {
            RaycastHit hitResult;
            if(Physics.Raycast(mainCam.ScreenPointToRay(Input.mousePosition), out hitResult))
            {
                var bullet = ObjectPool.GetObject(); // 수정
                var direction = new Vector3(hitResult.point.x, transform.position.y, hitResult.point.z) - transform.position;
                bullet.transform.position = direction.normalized;
                bullet.Shoot(direction.normalized);
            }
        }
    }
}

 

우선 생성하는 부분은 Shooter 클래스의 Update() 함수에 Instanitate()로 총알을 생성하던 부분을 ObjectPool.GetObject()로 대체하면 된다.

 

public class Bullet : MonoBehaviour
{
    private Vector3 direction;
    public void Shoot(Vector3 direction)
    {
        this.direction = direction;
        Invoke("DestroyBullet", 5f);
    }

    public void DestroyBullet()
    {
        ObjectPool.ReturnObject(this);
    }

    void Update()
    {
        transform.Translate(direction);   
    }
}

 

그 다음 파괴하는 부분은 Bullet 클래스에서 DestoryBullet() 함수에서 ObjectPool.ReturnObject()를 호출해서 오브젝트 풀에 돌려주도록 구현하고 Shoot() 함수에서 Destory() 함수를 호출하던 부분을 Invoke()로 5초 뒤에 DestoryBullet() 함수를 호출하도록 수정한다.

 

 

 

플레이를 해서 테스트 해보면 총알이 일정 갯수만큼 생성되고 나면 더 이상 생성되지 않으며, 시간이 지나면 소멸되어야 하는 총알 오브젝트들이 비활성화되서 오브젝트 풀로 반환되는 것을 확인할 수 있다.

 

 

프로파일러를 통해서 확인해보면 최대 생성량에 도달한 이후에는 Instatiate와 Destroy로 인한 작업이 발생하지 않는 것을 볼 수 있다.

 

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

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

 

에셋스토어

여러분의 작업에 필요한 베스트 에셋을 찾아보세요. 유니티 에셋스토어가 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

 

반응형
  1. 료용 2020.05.04 01:14 신고

    베르님 안녕하세요 유투브 잘보고있습니다.

    이 글 정말 좋은글인데 제가 이해를 못하는부분이있어서 질문드립니다.

    Initialze() 이 함수 awake에서 10으로 주시지않았습니까? 그러면 queue에 10개의 bullet의 오브젝트가 들어있는거지않습니까??

    그런데 그러면 10개가 넘는다면 새로운 bullet를 생성해주시는데

    var newObj = Instance.CreateNewObject();

    newObj.gameObject.SetActive(true);

    newObj.transform.SetParent(null);

    return newObj;

    여기서 이렇게 새로 생성한다음에

    ReturnObject()

    으로 받는다면 queue에는 지금 영상에서 보듯이 수십개의 bullet가 저장되는 개념아닌가요?

    그렇다면 처음에 Initialze() 에 10으로 줄이유가없지않나요? 이부분이 너무헷갈려서 .. 유투브에서 물어보려다가 너무길게 될 질문이라 여기다가 올립니다 ㅠㅠ

    • wergia 2020.05.04 12:47 신고

      Awake 함수에서 Initialize로 10개를 미리 만들어두는 것은 게임에서 사용될 오브젝트의 갯수를 예측해서 미리 만들어두는 것입니다.

      예시에서는 미리 만들어둔 갯수를 모두 사용하고나면 이후에 생성되는 것을 보여드리기 위해서 10개만 만들었지만 실제로는 게임을 여러 번 실행해서 테스트를 진행해보고 평균적으로 게임에 몇 개의 오브젝트가 필요한지 산출해낸 뒤 그 갯수를 미리 생성해두게 만듭니다.

      그러면 게임이 시작될 때 그 오브젝트 갯수를 미리 생성해두는 과정이 처리됩니다.

      이렇게 미리 필요한 갯수를 적당히 생성해두면 대부분은 미리 생성해둔 오브젝트를 사용하고 오브젝트가 부족해지면 새로 생성해서 사용하게 됩니다.

    • 료용 2020.05.04 15:20 신고

      그렇다면 혹시 10개정도만 쓰겠다! 라는느낌으로 awake에 해놓은건가요? 어차피 10개이상 bullet가 생성된다면 queue 의 사이즈는 bullet의 반납되는 수만큼 늘어났다가 다시 queue에 있던 bullet을 빌려주는 이런개념인거죠? 10개를 훨씬넘어서는

    • wergia 2020.05.04 22:21 신고

      10개 정도 사용할 것 같다고 예측하고 10개만 생성해둔 겁니다. 하지만 예시에서는 예측을 넘어서서 더 많은 양을 사용하게 되었으니 추가로 생성한 거구요.

      그리고 추가로 생성한 Bullet은 사용이 모두 완료된 후에 다시 Queue로 반납되어서 보관됩니다.

    • 료용 2020.05.08 19:12 신고

      감사합니다 베르님 확이해갔어요

    • wergia 2020.05.08 20:21 신고

      다행입니다 ㅜㅜ
      설명하기가 어려워서 이해 잘하셨을까 했는데

    • 료용 2020.05.08 20:57 신고

      죄송합니다 ...

      베르님 딱하나만 더...

      그런데 bullet을 많이생성하게되면 queue가 이때까지 최대로 많이생성했던 bullet의 갯수만큼의 사이즈를 가지게되지않나요?
      그러면 그거대로 느려지게되는 이유가되지않는건가요?

    • wergia 2020.05.09 11:08 신고

      만약 풀링하는 오브젝트가 사용하는 리소스의 용량이 매우 크다면 메모리를 많이 소모할 수도 있습니다.

      하지만 대부분 풀링하는 오브젝트는 총알 화살 같은 물체이기 때문에 사용하는 리소스의 용량이 크지 않은 물체가 대부분입니다.

      그리고 Bullet 같은 오브젝트가 쌓여서 메모리가 완전히 소모되어 게임이 느려지는 순간은 굉장히 늦게 오는 편이고 그렇게 될 오브젝트가 쌓이기도 어려운 구조입니다.

      하지만 가비지 컬렉터로 인한 프레임 드랍은 언제든지 찾아올 수 있습니다.

      이러한 프레임 드랍을 없애는게 주요한 목표이기 때문에 오브젝트 풀링을 사용합니다.

      오브젝트를 미리 생성해서 게임이 끝날때까지 사용하다가 게임이 끝나고 난 뒤 로딩 타임에 소멸시켜서 가비지 컬렉팅으로 인한 프레임 드랍을 로딩시간으로 편입시키는 등의 방법으로 게임 중의 프레임 드랍을 최소화하는 겁니다.

    • 료용 2020.05.09 16:37 신고

      감사합니다 베르님 마지막설명이 딱 이해하기쉬워졌어요

    • wergia 2020.05.10 22:08 신고

      언제든 궁금하신 점 있으시면 질문해주세요!
      아는 부분이라면 최대한 답변해드릴게요!

  2. moody 2020.06.17 13:09

    너무 좋은 자료 잘보고갑니다 ! 이런글 올려주셔서 너무 감사해요

    • wergia 2020.06.22 23:44 신고

      다음에는 심화과정으로 한 번 만들어 보겠습니다 ㅎㅎ

  3. Groza 2020.12.03 20:58

    정말 감사합니다 ㅎㅎ

  4. 베르사랑 2020.12.16 17:57

    베르님 현재 디펜스 게임 제작중에 있습니다.
    베르님의 강의대로 오브젝풀링 구현 후
    여러개의 타워에서 업데이트로 빌려오는데
    가끔 어느 타워가 공격을 하지 않습니다.ㅜ
    이게 동시에 꺼내오면서 몇몇 타워들이 가져오질
    못하는건가요?ㅜ
    여러 타워 업데이트 문에서 호출하여 사용하고 싶은데
    방법이 없을까요?ㅜ

  5. Cargold 2021.08.11 18:16

    풀링은 선택이 아니라 필수!

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

 

반응형
  1. 코딩캣츠 2021.01.20 11:26 신고

    항상 잘 보고 있어요!

  2. 레버리지랩 2021.09.14 22:31 신고

    프리팹 강의 대박이네요.. 왜쓰는지 이해안됐었는데 쏙쏙 잘됐어요

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