midas+son의 크리에이티브(creative) 이야기

유니티로 게임을 개발하고

스크립트에서 GetComponent는 자주 사용되는 문법이다.


특정 오브젝트의 Component를 가져와 

컨트롤 할 수 있어 꽤 유용하다.


하지만 함수 자체가 많이 무겁다.

빈번하게 동작되는 함수(Update 등등의 이벤트함수)에서 사용하거나

루프를 돌면서 쓰거나하면 

게임의 성능이 저하된다.


그래서 생각을 잘하여 사용하여야 하는데

보통은 Awake나 Start 함수에서 한번 전역 변수에 할당해주고

변수로 사용을 많이 하게 된다.


하지만 그 외에도 고려해야 하는 방식이 있는데

GetComponent를 사용하는 스크립트가 포함된

오브젝트가 얼마나 자주 새로 생성되느냐 이다.


쉽게 예를 들어서 설명해 보자.

런 게임이 하나 있다.

캐릭터 오브젝트가 있고

코인 오브젝트가 있다.

캐릭터 오브젝트는 단일 오브젝트고

코인은 개별적으로 하나하나 생성된다.

코인을 먹었을 때 나오는 음원 AudioSource의 clip을 Play 하고자 한다.


먹는 순간 Play를 해야 하므로 미리 AudioSource를 할당 받아야 한다.


그렇다면 

코인을 먹었을 때 나오는 음원 AudioSource 를 어디에 둘 것인가?

라는 문제가 발생한다.


코인을 먹는 소리이기 때문에 코인 종속일 것 같다는 생각이 먼저 들수 있지만

코인 하나하나 생성될 때 마다 Awake나 Start에서 GetComponent를 한다면

코인이 100개 라면 100번 GetComponent된다는 것이다.

성능이 생각만 해도 뚝뚝 떨어진다.


캐릭터도 코인을 먹을 때를 체크 할 수 있고

단일 오브젝트이므로 

캐릭터의 자식으로 

AudioSource를 GetComponent하는 것이 바람직하다.

몇 백의 코인을 먹어도 

AudioSource를 가져오기 위한

GetComponent를 캐릭 한번만 사용하므로

게임 성능 저하가 거의 없다.


동적으로 생성되거나 

수가 많을 오브젝트의 경우는 

GetComponent의 사용을 최소화 하자.

'공부 > Unity' 카테고리의 다른 글

Unity Scene Change(씬 바꾸기)  (0) 2016.06.04
AudioSource 다중 사용  (0) 2016.06.03
다중 트리거(Trigger) 조작 문제  (0) 2016.06.01
splat map  (0) 2016.05.30
[잡담]유니티에서 구현하는 2D에 관련하여  (0) 2016.05.27

유니티의 하나의 오브젝트에서

Collider에 Is Trigger를 체크 하면

물리적 충돌이 비물리적 충돌을 체크 할 수 있게 된다.

 

스크립트 상에서 

void OnTriggerEnter(Collider other) 

void OnTriggerStay(Collider other)

void OnTriggerExit(Collider other)

등의 이벤트 함수를 적용 할수 있다.


여기에도 문제가 하나 있다.


하나의 오브젝트에서

Collider를 여러가지 가질 수 있고

Trigger도 여러개 가질 수 있지만

스크립트 상의 이벤트 함수는

공용으로 사용을 한다는 것이다.


하지만 게임을 만들다 보면

Trigger마다 각각 체크하여 

동작을 다르게 해야 할 때가 있다.


이럴 때에는 어떻게 해야 하는가?


해당 오브젝트의 자식으로 Empty 오브젝트를 만들고

그 자식 Empty 오브젝트에 Collider를 달아 trigger로 만든후

OnTrigger 함수만 따로 스크립트로 작성하여 적용하는 것이다.


즉 Trigger 가 2개 이상 필요 하다면

Empty오브젝트로 자식을 만들어

각각 나누어서 적용시키면 된다.

어렵게 고민하지 말고 위처럼 쉽게 생각하여 적용하자.

'공부 > Unity' 카테고리의 다른 글

AudioSource 다중 사용  (0) 2016.06.03
GetComponent<T>() 사용에 대해  (0) 2016.06.02
splat map  (0) 2016.05.30
[잡담]유니티에서 구현하는 2D에 관련하여  (0) 2016.05.27
Ctrl + Shift + f  (0) 2016.05.25

splat map

공부/Unity2016. 5. 30. 13:18

splat map 이란 것이 unity에서 나온 것은 아니다.(셰이더 기법...)

하지만 unity 강의를 들으면서 존재를 첨 알았기에 분류를 unity로 빼겠다.


splat map 이란 것은 아래와 같은 이미지를 먼저 떠올려야 한다.



빨강 노랑 파랑 초록 하양 등등 형형색색이 이미지인데

