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

 

베르의 게임 개발 유튜브

안녕하세요! 여러분들과 함께 게임 개발을 공부하는 베르입니다! 게임 개발에 도움이 되는 강좌들을 올리는 채널입니다! [투네이션 후원] 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

 

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

이번에는 언리얼 엔진의 가장 기초가 되는 액터와 컴포넌트에 대해서 알아봅시다.

 

사용 엔진 버전 : 5.0.2

 

타임라인

0:00 인트로

0:18 액터

2:21 컴포넌트

3:44 아웃트로

스크립트

인트로

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

이번에는 언리얼 엔진에서 가장 기본이 되는 액터와 컴포넌트에 대해서 알아보도록 하겠습니다.

본격적인 설명에 들어가기 전에 언리얼 엔진을 실행하고 시작용 콘텐츠를 포함한 상태로 새 프로젝트를 하나 생성합니다.

액터

먼저 액터에 대해서 알아보자면, 아주 간단하게 설명해서 레벨에 배치할 수 있는 오브젝트를 말합니다.

우리가 방금 새로 생성한 언리얼 프로젝트에서 보면 기본 레벨에 있는 모든 것이 액터입니다.

의자나 테이블, 바닥은 물론 눈에 직접 보이지는 않지만 환경에 영향을 미치는 광원이나, 배경 소리를 구성하는 음원 등 여기 아웃라이너 패널에서 보이는 모든 것들은 액터라는 뜻입니다.

그럼 먼저 이 액터를 생성하는 방법을 간단하게 알아보겠습니다.

메인 툴바에서 육면체에 +모양이 표시된 액터 추가 버튼을 누르면 여러가지 항목을 볼 수 있습니다.

만들 수 있는 액터의 종류에 따라 분류가 되어있는데 먼저 기본 카테고리에서는 일반적으로 자주 사용되는 몇 가지 액터들을 생성할 수 있습니다.

일단 기본 카테고리에서 액터를 선택하면 완전히 비어있고 아무런 기능도 하지 않는 빈 액터를 생성할 수 있습니다.

비어있는 액터에는 기본적으로 씬 컴포넌트가 루트 컴포넌트로 붙어있고 이 루트 컴포넌트가 액터의 기본적인 위치나 회전, 크기와 같은 정보를 담고 있습니다.

그래서 이 액터가 레벨의 어디에 어떻게 배치되어있는지 확인할 수 있습니다.

라이트 카테고리에서는 씬에서 여러 액터들에게 빛을 비춰서 눈에 보이게 만들어줄 빛을 추가할 수 있습니다.

아웃라이너에서 라이트들을 끄면 레벨에 배치된 의자와 탁자들은 검게 어두워지면서 제대로 보이지 않게 되는 것을 볼 수 있습니다.

여기에 라이트 카테고리에서 포인트 라이트를 선택해서 레벨에 추가하고 테이블 위에 배치하면 어두워서 보이지 않던 주위가 보이게 됩니다.

이 외에도 셰이프 카테고리에서는 기본적인 육면체나 원뿔, 실린더, 구체 등의 액터를 추가할 수 있고, 카테고리마다 여러가지 다양한 기능을 가진 액터들을 생성할 수 있는 항목들을 가지고 있습니다.

각 카테고리에서 제공되는 액터들은 해당 기능에 대해서 알아보는 강좌에서 알아보도록 하겠습니다.

모든 클래스 카테고리에서는 언리얼 엔진에서 제공되는 모든 종류의 액터를 볼 수 있습니다.

물론 이렇게 계속 메인 툴바의 액터 배치 버튼을 눌러서 작업하기가 귀찮다면 액터 배치 패널을 열고 여기서 원하는 액터를 찾아서 배치하는 방법도 있습니다.

컴포넌트

앞에서 액터에 대해서 설명했으니 이제 그 액터에 부착되는 컴포넌트에 대해서 설명하겠습니다

사실 액터는 컴포넌트들을 담는 하나의 그릇이고 이 액터에 어떤 컴포넌트가 부착되느냐에 따라서 그 역할이 결정됩니다.

예를 들어 우리가 앞에서 만든 빈 액터는 루트 컴포넌트, 즉 액터의 가장 기본이 되는 컴포넌트에 씬 컴포넌트만 붙어있어서 아무런 기능을 하지 못하고 액터의 위치만 표현할 수 있습니다.

하지만 씬에 배치된 의자 액터의 경우에는 루트 컴포넌트에 스태틱 메시 컴포넌트라는 컴포넌트가 붙어있어서 3D 모델을 가지고 눈에 직접 보이는 형태로 렌더링됩니다.

우리가 배치한 포인트 라이트 액터 역시 라이트 컴포넌트가 붙어있어서 빛을 방출하고 주변을 보이도록 만들어 줍니다.

빈 액터 역시 디테일 패널에서 추가 버튼을 누르고 카메라 컴포넌트를 추가해주면 빈 액터가 아닌 카메라로서의 기능을 하게 됩니다.

앞에서도 말했다시피 액터에 어떤 컴포넌트를 붙이느냐 에 따라서 액터의 기능이 결정되는 것입니다.

언리얼 엔진에서는 게임 개발에 필요한 기능을 가진 굉장히 다양한 종류의 컴포넌트들을 제공합니다.

하지만 이 중에 여러분이 개발하고자 하는 게임의 기능을 지원하는 컴포넌트가 없을 수도 있습니다.

그럴 때는 C++나 블루프린트를 이용해 여러분 만의 커스텀 컴포넌트를 만들어서 액터에 붙이면 됩니다.

이런 커스텀 컴포넌트 제작 방법 역시 추후에 강좌의 진행에 따라서 하나씩 배워보도록 할겁니다.

