READ UNCOMMIT

- 다른 트랜젝션에서 아직 커밋 되지 않은 데이터를 읽어 올수 있는 level

- commit 되지 않아도 읽어 올 수 있기에 읽어 오고 난 뒤에 rollback 되서 읽어 온 값이 아무런 의미 없는 값이 될 수도 있다.

- 전체적으로 읽어온 데이터의 신뢰성이 낮다(낮다라는 의미는 신뢰 할수 없음이고 신뢰 할수 없다면 신뢰도가 0과 마찬가지다)

- 한 트랜젝션 내에서 동일한 select query의 결과가 다를 수 있다(select 하는 중에 해당 데이터가 변경 될수도 있다)

이전에 없던 row가 생겼거나 없어 졌을 수도 있다.

 

READ COMMITED

- 다른 트랜젝션에서 커밋된 데이터만 읽어 올수 있는 level

- 같은 트랜젝션에서 동일한 select query를 실행 한다고 하더라도 그 사이 다른 트랜젝션에서 commit이 발생 할 수 있으므로 동일 트랜젝션 같은 select query라도 결과가 다를 수 있다.

- 이전에 없던 row가 생겼거나 없어 졌을 수도 있다.

- select query 시 테이블 lock을 잡지 않고 매 select query 마다 snapshot을 구축하여 데이터를 읽어 온다

 

REPEATABLE READ

- 다른 트랜젝션에서 커밋된 데이터만 읽어 올수 있음

- 한 트랜젝션 내에서 동일한  select query의 결과가 같음

- 이전의 select query에 없던 row가 생기지 않는다

- select query 시에 테이블 lock을 잡지 않고 트랜젝션 시작시 구축한 snapshot에서 데이터를 읽어 온다
- 테이블 lock을 잡지 않았기에 select 결과는 같더라도 다른 트랜젝션에서 update, insert 한 것에 대해서는 영향을 받는다.

 

SERIALIZABLE

- 데이터 consistancy 100%

- 매 쿼리 마다 table, row lock 다 잡음

 

https://cecil1018.wordpress.com/2016/06/09/mysql-isolation-level/ 

 

MySQL Isolation level

MySQL은 innodb는  트랜잭션을 지원하며, 다양한 비즈니스 로직을 지원하기 위해 많이 사용되는 4가지 격리(isolation) 레벨을 지원한다. READ UNCOMMITTED 가장 낮은 수준의 격리 수준, 아래의 3가지 현상이 발생 dirty read: 트랜잭션을 수행하는 중 , 다른 트랜잭션에서 커밋하지 않은 수정 내용을 볼 수 있음 non-…

cecil1018.wordpress.com

https://jupiny.com/2018/11/30/mysql-transaction-isolation-levels/

Posted by kukuta
TAG MySQL

댓글을 달아 주세요

사전적 의미의 delegate는 '대리자'로써 뭔가를 대신 해준다는 의미고, C#에서의 delegate는 일종의 콜백 함수 '리스트'로써 입력과 출력이 동일한 함수들을 일괄 호출하는데 사용된다.


이게 C++ 관점에서 보면 함수 포인터 리스트를 들고 있는 간단한 클래스 정도인데, 개념적으로도 어렵지 않고 구현하는 것도 그렇게 어렵지 않다. 말 그대로 함수 포인터 리스트를 만들어도 되고, 인터페이스 클래스를 정의하고 그걸 상속 받은 클래스 리스트를 만들어도 된다. 다만...필요할때 마다 매번 만드는게 은근 귀찮아 template과 operator overriding을 이용하여 재사용 가능한 delegate 클래스를 만들어 보도록 하겠다(c++ 11 이상).


우선 C# delegate의 인터페이스를 살펴 보면 :

  • 콜백 함수를 등록하기 위한 operator '+='
  • 등록된 콜백 함수를 제거하기 위한 operator '-='
  • 등록된 콜백 함수를 개별로 호출 하기 위한 iterate 인터페이스
정도로 정리가 된다.