여러가지의 텍스쳐(texture)를 섞을 때 사용되는 셰이더 기법이다.


컴퓨터는 이미지를 RGB값으로 나누어 판단 할 수 있다.

거기에 alpha값(투명도)까지 추가하여 

RGBA(혹은 RGBW) 4가지의 값을 1바이트씩 (0~255)

총 4바이트로 해당 픽셀의 색상 값을 나타낸다.


하지만 splat map 자체가 이미지가 아니며

데이터라고 생각 해야 한다.


맵(혹은 그외 오브젝트)에 투영될 Texture가 하나가 아니라

2개 이상일 경우 합성을 하게 되는데

합성을 위한 데이터로서 splat map이 작성된다.


예를 들어 보자.

바위이미지 A와 풀 이미지B

2개의 이미지를 합성할 경우 

그 경계선부근은 바위 이미지 절반과

풀 이미지 절반의 색상을 구해서 삽입하게 된다.

그러면 A이미지의 RGB값에 0.5를 곱하고

B의 이미지 RGB값에 0.5를 곱해 더해주면 되고

영향도에 따라 비율을 각각 곱해주면 합성이 되는 것이다.


하지만 그 비율을 어떻게 데이터화 시키느냐가 문제가 된다.

각각 변수에 담기에는 맵의 크기에 따라 컨트롤이 힘들어 질수 있다.

그래서 생각해낸 것이 RGBA에 각각 하나씩 비율을 대입해보자였다.


즉 2가지 이미지를 사용한다면

원본 이미지가 아닌

별도의 이미지를 만들어

R값에 첫번째 이미지의 비율

G값에 두번째 이미지의 비율을 담는다는 것이다.

세번째와 네번째 이미지 소스가 있다면

각각 B, A 값에 비율을 담아 놓으면 되는 것이다.

그것이 바로 splat map이 된다.


splat map의 한 픽셀을 조사 하였는데

RGBA 값이 127, 63, 63, 0 이었다고 하면

첫번째 Texture의 값이 절반 정도, 

두번째 세번째 Texture는 1/4정도 값이 투영되고

네번째 이미지는 없거나 참조 되지 않는 것을 의미한다.


이리하여 Texture들을 합성 하기 위한 

데이터가 또다른 이미지로 나오게 되는 재밌는 기법이 나왔다.


그렇다면 4가지 Texture 보다 더 많은 가지수를 합성 하기 위해서 

어떻게 해야 하는가 궁금증이 있을 수 있다.

그건 별 수 없이 splat map이 하나 더 생성 되어야 한다.

그리고 splat map에 인덱싱을 해줘서

0번 splat map의 RGBA 값, 1번 splat map의 RGBA 값 체크가 

각각 되어야 한다.

그렇기에 물론 성능이 저하된다.

1~4개의 Texture 합성에는 splat map이 하나 필요하고

5~8개는 2개, 9~12는 3개의 splat map이 필요하며

성능은 splat map 수가 많을 수록 저하된다.


그래서 splat map에서 처리할 Texture는 

4개 이하를 권장한다.


요즘 버전의 Unity에서는 

프로젝트 만드는 단계에서부터

3D, 2D 프로젝트를 골라서 만들 수 있고

3D로 만들다가 2D로 전환 할 수도 있다.


하지만 유니티에서 구현 하는 2D는 진짜 2D는 아니다.

보통 2D라 하면 평면 이미지가 2차원적으로 움직이는 것이고

축의 개념 자체가 x, y밖에 없는게 보통이다.(Depth(깊이)는 별개)


유니티의 2D 프로젝트는 

이미지를 3D에 띄워서 2D처럼 한 평면으로 원근감을 없앨 뿐

x, y, z가 그대로 존재한다.


Collider나 rigidbody가 2D가 별도로 존재 하고 

이벤트 함수도 2D용이 별도로 있지만

엄연히 오브젝트들이 3D로 들어 있는 것이다.

2D를 가장한 눈속임용 3D라고 생각해도 

아예 틀린건 아니라는 것이다.


유니티에선 직관적으로 이미지들을 

바로바로 배치 할 수 있고 

테스트나 프로토 타입을 빨리 만들 수 있다는 장점은 확실하나

2D 게임은 복잡한 물리력이 필요한 게임도 많지 않아

저사양의 디바이스까지 생각하고

게임을 만든다면 유니티 엔진 보다는 

좀 많이(혹은 엄청 많이) 수고스럽더라도 

성능상 디바이스에서 자체 지원하는 언어로 

이미지를 띄워 순수 코딩으로 만드는게

낫지 않나라는 생각은 든다.


하지만 이 생각 역시 다른방면으로 돌려 생각하면 무조건이지 않다.