아웃트로

이번에는 언리얼 엔진의 가장 기본이 되는 액터와 컴포넌트에 대해서 알아보았습니다.

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

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

 

[투네이션]

 

-

 

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 프로퍼티를 통해서 할 수 있다.

 

 

 

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

 

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

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

일반 C# 클래스와 게임 오브젝트의 컴포넌트로써의 클래스


유니티로 게임을 개발할 때, 게임 씬에 배치되며 하이어라키 뷰에 존재하는 객체를 게임 오브젝트(Game Object)라고 하는데, 이 게임 오브젝트에 부착되는 컴포넌트를 컴포넌트 클래스라고 하고, 게임 오브젝트에 컴포넌트로 부착되지 않고 메모리 상에만 있는, 코드 상에서만 다루어질 클래스를 일반 C# 클래스라고 하자.


왜 이런 복잡한 분류가 있어야 되느냐 싶겠지만, 게임을 개발하다고 보면 유니티에서 기본적으로 제공하는 모노비헤이비어를 상속받는 게임 오브젝트에 컴포넌트로 부착될 클래스 이외의 일반적인 C# 클래스 역시 필요한 시점이 반드시 온다.



컴포넌트 클래스(Component Class)


public class ComponentClass : MonoBehaviour

{

    // Start is called before the first frame update

    void Start()

    {

        

    }


    // Update is called once per frame

    void Update()

    {

        

    }

}


유니티 엔진에서 C# 스크립트를 생성하면 생성된 클래스를 기본적으로 모노비헤이비어(MonoBehaviour) 클래스를 상속받으며 위의 예시 코드와 같이 기본적으로 Start() 함수와 Update() 함수가 만들어진 채로 스크립트가 생성된다.


 

이렇게 모노비헤이비어 클래스를 상속받는 클래스는 위의 이미지처럼 인스펙터 뷰에서 Add Component 버튼을 통해서 게임 오브젝트에 부착될 수 있으며, 모노비헤이비어 클래스에서 상속받는 다양한 프로퍼티와 함수를 활용할 수 있다. 그리고 게임 오브젝트가 생성될 때는 Start() 함수, 게임 오브젝트가 업데이트되는 동안에는 Update() 함수, 소멸될 때는 OnDestroy() 함수 등 다양한 상황에서 호출되는 콜백 함수 역시 제공받는다.



일반 C# 클래스


public class CSharpClass

{


}


일반 C# 클래스는 모노비헤이비어 클래스를 상속받지 않으며, 게임 오브젝트에 컴포넌트로 부착되지 않는 코드 내에서만 동작하는 클래스를 만들고자 할 때 사용된다. 모노비헤이비어 클래스로부터 상속받는 프로퍼티와 함수들을 사용하지는 못하지만, 컴포넌트로 부착될 필요가 없거나 씬에 배치될 필요가 없는 오브젝트 일 때 사용된다.


 

일반 C# 클래스는 인스펙터 창의 Add Component 버튼에서 검색해도 게임 오브젝트에 부착할 수 없게 표시되지 않는다.



일반 C# 클래스를 다룰 때 실수할 수 있는 부분


public class CSharpClass : MonoBehaviour

{

    // Start is called before the first frame update

    void Start()

    {

        

    }


    // Update is called once per frame

    void Update()

    {

        

    }

}

 

그런데 유니티 에디터에서 .cs파일을 처음 생성하면 위와 같이 코드가 생성된다. 일반적으로 유니티에 입문한지 얼마 되지 않은 개발자들은 이때 생성한 클래스의 모노비헤이비어(MonoBehaviour) 클래스 상속을 그대로 두고 사용한다.


이 클래스가 컴포넌트 클래스라면 상관없는 문제지만, 일반 C# 클래스라면 문제가 발생할 수 있다. 우선은 모노비헤이비어 클래스를 상속받음으로써 불필요한 프로퍼티가 생성되는 점이 첫 번째 문제이고, 두 번째 문제는 일반 C# 클래스로써 설계해놓고 게임 오브젝트와 혼용해서 사용하려는 시도가 발생할 수 있다는 점이다.

 

예시로 코드 내에서 CSharpClass에 모노비헤이비어 클래스를 상속시키고 일반 C# 클래스에서도 실행가능한 기능과 컴포넌트 클래스로서 게임 오브젝트에 부착되었을 때만 가능한 기능을 섞어둔 코드를 아래와 같이 작성해보겠다.


public class CSharpClass MonoBehaviour

{

    public int i = 10;


    void Start()

    {

        Debug.Log("CSharpClass :: Start()");  

    }


    void Update()

    {

        Debug.Log("CSharpClass :: Update()");

    }


    public void SomeFunction1()

    {

        Debug.Log(string.Format("CSharpClass :: Function1({0})", i));

    }


    public void SomeFunction2()

    {

        Debug.Log(string.Format("CSharpClass :: Function2()"));

        StartCoroutine(SomeCoroutine());

    }


    public IEnumerator SomeCoroutine()

    {

        yield return null;

        Debug.Log("CSharpClass :: SomeCoroutine()");

    }

}

 

이런 CSharpClass를 컴포넌트가 아닌 일반 C# 오브젝트처럼 사용하려고 하면 생성해서 사용하려고 시도할 것이고 아직 유니티에서의 스크립팅 작업에 익숙하지 않은 개발자라면 일반 C#과 모노비헤이비어에서 상속받는 기능을 혼용해서 사용하려고 시도할 수 있다. 마치 아래의 코드 예시와 같이 :


public class ComponentClass : MonoBehaviour

{

    void Start()

