블로그 이미지
Every unexpected event is a path to learning for you. blueasa

카테고리

분류 전체보기 (2795)
Unity3D (852)
Programming (478)
Server (33)
Unreal (4)
Gamebryo (56)
Tip & Tech (185)
협업 (61)
3DS Max (3)
Game (12)
Utility (68)
Etc (98)
Link (32)
Portfolio (19)
Subject (90)
iOS,OSX (55)
Android (14)
Linux (5)
잉여 프로젝트 (2)
게임이야기 (3)
Memories (20)
Interest (38)
Thinking (38)
한글 (30)
PaperCraft (5)
Animation (408)
Wallpaper (2)
재테크 (18)
Exercise (3)
나만의 맛집 (3)
냥이 (10)
육아 (16)
Total
Today
Yesterday

[출처] http://sjava.net/?p=416



이 내용은 소프트웨어 엔지니어이자 기업가인 마틴 클레프만(Martin Kleppmann)의 블로그에 있는(http://martin.kleppmann.com/2012/12/05/schema-evolution-in-avro-protocol-buffers-thrift.html) 있는 내용이다. 이 내용의 번역은 마틴 클레프만의 동의를 얻어서 진행했다.

에이브로(Avro), 프로토콜 버퍼(Protocol Buffers) 그리고 스리프트(Thrift)의 스키마 변경(evolution).
자 여러분은 파일로 저장하거나 네트워크로 전송하려는 데이터를 가지고 있다. 그리고 변경(진화)의 여러 단계를 거치면서 스스로 찾아갈 수도 있다:

  1. 자바 직렬화(Java serialization), 루비 마샬(marshal)과 파이썬 픽클(pickle)같은 프로그래밍 언어가 지원하는 직렬화를 사용하거나, 자신의 포맷을 개발해서 사용할 수도 있다.
  2. 다음에, 단일 프로그래밍 언어에 종속된다는 것을 알게 될 것이고, 그래서, 더 넓게 지원하도록 JSON(XML) 같이 언어에 독립적인(language-agnostic) 포맷을 사용하도록 변경한다.
  3. 그다음으로, “JSON은 너무 장황(verbose)하고, 파싱이 느리다” 라고 판단하고, 부동 소수(floating point)와 정수(integers)를 구분할 수 없는 것에 실망하고, 유니코드 문자열뿐 아니라 바이너리 문자열도 필요하다고 생각한다. 그래서, JSON의 일종으로 바이너리(MessagePackBSONUniversal Binary JSONBJSONBinary JSON Serialization) 포맷을 만든다.
  4. 또 다음으로, 사람들이 일관성 없는 타입을 사용해서, 객체에 여러 임의(random)의 필드를 넣고 있는 것을 발견하고, 스키마(schema)나 문서(documentation)에 대해서 매우 감사할 것이다. 아마도 당신 역시, 정적 타입 프로그래밍 언어를 사용하고 있고, 스키마에서 모델 클래스를 생성하길 원할 것이다. 그리고 바이너리 JSON은 계속해서 필드 이름을 저장하기에 실제로는 간결하지 않다는 것을 알게 될 것이다; 만약 스키마가 있다면, 객체 필드 이름을 저장하지 않아도 되고, 더 공간(bytes)을 절약할 수 있다.

만약, 4.의 단계를 고민한다면, 일반적으로 선택할 수 있는 옵션은 스리프트(Thrift), 프로토콜 버퍼(Protocol Buffers)와 에이브로(Avro)가 있다. 이 세 가지는 효율적이고, 스키마를 이용한 데이터의 언어에 독립적인(cross-language) 직렬화와 자바 진영(folks)을 위한 코드 생성도 제공한다.

이것들에 대한 비교는 이미(Protocol Buffers, Avro, Thrift & MessagePackThrift vs Protocol Bufffers vs JSONComparison of Protobuff, Thrift, Avro, etc) 확인할 수 있다. 하지만 많은 글이 일상적으로 보이지만, 중요한 세부내용을 간과하고 있다: 스키마를 변경하면, 어떻게 될까?

실제로 데이터는 항상 유동적이다. 완성된 스키마를 가지고 있다고 생각하는 순간, 누군가 예상치 않은 사례(use case)를 가지고 올 것이고, “단지 빠르게 필드를 추가”하길 원한다. 다행스럽게도, 스리프트, 프로토콜 버퍼 그리고 에이브로 모두 스키마 변경(schema evolution)를 지원한다: 스키마를 변경할 수 있는 동시에, 스키마의 다양한 버전에 대한 프로듀서(producers)와 컨슈머(consumers)를 사용할 수 있어서, 이전 버전의 스키마를 계속 사용할 수 있다. 이것으로, 호환성 걱정 없이, 서로 다른 시간에, 시스템의 각 컴포넌트를 독립적으로 갱신할 수 있게 해서, 매우 큰 시스템을 다룰 때, 매우 중요한 기능이다.

이것이 이 글의 주제이다. 실제로 프로토콜 버퍼, 에이브로 그리고 스리프트가 데이터를 데이터를 바이트로 인코딩하는 방법을 살펴본다. 이 내용은 개별 프레임웍이 스키마 변경을 처리하는 방법을 이해하는데 도움이 될 것이다. 이 프레임웍들이 만들어진 설계의 선택에 대해서 관심과 비교를 통해서 더 좋은 엔지니어가 될 수 있을 것이다.

예로, Person을 나타내는 작은 객체를 사용할 것이고, JSON으로 아래처럼 기술할 수 있다:

01
02
03
04
05
{
  “userName”: “Martin”,
  “favouriteNumber”: 1337,
  “interests”: [“daydreaming”, “hacking”]
}

이 JSON 인코딩이 기준이 될 수 있다. 모든 공백을 지우면, 82바이트다.

프로토콜 버퍼
Person 객체에 대한 프로토콜 버퍼의 스키마는 아래와 같다:

01
02
03
04
05
message Person {
    required string user_name        = 1;
    optional int64  favourite_number = 2;
    repeated string interests        = 3;
}

이 스키마로 데이터를 인코딩하면, 아래와 같이 33 바이트를 사용한다.

바이너리 형태가 어떻게 바이트 단위로 구성되는지 정확하게 살펴봐라. Person 레코드는 단지 필드의 연속이다. 각 필드는 태그 넘버(위의 스키마에서 1, 2, 3)를 지정하는 1 바이트로 시작하고, 다음으로 필드의 타입이다. 필드의 첫 바이트를 타입인 문자열로 지정한다면, 다음으로 문자열의 바이트 수와, UTF-8로 인코딩된 문자열이 따라온다. 첫 바이트를 정수로 지정한다면, 가변 길이로 인코딩된 수(값)가 나온다. 배열 타입은 없지만, 다중 값(multi-valued) 필드를 나타내기 위해서 태그 번호를 여러 번 사용할 수 있다.

이 인코딩 형태는 스키마 변화에 대해서 아래의 결과를 가진다:

  • 선택적(optional), 필수적(required) 그리고 반복(repeated)되는 필드(태그 번호가 나타나는 횟수를 제외하고)의 인코딩 차이는 없다. 이것은 선택적에서 반복으로 변경할 수 있다는 것이고, 반대로 변경해도 같다(파서가 예상한 선택적 필드에, 한 레코드에 같은 태그 번호가 여러 번 보이는 경우, 마지막 값을 제외하고 모두 삭제한다.)는 것이다. 필수적 필드는 추가로 유효성 검사를 한다. 그래서 스키마를 바꾼다면, 런타임 에러(메시지를 보내는 사람은 선택적이라고 생각했지만, 받는 쪽이 필수라고 생각하는)의 위험이 있다.
  • 빈 값의 선택적 필드 또는 0 값이 반복되는 필드는 인코딩된 데이터에 전혀 나타나지 않는다-다만 태그 번호만 나온다. 따라서 스키마에서 이런 종류의 필드는 삭제하는 것이 안전하다. 하지만 아직도 삭제한 필드의 태그를 사용해서 저장된 데이터가 있을 수 있기에, 앞으로는 다른 필드에서 태그 번호를 재사용하면 안 된다.
  • 태그 번호를 제공할 수 있다면, 레코드에 필드를 추가할 수 있다. 프로토콜 버퍼 파서가 스키마에 정의되지 않은 태그 번호를 만나면, 그 필드가 호출됐는지 알 수가 없다. 하지만 필드의 첫 바이트에 3-bit 타입코드가 포함되어 있어서, 대략 어떤 타입인지 알고 있다. 이것은 파서가 정확히 필드를 해석할 순 없지만, 이것으로 레코드에서 다음 필드를 찾기 위해, 몇 개의 바이트를 생략해야 하는지 알 수 있다.
  • 직렬화된 바이너리에 필드 이름이 없기에, 필드 이름을 바꿀 수 있지만, 태그 번호는 바꿀 수 없다.

개별 필드를 나타내기 위해서 태그 번호를 사용하는 방식은 간단하고 효과적이다. 하지만 이 방식이 유일하지 않다는 것은 금방 알 수 있을 것이다.

에이브로
에이브로 스키마는 2가지 방법으로 사용할 수 있고, 한 가지 방법은 JSON 포맷을 사용하는 것이고:

01
02
03
04
05
06
07
08
09
{
  “type”: “record”,
  “name”: “Person”,
  “fields”: [
      {“name”: “userName”,        “type”: “string”},
      {“name”: “favouriteNumber”, “type”: [“null”, “long”]},
      {“name”: “interests”,       “type”: {“type”: “array”, “items”: “string”}}
  ]
}

다른 방법은 IDL을 사용하는 것이다:

01
02
03
04
05
record Person {
    string               userName;
    union { null, long } favouriteNumber;
    array<string>        interests;
}

스키마에 태그 번호가 없는 것을 확인해라! 그럼 어떻게 동작할까?
아래는 32바이트로 인코딩(http://avro.apache.org/docs/current/spec.html)된 같은 예제 데이터다.

문자열 앞은 길이(length)이고 다음으로 UTF-8 바이트지만, 바이트스트림(bytestream)은 그것이 문자열이라고 알려주지 않는다. 이것(바이너리 데이터)은 단지 가변 길이 정수가 될 수 있지만, 완전히 다를 수 있다. 이 바이너리 데이터를 파싱할 수 있는 유일한 방법은 스키마와 같이 읽는 것이고, 스키마는 다음에 나오는 타입이 무엇인지 알려준다.
데이터의 작성자(writer)가 사용한 스키마의 정확히 같은 버전이 필요하다. 다른 스키마를 가지고 있다면, 파서는 바이너리 데이터의 헤드(head)나 테일(tail)을 만들 수 없게 될 것이다.

그런데 에이브로는 어떻게 스키마 변화를 지원할까? 비록 작성자의 스키마로 작성된 데이터와 정확한 스키마를 알 필요가 있지만, 컨슈머(consumer)가 기대하는(구독자(reader)의 스키마) 스키마가 같아야 한다는 것은 아니다. 실제로, 에이브로 파서에 다른 두 개의 스키마를 제공할 수 있고, 파서는 작성자 스키마를 구독자 스키마로 변환하기 위해서 해결 규칙(resolution rules)을 사용한다.

이것은 스키마 변화에 대해 몇 가지 흥미로운 결과를 가지게 한다:

  • 에이브로 인코딩은 어떤 필드가 다음인지 알려주는 지시자(indicator)가 없다: 단지, 스키마에 나타나는 차례대로 인코딩한다. 파서는 필드가 생략되었다는 것을 알 방법이 없기에, 에이브로에는 선택적인 필드가 없다. 대신, 값을 제외하길 원한다면, 위에서 유니온(union { null, long })과 같은 타입을 사용할 수 있다. 유니온 타입을 사용하기 위해서, 파서에게 1바이트로 인코딩해서 알려준다. null 타입(간단히 0바이트로 인코딩됨)으로 유니온을 만들어서, 필드를 선택적으로 만들 수 있다.
  • 유니온 타입은 강력하지만, 변경 시에는 주의해야 한다. 유니온에 한 타입을 추가하려면, 우선, 모든 구독자에 새로운 스키마 업데이트가 필요하고, 변경 내용을 알게 된다. 모든 구독자(readers)가 업데이트되면, 작성자(writers)는 생성하는 레코드에 새로운 타입을 추가할 수 있다.
  • 원하는 대로, 레코드에서 필드를 재정렬할 수 있다. 비록 필드는 정의된 순서로 인코딩되지만, 파서는 구독자와 작성자 스키마에서 이름으로 필드를 찾을 수 있고, 이것이 에이브로에서 태그 번호가 필요없는 이유이다.
  • 이름으로 필드를 찾을 수 있기에, 필드의 이름을 변경하는 것이 까다롭다. 새로운 필드 이름을 사용하기 위해서, 기존 이름의 별칭(구독자의 스키마에서 별칭을 이름 매칭에 사용한 이래)으로 유지하는 동안, 데이터의 모든 구독자는 먼저 업데이트가 필요하다. 다음에, 새로운 필드를 사용하기 위해서 작성자의 스키마를 업데이트할 수 있다.
  • 또, 기본 값(예로 null, 필드의 타입이 union이고 기본값이 null이면)을 정해서, 레코드에 필드를 추가할 수 있다. 구독자가 새로운 스키마로 기존 스키마(필드가 부족한)로 작성된 레코드를 파싱할 때, 대신 기본값으로 채울 수 있기에, 기본값이 필요하다.
  • 반대로, 레코드에서 기존에 기본값을 가지고 있는 필드를 삭제할 수 있다(이것이, 가능한 모든 필드에 기본값을 제공하는 것이 유익하다는 이유다). 구독자가 기존 스키마로 새로운 스키마로 작성된 레코드를 파싱할 때, 기본값으로 돌릴 수 있다.

이것은 작성된 특정 레코드의 정확한 스키마를 알아야 하는 문제를 남긴다. 그래서 가장 좋은 해결책은 데이터가 사용되고 있는 상황(context)에 따르는 것이다.

  • 일반적으로, 하둡(Hadoop)에서는 모두 같은 스키마로 인코딩된, 수백만의 레코드를 유지하는 파일이 많이 있다. 객체 컨테이너 파일(Object container files)이 이 파일들을 처리한다: 단지 파일의 시작 부분에서 스키마를 가지고 있고, 나머지 부분에 스키마로 디코딩할 수 있다.
  • RPC(Remote Procedure Call)에서, 요청과 응답에서 매번 스키마를 보내는 것은 너무 오버헤드가 클 것이다. 하지만 RPC 프레임웍이 오래 유지하는(long-lived) 연결을 사용한다면, 연결 시점에서 한번 스키마를 협상(주고받으면서 확인)할 수 있고, 많은 요청의 오버헤드 비용을 상쇄할 수 있다.
  • 데이터베이스에 하나씩 레코드를 저장한다면, 다른 시간대에 작성된 여러 스키마 버전으로 저장할 수 있고, 그래서 스키마 버전의 각 레코드에 주석이 필요하다. 스키마 자체를 저장하는 것이 너무 오버 헤드가 큰 경우, 스키마 해시나 차례대로 스키마 버전 번호를 사용할 수 있다. 이때는, 특정 버전 번호에 대해서, 정확한 스키마 버전을 찾을 수 있는 스키마 레지스트리(schema registry)가 필요하다.

이것을 고려하는 한 방법으로: 프로토콜 버퍼에서는, 레코드의 모든 필드를 태그하고, 에이브로에서는, 전체 레코드, 파일 또는 네트워크 연결을 스키마 버전으로 태그한다.

언뜻 보기에는, 스키마 배포에 부가적인 노력이 필요하기에, 에이브로 방식이 더 복잡하게 보일 수 있다. 하지만, 에이브로 방식이 가지고 있는 몇 가지 의미있는 장점에 대해서 생각해 보자:

  • 객체 컨테이너 파일의 스스로-기술하는(self-describing) 것은 매우 좋다: 파일에 내장된 작성자 스키마는 모든 필드의 이름과 타입을 포함하고 있고, 심지어 문서 문자열(스키마의 작성자(author)가 귀찮아하는 일부 일들)도 포함하고 있다. 이것이 피그(Pig)와 같은 대화형(interactive) 툴에 직접 해당 파일을 읽거나, 어떤 구성 없이도 바로 작업(Works™)할 수 있게 한다.
  • 에이브로 스키마는 JSON으로, 자신의 메타데이터를 파일에 추가(예로 어플리케이션 수준에서 필드에 대한 의미를 기술하는)할 수 있다.
  • 스키마 레지스트리는 아마도 문서(documentation)를 제공하고, 데이터를 찾거나 재사용하는 것을 도와줄 때에 유익할 것이다. 그리고 스키마 없이는 에이브로 데이터를 파싱할 수 없기에, 스키마 레지스트리는 최신으로 보장되어 있다. 물론, 프로토콜 버퍼 스키마 레지스트리도 역시 설정할 수 있지만, 필요한 기능이 없으므로, 단지 최선을 다했다는 근거만 될 것이다.

스리프트
스리프트는 데이터 직렬화 라이브러리 뿐 아니라, 전체가 RPC 프레임워크라서, 에이브로나 프로토콜 버퍼보다 큰 프로젝트이다. 스리프트는 또 다른 방식을 가지고 있다: 에이브로나 프로토콜 버퍼는 단일 바이너리 인코딩을 표준으로 하지만, 스리프트는 다양한 직렬화 포맷(“프로토콜”이라고 하는)을 포함(embraces)한다.

사실, 스리프트는 두 가지(12) JSON 인코딩 방식이 있으며, 최소한 3가지의 바이너리 인코딩 방식을 가지고 있다(하지만, 바이너리 인코딩의 하나인 DenseProtocol은 오직 C++ 구현만 지원하고 있다: 언어 중립적인(cross-language) 직렬화에 관심이 있기에, 다른 두 가지에 중점을 둘 것이다).

모든 인코딩은 스리프트 IDL에서 정의된 같은 스키마를 공유한다:

01
02
03
04
05
struct Person {
  1: string       userName,
  2: optional i64 favouriteNumber,
  3: list<string> interests
}

바이너리 프로토콜(BinaryProtocol) 인코딩은 매우 간단하지만, 상당히 비효율적(위의 예제 레코드를 인코딩하기 위해서 59바이트를 사용)이다:

컴팩트 프로토콜(CompactProtocol) 인코딩은 의미상 같지만, 크기를 34바이트로 줄이기 위해서 가변-길이 정수와 비트 패킹을 사용한다:

보다시피, 스리프트의 스키마 변화에 대한 대처 방식은 프로토콜 버퍼와 같다: 각 필드는 IDL에 수동으로 태그를 할당하고, 태그와 필드 타입은 인코딩된 바이너리에 저장하고, 이것으로 파서가 알지 못하는 필드는 생략할 수 있도록 한다. 스리프트는 프로토콜 버퍼의 반복 필드 방식보다 명시적인 리스트 타입을 정의하지만, 이 두 개는 매우 유사하다.

사상적인 관점에서, 위 라이브러리들은 매우 다른 관점을 가지고 있다. 스리프트는 전체가 통합된 RPC 프레임웍이고, 많은 선택(다양한 언어를 지원)을 주는 “한 상점에서 여러 가지 상품을 파는(one-stop shop)” 형태를 선호한다. 반면에, 프로토콜 버퍼와 에이브로는 “한 가지 일을 더 잘하는” 형태를 더 선호하는 것으로 보인다.

원 글에 있는 답글 중에 하나로 “에이브로는 스리프트보다는 작지만 RPC 프레임웍이고, IPC도 지원한다”고 한다.

반응형
Posted by blueasa
, |


[사진 : 서브웨이 서퍼 플레이 스크린샷]



출처 : http://answers.unity3d.com/questions/288835/how-to-make-plane-look-curved.html

http://devkorea.co.kr/bbs/board.php?bo_table=m03_lecture&wr_id=3315&page=0&sca=&sfl=&stx=&sst=&sod=&spt=0&page=0&currentId=42


위의 사진처럼 땅이 평평하지 않고 휘어지는 듯한 효과를 연출하기 위함이다. 

땅 자체를 곡선으로 만들수도 있겠지만 

카메라가 보는 부분의 렌더링을 수정함으로써 효과를 낼 수 있다.



BendTheWorldShader.unitypackage


위의 파일은 예제 파일이며, 

https://dl.dropboxusercontent.com/u/7761356/UnityAnswers/Web/BendTheWorld/WebPlayer.html

웹에서도 실행을 해볼 수 있다.



적용 방법 : 


위의 예제파일이나, 웹에서 보듯이 shader프로그래밍을 하면 된다.

shader 파일을 생성한다.



shader 파일에 아래와 같이 코드를 입력한다.


접기


Shader "Custom/Curved" {

Properties {

_MainTex ("Base (RGB)"2D) = "white" {}

_QOffset ("Offset"Vector) = (0,0,0,0)

_Dist ("Distance"Float) = 100.0

}

SubShader {

Tags { "RenderType"="Opaque" }

Pass

{

CGPROGRAM

#pragma vertex vert

#pragma fragment frag

#include "UnityCG.cginc"


            sampler2D _MainTex;

float4 _QOffset;

float _Dist;

struct v2f {

    float4 pos : SV_POSITION;

    float4 uv : TEXCOORD0;

} ;

v2f vert (appdata_base v)

{

    v2f o;

    // The vertex position in view-space (camera space)

    float4 vPos = mul (UNITY_MATRIX_MV, v.vertex);

    

    // Get distance from camera and scale it down with the global _Dist parameter

    float zOff = vPos.z/_Dist;

    // Add the offset with a quadratic curve to the vertex position

    vPos += _QOffset*zOff*zOff;

    

    o.pos = mul (UNITY_MATRIX_P, vPos);

    o.uv = mul( UNITY_MATRIX_TEXTURE0, v.texcoord );

    return o;

}


half4 frag (v2f i) : COLOR

{

    half4 col = tex2D(_MainTex, i.uv.xy);

    return col;

}

ENDCG

}

}

FallBack "Diffuse"

}






작성한 shader 파일을 texture의 shader로 적용한다.





그러면 카메라의 위치에서 멀리 있는 객체일수록 z축의 아래로 쳐져 있는 듯한 효과를 나타낼 수 있다. 




이제, 휘어지게 보여질 객체에 스크립트에서 처리해야 한다



mOffset =  new Vector2(0.0f,-1.8f)

        mModelsRenderer = mModel.GetComponentsInChildren<Renderer>();

mModelsMeshFilter = mModel.GetComponentsInChildren<MeshFilter>();

foreachRenderer render in mModelsRenderer )

render.material.SetVector("_QOffset", mOffset );

foreachMeshFilter meshfilter in mModelsMeshFilter )