유니티 엔진은 멀티 플렛폼 빌드가 지원되므로 

하나에서 만들어 놓으면

다른 플렛폼에서도 거의 그대로 써도 되므로

시간과 인력을 많이 절약 할 수 있으므로

나쁘지 않다고 할 수 있다. 


시간과 인력은 즉 돈이고 사업이고 운영인것이다.


이러한 이유로 순수 디바이스 코딩으로 게임이 만들어지는 것은 

요즘 세상엔 힘들다는 것이 되겠다.


천재들이 만들어 놓은 엔진을 많이 접해보고

각 게임에 맞는 것을 찾아 쓰도록 하자.

(회사에서 쓰라는 거 쓰면 된다.)

'공부 > Unity' 카테고리의 다른 글

다중 트리거(Trigger) 조작 문제  (0) 2016.06.01
splat map  (0) 2016.05.30
Ctrl + Shift + f  (0) 2016.05.25
InvokeRepeating - 특정 함수 반복 호출, CancelInvoke - 호출 취소  (0) 2016.05.24
Awake(), Start() 의 궁극적 차이  (0) 2016.05.22

Ctrl + Shift + f

공부/Unity2016. 5. 25. 16:24

Ctrl + Shift + f 는 Hierarchy 의 게임 오브젝트 들의 

위치를 Scene에서 바라보고 있는 지점(eye)으로 옮기는 기능이다.


보통 Game뷰에서 바라보는

카메라의 위치를 Scene뷰와 맞추기

바꾸는데 사용한다.


하지만 카메라 뿐만 아니라

조명이든 지형이든 그외 오브젝트 들이든

위치를 Scene뷰의 바라보고 있는 부분으로

조정 할 수 있다는 것을 기억하자.

유니티 내에서 특정 함수를 일정 간격으로 계속 실행 시켜줄 수 있는 함수이다.

이런건 참 잘 만들어 놓은 것 같다.


아래와 같이 사용법은 아주 간단하다.


InvokeRepeating("호출할 함수명", 맨처음 호출 할 딜레이 시간, 첫 시간 이후 반복 시간);



만약 위의 함수를 중지 하고자 한다면


CancelInvoke("호출할 함수명");


를 하면 된다.


나중에 추가될 내용이 있으면 더 추가하도록 하겠다.

지난 글에 이벤트 함수에 대해 말한 게 있었는데 오늘 공부 하다가 하나 확실히 안게 있다.


http://docs.unity3d.com/kr/current/Manual/ExecutionOrder.html

//스크립트 라이프 사이클 플로우차트(Script Lifecycle Flowchart)



Awake(), Start() 두가지 함 수 모두 보통 맨처음 초기화 해줄 때 사용 해줬는데

본인은 Awake를 자주 사용해서 

Awake만 쓰다가 문제가 발생하였었다.


폭탄을 포물선으로 날리기 위해

폭탄을 prefab(자식에 리지드바디가 있는 Sphere가 있음)으로 만들고 

Player가 스크립트에서 

GameObject.Instantiate()을 이용하여

폭탄을 동적 생성을 하게 만들었다.

그리고 폭탄의 방향을 주기 위해 

GameObject bomb = GameObject.Instantiate(bombTemp, this.transform.position, Quaternion.identity) as GameObject;

bomb.transform.rotation = this.transform.rotation;

위와 같이 자신의 방향을 넘겨 주었는데

이부분에서 문제가 있었다.


폭탄 prefab의 자식 ball에 script가 있는데

맨처음에는 

    public void Awake()

    {

        enemyMask = LayerMask.GetMask("Shootable");

        floorMask = LayerMask.GetMask("Floor");

        start = false;

        explosion = false;


        bombRigidBody = transform.GetComponent<Rigidbody>();

        Vector3 startPosition = transform.forward * 0.7f + transform.up * 0.3f;

        bombRigidBody.AddForce(startPosition * 500.0f);   

    }

위 스크립트와 같이 초기화해줄 변수는 초기화 해주고

방향을 가지고 와 힘을 줬는데

방향에서 계속 초기 값이 들어가는 것이었다.


그래서 이것 저것 테스트 해보니 

Player에 있는 bomb.transform.rotation = this.transform.rotation; 부분보다

생성된 Bomb의 Awake()가 먼저 실행된다는 것을 알았다.


그럼 어떻게 하였느냐?


    public void Awake()

    {

        enemyMask = LayerMask.GetMask("Shootable");

        floorMask = LayerMask.GetMask("Floor");

        start = false;

        explosion = false;

    }


    public void Start()

    {

        bombRigidBody = transform.GetComponent<Rigidbody>();

        Vector3 startPosition = transform.forward * 0.7f + transform.up * 0.3f;

        bombRigidBody.AddForce(startPosition * 500.0f);   

    }