    {

        StartCoroutine(CreateCSharpClassObject());

    }


    private IEnumerator CreateCSharpClassObject()

    {

        var some = new CSharpClass();

        Debug.Log(some);

        some.SomeFunction1();

        yield return StartCoroutine(some.SomeCoroutine());

        some.SomeFunction2();

    }

}

 

ComponentClass는 게임 오브젝트에 부착될 컴포넌트 클래스이며, 일반적인 C#의 오브젝트 생성 방식을 통해서 CSharpClass를 생성하고 멤버 함수들을 호출하는 역할을 한다. SomeCoroutine()의 호출순서를 보장하기 위해서 코루틴 함수를 통해서 호출했다.


 

모노비헤이비어를 상속받은 CSharpClass를 기존 C# 방식으로 생성한 뒤 호출 테스트를 하기위해서 씬에 빈 게임 오브젝트를 ComponentClass 컴포넌트를 부착하고  플레이 버튼을 눌러보자.


  

그러면 위와 같은 로그를 얻을 수 있는데, 위 로그를 통해서 확인할 수 있는 사실은 다음과 같다.


1. 게임 오브젝트가 시작될 때, 실행되어야 하는 Start() 함수와 게임 오브젝트가 존재하는 동안 호출되어야할 Update() 함수가 호출되지 않는다.

2. Debug.Log(some)은 null이라고 표시된다. 즉, 오브젝트가 null reference 상태이다.

3. 하지만 SomeClass의 멤버함수인 SomeFunction1() 함수는 정상적으로 호출되었고 멤버변수 i의 값도 정상적으로 출력되었다. 즉, 오브젝트 자체는 생성되었다.

4. ComponentClass의 게임 오브젝트가 매개체가 되어 호출한 코루틴은 정상으로 동작했다.

5. CSharpClass의 게임 오브젝트가 매개체가 되어 호출한 코루틴은 null reference가 발생하며 동작에 실패했다.


이를 통해서 알 수 있는 사실은 C# 방식으로 모노비헤이비어를 상속받은 클래스를 생성하면 오브젝트는 생성되지만, 게임 오브젝트는 생성되지 않는다는 것이다. 그렇기 때문에 모노비헤이비어에서 상속받아오는 Start() 함수, Update() 함수, StartCoroutine() 함수의 호출에 실패하는 것이다. 이런 문제가 발생하는 것을 막기 위해서 일반 C# 클래스로 설계된 클래스의 .cs 파일을 유니티 엔진에서 생성하면 반드시 모노비헤이비어 클래스 상속을 제거해주어야만 한다.





컴포넌트 클래스와 일반 C# 클래스의 생성


위의 예시를 통해서 알 수 있는 점은 일반 C# 클래스에서는 모노비헤이비어를 상속받지 말아야 한다는 점과 컴포넌트 클래스와 일반 C# 클래스의 생성방법은 다르다는 것이다. 그렇다면 컴포넌트 클래스와 일반 C# 클래스는 각각 어떻게 생성해주어야 하는가를 알아보자.


우선 CSharpClass와 ComponentClass의 코드를 다음과 같이 수정하자.


CSharpClass.cs

public class CSharpClass

{

    public int i = 10;


    public void SomeFunction1()

    {

        Debug.Log(string.Format("CSharpClass :: Function1({0})", i));

    }


    public IEnumerator SomeCoroutine()

    {

        yield return null;

        Debug.Log("CSharpClass :: SomeCoroutine()");

    }

}

ComponentClass.cs

public class ComponentClass : MonoBehaviour

{

    void Start()

    {

        Debug.Log("ComponentClass :: Start");

    }


    void Update()

    {

        Debug.Log("ComponentClass :: Update");

    }

}


그 다음에는 ObjectGenerator라는 이름으로 클래스를 만들고 다음처럼 코드를 작성한다.


public class ObjectGenerator : MonoBehaviour

{

    void Start()

    {

        var gameObj = new GameObject();

        gameObj.AddComponent<ComponentClass>();


        var obj = new CSharpClass();

        obj.SomeFunction1();

        StartCoroutine(obj.SomeCoroutine());

    }

}


간단한 코드 해설을 덧붙이자면, 게임 오브젝트의 경우 new GameObject()를 호출하면 자동으로 씬에 빈 게임 오브젝트 하나가 배치된다. 그리고 컴포넌트 클래스는 게임 오브젝트의 AddComponent<>() 함수를 호출해서 해당 게임 오브젝트에 컴포넌트로 부착할 수 있다.


일반 C# 클래스는 C#에서와 같이 new 연산자를 통해서 오브젝트를 생성할 수 있다. 그리고 일반 C# 클래스의 멤버 함수로 들어가 있는 코루틴 함수의 경우에는 일반 C# 클래스가 스스로 Start Coroutine을 할 수는 없지만, 다른 게임 오브젝트의 Start Coroutine을 통해서는 코루틴을 시작할 수 있다.


 

이를 테스트하기 위해서 씬에 게임 오브젝트 하나를 배치하고 Object Generator 컴포넌트를 붙여준다.


 

그리고 에디터에서 플레이 버튼을 눌러 실행해보면 New Game Object라는 이름의 게임 오브젝트가 하나 새로 생성되고 ComponenetClass가 컴포넌트로 부착되는 것을 볼 수 있으며


 

로그를 통해서는 컴포넌트 클래스의 함수와 일반 C# 클래스의 함수가 정상적으로 동작하는 것을 확인할 수 있다.

반응형

Tutorial (6)

 

게임 오브젝트와 컴포넌트

 

작성 기준 버전 :: 2018.3.2f1

 

이번 섹션에서는 게임 오브젝트와 컴포넌트에 대해서 알아보자.

 