meshfilter.mesh.bounds = new BoundsVector3.zero, Vector3.one * 3000 );



render.material.SetVector("_QOffset", mOffset )

 는 휘어지는 방향과 크기를 정하며


meshfilter.mesh.bounds = new BoundsVector3.zero, Vector3.one * 3000 );

는 mesh범위를 키워 카메라에서 멀리 있어도 휘어지는 효과 그대로 화면에 보이도록 함이다.




출처 : http://jenemia.tistory.com/258

반응형
Posted by blueasa
, |

유니티 5.3.1 f1 로 버전업 했더니, NGUI 에서 범상치 않은 워닝을 내뱉었다.


1
2
3
Assets/NGUI/Examples/Scripts/Other/LoadLevelOnClick.cs(15,37): 
warning CS0618: `UnityEngine.Application.LoadLevel(string)' is obsolete: 
`Use SceneManager.LoadScene'

cs



Application.LoadLevel 로 씬 이동 하던 것을 SceneManager.LoadScene 으로 사용하란다.


그래서 유니티의 바람대로 SceneManager.LoadScene 으로 고쳐썼더니 네임스페이스가 없다고 한다....


검색해보니 UnityEngine.SceneManagement 라는 네임 스페이스가 추가된 듯.. 



1
using UnityEngine.SceneManagement;
cs