위의 script와 같이 

본인만 사용할 뿐인 변수들의 초기화는 Awake() 에

다른 곳에서 값 설정 한 후

컨트롤 하기 위한 초기화는 Start()에 하는 것이

의도한대로 동작 할 방법이었다.


Awake()는 생성 하자마다 들어가는 함수이고

Start()는 Awake() 후에 Update() 전에 동작 하는 이벤트 함수임을 기억하자.

(맨위의 url로 들어가서 다시 한번 이벤트 사이클에 주목하자.)

Awake(), Start(), FixedUpdate(), LateUpdate(), OnCollisionStay(Collision collision), OnCollisionEnter(Collision collision), OnMouseDown(), OnMouseDrag(), OnTriggerEnter(Collider other), OnTriggerStay(Collider other), Reset(), ...

....등등

스크립트에서 유니티 워크플로우(WorkFlow)에 따른 이벤트 함수들을 가져다가 쓸 수 있다.

유니티에서 만든 스크립트들은 기본적으로 MonoBehaviour 를 상속 받아 쓰기 때문이다.


이벤트 함수들의 순서는 아래 공식홈페이지를 참조 하기 바란다.

http://docs.unity3d.com/kr/current/Manual/ExecutionOrder.html

//스크립트 라이프 사이클 플로우차트(Script Lifecycle Flowchart)


위의 함수들을 직접 치다가 오타가 발생하면 

본인이 원하는 타이밍에 함수가 실행이 되지 않아

에러는 안나는데 적용이 안되는 등 원인을 찾기 힘들어 질 수 있다.


그래서 보통 위의 함수들은 찾아서 쓰는 것이 좋다.

Visual Studio 2015 기준 

상단 메뉴 Tools > Extensions and Updates... 에서

Visual Studio 2015 Tools for Unity 가 설치 되어있다면

키보드 Ctrl + Shift + m 을 누르면

위와 같은 창이 뜨고 

원하는 함수를 찾아서 클릭 후 OK누르면 오타 없이 추가된다.


이미 추가한 함수들은 빠져 있다.




Collier 가 있는 GameObject 가

리지드바디(Rigidbody) 가 없으면 

움직이지 않을 정적 오브젝트로 취급이 된다.


유니티(Unity)에서는 정적오브젝트들을 모두 찾아

캐시에 저장하여 처리한다.


하지만 이런 정적인 오브젝트가

transform(Position, Totation, Scale)에 변화가 생기게 되면

유니티엔진은 다시 정적인 오브젝트들을 찾는 연산을 하게 된다.


만약 하나의 Cube 오브젝트를 생성하고 

이 오브젝트를 계속 돌려 배경으로 삼는 다면

매 Frame 마다 정적오브젝트를 찾는 연산을 하므로

최적화된 게임을 만들수 없게 된다.


그렇다면 어떻게 해야 할까?

리지드바디(Rigidbody) 컴포넌트를

해당 GameObject에 추가해서 컨트롤 하면

정적인 오브젝트 취급을 받지 않게 된다.


Add Component 로 Rigidbody를 그냥 추가하게 되면

기본적으로 중력을 받게 설정되어 있으므로

문제가 될 수 있다.

그렇다고 Use Gravity 만 체크 해제 하면 될까?

아니다.

Rigidbody 는 물리력으로 움직이기 때문에 

어떠한 다른 문제가 생길 지 모른다.


그리하여 그 아래에 is Kinematic 을 체크 해주는 것을 권장한다.


Kinematic 이 체크된 객체들은 모든 물리력에 반응 하지 않게되고

스크립트에서 transform을 이용하여 움직여도

정적 오브젝트에 의한 재검사가 일어나지 않아 문제가 되지 않는다.


보통 오브젝트 안에 다른 오브젝트가 들어가거나 

얹게 되거나 

트리거로 된 오브젝트, 고정된 배경 등등

Collider가 있지만 물리력 적용을 

안시킬 객체들에 유용하게 쓰인다.


유니티 하이어라키에 있는 오브젝트 들을 

스크립트에서 삭제 할 수 있다.


Destroy(gameObject);


GameObject 형의 변수를 넣으면 해당 오브젝트는 

삭제된다.


Destroy(gameObject, 3.0f);


이런식으로 뒤에 float 숫자를 넣게 되면 

그만큼 시간이 지나고 삭제가 된다. 

1.0f 당 1초 이다.


하지만 아예 없애는 것보다 

활성, 비활성을 컨트롤 하여

리소스 소모를 줄이는 것을 권장한다.


gameObject.SetActive(false);    //비활성

gameObject.SetActive(true);     //활성


비활성화된 오브젝트는 게임 상에서 안보이게 된다.

비활성화 시킨 오브젝트를

활성 시키면 게임상에서 다시 보인다.