이 포스트에서는 전체 코드는 살펴 보지 않고 부분 별 구현 내용만 살짝씩 살펴 볼 예정이다. 전체 코드는 다음 링크를 타고가면 된다(전체 코드 보기 : https://github.com/ChoiIngon/gamnet/blob/master/Gamnet/Library/Delegate.h)

클래스 선언 :
template <class T> class Delegate; /* undefiend */
template <class R, class... ARGS> class Delegate<R(ARGS...)>
template <class... ARGS> class Delegate<void(ARGS...)>
기본적으로 std::function과 비슷한 문법으로 사용하기 위해서 템플릿 특수화를 적용했다. 선언만 하고 정의는 구현되지 않은 기본 클래스를 만들고, 그 후 템플릿 특수화가 적용된 Delegate 클래스를 선언한다. std::function 처럼 사용 할 수 있도록 '리턴타입(인자 리스트)' 와 같은 형태로 선언하는데 여기서 눈여겨 봐야 할 부분은 리턴 타입이 void 형인지 아닌지에 따라 특수화를 나눴다는 것이다. 콜백 함수 리스트를 호출하는 부분에서 리턴 타입에 따라 구현이 달라져야 하기 때문에 클래스 자체를 템플릿 특수화를 통해 구분했다.


함수 포인터 리스트 :

std::list<std::function<R(ARGS...)>> funcs;

일반 함수 포인터 뿐만 아니라 클래스 멤버 함수도 저장 할수 있도록 std::function 리스트를 사용 했다.


operator += : 

Delegate<R(ARGS...)>& operator += (std::function<R(ARGS...)> const& arg)

...자세한 설명은 생략한다...


operator -= :

기존에 등록된 함수 포인터 주소를 비교하여 리스트에서 삭제해주는 오퍼레이터다. std::function::target 템플릿 멤버 함수를 사용하는데 여기서 주의 해야 할 부분이 템플릿 클래스의 템플릿 멤버 함수를 호출하게 되면 컴파일러가 타입을 제대로 추론하지 못해 템플릿 멤버 함수에 대해서 컴파일 오류를 발생 시킨다. 이럴 경우 명시적으로 템플릿 함수라는 것을 지정하기 위해 함수의 호출 부 앞에 'template' 키워드를 붙여 준다.

Delegate<R(ARGS...)>& operator -= (std::function<R(ARGS...)> const& arg)

{

R (*const* ptr)(ARGS...) = arg.template target<R(*)(ARGS...)>();

...

참고 : [진리는어디에] - 'template' 키워드를 한정자로 사용하기(C++)


사용 :

#include <iostream>


int f_1(int a, int b)

{

return a + b;

}


int g_1(int a, int b)

{

return a - b;

}


void f_2(int a, int b)

{

std::cout << a + b << std::endl;

}


void g_2(int a, int b)

{

std::cout << a - b << std::endl;

}


int main()

{

Delegate<int(int, int)> delegate_1;

delegate_1 += f_1;

delegate_1 += g_1

std::cout << delegate_1(100, 10) << std::endl; // print 90 only


for(auto itr=delegate_1.begin(); itr != delegate_1.end(); itr++)

{

std::cout << (*itr)(100, 10) << std::endl; // print 110 and 90

}


Delegate<void(int, int)> delegate_2;

delegate_2 += f_2;

delegate_2 += g_2

delegate_1(100, 10); // print 110 and 90


return 0;

}


기본적으로 위와 같이 구현 되어 있고 설명에 언급 되지 않은 것은 리턴 타입이 void인 경우를 위한 클래스를 하나 더 만든 부분이다.


전체 코드 보기 : https://github.com/ChoiIngon/gamnet/blob/master/Gamnet/Library/Delegate.h


Posted by kukuta

댓글을 달아 주세요

  1. Favicon of https://tood-re.tistory.com BlogIcon 먹튀 검증 2018.08.02 14:04 신고  댓글주소  수정/삭제  댓글쓰기

    잘보고갑니다~

템플릿 멤버 함수를 특별하게 구분해야 할 필요가 있는 경우 'template' 키워드를 사용한다. 아래 예제 코드를 보고 언제 'template' 키워드를 한정자로 사용해야 하는지 살펴 보도록 하자 :


class A

{

public :

template<class T> T function_m() {};

};


template<class U> void function_n(U argument)

{

char object_x = argument.function_m<char>();

}


위 예제에서, 컴파일러는 템플릿 인자 U가 클래스 A의 인스턴스라는 것을 추론하지 못하기 때문에 'function_m'이 템플릿 멤버 함수라는 것을 알지 못하고 '<'를 비교연산자로 처리 버려 컴파일 오류가 발생한다. 컴파일러에게 이것이 템플릿 함수를 호출하는 것이라는 것을 알리기 위해선 아래와 같이 'template' 한정자를 함수 호출부 앞에 추가 해주어야 한다 :

char objec_x = argument.template function_m<char>();


만일 템플릿 특수화 멤버 함수의 이름이 ., -> 또는 :: 연산자 뒤에 나온다면 , 그리고 그 이름이 명시적으로 한정된 템플릿 인자라면, 'template' 키워드를 꼭 앞에 붙여 줘야 한다. 다음 예제를 살펴 보자 :

#include <iostream>

using namespace std;


class X

{

public :

template<int j> struct S

{

vodi h()

{

cout << "member template's member function:" << j << endl;

}

};


template<int i> void f()

{

cout << "primary: " << i << endl;

}

};


template<> void X::f<20>()

{

cout << "specialize, not-type argument = 20" << endl;

}


template<class T> void g(T* p)

{

p->template f<100>();

p->template f<20>();

typename T::template S<40> s; //멤버 템플릿 앞에 스코프 오퍼레이터를 사용한다

s.h();

}


int main()

{

X temp;

g(&temp);

}


위 예제의 결과물을 보자 :

primary: 100

specialized, non-type argument = 20

member template's member function: 40


위에서 'template' 키워드를 사용하지 않았다면 컴파일러는 '<'를 비교 연산자로 해석한다. 아래는 오류를 발생 시키는 예다 :

p->f<100>();

컴파일러는 템플릿 멤버 함수 f를 일반 함수로 해석하고 '<'를 비교 연산자로 해석하여  오류를 발생 시킨다.


결론 :

  • 템플릿 형태로 넘겨 받은 인자의 템플릿 클래스 인스턴스의 템플릿 함수를 호출 할때는 'template' 키워드를 한정자로 붙여야 한다.

  • 템플릿 멤버 함수를 썼는데 오류가 발생하면 일단 'template'을 앞에 붙여 보자.


원문 : 

https://www.ibm.com/support/knowledgecenter/en/SSLTBW_2.3.0/com.ibm.zos.v2r3.cbclx01/keyword_template_qualifier.htm

Posted by kukuta
TAG C++, template

댓글을 달아 주세요

Unity가 편한 개발 엔진이라고 아무런 기본 지식 없이 마냥 닥치는대로 하려다보니 이래저래 어려움이 많다.

이 포스팅에서는 Unity를 사용하기 전에 미리 알면 도움이 되는 기본 지식들에 대해서 다뤄 보자

  • SetParent 
    • 개인적으로 worldPositionStays 파라메터를 자주 잊거나 헷깔림
    • worldPositionStays 가 true일 경우. 자식이 되는 오브젝트의 월드 좌표는 변경되지 않는다(화면상으로 보기엔 그냥 그대로 있는것 같다는 말). 하지만 부모가 변경 되었으므로 로컬 좌표가 변경된다.
      예를 들어 부모가 없는 두 오브젝트(부모가 없다는 것은 월드 포지션과 로컬 포지션이 같다는 의미) A가 (0, 1)에, 오브젝트 B가 (1, 0)에 있는 경우, SetParent 함수 호출시 worldPositionStays를 true로 하면 자식이 되는 오브젝트 B의 월드 포지션은 여전히 (1, 0)이고 부모와의 관계를 나타내는 로컬 포지션은 (1, -1)로 변경 된다(유니티 인스펙터에는 로컬 포지션이 표시된다).
    • worldPositionStatys가 false일 경우. 자식이 되는 오브젝트의 로컬 좌표 값을 새로운 부모와 그대로 유지한다. 오브젝트 B가  오브젝트 A의 자식이 되는 경우 B의 로컬 포지션(1, 0)은 그대로 유지되며 대신 월드 포지션이 변경 된다.
  • LLVM(Low Level Vritual Machine) : 프로그램을 컴파일 타임, 링크 타임, 런타임 상황에서 프로그램의 작성 언어에 상관 없이 최적화를 쉽게 구현할 수 있도록 구성. LLVM으로 언어에 가상 기계를 생성, 가상 기계가 언어에 독립적인 최적화를 실행한다.
  • IL2CPP : https://blogs.unity3d.com/2015/05/06/an-introduction-to-ilcpp-internals/
    • Intermediate Language to CPP
  • 좌표계 : Unity uses a number of 2D coordinate spaces, most of which define X as increasing to the right, and Y increasing upwards. The one exception is in the GUI and GUILayout classes, where Y increases downwards.
  • Resources - http://docs.unity3d.com/ScriptReference/Resources.html
    • Assets 폴더 밑 어느 곳에라도 Resources 폴더를 만들고 asset들을 넣어두면 Resources.Load로 접근 가능
    • 개인적으로 Resources 폴더를 어디에 만들어야 할지 몰라 한참을 고생했었기에 기록에 남긴다.
    • Resources 디렉토리 밑에 스프라이트를 두면 SpritePacker로 아틀라스를 만들지 못한다.
  • GameObject - http://docs.unity3d.com/ScriptReference/GameObject.html
    • Unity scene 안에 있는 모든 것들의 베이스 클래스
  • Transform - http://docs.unity3d.com/ScriptReference/Transform.html
    • scene 안에 있는 모든 객체들이 가지고 있는 위치, 스케일, 회전 정보
    • transform은 부모 transform을 가질 수 있다
    • localPosition은 부모의 transform에 상대적 위치 정
    • GetComponentXXX<T> 함수들을 잘 이용하면 객체들을 찾기가 쉬워진다.
  • RectTransform - http://docs.unity3d.com/ScriptReference/RectTransform.html
    • 주로 UI를 위해 사용 됨(아마도..)
    • 위치, 사이즈, 앵커, 피봇 관련 정보들을 담고 있음
    • sizeDelta : RectTransform의 앵커 기준 상대적 사이즈. 만일 앵커가 붙어 있다면 sizeDelta는 size와 동일하다. 만일 앵커가 부모의 각 네 코너에 맞춰져 있다면 sizeDelta는 부모의 사각형 대비 얼마나 크거나 작은지를 나타낸다.
    • rect : eg. rectTransform.rect.height 으로 RectTransform 객체의 높이를 가져 올 수 있다
  • Gizmo : Gizmos are used to give visual debugging or setup aids in the scene view
  • 애니메이션 종료 여부 알아 내기 - http://kukuta.tistory.com/181
  • svn 
  • Help with access/write files on Android : http://forum.unity3d.com/threads/help-with-access-write-files-on-android.72819/
  • 4.6 UI Text rect does not expand automatically
  • 유니티의 입력 값은 스크린 좌표 값인데 게임월드 상의 오브젝트를 다루려면 월드 좌표를 사용해야 한다. 이를 위해서 유니티에서는 ScreenToWorldPoint(http://docs.unity3d.com/ScriptReference/Camera.ScreenToWorldPoint.html) 함수를 제공한다.Event.current.mousePosition은 화면의 좌하단이 (0, 0)이다. 터치 입력과 같은 좌표계(화면 좌 상단이 0, 0)를 사용하는 Input.mousePosition(http://docs.unity3d.com/ScriptReference/Input-mousePosition.html)을 사용하자.
  • BoxCollider 를 설정하고 IsTrigger 옵션을 선택하면 OnCollisionXXX 대신 OnTriggerXXX 함수가 호출 된다. IsTrigger 셋팅이 되어있다면 오브젝트간 물리적 상호 작용은 없고 서로 겹칠때 알림만 온다.
  • saving and loading player game data in unity 
    • http://www.sitepoint.com/saving-and-loading-player-game-data-in-unity/
    • http://gamedevelopment.tutsplus.com/tutorials/how-to-save-and-load-your-players-progress-in-unity--cms-20934
  • Dictionary 시리얼라이즈 : http://answers.unity3d.com/questions/460727/how-to-serialize-dictionary-with-unity-serializati.html
  • content size fitter : 부모 객체의 rect를 자식 객체들에 맞춰서 변경 시켜주는 역할. 하지만 자식에 Vertical Layout 혹은 Horizon Layout 같은 Layout이 붙어야 한다. 텍스트 같은 경우는 아마도 예외인것 같다.
  • JsonUtility - http://docs.unity3d.com/ScriptReference/JsonUtility.html
  • 스크립트 직렬화 - http://docs.unity3d.com/kr/current/Manual/script-Serialization.html
  • Application.OpenURL - https://docs.unity3d.com/kr/current/ScriptReference/Application.OpenURL.html
  • AudioListener.volumn - https://docs.unity3d.com/ScriptReference/AudioListener-volume.html


Posted by kukuta
TAG unity

댓글을 달아 주세요

  1. unisushi 2016.10.25 22:00  댓글주소  수정/삭제  댓글쓰기

    감사합니다 굳!

동기화란?

먼저 '동기화'라는 것에 대해 정의를 하면 '동기화 그룹 내 개체들의 상태 변화와 이벤트 발생 순서를 모든 개체들이 동일하게 인지 할수 있게 하는 작업' 정도로 정의 할수 있겠다. 여기에 추가하여 '실시간'이라는 단어가 추가 되면 최대 지연시간 제한과 같은 조건이 추가 된다.


오늘 생각해 볼것은 게임에서 클라이언트간 동기화를 하려면 무엇을 해야 할까 이다. 이야기에 앞서 먼저 상황을 만들어 보자.


* 하나의 던전에 A, B, C 세개의 클라이언트가 접속 해있다. 여기서 '던전'이 동기화 그룹이다.

* 경과 시간 0 ms 에 클라이언트 A가 좌표 (0, 0)에서 (0, 1)로 초속 1로이동한다.

* 경과 시간 2 ms 에 클라이언트 B가 몬스터1에게 공격을 한다. 하지만 클라언트 B의 네트워크 속도는 매우 느리다.

* 경과 시간 3 ms 에 클라이언트 C가 몬스터1에게 공격을 한다. 클라이언 C는 좋은 네트워크 환경에 있다.


시간 경과 시간 0 ms 부터 3 ms사이에 위와 같은 이벤트들이 발생 했다. 이 상황을 가지고 동기화에 대해서 하나씩 살펴 보자.


위치 동기화

간단하게는 모든 일정 주기 마다 현재 위치를 공유하면 된다. 하지만 메시지가 네트워크를 통해 다른클라이언트들에게 전달 되는 동안 지연은 필수적으로 발생하게 되므로 현재 도착한 위치 정보는 네트워크 지연 만큼의 전 상태다. 그래서 지연 시간을 고려해서 얼마만큼의 상태가 변했는지 예측해서 표시해 줘야 할 필요가 있다. 이것을 '예측'이라고 한다.  예를 들어 클라이언트 A와 B의 평균 네트워크 지연이 100ms일 경우 B에서 A가 보낸 좌표 이동 메시지를 받은 시점은 이미 100 ms 가 지난 상태이므로 클라이언트 A의 움직임을 좌표 (0, 0)에서 부터가 아니라 100ms가 지난 이후 즉, (0, 0.1) 정도로 예측한다.


그리고 '예측'이란 언제나 틀릴수 있다. 예를 들어 A는 (0, 1)로 이동 하려 했으나, 이동 중간에 공격을 받았다거나, 장애물에 부딫히는 등 여러가지 이유로 예측했던 위치와 다른 현상이 발생 할 수 있다. 가장 간단한 해별 방법은 가장 최신의 정보를 받는대로 갱신해주면 된다. 하지만 예측한 위치와 신규 갱신된 위치의 차이가 크다면 캐릭터가 갑자기 텔레포트 하는 것 같은 현상이 보이기도 한다. 이런 현상을 줄이기 위해 사용되는 개념이 '보간(interpolation)' 이다.


보간의 방식은 아래 링크들을 참고하자 :

 * 선형 보간 :

 * Cubic-spline : https://www.gamedev.net/resources/_/technical/multiplayer-and-network-programming/defeating-lag-with-cubic-splines-r914

* cubic hemite interpolation(spline) : http://adnoctum.tistory.com/147


위치 동기화에서 필요한 항목 :

 - 현재 위치

 - 이동 방향

 - 이동 속도(가속도도 고려해 주면 좋겠지만 그럼 너무 계산하기가 어려워지고 거의 10ms 마다 한번씩 동기화가 되는지라 눈에 보이지 않을 정도로 빠르게 이동하는 것이 아니라면 어지간하면 가속도는 고려하지 않아도 된다)

 - 현재 상태, 바라 보고 있는 방향, 애니메이션 프레임 등등

 - 해당 클라이언트와 나와의 평균 ping 타임


이벤트 동기화 :

공격 판정, 피격판정, 이동 시작, 방향 전환 등등의 이벤트 발생 순서가 게임의 결과에 영향을 미치는 것. 즉, 위치 동기화와 같이 순서와 상관 없이 동일한 결과를 내는 것이 아닌 발생 순서에 따라 결과가 달라지는 것들에 대한 동기화를 말한다. 


예를 들어 동기화가 지켜 지지 않는 경우 경과 시간 2ms에 발생한 클라이언트 B의 몬스터1에 대한 공격과 3ms에 발생한 클라이언트 C의 공격이 각 클라이언트 마다 아래와 같이 다르게 보일 수 있다 :

* 클라이언트 A가 보는 시점 : (실제로 B가 먼저 때렸지만 네트워크 지연 때문에 메시지가 늦게 도착 했으므로) C가 먼저 때림

* 클라이언트 B가 보는 시점 : 클라이언트 B가 먼저 때림

* 클라이언트 C가 보는 시점 : 클라이언트 C가 먼저 때림

과 같이 각 클라이언트 마다 다르게 보일 수 있다. 만일 공격 이후 넉백이 있다면 몬스터는 각 클라이언트 마다 서도 다른 방향으로 날아가는 것으로 보일 것이고, 몬스터의 HP가 얼마 남지 않았다면 각 클라이언트 마다 몬스터를 처리한 캐릭터는 서로 다르게 보일 것이다.


위와 같은 이유로 모든 클라이언트들은 동일한 이벤트 발생 순서를 볼수 있어야 하는데 이를 이벤트 동기화라고 한다. 여기서 우리가 주목해야 할 점은 실제 이벤트가 어떤 순서로 발생했는지는 그렇게 중요하지 않다는 것이다. 사실 위의 예에서 B가 먼저 때렸다고 판단하든, C가 먼저 때렸다고 판단하든 그 자체는 별로 중요한 것이 아니다. 설령 실제 늦게 움직였던 C가 먼저 때렸다고 판단하더라도 모든 클라이언트가 동일하게 판단하고 있다면 동기화는 성공한 것이다.


동기화 방식은 Lamport algoritim(http://kukuta.tistory.com/143)과 같은 total oredered multicasting을 사용 할 수도 있고 중앙 집중형 동기화 제어 컴포넌트를 하나 두고 모든 동기화를 해당 컴포넌트에게 맡겨 버리는 방식이 있다. total oredered multicasting 과 같은 방식이 장애에 강하고 로드를 분산 시킬수 있다는 장점이 있긴 하지만 모든 동기화 그룹내에 모든 멤버들에게 브로드캐스팅하고 응답을 받아야 하므로 동기화 가능 최소 시간이 가장 늦은 클라이언트 기준으로 잡히는 단점이 있다. 게임에서는 반응 속도가 중요하므로 중앙집중형을 선택하는 것도 좋을 수 있다.


Posted by kukuta

댓글을 달아 주세요

"터는 두 점들 사이의 차이 또는 변위(displacement)를 나타낸다."

기하학에서의 벡터..
기하학에서의 벡터 (일반적으로 'v' 라고 표현 된다)는 크기(or 길이)와 방향을 가지는 요소이고 끝이 화살표인 선분으로 표현된다(그림 1).

그림 1그림 1


길이(or 크기)가 1인 벡터는 '단위' 또는 '정규화된' 벡터라고 한다. '제로 벡터'는 길이(or 크기)가 0이고 방향을 가지지 않는다. 또한 벡터는 위치를 가지지 않는다. 위치와 상관 없이 동일한 크기와 방향을 가지는 벡터는 같다는 것을 기억하자.

게임에서 벡터는 '방향'과 '변화', 이 두가지 의미로 사용 될 수 있다. 설명은 뒤에 벡터의 연산부분에서 차차 하도록 하겠다.

벡터의 연산
실수와 마찬가지로 벡터도 산술 연산이 가능하다. 기본적인 연산은 '덧셈'인데 기하학적으로 덧셈은 두 벡터를 결합해서 새로운 벡터를 만든다. 
R = A + B

그림 2


R = A- B

그림 3


뺄셈의 결과 벡터는 두번째 벡터(B)의 머리에서 첫번째 벡터(A)의 머리로 이어진 다는 것을 기억하자.

'벡터 덧셈'의 대수학적 법칙들은 실수의 그것과 상당히 유사 하다 :
  1. v + w = w + v (교환 법칙)
  2. u + (v + w) = (u + v) + w (결합 법칙)
  3. v + 0 = v (덧셈의 항등원)
  4. 모든 v에 대해 v + (-v) = 0 식을 만족하는 벡터 -v가 존재한다. (덧셈의 역원)

또 다른 연산자로 '스칼라 곱셈'이 있는데, 하나의 실수 값을 벡터에 곱해서 벡터의 길이를 변화 시킨다  :
  5. (ab)v = a(bv) (결합 법칙)
  6. (a + b)v = av + bv (분배 법칙)
  7. a(v + w) = av + aw (분배 법칙)
  8. 1 dot v = v (곱셈의 항등원)

내적(dot product)
내적은 scalar product(스칼라 곱) 또는 dot product라고도 하며 기호로 ·(dot)을 사용한다.
내적을 구하는 방법은 두가지 :
1. 벡터의 각 성분값을 곱하여 더하는 것
  A·B(벡터 A와 B의 내적)
  = (Ax * Bx) + (Ay * By)
2. 두 벡터의 크기를 곱하는 것
<u,v>= u·v = |u| |v| cosθ
- |u|는 u벡터의 크기를, |v|는 v벡터의 크기를 의미합니다. (벡터의 크기에 관해서는 [진리는어디에] - 벡터(vector)의 길이를 참고해 주세요)
θ는 u라는 벡터와 v라는 벡터가 이루고 있는 0과 180도 사이의 각입니다
- 여기서 벡터 v의 크기가 아니라 |v|cosθ 와 같이 곱해주는 이유는 실제 벡터 u에 미치는 영향은 |v|cosθ 이기 때문이다.
- 폴리곤의 노말 벡터와 라이트의 디렉션 벡터를 내적해서 빛의 반사량을 결정 할수 있다.
- 프로그래밍을 할때 내적은 보통 단위 벡터와 함께 사용된다.
외적(cross product)
a X b : a cross b로 읽음

두 갱의 벡터에 수직하는 벡터의 방향과 크기를 구하는 연산

...
오늘은 여기까지..
http://oldadams.springnote.com/pages/5135455
http://icprmr.snu.ac.kr/PHP/ICPR_IP/DRSong/PDF_FILES/Ch03.pdf


'진리는어디에' 카테고리의 다른 글

Unity를 사용하기 전에 알아두면 좋은 기본 개념들  (1) 2017.04.07
게임에서의 동기화  (0) 2017.03.28
벡터(vector)의 연산  (0) 2016.09.21
삼각함수  (0) 2016.06.27
리팩토링 - Position 클래스  (0) 2016.02.14
Unity Asset Bundle  (0) 2016.01.29
Posted by kukuta
TAG 3d, math

댓글을 달아 주세요

삼각함수

진리는어디에 2016. 6. 27. 20:36

기본적인 삼각함수의 개론은 위키 페이지 참조 : https://ko.wikipedia.org/wiki/%EC%82%BC%EA%B0%81%ED%95%A8%EC%88%98


1. 라디안 :

  • 원에서 반지름의 길이와 같은 길이를 갖는 호에 대응하는 중심각의 크기
  • 1 rad = 180도 / π
  • 1도 = 1 rad * π / 180
  • 360도 = 2π rad
  • θ (세타) - 수학에서 미지의 각도를 나타낼때 쓰임(https://ko.wikipedia.org/wiki/%CE%98)

2. 삼각함수


  • 2. 단위원 정의 : 
    • 좌표평면에서 원점을 중심으로 하고 반지름 r의 길이가 1인원을 '단위원'이라 한다.
  • 단위원 위의 점 A(x, y)에 대해, x축과 원점의 선분, 점A와 원점을 잇는 직선간의 각을 θ 라디안이라고 한다.
  • 위와 같을 경우 :


 



'진리는어디에' 카테고리의 다른 글

게임에서의 동기화  (0) 2017.03.28
벡터(vector)의 연산  (0) 2016.09.21
삼각함수  (0) 2016.06.27
리팩토링 - Position 클래스  (0) 2016.02.14
Unity Asset Bundle  (0) 2016.01.29
Unity Coroutine  (0) 2016.01.22
Posted by kukuta

댓글을 달아 주세요

만들다 보니 각 오브젝트의 위치 동일 여부를 매번 if(a.x == b.x && a.y == b.y)와 같이 비교하는것이 여간 귀찮은 일이 아닙니다. 그리고 위치를 리턴할 때도 애매합니다. 리턴 값은 한번에 하나 밖에 할수 없기 때문에 x 좌표와 y 좌표를 얻어 오는 함수를 각각 만들수 밖에 없습니다. 너무 번거롭고 귀찮은 일입니다. 그래서 이번 장에서는 좌표를 추상화한 'Position' 클래스를 만들겠습니다. 그리고 추가적으로 C#의 '참조 일치'와 '값 일치'에 대한 개념을 배워 보도록 하겠습니다.


먼저 Position 클래스를 정의해보겠습니다.

위치를 나타내는 x와 y를 가진 단순한 클래스 입니다. 그런데 우리의 불편함이 뭐였죠? 네, 좌표의 비교였습니다.

if(a.x == b.x && a.y == b.y) 가 아닌 if(pos1 == pos2) 처럼 간단하게 비교 구문을 작성하는 것이었습니다. 위의 코드까지만 작성하더라도 아래와 같은 비교 구문에서 아무런 에러 없이 컴파일이 되긴합니다. 하지만 결과는 우리가 원하는 형태가 아닙니다.


결과는 분명히 둘다 (0, 0) 좌표를 가지고 있는데 다르다고 출력 합니다. 왜일까요?


원인은 C#의 일치에는 '참조 일치(reference equal)'와 '값 일치(value equal)'라는 두 종류의 '일치'가 있기 때문이고 우리가 == 오퍼레이터에 기대하는 결과는 값 일치이지만 C#클래스의 == 디폴트 오퍼레이터는 참조 일치를 평가 합니다. 


만일 두개의 객체가 동일한 값을 가지고 있다고 하더라도 이것은 객체가 같음을 의미하는 것은 아닙니다. 기본적으로 C#에서 객체에 대한 == 또는 != 비교 연산은 '참조 일치'를 비교하는 것이지 '값 일치를' 비교하는 것이 아니기 때문에 위와 같은 현상이 발생한것 입니다. 


"참조가 일치하면 값도 일치하지만, 값이 일치한다고 반드시 참조가 일치하는 것은 아니다."


우리가 지금 필요한 것은 Position 클래스의 == 혹은 != 연산자를 사용했을 때 값 일치를 비교하는 것입니다. 이를 위해서 연산자 오버로딩을 이용하여 참조 비교를 값 비교로 변경해 주도록 하겠습니다.



잠깐 ReferenceEquals 살펴 보도록 하겠습니다. 객체를 null과 비교 할때 if(null == rhs) 와 같은 방법이 아닌 ReferenceEquals 와 같은 별도의 비교 함수를 이용했습니다. 이는 '==' 오퍼레이터의 재귀적 호출을 막기 위해서 입니다. 만일 '==' 연산자를 사용한다면 재귀적으로 == 오퍼레이터 오버라이딩 부분을 스택 오버플로우가 발생 할때 까지 호출하게 됩니다. null은 아무런 객체를 참조하지 않고 있다라는 의미이고 이런 참조 일치는 'ReferenceEquals'를 통해 체크합니다. 참고로 값 일치를 체크하는 함수는 'Equals'입니다.


Position 클래스를 Dictionary 와 같은 자료구조에서 키로 사용 할 수 있도록 하기위해 GetHashCode도 재정의 해줍니다.


하는 김에 Position 클래스를 유니티에서 기본 좌표 객체로 사용되는 Vector2로 묵시적 변환 해주는 오퍼레이터도 추가했습니다.위 오퍼레이터로 인해 Vector2.Distance과 같은 Vector2를 인자로 받는 유니티 함수들을 Vector2.Distance(new Position(0, 0), new Position(1, 1))과 같이 별도의 자료구조 변환 코드 없이 사용 할 수 있습니다.


이상으로 좌표를 추상화한 Position 클래스를 만들었습니다. 이제 이 클래스를 기존 플레이어와 몬스터 클래스에 적용해 보도록 하겠습니다. 다행히도 우리는 이전 장에서 Character 클래스를 통해 플레이어와 몬스터 클래스의 공통부분을 분리해 두었습니다. 리펙토링 이전이라면 두 클래스에 각각의 변경을 적용해 주어야 하지만 이젠 Character클래스의 수정만으로 두 자식 클래스에 변경을 적용할 수 있습니다.

Character.cs


'진리는어디에' 카테고리의 다른 글

벡터(vector)의 연산  (0) 2016.09.21
삼각함수  (0) 2016.06.27
리팩토링 - Position 클래스  (0) 2016.02.14
Unity Asset Bundle  (0) 2016.01.29
Unity Coroutine  (0) 2016.01.22
브레즌햄(Bresenham) 알고리즘  (0) 2016.01.16
Posted by kukuta

댓글을 달아 주세요

이번 포스팅에서는 어셋 번들의 기본 개념과 유니티에서 어셋스토어를 통해 제공하고 있는 '어셋번들 매니저'를 이용하여 어셋번들을 만들고 로드하는 과정에 대해 알아 보도록 하겠습니다.

'어셋번들(Asset Bundle)' 이란?
어셋번들(Asset Bundle)은 유니티에서 사용하는 실행 어플리케이션과 별도로 배포/로딩 될 목적으로 저장된 어셋들과 씬들을 집합한 파일로써 필요시에 로컬에서 직접 로딩 혹은 원격 저장소에서 다운로드 받은 후 로딩 할수 있는 기능을 제공합니다. 이를 통해 새로 빌드된 어플리케이션의 자체의 배포 없이 변경 되거나 신규로 추가된 데이터만을 필요시에 동적으로 배포 할수 있도록 해줍니다. 이를 통해 어플리케이션의 데이터가 변경 될때 마다 각 스토어에 검수를 받을 필요 없이 컨텐츠를 업데이트 한다던지, DLC 등을 쉽게 구현 할 수 있습니다. 

좀 더 구체적으로, 어셋번들에 포함 될수 있는 어셋은 스크립트를 제외한 유니티에서 사용하는 거의 모든 것 - 모델, 머트리얼, 텍스쳐, 오디오 클립, 심지어는 커스텀 바이너리 데이터 까지 - 이며 스트리밍 기반의 비동기 로딩을 가능하게 도와 줍니다.

어셋번들의 기본적인 속성은 아래와 같습니다 :
  • 어셋번들은 전체가 다운로드 되고 캐싱 됩니다. 그렇다고 전체를 로드 할 필요는 없다. 번들 내에 필요한 어셋만 구분하여 로드할 수 있습니다.
  • 어셋번들에 포함된 어셋들은 서로 의존성을 가질 수도 있습니다. 그리고 그 의존성을 공유 할 수 있다. 예를 들어 게임 오브젝트 A와 B가 동일한 머트리얼과 텍스쳐 어셋을 공유 할 수 있다는 의미 입니다.
  • 각 어셋 번들은 파일의 사이즈와 관리하는데 기술적 오버헤드를 가진다. 
  • 어셋 번들은 각 타겟 플랫폼에 맞추어 빌드 되어야 한다.

어셋번들의 메니페스트(Manifest)와 의존성 관리
어셋번들에서 어셋의 의존성은 절대 누락되지 않습니다. 여기서 '의존성'이란 아래 그림과 같이 모델이 머트리얼을 참조하고, 머트리얼은 텍스쳐를 참조하는 각 어셋들의 참조 관계를 의미합니다. 

탱크 모델 어셋의 의존성 체인 : 모델 > 머트리얼 > 텍스쳐

서로 의존성을 가지고 있는 어셋은 명시적으로 어셋번들 이름이 지정되어 있지 않다고 하더라도 자동적으로 어셋번들에 포함되어 빌드 됩니다. 이런 방법은 모든 참조 어셋들에 대해 일일이 관리할 필요 없이 모델과 같은 최상위 어셋만 어셋번들을 명시적으로 지정해 주면 된다는 편리함이 있는 반면, 어셋번들에 포함되는 어셋들의 중복을 유발 할 수 있다는 단점이 있습니다. 예를 들어 두개의 모델이 하나의 머트리얼을 공유한다고 가정해 보겠습니다. 그리고 각각의 모델은 다른 어셋번들에 포함되도록 명시적으로 지정되어 있고, 머트리얼에는 명시적으로 어셋번들이 지정 되어 있지 않다고 하겠습니다. 이런 경우 머트리얼은 자동적으로 두 어셋번들에 각각 독립적으로 추가 됩니다. 결국 머트리얼의 공유라는 것이 의미가 없어집니다. 이런것을 방지하기 위해서는 머트리얼은 명시적으로 어셋번들에 할당 되어야 합니다. 


어셋번들의 의존성과 기타 다른 정보들은 메니페스트(Manifest)에 저장됩니다. 메니페스트는 일종의 컨텐츠 테이블 같은 것으로써, 어셋번들이 빌드 되면 유니티는 많은 양의 데이터를 생성하고 해당 데이터들의 상세정보가 메니페스트에 저장됩니다. 메니페스트는 어셋번들의 타겟 빌드마다 따로 만들어지며 이 메니페스트 파일을 통해 모든 어셋번들에 대한 질의와 의존성 참조가 이루어 집니다. 추후 뒤에 나오는 어셋번들의 빌드 과정이후 각 결과 폴더를 살펴 보면 어셋번들 하나당 .manifest라는 확장자를 가진 파일이 만들어 지는 것을 확인 할 수 있습니다.


어셋번들 매니저(Asset Bundle Manager)?
어셋번들을 사용 할때 가장 많은 노력이 필요한 부분은 어셋번들을 빌드하고 정상적으로 로딩 되는지 테스트 하는 부분입니다. 개발 중 어셋번들에 포함 되는 어셋들은 얼마든지 변경되거나 추가, 삭제 될 수 있습니다. 기본적으로  이런 경우 전체 어셋들을 다시 빌드하고, 원격 저장소에 업로드하고, 정상적인 다운로드와 로딩을 테스트 하는 과정을 반복해야 합니다. '어셋번들 매니저'(AssetBundle Manager)는 이와 같은 복잡하고 반복적인 작업을 단순한 클릭과 간단한 스크립트 작업으로 해결할 수 있도록 도와 줍니다.

어셋번들을 만들고 사용하는 과정을 요약하면 대충 아래와 같습니다 :
  1. 유니티 에디터에서 어셋번들에 포함될 어셋들을 지정
  2. 어셋번들 빌드
  3. 외부 저장소에 어셋번들을 업로드
  4. 어플리케이션에서 어셋번들을 다운로드
  5. 어플리케이션에서 어셋번들을 로드

어셋번들 매니저를 이용하면 위 과정중 1번. 어셋번들에 포함될 어셋들을 지정하는 작업외에는 모든 부분을 어셋번들 매니저에게 맡길수 있습니다.


1. 어셋번들에 포함될 어셋들을 지정
어셋들을 어셋번들로 빌드하기 위한 첫번째 과정은 해당 어셋이 어떤 어셋번들에 포함될 것인지, 어셋번들의 이름을 지정해 주는 것입니다. 여러분들은 이것을 위해서 유니티 에디터에서 간단한 설정 몇가지만 해주면 됩니다. 프로젝트(Project) 탭의 어셋을 클릭하면 인스펙터(Inspector)창에 해당 아래와 같은 어셋의 프리뷰를 볼수 있습니다.

어셋을 어셋번들에 포함되게 하기 위해서는 Asset Bundle Name 드롭 다운 메뉴를 이용하여 어셋이 어떤 어셋번들에 포함 될 것인지 지정해 주기만 하면 됩니다.


  1. 드롭 다운 메뉴를 클릭
  2. 포함 될 어셋번들의 이름을 선택.
    새로운 번들을 만들고 싶다면 'New...'를 선택 후 이름을 할당해 주고, 제외하고 싶다면 'None'을 선택
  3. 사용되지 않는 어셋번들을 지우고 싶다면 'Remove Unused Name' 선택

2. 어셋번들 베리언트(Asset Bundle Variant) 사용

어셋번들은 어셋 리소스를 저장하고 로드하는데 마주하는 많은 어려움들을 해결하는데 도움을 줍니다. 특히 이번 장에서 알아볼 어셋번들 베리언트는 타겟 디바이스나 로컬라이징, 유저 설정에 따라 다양한 어셋번들중 특정 버젼을  쉽게 로딩 할수 있도록 도와 줍니다.

예를 들어 어플리케이션이 한국에서 실행 되느냐 미국에서 실행 되느냐에 따라 메뉴가 한글이 될수도 있고 영어가 될수 있습니다. 이런 경우 모든 언어에 대응하는 텍스트 어셋번들을 만들고 배포하는 것이 아니라 한글버젼 어셋번들과 영어버젼 어셋번들을 만들고 어셋번들 베리언트를 이용해 구분하여 필요한 국가의 언어팩만 로드 할 수 있습니다.

어셋번들 베리언트를 이용하기 위해서는 :

  • 'Asset Bundle Name'은 동일해야 합니다
  • 유일하게 달라야 할 부분은 Asset Bundle Variant 부분 입니다.
  • 어셋번들 베리언트를 이용한 어셋번들간에 호환성을 가지려면 어셋번들에 포함 되는 컨텐츠의 폴더와 모든 어셋들은 같은 이름과 같은 계층구조를 가져야 합니다.

Project 탭 


어셋번들 실전에서 사용하기

지금까지 어셋 번들의 기본 개념에 대해서 알아 보았습니다. 이번 장에서는 우리가 어셋 번들과 관련된 작업을 함에 있어서 큰 도움을 줄수 있는 '어셋번들 매니저'(AssetBundleManager)를 이용하여 어셋 번들과 번들에 포함된 어셋들을 로드하는 방법에 대해서 알아보도록 하겠습니다. 


이전 장에서 우리는 어셋 번들을 사용하기 위해서는 빌드를 해야 하고, 원격 저장소 혹은 로컬 저장소에 배포를 해야 하며, 우리의 어플리케이션 내에서 API를 사용하여 로드를 해야 함을 배웠습니다. 만일 어셋 번들에 포함되는 어셋 중 하나라도 변경이 된다면, 변경이 발생할 때 마다 우리는 빌드 -> 배포 -> 테스트와 같은 지루하고 오랜 시간이 걸리는 작업을 반복 해야 합니다. 어셋 번들 매니저는 '시뮬레이션 모드'와 '로컬 어셋 번들' 서버를 제공함으로써 실제 빌드, 배포 과정을 거치지 않고도 정상적으로 어플리케이션이 어셋번들과 연동되는지 테스트 해볼수 있는 기능을 제공합니다.

  1. 시뮬레이션 모드
    시뮬레이션 모드가 활성화 되면 어셋번들 매니저는 프로젝트에 포함된 어셋들을 빌드하는 시뮬레이션을 수행합니다. 단순히 유니티 에디터 메뉴에서 “Assets/AssetBundles/Simulation Mode”를 체크해주면 시뮬레이션 모드가 활성화 됩니다. 시뮬레이션 모드에서는 실제 어셋번들 빌드를 수행하는 것이 아닌 프로젝트 런타임 시점에 빌드과정에 대해 시뮬레이션만 진행하는 것이므로 실제 빌드 보다 훨씬 빠른 속도를 제공합니다. 단, 주의할 점은 어셋번들 베리언트를 사용시 시뮬레이션 모드에서는 동작을 하지 않습니다.
  2. 로컬 어셋 번들 서버
    로컬 어셋 번들 서버는 보다 정확한 어셋 번들 배포 시뮬레이션을 제공합니다. 로컬 어셋 번들 서버를 사용하기 위해서는 유니티 에디터 메뉴에서 “Assets/AssetBundles/Local AssetBundle Server”를 활성화 하고 어셋 번들 빌드을 빌드하고 빌드된 어셋번들을 기본 폴더(프로젝트 최상위 폴더/AssetBundles)에 저장하는 과정이 필요합니다. 로컬 어셋 번들 서버가 활성화 되면 빌드된 어셋번들은 에디터와 모든 로컬 빌드에서 접근이 가능합니다. 로컬 어셋 번들 서버는 어셋 번들 베리언트를 로컬에서 테스트 해볼수 있는 유일한 방법이며 샘플로 제공된 씬을 구동하기 위해서 로컬 어셋 번들을 활성화 시키는 과정이 반드시 필요한 경우도 있습니다.

    어셋 번들 매니저가 제공하는 기능들을 살펴 보았으니 지금 부터는 로드에 필요한 API들에 대해 어셋 번들 매니저 샘플 예제를 통해 살펴 보도록 하겠습니다. 먼저 어셋 번들 매니저가 제공하는 API를 살펴 보도록 하죠 (AssetBundleManager/AssetBundleManager.cs 참고) :

    • Initialize() : 어셋번들 메니페스트(http://docs.unity3d.com/ScriptReference/AssetBundleManifest.html) 객체를 초기화 합니다.
    • LoadAssetAsync() : 어셋번들로 부터 지정된 어셋과 의존성을 가지고 있는 모든 다른 어셋들을 로드 합니다.
    • LoadLevelAsync() : 어셋번들로 부터 지정된 신과 의존성을 가지고 있는 모든 다른 어셋들을 로드 합니다.
    • LoadDependencies() : 지정된 어셋번들이 의존성을 가지고 있는 모든 다른 어셋번들들을 로드 합니다.
    • BaseDownloadingURL : 기본 다운로드 URL 경로를 지정합니다.
    • SimulateAssetBundleInEditor : 유니티 에디터의 시뮬레이션 모드를 활성화 합니다.
    • ActiveVariants : 어셋번들 베리언트를 저장합니다.
    • RemapVariantName() : 어셋번들 베리언트 네임을 이용하여 적합한 어셋번들 이름을 리턴합니다.

    AssetBundleManager 패키지에는 위 API들의 사용 예를 보여주는 샘플 파일들이 AssetBundleSample 폴더에 포함되어 있습니다. 샘플에는 세가지 기본 예제 씬과 세가지 예제들을 종합적으로 보여 주는 예제 씬이 'AssetBundleSample/Scenes'에 있습니다 :

    • AssetLoader : 어셋번들로 부터 일반 어셋을 어떻게 로드하는지 보여 줍니다.
    • SceneLoader : 어셋번들로 부터 씬을 어떻게 로드하는지 보여 줍니다.
    • VariantLoader : 어셋번들 베리언트를 이용하여 어셋을 로드하는 방법을 보여 줍니다.
    • LoadTanks : 위 세가지 예제를 조합하여 보여 줍니다.

    각각의 씬들은 LoadAssets.cs, LoadScenes.cs, LoadVariants.cs, LoadTanks.cs 스크립트에 구현 되어 있습니다.


    예제 1. 어셋 로딩하기

    1. 유니티 에디터 메뉴 "Assets > AssetBundles > Simulation Mode" 에서 시뮬레이션 모드 활성화 합니다.
    2. 프로젝트 탭에서 "AssetBundleSample/Scenes/AssetLoader" 씬을 엽니다. 플레이 모드를 실행하기 전에 씬엔 메인 카메라와 라이트, 로더 게임 오브젝트 외에는 아무것도 없음을 주목해주세요.
    3. 플레이 버튼을 눌러 프로젝트를 실행 합니다. 이때 어셋번들로 부터 큐브를 로딩하여 씬에 보여 줍니다.
    4. 씬과 관련된 스크립트는 'LoadAssets.cs'에서 확인 할 수 있습니다.
    AssetBundleSample/Scripts/LoadAssets.cs
    "AssetBundleSample/Scripts/LoadAssets.cs" 스크립트를 에디터에서 열어 주세요. LoadAssets 클래스에는 두개의 public 변수가 있습니다.
    • public string assetBundleName : 로드 될 어셋 번들의 이름
    • public string assetName : 로드 될 어셋의 이름

    이 스크립트는 Start() 함수와 Start() 함수에 의해 호출 되는 두개의 코루틴으로 구성되어 있습니다. 

    • Initialize() 함수에서는 DontDestroyOnLoad()를 호출 하고 난 후, 어셋 번들의 패스와 어셋 번들 매니페스트를 초기화 합니다. 필요해 보이지 않는 DontDestroyOnLoad()를 굳이 사용하는 이유는, 어셋번들 로딩을 관리하는 이 게임 오브젝트가 씬이 변경된다거나 할 때 지워지는 것을 방지하기 위해서 입니다.
    • InstantiateGameObjectAsync() 함수에서는 어셋과 어셋번들의 이름을 인자로 받아 AssetBundleManager.LoadAssetAsync() 함수를 호출 합니다. 이때 리턴 값이 null이 아닌 경우 해당 객체(프리팹)을 인스턴스 화 합니다.

    여기서 주의 깊게 볼 한가지는 프로젝트 탭 "AssetBundleSample/Assets"에 있는 "MyCube" 어셋은 "MyMaterial"에 의존성을 가지고 있고, "MyMaterial"은 "UnityLogo"에 의존성을 가지고 있는데, 우리가 한 일은 단지 "MyCube"만을 로드 했을 뿐이지만 어셋번들 매니저는 메니페스트를 통해 의존성을 파악하고 관련 모든 어셋들을 정확하게 로드해 준다는 것입니다. 또 한가지는 어떻게 어셋 번들의 패스가 셋팅 되는가 입니다. "LoadeAsset" 클래스의 코드에서는 씬이 로딩 될때 실행 환경이 에디터이거나 개발 버젼 빌드일 경우 어셋번들 위치를 로컬 어셋번들로 셋팅하는 것입니다(보다 정확하고 다양한 정보를 원하신다면 퍼블리싱 빌드 문서를 참고 해주세요).

    시뮬레이션 모드가 활성화 되어 있는 동안 에디터에서 플레이 모드를 실행 시키면 어셋번들은 실제 빌드와 다운로드 과정을 거치지 않고 단지 위과정들을 시뮬레이션만 합니다.


    예제 2. 씬 로딩하기

    1. 유니티 에디터 메뉴 "Assets > AssetBundles > Simulation Mode" 에서 시뮬레이션 모드 활성화 합니다.
    2. 프로젝트 탭에서 "AssetBundleSample/Scenes/SceneLoader" 씬을 엽니다. 플레이 모드를 실행하기 전에 씬엔 메인 카메라와 라이트, 로더 게임 오브젝트 외에는 아무것도 없음을 주목해주세요.
    3. 플레이 버튼을 눌러 프로젝트를 실행 합니다. 이때 어셋번들로 부터 큐브와 펑면을 로딩하여 씬에 보여 줍니다.
    4. 씬과 관련된 스크립트는 'LoadScene.cs'에서 확인 할 수 있습니다.
    AssetBundleSample/Scripts/LoadAssets.cs
    "AssetBundleSample/Scripts/LoadScene.cs" 스크립트를 에디터에서 열어 주세요. LoadScene 클래스에는 두개의 public 변수가 있습니다.
    • public string sceneAssetBundle : 로드 될 어셋 번들의 이름
    • public string sceneName : 로드 될 씬의 이름

    이 스크립트는 Start() 함수와 Start() 함수에 의해 호출 되는 두개의 코루틴으로 구성되어 있습니다. 

    • Initialize() 함수에서는 DontDestroyOnLoad()와 어셋 번들의 패스를 셋팅하고 어셋번들 메니페스트를 초기화 합니다.
    • InitializeLevelAsync() 함수에서는 씬의 이름과 isAdditive 변수를 인자로 받아 AssetBundleManager.LoadAssetAsync() 함수를 호출 합니다. 이때 리턴 되는 씬이 null인 경우 콘솔창에 에러 메시지를 출력하고 코루틴은 종료 됩니다. 
      InitializeLevelAsync()함수는 isAddtive 인자에 따라 내부적으로 Application.LoadLevelAdditiveAsync() 함수 혹은 Application.LoadLevelAsync()함수를 호출합니다. 두 함수 모두 씬 이름을 인자로 받아 씬을 백그라운드에서 비동기 로딩한다는 것은 같지만 LoadLevelAdditiveAsync()함수의 경우는 기존 씬에 로딩 되어 있는 게임 오브젝트들을 삭제하지 않고 추가만 한다는 것에 차이가 있습니다.
      만일 여러분께서 InializeLevelAsync() 함수의 isAdditive 인자를 false로 수정한 후 실행 한다면 Hierarchy 탭에 기존 신에서 로드 되어 있던 카메라와 라이트 오브젝트가 사라지고 큐브와 바닥만 로드 되어 있는 것을 확인 할수 있을겁니다.

    어셋 번들 매니저를 이용한 씬 로딩 역시 예제 1과 동일하게  의존성에 따라 필요한 어셋들을 자동적으로 로드 합니다. 우리가 로드한 것은 "TestScene" 하나지만 "TestScene"에 포함되어 있던 "Cube"와 그외 모든 어셋들이 로드 됩니다.


    예제 3. 어셋번들 베리언트 사용하기

    이번 장은 예제에 대한 본격적인 설명에 들어가기 앞서 약간의 부연 설명이 필요합니다. 어셋번들 베리언트는 시뮬레이션 모드에서는 구동되지 않고 로컬 어셋번들 서버를 사용하거나 실제 원격 저장 공간을 이용해야 합니다. 때문에 필수적으로 어셋번들이 빌드 되어야 합니다. 이전에 언급 되었듯이 어셋번들과 번들에 포함된 베리언트를 빌드하기 위해서는 빌드 대상 어셋들에 어셋번들 이름이 지정되어 있어야 합니다. 물론 베리언트를 사용할 어셋이라면 어셋번들 베리언트 네임 역시 지정되어야 합니다.


    어셋에 어셋번들 이름과 베리언트가 지정되면 에디터 메뉴 "Assets/AssetBundles/BuildAssetBundles"를 통해 어셋번들을 빌드 할 수 있습니다. 기본적으로 어셋번들은 현재 빌드 타겟에 최적화 되어 빌드 된 후 "프로젝트 폴더/AssetBundles" 폴더 아래에 빌드 타겟별로 그룹화(Android/OSX..) 되어 저장됩니다.


    편의성과 새로 빌드된 어셋번들을 배포 과정 없이 테스트 해보기 위해 로컬 어셋 번들 서버가 활성화 되어 있어야 합니다. 에디터 메뉴 "Assets/AssetBundles/Local AssetBundle Server"를 통해 로컬 어셋번들 서버를 활성화 할 수 있습니다. 로컬 어셋 번들 서버 역시 일반 원격 서버와 마찬가지로 접근 권한이라던지 방화벽과 같은 일반 다른 원격 서버와 동일한 제약 사항을 적용 받습니다. 로컬 어셋번들 서버가 정상적으로 활성화 된다면 이제 부터 어셋번들 다운로드를 로컬에서 테스트 해볼수 있습니다.

    1. 에디터 메뉴 "Assets/AssetBundles/Simulation Mode"에서 시뮬레이션 모드를 비활성화 시킵니다.
    2. 에디터 메뉴 "Assets/AssetBundles/Local AssetBundle Server"에서 로컬 어셋번들 서버를 활성화 시킵니다.
    3. 프로젝트 탭의 "AssetBundleSample/Scenes/VariantLoder" 신을 엽니다. 플레이 모드를 실행하기 전에 씬엔 메인 카메라와 라이트, 로더 게임 오브젝트 외에는 아무것도 없음을 주목해주세요.
    4. 플레이 버튼을 눌러 프로젝트를 실행 합니다.
    5. "Load SD"를 선택 합니다. 빨간색 큐브와 저해상도 스프라이트가 로드 된것을 확인합니다.
    6. 플레이 모드를 빠져나온 후 다시 플레이 버튼을 눌러 프로젝트를 실행합니다.
    7. "Load HD"를 선택 합니다. 이번에는 녹색 큐브와 고해상도 스프라이트가 로드 된것을 확인합니다.
      (만일 해당도나 큐브의 색이 바뀌지 않았다면 로컬 어셋번들 서버 활성화 여부, 빌드 여부를 체크해 주세요)
    8. 씬과 관련된 스크립트는 'LoadVariants.cs'에서 확인 할 수 있습니다.

    "AssetBundleSample/Scripts/LoadVariants.cs"

    "AssetBundleSample/Scripts/LoadVariants.cs" 스크립트를 에디터에서 열어 주세요. LoadVariants 클래스에는 두개의 public 변수와 두개의 private 변수를 가지고 있습니다.

    • public string variantSceneAssetBundle : 로드 될 어셋 번들의 이름
    • public string variantSceneName : 로드 될 신의 이름
    • private string[] activeVariants : "Load SD" 혹은 "Load HD" 버튼이 눌러지면 해당 어셋번들 네임을 저장하여 AssetBundleManager.ActiveVariants 변수에 셋팅 됩니다.
    • private bool bundlesLoaded : 버튼 UI의 디스플레이 여부를 판단하는 변수. 어셋번들이 로드 되고 난후 버튼을 숨기는데 사용 됩니다.

    이 스크립트는 BeginExample()과 Start()에 의해 호출되는 두개의 코루틴으로 구성됩니다. BeginExample()함수는 "Load SD" 또는 "Load HD" 버튼을 누름으로써 호출 됩니다.

    • Initialize() 함수에서는 DontDestroyOnLoad()와 어셋 번들의 패스를 셋팅하고 어셋번들 메니페스트를 초기화 합니다.
    • BeginExample() 함수에서 Initialize() 함수와 InitailizeLevelAsync() 함수가 호출되는 가운데 AssetBundleManager.ActiveVariants 변수가 셋팅 됩니다. 여기에서 셋팅 되는 값은 OnGUI()의 "Load SD" 또는 "Load HD" 버튼을 누를때 "sd" 또는 "hd"로 셋팅이 됩니다.

    여기서 우리가 주의 깊게 살펴봐야 하는 부분은 어셋번들에서 베리언트에 따라 어떻게 구분 되어 씬에 로드 될수 있는가 입니다. 어셋번들 베리언트를 지정하게 되면 "."을 구분자로 하여 어셋번들 이름이 메니페스트에 저장 됩니다. 이 예에서는 "AssetBundleSample/SampleAssets/Variants" 하위의 "MyAssets-HD" 폴더와 "MyAssets-SD"  폴더의 하위 어셋들이 어셋 각각 "variants/myassets.hd" 와 "variants/myassets.sd"와 같은 이름을 가집니다. AssetBundleManager는  "."을 구분자로 어셋번들 이름과 AssetBundleManager.ActiveVarintans에 지정된 베리언트 이름이 일치하는 어셋을 로드합니다.


    AssetBundleManager.ActiveVariants는 여러 개의 엔트리를 가지는 것도 가능합니다. 예를 들어 my-material.sd, my-material.hd, my-text.english, my-text.danish, my-text.catalan, my-text.welsh와 같이 어셋번들 베리언트가 지정되었다고 하면, ActiveVariants는 "hd"와 "danish" 혹은 "sd"와 "english"와 같이 다양한 조합을 선택할 수 있습니다. 이와 같은 방법으로 어셋번들 매니저는 hd 혹은 sd의 해상도와 언어 선택을 개별적으로 처리 할 수 있습니다.


    만일 ActiveVariants에 아무런 어셋번들 베리언트 네임이 셋팅되어 있지 않다면 어셋번들 매니저는 베리언트 이름을 무시하고 지정된 이름의 어셋번들 중 가장 첫번째에 검색되는 어셋번들을 로드 합니다. 또한 동일한 어셋번들 네임을 가진 다른 베리언트들이 동시에 지정되어 있다면 - 예를 들어 "sd"와 "hd"가 동시에 지정 - 어셋번들 매니저는 ActiveVariants에 셋팅되어 있는 베리언트 네임 중 가장 앞에 있는 어셋번들을 로드 합니다.


    예제 4. 탱크

    이번 장에서는 이전까지 살펴 보았던 부분들을 되세기며 기본적인 씬과 어셋들을 로드하고 특정 게임 오브젝트에 대해서는 어셋번들 베리언트를 사용하여 해상도와 컨텐츠, 로컬라이징에 따른 언어팩을 선택 할수 있는 통합 예제를 살펴 보도록하겠습니다.

    1. 에디터 메뉴 "Assets/AssetBundles/Simulation Mode"에서 시뮬레이션 모드를 비활성화 시킵니다.
    2. 에디터 메뉴 "Assets/AssetBundles/Local AssetBundle Server"에서 로컬 어셋번들 서버를 활성화 시킵니다.
    3. 프로젝트 탭의 "AssetBundleSample/Scenes/TanksLoder" 신을 엽니다.
      씬엔 메인 카메라와 라이트, "Loader" 게임 오브젝트 외에는 아무것도 없음을 주목해주세요.
    4. 플레이 버튼을 눌러 프로젝트를 실행 합니다.
    5. 해상도와 스타일, 언어를 각각 선택 합니다.
    6. 선택에 따라 각각의 어셋이 로드된것을 확인 합니다. 만일 명시적으로 선택된 옵션이 없는 경우 어셋번들 매니저가 자동으로(예제 3에서 살펴본 룰에 따라) 한가지를 선택, 콘솔창에 경고를 출력하는 것을 확인 합니다.
    7. 씬과 관련된 스크립트는 'LoadTanks.cs'에서 확인 할 수 있습니다.
      "AssetBundleSample/Scripts/LoadTanks.cs" 스크립트를 에디터에서 열어 주세요.

    이 스크립트는 이전 예제의 "LoadScenes.cs", "LoadAssets.cs"와 매우 비슷합니다. 이전 예제에서는 하나의 어셋번들만 로드 했지만 이 예제에서는 씬을 가지고 있는 어셋과 텍스트 어엣을 가지고 있는 어셋번들 두개를 로드 합니다. 이전 예제에서 이미 살펴 보았던 어셋과 씬의 로딩을 통합하여 하나의 클래스에서 호출하는 것 외에는 이전 예제들과 크게 다른 점은 없기 때문에 많은 설명이 필요하진 않습니다.

    • public string sceneAssetBundle : 로드 될 씬 어셋번들의 이름
    • public string sceneName : 로드 될 신의 이름
    • public string textAssetBundle : 로드 될 텍스트 어셋번들의 이름
    • public string textAssetName : 로드 될 텍스트 어셋의 이름
    • private string[] activeVariants : 어셋번들 매니저에게 전달 될 활성화 될 어셋번들 베리언트의 이름 배열
    • private bool bundlesLoaded : 버튼 UI 표시/숨김에 사용될 변수
    • private bool sd, hd, normal, desert, english, danish : ActiveVarinat에 셋팅 될 옵션 선택 여부
    • private string tankAlbedoStyle, tankAlbedoResolution, language : ActiveVarinat에 셋팅 될 옵션 선택 여부

    이 스크립트는 BeginExample() 함수와 Start()에서 호출 되는 세개의 코루틴으로 구성되어 있습니다. BeginExample() 함수는 "Load Scene" 버튼을 클릭 시 호출 됩니다.

    • Initialize() 함수에서는 DontDestroyOnLoad()와 어셋 번들의 패스를 셋팅하고 어셋번들 메니페스트를 초기화 합니다.
    • BeginExample() 함수에서 Initialize() 함수와 InitialzeLeveAsync() 함수 가운데 AssetBundleManager.ActiveVariants 변수가 셋팅 되며 세부 내용은 이전 장에서 살펴 봤던것과 동일 합니다.
    • InitialzeLeveAsync() 함수와 InstantiateGameObjectAsync() 함수를 호출하여 신과 텍스트 어셋을 로드 합니다.


    이상 어셋번들의 기본 개념과 어셋번들의 빌드/배포/로드를 도와 주는 어셋번들 매니저의 사용 방법에 대해 알아 보았습니다. 보다 자세한 정보를 얻고 싶다면 다음 링크를 참고해 주세요.

    원문 : http://unity3d.com/kr/learn/tutorials/topics/scripting/assetbundles-and-assetbundle-manager?playlist=17117


    문제 해결 : 어셋번들 로드 시 쉐이더가 정상적으로 표현 되지 않는 문제 

     - http://forum.unity3d.com/threads/custom-shaders-and-asset-bundles-on-ios.144014/

     - http://baramlife.tistory.com/9


    '진리는어디에' 카테고리의 다른 글

    삼각함수  (0) 2016.06.27
    리팩토링 - Position 클래스  (0) 2016.02.14
    Unity Asset Bundle  (0) 2016.01.29
    Unity Coroutine  (0) 2016.01.22
    브레즌햄(Bresenham) 알고리즘  (0) 2016.01.16
    unity Animator 사용하여 애니메이션 종료까지 기다리기  (0) 2015.12.14
    Posted by kukuta

    댓글을 달아 주세요

    Unity Coroutine

    진리는어디에 2016. 1. 22. 18:12

    http://gamecodingschool.org/2015/05/16/%EC%BD%94%EB%A3%A8%ED%8B%B4coroutine-%EC%9D%B4%ED%95%B4%ED%95%98%EA%B8%B0/

    http://www.gamedevforever.com/291

    http://unityindepth.tistory.com/21

    https://en.wikipedia.org/wiki/Coroutine

    '진리는어디에' 카테고리의 다른 글

    리팩토링 - Position 클래스  (0) 2016.02.14
    Unity Asset Bundle  (0) 2016.01.29
    Unity Coroutine  (0) 2016.01.22
    브레즌햄(Bresenham) 알고리즘  (0) 2016.01.16
    unity Animator 사용하여 애니메이션 종료까지 기다리기  (0) 2015.12.14
    AddComponent 시 return null  (0) 2015.12.09
    Posted by kukuta

    댓글을 달아 주세요