위와 같이 네임스페이스를 추가하니 정상적으로 작동한다.




알고보니 유니티 5.3에서 멀티 씬 편집(Multi-Scene Editing) 을 지원한다고 한다.


아래와 같이 프로젝트 뷰 에서 오른쪽 클릭을 한 후, Open Scene Additive 를 클릭하면...



▼ 이렇게 하이라키 뷰에 여러 씬이 열린다.





또한 SceneManager 추가되었고, 덕분에 자주쓰는 메소드들도 거의 다 구식이 되어버렸다.


변경점은 다음과 같다. 


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
// old
Application.LoadLevel(0);                                       // 로드. 
Application.LoadLevel("SceneName");
AsyncOperation ao = Application.LoadLevelAsync(0);              // 로드. (비동기)
AsyncOperation ao = Application.LoadLevelAsync("SceneName");
Application.LoadLevelAdditive(0);                               // 씬 병합 추가. 
Application.LoadLevelAdditive("SceneName");
Application.LoadLevelAdditiveAsync(0);                          // 씬 병합 추가. (비동기)
Application.LoadLevelAdditiveAsync("SceneName");
Application.UnloadLevel(0);                                     // 언로드. 
Application.UnloadLevel("SceneName");
Application.levelCount;                                // BuildSetting 에 등록 된 씬 개수. 
Application.loadedLevel;                                        // 현재 씬 인덱스. 
Application.loadedLevelName;                                    // 현재 씬 이름. 
 