게임 오브젝트(Game Object)

 

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

 

기본적인 게임 오브젝트에 나중에 설명할 컴포넌트를 추가로 붙임으로써 게임 오브젝트가 캐릭터가 되느냐, 아이템이 되느냐, 아니면 간단한 배경이 되느냐를 결정하게 된다.

 

 

게임 오브젝트는 기본적으로 이름(Name - "GameObject"), 태그(Tag - "Untagged"), 레이어(Layer - "Default")를 가지고 있다. 이를 응용해서 나중에 원하는 오브젝트를 찾거나 할 수 있다.

 

 

컴포넌트(Component)

 

컴포넌트는 게임 오브젝트에 붙일 수 있는 다양한 기능을 가진 구성요소들로, 비어있는 게임 오브젝트에 어떤 컴포넌트를 붙이느냐에 따라서 그 게임 오브젝트의 역할이 달라진다.

 

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

 

모든 컴포넌트 중에서 트랜스폼(Transform) 컴포넌트는 모든 게임 오브젝트에 기본으로 부착되며 제거할 수 없다.

 

 

이 트랜스폼 컴포넌트는 해당 게임 오브젝트가 씬에 어느 위치에, 얼마나 회전되어서, 어떤 크기로 배치되어 있는지를 정하는 컴포넌트이다.

 

기타 컴포넌트

 

게임 오브젝트에는 여러 컴포넌트를 붙일 수 있고 어떤 컴포넌트를 붙이느냐에 따라서 게임 오브젝트의 역할이 달라진다.

 

 

게임 오브젝트에 Light 컴포넌트를 붙이면 광원이 되고 Camera 컴포넌트를 붙이면 게임 내에서 씬을 보여주는 카메라가 된다. 이렇게 유니티에서 기본적으로 제공하는 컴포넌트 이외에도 개발자가 유니티 스크립트 API를 이용해서 직접 컴포넌트를 만들어서 게임 오브젝트에 붙일 수도 있다.

 

컴포넌트 추가하기

 

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

 

 

커스텀 컴포넌트

 

유니티에서 기본적으로 제공하는 기본적인 컴포넌트 이외에도 개발자가 직접 컴포넌트를 만들어서 게임 오브젝트에 붙일 수도 있는데 대부분의 실제적인 게임 기능을 하는 시스템들은 커스텀 컴포넌트로 만들어지고 게임 오브젝트에 추가될 것이다.

 

 

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

 

 

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

 

 

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

반응형

제대로 따라가기 (3) C++ 프로그래밍 튜토리얼 :: 컴포넌트와 콜리전

 

작성버전 :: 4.21.0

 

언리얼 엔진 튜토리얼인 컴포넌트와 콜리전에서는 컴포넌트를 만들어 계층구조에 넣고 게임플레이 도중 제어하는 법과, 컴포넌트를 사용하여 폰이 입체 오브젝트로 된 월드를 돌아다니도록 만드는 법을 배울 수 있다..

 

튜토리얼대로 하면 문제가 발생해서 제대로 따라갈 수 없는 부분으로 동작이 가능하게 수정해야하는 부분은 빨간 블럭으로 표시되어 있다.
 

이번 튜토리얼에서 새로 배우게 되는 내용은 글 제일 끝에 "이번 섹션에서 배운 것"에 정리된다.

 

 

1. 컴포넌트 만들고 붙이기(문서)

 

프로젝트를 새로 생성하고 Pawn 클래스를 상속받는 "CollidingPawn"을 생성한다. 이 폰은 컴포넌트를 가지고 레벨 안에서 이동하고 입체 오브젝트와 충돌하게 된다.

 

 

 

 

CollidingPawn.h의 클래스 정의 하단부에 UParticleSystemComponent를 추가한다.

 

UParticleSystemComponent* OurParticleSystem;

 

UParticleSystemComponent가 정의되어 있지 않다고 에러가 발생한다면, CollidingPawn.generated.h 포함 전처리기 위쪽에서 "Engine/Classes/Particles/ParticleSystemComponent.h"을 포함시켜 주면 된다.

 

// Fill out your copyright notice in the Description page of Project Settings.

#pragma once

#include "CoreMinimal.h"
#include "GameFramework/Pawn.h"
#include "Engine/Classes/Particles/ParticleSystemComponent.h"
#include "CollidingPawn.generated.h"

 

여기에 대한 또 다른 해결책으로는 UParticleSystemComponent 타입의 변수를 선언할 때, 아래처럼 앞에 class를 붙여주면 헤더를 .h에 포함하지 않아도 에러가 발생하지 않는다.

 

class UParticleSystemComponent* OurParticleSystem;

 

대신 이 경우에는 .cpp에서 해당 타입의 변수를 사용할 때, 불완전한 형식을 사용할 수 없다는 에러가 발생할 것이기 때문에 .cpp의 헤더 포함 전처리기에 "Engine/Classes/Particles/ParticleSystemComponent.h"를 포함하는 코드를 추가시켜주어야 한다.

 

멤버 변수로 만들지 않아도 컴포넌트를 만들 수 있지만, 코드에서 컴포넌트를 사용하려면 클래스 멤버 변수로 만들어야 한다.

 

이 다음에는 CollidingPawn.cpp의 ACollidingPawn::ACollidingPawn() 생성자 함수를 편집해서 필요한 컴포넌트들을 스폰할 코드를 추가하고 계층구조로 배치해야 한다. 물리 월드와 상호작용을 위한 Sphere Component, 콜리전 모양을 시각적으로 보여줄 Static Mesh Component, 시각적인 효과를 더하며 켜고 끌 수 있는 Particle System Component, 게임 내의 시점 제어를 위해 Camera Component에 덧붙일 Spring Arm Component를 만든다.

 