// 5.3
SceneManager.LoadScene(0);                                      // 로드. 
SceneManager.LoadScene("SceneName");
AsyncOperation ao = SceneManager.LoadSceneAsync(0);             // 로드. (비동기)
AsyncOperation ao = SceneManager.LoadSceneAsync("SceneName");
SceneManager.LoadScene(0, LoadSceneMode.Additive);              // 씬 병합 추가. 
SceneManager.LoadScene("SceneName", LoadSceneMode.Additive);
SceneManager.LoadSceneAsync(0, LoadSceneMode.Additive);         // 씬 병합 추가. (비동기)
SceneManager.LoadSceneAsync("SceneName", LoadSceneMode.Additive);
SceneManager.UnloadScene(0);                                    // 언로드. 
SceneManager.UnloadScene("SceneName");
SceneManager.sceneCount;                                        // 현재 로드 된 씬 개수. 
SceneManager.sceneCountInBuildSettings;                // BuildSetting 에 등록 된 씬 개수. 
SceneManager.GetActiveScene().buildIndex;                       // 현재 씬 인덱스. 
SceneManager.GetActiveScene().name;                             // 현재 씬 이름. 
 
// 씬 정보 조회. 
Scene activeScene = SceneManager.GetActiveScene();
Scene scene1 = SceneManager.GetSceneAt(0);
Scene scene2 = SceneManager.GetSceneByName("SceneName");
Scene scene3 = SceneManager.GetSceneByPath("Assets/SceneName.unity");
Scene[] loadedScenes = SceneManager.GetAllScenes();
 
// Scene 구조체. 
int buildIndex;
string name;
string path;
bool isLoaded;
bool isDirty;       // 씬을 변경(수정)했는지 여부. 
int rootCount;      // 씬의 Root에 있는 GameObject 개수. 
bool IsValid();     // 유효한 씬인지 여부. 
 
// 기타. 
Scene scene = gameObject.scene;                 // 게임오브젝트가 속해있는 씬을 가져오기. 
GameObject go = new GameObject("New Object");   // 게임오브젝트를 생성하면 현재 씬에 추가 됨. 
SceneManager.MoveGameObjectToScene(go, scene);      // 게임오브젝트를 다른 씬으로 이동. 
SceneManager.MergeScenes(sourceScene, destinationScene);    // 씬을 병합. 
 
// SceneManager.Get~() 으로 가져올 수 있는 것은 로드가 끝난 씬만 가능. 
Scene scene = SceneManager.GetSceneByName("SceneName");
bool isValid = scene.IsValid();     // false 가 리턴 됨.
cs



정리하다보니 꽤 많네;;;


아, 그리고 DontDestroyOnLoad 메소드 비스므리 한 것은 아예 없었는데,


Tips 를 참조해 보면, DontDestroyOnLoad 의 사용은 피하는 것이 좋고, Manager 씬을 만들어서 병합하는 것이 좋다고 한다.


앞으로 개발 시 참고해야 할 사항인 것 같다.



PS. Unity 5.3 에서는 과금 플러그인을 이용할 필요가 없을 수도 있겠다.


Unity Service Tab 에서 여러가지를(Unity Ads, Analytics, Cloud Build, In-App Purchasing, Multiplayer) 지원함..






출처 : http://worksp.tistory.com/10

반응형
Posted by blueasa
, |

[전체 링크]


초보가 초보를 위해 쓰는 시놀로지 DS215J NAS 설치 및 활용기

반응형
Posted by blueasa
, |