먼저 계층구조에서 루트가 될 컴포넌트를 결정해야 한다. 이 튜토리얼에서는 Sphere Component가 루트 컴포넌트가 된다. 물리적으로 실존이 있고, 게임 월드와의 상호작용이 가능하기 때문이다. 참고로 액터에는 계층구조 안에서 다수의 물리 기반 컴포넌트가 있을 수 있지만, 이 튜토리얼에서는 하나만 사용한다.

 

USphereComponent* SphereComponent = CreateDefaultSubobject(TEXT("RootComponent"));
RootComponent = SphereComponent;
SphereComponent->InitSphereRadius(40.0f);
SphereComponent->SetCollisionProfileName(TEXT("Pawn"));

 

이 파트에서는 두 가지 문제로 진행이 방해받는다. 언리얼 튜토리얼 문서의 고질적인 문제로 CreateDefaultSubobject() 함수 문제와 USphereComponent가 정의되어 있지 않다고 하는 문제이다.

 

CreateDefaultSubobject() 함수 문제는 템플릿 매개변수에 값을 반환받는 변수에 맞는 타입을 넣어주면 해결된다.

 

USphereComponent* SphereComponent = CreateDefaultSubobject<USphereComponent>(TEXT("RootComponent"));

 

USphereComponent가 정의되지 않은 문제는 CollidingPawn.cpp의 전처리기에 "Engine/Classes/Components/SphereComponent.h"를 포함시켜주면 된다.

 

// Fill out your copyright notice in the Description page of Project Settings.

#include "CollidingPawn.h"
#include "Engine/Classes/Components/SphereComponent.h"

 

다음은, 구형의 스태틱 메시 컴포넌트를 만들어서 적절한 크기와 위치로 만들어서 루트 컴포넌트에 붙여준다.

 

UStaticMeshComponent* SphereVisual = CreateDefaultSubobject(TEXT("VisualRepresentation"));
SphereVisual->SetupAttachment(RootComponent);
static ConstructorHelpers::FObjectFinder SphereVisualAsset(TEXT("/Game/StarterContent/Shapes/Shape_Sphere.Shape_Sphere"));
if (SphereVisualAsset.Succeeded())
{
    SphereVisual->SetStaticMesh(SphereVisualAsset.Object);
    SphereVisual->SetRelativeLocation(FVector(0.0f, 0.0f, -40.0f));
    SphereVisual->SetWorldScale3D(FVector(0.8f));
}

 

UStaticMeshComponent 정의되지 않음 문제는 CollidingPawn.cpp에 "Engine/Classes/Components/StaticMeshComponent.h"를 포함시켜주면 해결된다.

 

#include "Engine/Classes/Components/StaticMeshComponent.h"

 

CreateDefaultSubobject() 함수 문제는 템플릿 매개변수에 UStaticMeshComponent 타입을 넣어주면 해결된다.

 

UStaticMeshComponent* SphereVisual = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("VisualRepresentation"));

 

ConstructorHelpers가 정의되어 있지 않은 문제는 CollidingPawh.cpp에 "ConstructorHelpers.h"를 포함시켜주면 된다.

 

#include "ConstructorHelpers.h"

 

여기까지 해결하고 나면 ConstructorHelpers::FObjectFinder에서 [클래스 템플릿 "ConstructorHelpers::FObjectFinder"에 대한 인수 목록이 없습니다.] 라는 에러가 발생할 것이다. 이 문제를 해결하기 위해서 ConstructorHelpers::FObjectFinder의 원형을 살펴보면 ConstructorHelpers::FObjectFinder는 템플릿을 사용하는 것을 알 수 있다. 그렇다면 여기서 중요한 점은 템플릿 인자에 어떤 타입이 들어가야 하는가가 문제인데, 이 것은 SphereVisualAsset의 선언 2줄 아래를 보면 이 변수가 SetStaticMesh() 함수에 대입되는 것을 알 수 있다. 이 함수가 받는 매개변수의 타입은 UStaticMesh로서 SphereVisualAsset.Object는 UStaticMesh 타입임을 유추할 수 있다.

 

static ConstructorHelpers::FObjectFinder<UStaticMesh> SphereVisualAsset(TEXT("/Game/StarterContent/Shapes/Shape_Sphere.Shape_Sphere"));

 

이번엔 Particle System Component를 붙인다. 이 컴포넌트는 코드를 통해서 켜고 끄는 등의 제어를 할 수 있으며, 루트가 아닌 스태틱 메시에 붙어있으며 게임 플레이 도중에 더 잘보이게 하기 위해 메시의 정중앙이 아닌 약간 아래쪽에 오프셋되어 있다.

 

OurParticleSystem = CreateDefaultSubobject(TEXT("MovementParticles"));
OurParticleSystem->SetupAttachment(SphereVisual);
OurParticleSystem->bAutoActivate = false;
OurParticleSystem->SetRelativeLocation(FVector(-20.0f, 0.0f, 20.0f));
static ConstructorHelpers::FObjectFinder ParticleAsset(TEXT("/Game/StarterContent/Particles/P_Fire.P_Fire"));
if (ParticleAsset.Succeeded())
{
    OurParticleSystem->SetTemplate(ParticleAsset.Object);
}

 

CreateDefaultSubobject() 함수 문제는 템플릿 매개변수에 UParticleSystemComponent 타입을 넣어주면 해결된다.

 

OurParticleSystem = CreateDefaultSubobject<UParticleSystemComponent>(TEXT("MovementParticles"));

 