링크 : http://www.gamecodi.com/board/zboard-id-GAMECODI_Book-no-304-z-4.htm

반응형
Posted by blueasa
, |



출처 : http://www.torrentbon.net/humor/341761

반응형
Posted by blueasa
, |


출처 : http://www.torrentbon.com/humor/338074

반응형
Posted by blueasa
, |

[파일]


Albatross-Programming-Unity-Unity로 다수의 인원이 개발할 때 알아두면 좋은 9가지.pdf


반응형
Posted by blueasa
, |


[파일]


인게임_광고의_올바른_방법(UnityAds).pdf


반응형
Posted by blueasa
, |

이전에 Coroutine을 사용했을때는 그냥 사용했는데.. 어디에 제대로 사용하는 것이 맞는것일까? 고민도 하구..
unityStudy 사이트에 너무 잘 설명해 놓은 글이 있어서 차라리 그 글을 한번 정독하면 큰 도움이 될것입니다
.

URL : 
[스크립팅] 코루틴(Coroutine) 기본 개념  활용 
두번째 해당 사이트는 영문입니다. : http://unitygems.com/coroutines/

아래는 백과사전 및 위 사이트에 대한 부분을 이해하기 위해 정리했습니다.

 

1.    동시 실행(협동) 루틴이라 불리 우는 Coroutine 정의

A.     호출 루틴과 피호출 루팅이 대등 관계를 유지하는 처리 절차.
부차적 프로그램의 수행이 완전히 끝나기 전에 제어가 호출 루틴으로 돌아가는 것이 동시 실행 과정이다. 그리고 제어가 부차적 프로그램으로 돌아 왔을 때는 중단된 부분부터 다시 수행이 계속 된다. 주종 관계를 갖지 않고 서로 호출하는 둘 이상의 모듈들.서브루틴의 제어 전달 개념과 유사한것. 각 호출에서 초기화되는 서브루틴과는 달리, 호출 시 관련된 모든 정보를 보존하는 능력을 갖는다. 그리고 다음에 다시 시작할 때에는 이전에 실행했던 다음부터 실행 할 수 있는 논리를 갖는다.

B.     프로그램에서 순서는 일반적으로 불려지는 쪽이 부르는 쪽에 속하고 있는 것이 대부분이지만 어느 쪽도 종속 관계가 아니라 대등한 관계로 서로 호출하는 것이다.
예를 들면, 게임 프로그램에서 각 플레이어 루틴은 서로 Coroutine 된다. 복수 프로세스 간에서 한정된 형태의 통신을 행하는 프로그램을 순차 제어로 실현한 것으로 볼 수 있다.

C.     C언어 등에서 일반적으로 사용하는 함수는 시작할 때 진입하는 지점이 하나 존재하고 함수가 모두 실행되거나,  return 구문에 의해서 종료되는 지점을 설정 할 수 있다.
이러한 함수를 서브 루틴(subroutine)이라 부르는데, 코루틴은 이를 더 일반화한 개념으로 진입하는 지점까지 여러 개를 가질 수 있는 함수를 의미. (서브 루틴도 코루틴의 한 종류)

D.     Coroutine 은 프로그램 상에서 보면 멀티 쓰레드와 비슷한 개념이다.
별도의 스택과 지역변수들을 가진 실행 중, 각자의 메모리 영역과 실행 명령 포인터를 가지고 있어
,
재개(Resume)와 양보(yield)가 가능한 구조로 되어 있다
.
중지(yield)하면 실제 프로그램(함수)는 종료되지 않고 위치를 대기시킨 후 값이 만들어지는 시점부터 다시 시작(resume) 할수 있다는 말이다.

 

2.    왜 필요한 것인가?

유니티에서 코루틴과 상호 작용하는 주체는 엔진이다
.
코루틴을 사용 할 때는 C#의 언어적인 기능을 익혀서 모든 것을 해결한다는 개념보다 스크립트에서 엔진이 가지는 기능을 활용하는 또 한가지의 관점으로 접근해야 한다
.
스크립트에서 StartCoroutine 함수를 통해 코루틴을 구동하면 코루틴은 첫 번째 데이터를 엔진에 전달하고 이 데이터를 분석한 엔진은 내부 루푸를 돌면서 필요한 때가 되면 다음 데이터를 전달하도록 코루틴을 다시 호출해 준다. 이때 엔진에 전달할 데이터가 정말 중요한데, 데이터들은 코루틴이 유니티 엔진에게 보내는 일종의 명령으로 생각하면 좋다
.



 

3.    자주 등장하는 조연 배우들…

A.     Yield 
Coroutine 
은 Yield 를 사용하여 중간에 제어권을 다른 함수에 넘겨주는 역할을 한다.
yield 를 사용하여 일정시간 대기 할 수 있는 함수도 있다
.
yield return new WaitForSecond(3.0f); 
3초  대기 후에 리턴을 해주는 순서다.  여기서 기본적인 추가 특징으로 update 함수상에서는 yield 문의 직접적인 사용이 금지가 됩니다.

B.     IEnumerator
(msdn) 
제네릭이 아닌 컬렉션을 단순하게 반복할 수 있도록 지원하는 인터페이스
우리말로 열거자 라고 하는데 , 데이터의 목록을 하나씩 넘겨 줄 때 사용되는 인터페이스 이다.
코루틴은 호출한 함수와 서로 상호작용하면서 진행하도록 설계되어 있습니다
.
코루틴은 자신을 호출한 함수에 데이터를 하나 넘겨주고 쉰다
.
받은 측에서는 데이터를 받고 나서 처리한 후에 코루틴에게 다음 데이터를 달라고 깨운다
.
쉬고 있던 코루틴은 일어나서 다시 데이터를 전달하고 .. 이를 계속 반복하는 구조로 동작한다
.
이런 작업에 적절한 인터페이스가 IEnumerator이며, C# 에서 코루틴을 사용할 때는 이를 사용한다
.
일반적으로 호출한 함수에게 데이터를 전달 할때, return 구문을 사용하게 되면 데이터를 전달하고 함수는 종료 된다.

C.     StartCoroutine 함수
IEnumerator 반환형을 받아 yield 이 만날 때 함수를 일시 정지하고, 다른 스크립트나 함수에게 활동을 넘긴다. 다시 말해 코루틴 함수를 구동하는 함수다.



 

4.    엔진이 제공하는 데이터들과 이를 수행하는 명령들.

A.     Yield return null : 다음 프레임까지 대기

B.     Yield return new WaitForSeconds(flat) : 지정된 초(float) 만큼 대기

C.     Yield return new WaitForFixedUpdate() : 다음 물리 프레임까지 대기

D.     Yield return new WaitForEndOfFrame() : 모든 렌더링 작업이 끝날 때 까지 대기

E.     Yield return StartCoroutine(string) : 다른 코루틴이 끝날 때 까지 대기

F.      Yield return new WWW(string) : 웹 통신 작업이 끝날 때까지 대기

G.     Yield return new AsyncOperation : 비동기 작업이 끝날 때 까지 대기 (씬로딩)

•null - the coroutine executes the next time that it is eligible

•WaitForEndOfFrame - the coroutine executes on the frame, after all of the rendering and GUI is complete

•WaitForFixedUpdate - causes this coroutine to execute at the next physics step, after all physics is calculated

•WaitForSeconds - causes the coroutine not to execute for a given game time period

•WWW - waits for a web request to complete (resumes as if WaitForSeconds or null)

•Another coroutine - in which case the new coroutine will run to completion before the yielder is resumed

 


 

 

 

5.    Coroutine 의 장점

A.     성능
yield return new WaitForSecond(3.0f); 
이라는 명령을 수행하면, 코루틴은 유니티 엔진에게 WaitForSeconds(3.0f) 라는 데이터를 보내고 쉬기 시작합니다.
코루틴이 없이 일반적으로 이를 구현한다면, Update 문에서 Time.deltaTime 을 사용하여 매 프레임마다 시간을 더해서 10초가 지났는지 감지해야 하는데, 프레임의 평균 소요 시간이 0.01초 라고 한다면 아이러니하게 10초 동안 대기 하기 위해 스크립트는Update 함수를 1000번 호출해야 합니다
.
특히 모바일 기기에서 코루틴의 활용은 성능 향상에 큰 영향을 미친다
.

B.     가독성
코루틴을 사용하면 읽기 좋은 코드가 만들어 진다. 같은 결과물을 나타내는 두개의 코드로 비교하면

//update 상에서 코루틴을 사용하지 않고 직접 구현시

public class UpdateTimer : MonoBehaviour {
    float accumulator = 0.0f;
    bool wait1Finished = false;
    bool wait2Finished = false;
    float waitTime1 = 1.0f;
    float waitTime2 = 2.0f;

    void Start() {
        Debug.Log ("Action Start!");
    }
    void Update () {
        accumulator += Time.deltaTime;
        if(!wait1Finished && !wait2Finished) {
            if(accumulator >= waitTime1) {
                Debug.Log ("Action1 End");
                wait1Finished = true;
                accumulator = 0.0f;  
            }
        } else if(wait1Finished) {
            if(accumulator >= waitTime2) {
                Debug.Log ("Action2 End");
                wait2Finished = true;
                accumulator = 0.0f;  
            }
        }
    }
}