SetTamplate() 함수의 매개변수를 확인해본 결과 ParticleAsset의 템플릿 인자는 UParticleSystem 타입임을 알 수 있다.

 

static ConstructorHelpers::FObjectFinder<UParticleSystem> ParticleAsset(TEXT("/Game/StarterContent/Particles/P_Fire.P_Fire"));

 

Spring Arm Component는 폰보다 느린 가속/감속을 따라다니는 카메라에 적용시킬 수 있기 때문에, 카메라의 부드러운 부착점이 된다. 또한 카메라가 입체 오브젝트를 뚫고 지나가지 못하게 하는 기능을 내장하고 있어서, 삼인칭 게임에서 구석에서 벽을 등지는 상황에 유용하게 사용된다.

 

USpringArmComponent* SpringArm = CreateDefaultSubobject(TEXT("CameraAttachmentArm"));
SpringArm->SetupAttachment(RootComponent);
SpringArm->SetRelativeRotation(FRotator(-45.0f, 0.0f, 0.0f));
SpringArm->TargetArmLength = 400.0f;
SpringArm->bEnableCameraLag = true;
SpringArm->CameraLagSpeed = 3.0f;

 

USpringArmComponent가 정의되지 않은 문제는 CollidingPawn.cpp에 "Engine/Classes/GameFramework/SpringArmComponent.h"를 포함시켜주면 해결된다.

 

#include "Engine/Classes/GameFramework/SpringArmComponent.h"

 

CreateDefaultSubobject() 함수 문제는 템플릿 매개변수에 USpringArmComponent 타입을 넣어주면 해결된다.

 

USpringArmComponent* SpringArm = CreateDefaultSubobject<USpringArmComponent>(TEXT("CameraAttachmentArm"));

 

Camera Component를 생성해서 Spring Arm Component에 붙여준다. Spring Arm Component에는 소켓이 내장되어 있어서 베이스가 아닌 소켓에 카메라를 붙일 수 있다.

 

UCameraComponent* Camera = CreateDefaultSubobject(TEXT("ActualCamera"));
Camera->SetupAttachment(SpringArm, USpringArmComponent::SocketName);

 

UCameraComponent가 정의되지 않은 문제는 CollidingPawn.cpp에 "Engine/Classes/Camera/CameraComponent.h"를 포함시켜주면 해결된다.

 

#include "Engine/Classes/Camera/CameraComponent.h"

 

CreateDefaultSubobject() 함수 문제는 템플릿 매개변수에 UCameraComponent 타입을 넣어주면 해결된다.

 

UCameraComponent* Camera = CreateDefaultSubobject<UCameraComponent>(TEXT("ActualCamera"));

 

모든 컴포넌트를 붙인 뒤에는, 기본 플레이어가 이 폰을 조종하도록 설정해야 한다.

 

AutoPossessPlayer = EAutoReceiveInput::Player0;

 

위의 작업이 모두 끝났다면 언리얼 에디터로 돌아가자.

 

 

 

 

2. 입력 환경설정 및 폰 무브먼트 컴포넌트 생성(문서)

 

언리얼 에디터로 돌아왔다면, 프로젝트의 입력 세팅을 할 차례다. 이 세팅은 편집 드롭다운 메뉴의 프로젝트 세팅에서 찾을 수 있다.

 

 

 

프로젝트 세팅 창을 열었다면, 좌측의 엔진 섹션에서 입력을 찾아서 클릭한 뒤 아래와 같이 입력 매핑을 세팅하자.

 

 

 

이번에는 Pawn에서 모든 이동 처리를 하는 대신에, Movement Component를 만들어서 관리를 시키도록 해보자. 이 튜토리얼에서 Pawn Movement Component 클래스를 확장해서 사용한다.[각주:1] 파일 드롭다운 메뉴의 [새로운 C++ 클래스] 명령을 선택한다.

 

 

 

Pawn 클래스와 달리 Pawn Movement Component 클래스는 기본적으로 보이지 않기 때문에 모든 클래스 보기 옵션을 선택해야 한다.

 

 

 

검색창에 movement를 검색하면 찾고자 하는 클래스의 범위를 빠르게 좁힐 수 있다.

 

 

우리가 만든 Pawn 클래스의 이름이 "CollidingPawn"이기 때문에 이 Movement Component의 이름은 "CollidingPawnMovementComponent"로 정하자.

 

 

입력 환경설정에 대한 정의와 CollidingPawnMovementComponent의 생성으로 모두 끝마쳤으므로, 비주얼 스튜디오로 돌아가서 다시 코드 작업을 해야한다.

 

 

3. 폰 무브먼트 컴포넌트의 작동방식 코딩(문서)

 

비주얼 스튜디오로 돌아왔으면 이제 커스텀 폰 무브먼트 컴포넌트의 작동방식을 코딩하면 된다. Actor의 Tick() 함수 역할을 하는 TickComponent() 함수가 각 프레임 별로 어떻게 동작할지를 정의해야 한다. 우선은 부모 클래스의 TickComponent() 함수를 덮어쓰는 것으로 시작한다.

 

public:
    virtual void TickComponent(float DeltaTime, enum ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction) override;

 

정의한 함수를 CollidingPawnMovementComponent.cpp에 구현한다.

 

void UCollidingPawnMovementComponent::TickComponent(float DeltaTime, enum ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction)
{
    Super::TickComponent(DeltaTime, TickType, ThisTickFunction);

    if (!PawnOwner || !UpdatedComponent || ShouldSkipUpdate(DeltaTime))
    {
        return;
    }

    FVector DesiredMovementThisFrame = ConsumeInputVector().GetClampedToMaxSize(1.0f) * DeltaTime * 150.0f;
    if (!DesiredMovementThisFrame.IsNearlyZero())
    {
        FHitResult Hit;
        SafeMoveUpdatedComponent(DesiredMovementThisFrame, UpdatedComponent->GetComponentRotation(), true, Hit);

        if (Hit.IsValidBlockingHit())
        {
            SlideAlongSurface(DesiredMovementThisFrame, 1.0f - Hit.Time, Hit.Normal, Hit);
        }
    }
}

 