//Start 
함수를 IEnumerator 로 선언시. (엔진이 자동으로 코루틴으로 실행해줌.
public class CoRoutineTimer : MonoBehaviour {
    float waitTime1 = 1.0f;
    float waitTime2 = 2.0f;   

    IEnumerator Start () {
        Debug.Log ("Action Start");
        yield return new WaitForSeconds(waitTime1);
        Debug.Log ("Action1 End");
        yield return new WaitForSeconds(waitTime2);
        Debug.Log ("Action2 End");
    }     
}

 

6.    특징 및 활용

A.     가장 많이 사용되는 건 WaitForSeconds 명령

멀티 쓰레드 프로그래밍의 Thread.Sleep 함수 처럼 원하는 시간 만큼 잘수 있다. 하지만 더욱 좋은 점은 엔진과 동일한 싱글 쓰레드로 돌아가기 때문에, 멀티 쓰레드 프로그래밍의 어려운 부분인 자원 관리 및 컨텍스트 스위칭(Context Switching)과 같은 다양한 고려 사항의 필요가 없음.

B.     두번째로 많인 사용하는 부분은 비동기 작업

웹에서의 데이터 수신, 씬 로딩과 같이 오래 걸리는 작업은 엔진에게 넘기고, 자신은 진행상황을 점검하면서 UI 처리만 수행하는 형태로 사용합니다.
아래와 같이 WWW 클래스가 대표적인 비동기 작업인데, 웹에서 데이터를 다운로드 받기 위해 스크립트는 엔진에게 www 클래스를 만들어 데이터를 받아올 URL과 함께 넘기고 자신은 쉰다
.
이를 받은 엔진은 비동기 처리를 위한 쓰레드를 생성하고, 네이티브 라이브러리를 사용하여 웹에 접속한  후 데이터를 다운로드 하면서 스크립트로부터 넘겨 받은 www 객체에 직장 상사에게 보고하듯 현재 진행상황을 저장해 둔다.(이 때 클래스의 레퍼런스를 넘겼기 때문에 다른 스크립트에서 진행상황을 읽어 들이는 것이 가능함
.)
엔진에서 다운로드가 끝나면 www 객체에는 완료 상황보고와 함께 받은 데이터가 저장되며, 엔진은 스크립트를 깨운다. 스크립트에서는 바로 그 떄부터 그것을 가지고 사용한다
.

//코루틴이 쉬는 동안 엔진은 열심히 다운로드 받고, Update 함수는 이를 보면서 열심히 진행상황을 로그에 남기는 예제임
.
public class WebBasic : MonoBehaviour {
    public string url;
    WWW www;
    bool isDownloading = false;

    IEnumerator Start()
    {
        www = new WWW(url);
        isDownloading = true;
        yield return www;
        isDownloading = false;
        Debug.Log ("Download Completed!");
    }

    void Update()
    {
        if(isDownloading)
            Debug.Log (www.progress); 
    }
}

//update 에서 이런 처리 부담스러워…프로그래스 바를 넣는식으로 변경하는 방법으로
..
public class WebAdvanced : MonoBehaviour {
    public string url;
    WWW www;

    IEnumerator Start()
    {
       www = new WWW(url);
       StartCoroutine("CheckProgress");
       yield return www;
       Debug.Log ("Download Completed!");
    }

    IEnumerator CheckProgress()
    {
        Debug.Log (www.progress);
        while(!www.isDone)
        {
            yield return new WaitForSeconds(0.5f);
            Debug.Log (www.progress);
        }
    }
}

C.     마지막으로 코루틴을 사용하여 작업을 원하는 대로 순차적으로 수행 할수 있다.

C#과 같은 고급 언어(High Level Language) 에서 지원하는 리플렉션(Reflection) 기능과 코루틴 기능이 혼합된 활용성에 있어서 백미라고 할수 있다
.
이의 활용 예를 위해 RPG 게임등에서 흔히 보이는 패턴인 A 와 B 중 하나를 선택하게 하는 다이얼로그를 띄운 후 , 사용자의 결정에 따라 A 를 선택하면 ActionA 를 수행하고  B 를 선택하면 ActionB를 수행하는 구문을 프로그래밍 할 때 이를 코루틴으로 구현하기 위해, 먼저 모든 행동을 코루틴으로 정의해 준다
.
그리고 각 행동들의 순서를 제어하는 코루틴을 만들어주고, 코루틴 안에서 행동들이 순차적으로 실행되도록 yield return new StartCoroutine 구문으로 차례대로 지정해 준다
.
첫번째 코루틴의 결과에 따라 다음 행동이 A 가 될지 B 가 될지 결정되므로, 첫 번째 결과 값을 string 변수로 지정하고, 변수 값을 StartCoroutine 에 넣으면 실시간으로 결과에 따른 해당 코루틴이 실시간으로 실행 된다. 아래는 실 구현 예제 


public class DialogExample : MonoBehaviour {
    bool showDialog = false;
    string answer = "";

    IEnumerator Start()
    {
        yield return StartCoroutine("ShowDialog");
        yield return StartCoroutine(answer);
    }

    IEnumerator ShowDialog()
    {
        showDialog = true;
        do
        {
            yield return null;
        } while(answer == "");
        showDialog = false;
    }

    IEnumerator ActionA()
    {
        Debug.Log ("Action A");
        yield return new WaitForSeconds(1f);
    }

    IEnumerator ActionB()
    {
        Debug.Log ("Action B");
        yield return new WaitForSeconds(2f);
    }
    void OnGUI()
    {
        if(showDialog)
        {
            if(GUI.Button(new Rect(10f, 10f, 100f, 20f), "A"))
            {
                answer = "ActionA";   
            } else if(GUI.Button(new Rect(10f, 50f, 100f, 20f), "B")) {
                answer = "ActionB";
            }
        }
    }
}

//
결과 값의 유효성을 강화하고 싶다면 아래와 같이 enum 를 보조적으로 사용하는 것도 좋은 방법이 될수 있다.

using UnityEngine;
using System.Collections;

public class DialogExampleEnum : MonoBehaviour {
    enum DialogActions {
        ShowDialog,
        ActionA,
        ActionB
    };

    bool showDialog = false;
    DialogActions action = DialogActions.ShowDialog;
    IEnumerator Start()
    {
        yield return StartCoroutine(action.ToString());
        yield return StartCoroutine(action.ToString());
    }

    IEnumerator ShowDialog()
    {
        showDialog = true;
        do
        {
            yield return null;
        } while(action == DialogActions.ShowDialog);
        showDialog = false;
    }
    IEnumerator ActionA()
    {
        Debug.Log ("Action A");
        yield return new WaitForSeconds(1f);
    }
    IEnumerator ActionB()
    {
        Debug.Log ("Action B");
        yield return new WaitForSeconds(2f);
    }
    void OnGUI()
    {
        if(showDialog)
        {
            if(GUI.Button(new Rect(10f, 10f, 100f, 20f), "A"))
            {
                action = DialogActions.ActionA;         
            } else if(GUI.Button(new Rect(10f, 50f, 100f, 20f), "B")) {
                action = DialogActions.ActionB;
            }
        }
    }
}
이러한 특징을 잘 홀용하면 AI 를 위한 유한상태 기계(Finite State Machine) 을 구현할 때에도 별도의 클래스를 만들지 않고 쉽게 코딩이 가능해 진다
.

D.     Coroutine 는 for 루프 내에서는 사용할 수 없지만, while 문이나 foreach 문에서는 사용이 가능하다.
IEnumerator Foo()
{
 while(someCondition)
 { 
   // 여기서 시간이 오래 걸리는 처리를 수행한다
.
   // 다음 프레임 전에 일시 정지
.
 }
}


*Tip : 이전부터 Debug.Log 을 사용하는 것을 꺼려 했다. 실제 빌드 후에 자동으로 사라지는 것이 아니기 때문에,
이 함수를 별도로 따로 빼서 클래스를 생성해서, 그 클래스를 직접 사용하는 편이 가장 좋다
.
http://answers.unity3d.com/questions/126315/debuglog-in-build.html

public static void Log (String method)
  {
       Debug.Log (method);
    }



[출처] http://kimseunghyun76.tistory.com/304

반응형
Posted by blueasa
, |