이 코드는 적합한 면을 미끄러져 다니며 월드를 부드럽게 움직이도록 폰을 이동시킨다. 폰에는 중력이 적용되지 않으며, 최대 속력은 초당 150 언리얼 유닛 으로 하드코딩되어 있다.

 

 

4. 폰과 컴포넌트 함께 사용하기(문서)

 

CollidingPawnMovementComponent를 CollidingPawn 클래스에서 사용하기 위해서 CollidingPawn.h의 클래스 정의 내에 다음 코드를 추가한다.

 

class UCollidingPawnMovementComponent* OurMovementComponent;

 

그리고 CollidingPawn.cpp에 "CollidingPawnMovementComponent.h"를 포함시킨다.

 

#include "CollidingPawnMovementComponent.h"

 

그 다음엔 CollidingPawn.cpp의 ACollidingPawn::ACollidingPawn() 생성자 함수 하단에서 CollidingPawnMovementComponent의 인스턴스를 생성하고 루트 컴포넌트를 업데이트하게 코드를 작성한다.

 

OurMovementComponent = CreateDefaultSubobject(TEXT("CustomMovementComponent"));
OurMovementComponent->UpdatedComponent = RootComponent;

 

CreateDefaultSubobject() 함수 문제는 템플릿 매개변수에 UCollidingPawnMovementComponet 타입을 넣어주면 해결된다.

 

OurMovementComponent = CreateDefaultSubobject<UCollidingPawnMovementComponent>(TEXT("CustomMovementComponent"));

 

이 컴포넌트는 다른 컴포넌트들과 달리 컴포넌트 계층구조에 붙일 필요가 없다. 다른 컴포넌트들의 경우에는 모두 씬 컴포넌트로 물리적인 위치가 필요한 것들이었지만, 이 컴포넌트는 물리적 오브젝트를 나타내는 것이 아니기 때문에, 물리적인 위치에 존재한다든가 다른 컴포넌트에 덧붙인다던가 하는 개념을 가지지 않는다.

 

Pawn 클래스에는 GetMovementComponent() 라는 함수가 있는데 이것은 엔진의 다른 클래스들이 현재 Pawn이 사용중인 Pawn Movement Component에 접근할 수 있도록 하는데 사용된다. 이 함수가 커스터마이징한 CollidingPawnMovementComponent를 반환하도록 하려면 이 함수를 덮어씌워야 한다. CollidingPawn.h에 다음 코드를 추가한다.

 

virtual UPawnMovementComponent* GetMovementComponent() const override;

 

그리고 CollidingPawn.cpp에 이 함수의 구현을 추가한다.

 

UPawnMovementComponent * ACollidingPawn::GetMovementComponent() const
{
    return OurMovementComponent;
}

 

Pawn Movement Component에 대한 구성이 끝났다면, Pawn이 받을 입력 처리에 대한 코드를 만들자. CollidingPawn.h에 함수 몇 개를 선언한다.

 

void MoveForward(float AxisValue);
void MoveRight(float AxisValue);
void Turn(float AxisValue);
void ParticleToggle();

 

그리고 CollidingPawn.cpp에 함수들을 구현한다.

 

void ACollidingPawn::MoveForward(float AxisValue)
{
    if (OurMovementComponent && OurMovementComponent->UpdatedComponent == RootComponent)
    {
        OurMovementComponent->AddInputVector(GetActorForwardVector() * AxisValue);
    }
}

void ACollidingPawn::MoveRight(float AxisValue)
{
    if (OurMovementComponent && OurMovementComponent->UpdatedComponent == RootComponent)
    {
        OurMovementComponent->AddInputVector(GetActorRightVector() * AxisValue);
    }
}

void ACollidingPawn::Turn(float AxisValue)
{
    FRotator NewRotation = GetActorRotation();
    NewRotation.Yaw += AxisValue;
    SetActorRotation(NewRotation);
}

void ACollidingPawn::ParticleToggle()
{
    if (OurParticleSystem && OurParticleSystem->Template)
    {
        OurParticleSystem->ToggleActive();
    }
}

 

남은 것은 함수들을 입력 이벤트에 바인딩하는 것이다. 다음 코드를 ACollidingPawn::SetupPlayerInputComponent() 함수에 추가하자.

 

InputComponent->BindAction("ParticleToggle", IE_Pressed, this, &ACollidingPawn::ParticleToggle);
InputComponent->BindAxis("MoveForward", this, &ACollidingPawn::MoveForward);
InputComponent->BindAxis("MoveRight", this, &ACollidingPawn::MoveRight);
InputComponent->BindAxis("Turn", this, &ACollidingPawn::Turn);

 

이로써 프로그래밍 작업은 모두 끝났다. 에디터로 돌아가서 컴파일을 진행하고 테스트해보자.

 

 

 

 

 


 

이번 섹션에서 배운 것

 

1. UParticleSystemComponent

 

UParticleSystemComponent* ParticleSystemComponent;

 

액터에 파티클 시스템을 덧붙일 수 있는 컴포넌트

 

ParticleSystemComponent->bAutoActivate = true;

 

파티클 시스템이 생성되자마자 자동으로 켜질지에 대한 변수

 

ParticleSystemComponent->SetTemplate(ParticleAsset.Object);

 

파티클 시스템 컴포넌트의 파티클을 설정하는 함수

 

ParticleSystemComponent->ToggleActive();

 

파티클을 켜고 끄는 함수

 

2. USphereComponent

 

USphereComponent* SphereComponent;

 

액터에 구형 충돌 물리 효과를 줄 수 있는 컴포넌트

 

SphereComponent->InitSphereRadius(40.0f);

 

스피어 컴포넌트의 반지름은 설정하는 함수

 

SphereComponent->SetCollisionProfileName(TEXT("Pawn"));

 

콜리전의 프로필을 설정하는 함수. [프로젝트 세팅>엔진>콜리전] 하단에 Preset을 열어보면 각 콜리전 프로필마다 어떤 물리 설정을 가지고 있는지 확인할 수 있다.

 

3. UStaticMeshComponent

 

UStaticMeshComponent* StaticMeshComponent;

 

월드에 렌더링되는 스태틱 메시를 가진 컴포넌트

 

StaticMeshComponent->SetStaticMesh(SphereVisualAsset.Object);

 

스태틱 메시 컴포넌트의 스태틱 메시를 설정하는 함수

 

4. ConstructorHelpers::FObjectFinder<T>

 

static ConstructorHelpers::FObjectFinder<T> Asset(TEXT("AssetPath"));

 

프로젝트에서 필요한 콘텐츠나 리소스, 에셋을 불러오는데 쓰이는 구조체

 

Asset.Succeeded();

 

에셋을 불러오는데 성공했는지를 반환하는 함수

 

Asset.Object;

 

불러온 에셋을 담고 있는 변수

 

5. USpringArmComponent

 

USpringArmComponent* SpringArmComponent;

 

부모 오브젝트와 자식 오브젝트 사이에 일정한 거리를 유지하게 도와주는 컴포넌트. 충돌이 있는 경우라면 유연하게 부모와 자식 사이의 거리를 좁혔다가 충돌이 사라지면 다시 원래대로 돌아가게하는 기능을 제공한다.

 

SpringArmComponent->TargetArmLength = 400.0f;

 

아무런 충돌이 없을 때, 스프링 암의 자연적인 거리를 정할 수 있는 변수

 

SpringArmComponent->bEnableCameraLag = true;

 

true인 경우, 카메라가 목표 위치보다 뒤떨어져서 따라가도록 한다.

 

SpringArmComponent->CameraLagSpeed = 3.0f;

 

bEnableCameraLag가 true인 경우, 카메라가 목표 위치에 도달하는 속도를 제어한다.

 

6. UPawnMovementComponent

 

Pawn의 움직임을 업데이트하는데 사용되는 컴포넌트

 

PawnOwner;

 

이 컴포넌트를 소유하고 있는 폰

 

UMovementComponent::UpdatedComponent;

 

UPawnMovementComponent의 부모 클래스인 UMovementComponent 클래스에 속하는 변수로 이 무브먼트 컴포넌트가 이동시키고 업데이트 해야할 컴포넌트

 

UMovementComponent::ShouldSkipUpdate(DeltaTime);

 

이동된 컴포넌트가 이동할 수 없거나 렌더링되지 않은 경우인지를 판별하여 알려주는 함수

 

ConsumeInputVector();

 

대기중인 입력을 반환하고 다시 0으로 설정하는 함수

 

SafeMoveUpdatedComponent(DesiredMovementThisFrame, UpdatedComponent->GetComponentRotation(), true, Hit);

 

언리얼 엔진 피직스를 이용해서 입체 장애물을 피해서 폰 무브먼트 컴포넌트를 이동시키는 함수

 

SlideAlongSurface(DesiredMovementThisFrame, 1.0f - Hit.Time, Hit.Normal, Hit);

 

컴포넌트가 이동하다가 충돌이 발생했을 때, 제자리에 멈추는 대신 충돌체의 표면을 타고 미끄러지듯이 이동하도록 도와주는 함수

 

AddInputVector(Vector);

 

매개변수로 받은 벡터를 누적 입력에 더하는 함수

 

7. FVector

 

FVector Vector;

 

언리얼 엔진에서 3D 상의 위치나, 속도를 나타내는데 쓰이는 구조체

 

Vector.GetClampedToMaxSize(Value);

 

길이가 Value인 이 벡터의 복사본을 만들어서 반환하는 함수

 

Vector.IsNearlyZero();

 

지정된 허용오차 내에서 벡터의 길이가 0에 근접하는지 확인하는 함수

 

8. FHitResult

 

FHitResult Hit;

 

충돌에 대한 정보를 담고 있는 구조체

 

Hit.Time;

 

Hit가 발생했을 때, TraceStart와 TraceEnd 사이의 충돌이 발생한 시간을 의미한다. (0.0~1.0)

 

Hit.Normal

 

충돌이 발생한 오브젝트의 월드 공간 상의 법선 방향

 

Hit.IsValidBlockingHit();

 

막히는 충돌이 발생했을 때 true를 반환하는 함수

 

9. AActor

 

GetActorRotation();

 

액터의 현재 회전을 반환하는 함수

 

SetActorRotation(FRotator());

 

액터의 회전을 설정하는 함수

 

 

  1. Pawn Movement Component 에는 흔한 물리 함수성에 도움이 되는 강력한 내장 기능이 몇 가지 들어있어, 여러가지 폰 유형에 무브먼트 코드를 공유하기가 좋다. 컴포넌트 를 사용하여 함수성을 분리시켜 놓는 것은 매우 좋은 습관인데, 프로젝트의 덩치가 커지면서 폰 도 복잡해 지기 때문이다. [본문으로]
반응형

+ Recent posts