'Work'에 해당되는 글 183건

  1. 2016.04.02 MCG 3VectorClone 1.0 제작 완료
  2. 2016.03.22 Max Creation graph help
  3. 2016.03.22 SCRIPTSPOT / MCG
  4. 2016.03.22 Max Creation Graph (MCG) Sample Pack for 3ds Max 2016
  5. 2016.03.22 Unity Editer 단축키
  6. 2016.03.22 XML의 문법
  7. 2016.03.22 Unity json 파싱
  8. 2016.03.22 ngui 줄바꿈 버그 수정
  9. 2015.07.21 7. Reflaction Shader(사내 교육용으로 작성한 PPT) 3
  10. 2015.07.12 객체의 자식을 찾을 떄, 객체를 자식으로 넣을때..
  11. 2015.07.06 6. Alpha Bland & Alpha Test(사내 교육용으로 작성한 PPT)
  12. 2015.07.06 5. texture Mask & Vertex Color Mask(사내 교육용으로 작성한 PPT)
  13. 2015.07.06 4. Normal Map & Emision & reflection(사내 교육용으로 작성한 PPT)
  14. 2015.07.06 3. Specular & Fake BDRF(사내 교육용으로 작성한 PPT)
  15. 2015.07.06 2 – Lambert Light & Half Lambert Light(사내 교육용으로 작성한 PPT)
  16. 2015.07.06 1 – Vertex Shader & Pixel Shader(사내 교육용으로 작성한 PPT)
  17. 2013.10.25 카메라 이동 영역 제한을 위한 방법(4개 포지션)
  18. 2013.10.15 간단한 씬 로딩
  19. 2013.09.27 VSync - WaitForTargetFPS
  20. 2013.09.11 유니티 NGUI 논리 해상도와 픽셀 퍼펙트
  21. 2013.09.11 unity 에디터 확장 2
  22. 2013.09.11 로딩 페이지 및 로딩 프로그래스바 사용하기.
  23. 2013.09.09 효과적인 C# 메모리 관리 기법 1
  24. 2013.09.09 [link] C#, Unity3D Optimization for Mobiles
  25. 2013.09.09 unity 그래픽 성능 최적화
  26. 2013.09.09 Unity 스크립트 성능 최적화
  27. 2013.09.05 문자를 숫자로/숫자를 문자로
  28. 2013.08.29 유니티게임 구글 인앱 결제 붙이기!!
  29. 2013.08.29 유니티 페이스북 연동 - 안드로이드(로그인&아웃,내사진&글 올리기&정보,친구목록)
  30. 2013.07.23 SDK....빌드셋팅 한방 Xamarin...
TA/3Dmax Script Work2016. 4. 2. 12:01



MCG 3VectorClone 1.0 제작 완료


첫 3ds MCG Script 를 제작 했습니다.

정보가 너무 없어서 힘들었지만 알아가는 기쁨이 있네요.


시간되면 주요 노드 구성방법을 올려볼까 합니다.




'TA > 3Dmax Script Work' 카테고리의 다른 글

3DMax MCG 설치방법  (0) 2016.11.09
MCG 3VectorClone 1.0 수정및 배포  (0) 2016.11.09
Max Creation graph help  (0) 2016.03.22
SCRIPTSPOT / MCG  (0) 2016.03.22
Max Creation Graph (MCG) Sample Pack for 3ds Max 2016  (0) 2016.03.22
Posted by 프리랜서 디자이너
TA/3Dmax Script Work2016. 3. 22. 01:23

http://help.autodesk.com/view/3DSMAX/2016/ENU/?guid=GUID-608EC963-75ED-4F63-96B7-D8AE57E75959






'TA > 3Dmax Script Work' 카테고리의 다른 글

3DMax MCG 설치방법  (0) 2016.11.09
MCG 3VectorClone 1.0 수정및 배포  (0) 2016.11.09
MCG 3VectorClone 1.0 제작 완료  (0) 2016.04.02
SCRIPTSPOT / MCG  (0) 2016.03.22
Max Creation Graph (MCG) Sample Pack for 3ds Max 2016  (0) 2016.03.22
Posted by 프리랜서 디자이너
TA/3Dmax Script Work2016. 3. 22. 01:23

http://www.scriptspot.com/3ds-max/mcg

 

 

 

 

'TA > 3Dmax Script Work' 카테고리의 다른 글

3DMax MCG 설치방법  (0) 2016.11.09
MCG 3VectorClone 1.0 수정및 배포  (0) 2016.11.09
MCG 3VectorClone 1.0 제작 완료  (0) 2016.04.02
Max Creation graph help  (0) 2016.03.22
Max Creation Graph (MCG) Sample Pack for 3ds Max 2016  (0) 2016.03.22
Posted by 프리랜서 디자이너
TA/3Dmax Script Work2016. 3. 22. 01:23

Max Creation Graph (MCG) Sample Pack for 3ds Max 2016





Now that 3ds Max 2016 is available for download I'm pleased to announce a set of 30 sample MCG tools and 80 new compounds that you can download, use, and learn from! 

In order to install the tools unzip the contents of MaxCreationGraph.zip and it into your Max Creation Group user folder. You can navigate directly to that folder by pasting
%userprofile%/Autodesk/3ds Max 2016/Max Creation Graph
into your Windows explorer. 

If you have any other compounds and tools installed, be sure to back them up first, in case some files in the zip package have the same name as existing files. When you restart 3ds Max you should see a number of new modifiers in the modifier pull-down. Each name of the new modifier starts with "A" (e.g. "APush"). There will also be a new category in the object creation panel called "Max Creation Graph" with several new tools. 

I have also created a sample test file that you can load once you unzip the tools and restart 3ds Max here: MCGSampleTestScene.zip

Your comments and questions are certainly appreciated, enjoy!



'TA > 3Dmax Script Work' 카테고리의 다른 글

3DMax MCG 설치방법  (0) 2016.11.09
MCG 3VectorClone 1.0 수정및 배포  (0) 2016.11.09
MCG 3VectorClone 1.0 제작 완료  (0) 2016.04.02
Max Creation graph help  (0) 2016.03.22
SCRIPTSPOT / MCG  (0) 2016.03.22
Posted by 프리랜서 디자이너
TA/Unity2016. 3. 22. 01:21

에디터 단축키 사용법 팁


하이어라키 하위노드 펼치고 닫기 -> 마우스를 화살표위에 두고 + Ctrl + 마우스 오른클릭


하이어라키 하위노무 모두 펼치고 닫기 -> 마우스를 화살표위에 두고 + Alt + 마우스 오른클릭


인스펙터창 끄고 켜기 -> 하이어라키에서 노드선택 + Shift + Alt + A

'TA > Unity' 카테고리의 다른 글

ngui - 계속 누르기 버튼 스크립트  (0) 2017.01.22
Unity 해상도 설정  (0) 2016.12.29
XML의 문법  (0) 2016.03.22
Unity json 파싱  (0) 2016.03.22
ngui 줄바꿈 버그 수정  (0) 2016.03.22
Posted by 프리랜서 디자이너
TA/Unity2016. 3. 22. 01:20

Well-formed XML Document

 

XML 1.0 스펙에 정의된 특정한 문법규칙을 따르는 XML.
XML문서를 설계하려면 게층구조의 트리로 표현하는것이 가장 바람직하다.

XML 문서는 Well-formed 를 만족시키기 위해 반드시 요소 작성 규칙을 따라야 한다.

 

XML의 문법

<?xml version="1.0"?>
<note>
    <to>철수</to>
    <from>다니엘</from>
    <heading>기억할 것!</heading>
    <body>이번 주 약속 잊지말길!</body>
</note>
* '<' 와 '>' 사이의 단어들은 xml태그이다.
* 정보는 시작 태그와 끝 태그 사이에 담겨지게 된다
* 태그는 짝을 이루고 있다.
요소의 시작 태그와 끝 태그 사이에 들어 있는 텍스트는 요소 컨텐츠(element content)라고
부른다. 태그 사이에 있는 컨텐츠는 대부분의 경우 다른 요소와 달리 그냥 데이터이다.
이런 경우에는 요소 컨텐츠는 파싱된 문자 데이터(Parsed Character DATA)라고 부른다.
보통 줄여서 PCDATA라고도 한다.
* XML요소 작성 규칙
1. 모든 XML 요소는 시작태그 다음에는 반드시 마침태그(closing tag)를 가져야 한다.
HTML에서는 마침 태그(closing tag)가 필요 없는 요소들도 있었다:
<p>이것은 하나의 ?騈甄?amp;lt;p>이것은 또 다른 문단이다
하지만 XML에서는 다음과 같이 마침 태그를 반드시 가져야 한다:
<p>이것은 하나의 문단이다</p>
<p>이것은 또 다른 문단이다</p>
2. XML 태그는 대소문자 구분이 있다.
XML 태그들은 대소문자 구분을 한다. 태그 <Letter>와 태그 <letter>는 서로 다르다.
그러므로 시작 태그와 마침 태그는 반드시 대소문자를 맞춰줘야 한다:
<Message>이렇게 하면 잘못된 것이다</message> 
<message>이렇게 해야 맞다</message>
3. 모든 XML 요소는 중첩 원리를 잘 지켜야 한다.
HTML에서 어떤 요소들은 다음과 같이 서로 부적절하게 중첩될 수 있었다:
<b><i>이 문장은 볼드체와 이탤릭체로 나타난다</b></i>
 
하지만 XML에서는 다음과 같이 서로 중첩을 잘 시켜줘야 한다:
<b><i>이 문장은 볼드체와 이탤릭체로 나타난다</i></b>
4. 모든 XML 문서는 반드시 최상위 요소를 하나만 가질수 있다.
모든 XML 문서는 한 쌍의 최상위 요소 태그를 포함해야 한다. 
모든 다른 요소들은 이 최상위 요소 하부로 중첩되어야 한다.
모든 요소들은 자신의 하부로 자식 요소들을 가질 수 있으며
모든 요소들은 시작 태그와 마침 태그의 쌍을 정확히 이뤄야 한다.
또한 모든 하위 요소들은 자신의 부모 밑에서 순서를 잘 지켜 중첩되어 있어야 한다:
<root>
  <child>
    <subchild>
    </subchild>
  </child>
</root>
5. 속성 값은 쿼테이션(' 또는 ")으로 감싸줘야 한다.
XML 요소들은 HTML에서와 마찬가지로 name과 value 속성을 가질 수 있다. 
XML에서 속성 값은 항상 쿼테이션(' 또는 ")으로 감싸줘야 한다.
아래 두 개의 XML 문서를 보면, 첫 번째 것은 틀렸고, 두 번째 것은 맞았다.
어떤 부분이 맞고 틀렸는지 살펴보기 바란다:
<?xml version="1.0"?>
<note date=12/11/99>
 <to>철수</to>
 <from>다니엘i</from>
 <heading>기억할 것</heading>
 <body>이번 주 약속 잊지말길!</body>
</note>
 
<?xml version="1.0"?>
<note date="12/11/99">
 <to>철수</to>
 <from>다니엘i</from>
 <heading>기억할 것</heading>
 <body>이번 주 약속 잊지말길!</body>
</note>
6. XML은 텍스트 안에 들어 있는 공백을 유지한다.
HTML에서는 두개 이상의 공백문자을 포함하기 위해서는 &nbsp;를 사용해야만 했다. 
<b>&nbsp; 텍스트 &nbsp;</b>
하지만 XML에서는 태그사이에 텍스트와 함께 사용된 공백문자는 그대로 유지된다.
<b> 텍스트 </b>
-------------------------------------------------------------------------------------------------------
* XML 요소이름 작성규칙
    -  시작문자 가능 ' 문자, '_'
    -  시작문자 불가능 ' 숫자, xml
    -  첫문자 뒤에는 '-'나 '.', 숫자를 사용할 수 있다.
    -  이름은 공백을 포함할 수 없다.
    - ':'를 포함하지 말자. 예약어 이다.
    -  대소문자를 확실히 구분한다.
    -  여는 문자('<') 뒤에 공백을 두면 안 된다.

* 빈요소
아무 데이터도 갖지 않는 요소
<name></name> // 다음과 같은 방법도 사용가능 : <name/>
빈 요소가 자주 쓰이는 장소는 PCDATA를 전혀 갖지 않거나 요소에서 모든 정보를
속성으로 지정하는 경우에 많이 쓰인다.


* XML 선언

문서가 어떤 특정한 타입이라고 알려줄 수 있는 것은 종종 매우 유용하다. XML은 파서에게 몇가지 정보를 알려줌으로써 XML이 어떤 문서인지를 알려 줄 수 있는 XML선언을 제공한다.

 

<?xml version='1.0' encoding='euc-kr' standalone='yes'?>

 

 위의 코딩은 전형적인 XML선언이다.
 XML선언은 "<?xml"문자로 시작해서 "?>"로 끝이 난다.
 선언문에 반드시 version 속성을 표시 해야 하지만 encoding과 standalone 속성은 선택적이다.  속성은 반드시 version, encoding, standalone 순으로 쓰여져야 한다.


Encoding : XML 파서에게 텍스트가 어떤 인코딩을 사용할 것인지 지정해 준다.
standalone : 이 속성의 값으로는 "yes"또는 "no"를 가져야만 한다.
    - Yes : 이 문서가 외부 파일의 요소에 의존하고 있지 않다는 것을 알림.
             (파서에게 이 문서가 갖고 있는 정보를 완벽하게 처리하기 위해 
               필요한 파일이 단지 이것 하나면 충분하다는 사실을 알림)
    - No : 이 문서가 다른 파일에 의존 한다.(외부 파일의 그림, 개체 등을 참조)
 

 


* 주석사용

 

    -  주석은 '<!--'로 시작해서 '-->'로 끝난다

    -  주석은 XML선언 이전에 표현할수 없다.

    -  태그 안에는 주석을 쓸수 없다.
        <first></first <!--이름을 나타낸다-->>

 

    -  주석문 안에다 '--'를 사용할 수 없다. 
        <!--여기 부터는 해석을 하지 않는다 --주석이니까^^-->


 

[사용예제]


    <?xml version="1.0" encoding="euc-kr"?>
    <birth>
          <!--생일의 월 정보-->
              <month>09</month>
          <!--생일의 날짜 정보-->
          <day>33</day>
    </birth>

 

IE에 포함되어 있는 XML파서는 주석을 애플리케이션에 전달한다. 그래서 IE는 우리가 작성한 주석을 화면에 표시한다. 하지만 대부분의 경우, 이런 정보는 오직 소스 파일을 읽는 사람만이 사용할 것이라고 가정하고 만들어졌기 때문에, 주석에 적혀 있는 정보가 애플리케이션에 전달될 지 안될 지는 어떤 파서를 사용하느냐에 달려 있는 것이다.

 

자료출처 : http://korea.internet.com

[출처] [펌] XML문법|작성자 메멘토


'TA > Unity' 카테고리의 다른 글

Unity 해상도 설정  (0) 2016.12.29
Unity Editer 단축키  (0) 2016.03.22
Unity json 파싱  (0) 2016.03.22
ngui 줄바꿈 버그 수정  (0) 2016.03.22
객체의 자식을 찾을 떄, 객체를 자식으로 넣을때..  (0) 2015.07.12
Posted by 프리랜서 디자이너
TA/Unity2016. 3. 22. 01:19

Unity json 파싱


외부 텍스트 파일을 불러 와야 할일이 있어서 XML을 살펴봣는데 생각보다 예외 상황도 많은듯하고, 복잡했습니다. @..@;;

제겐 어렵네요... 구조 파악도 잘안되고.. 어휴...


결국 조금더 편리하다는 json으로 다시 살펴봤고 일단 구현은 했습니다.


배열구조로 가야하나 했지만 제가 만들려는 것이 배열구조와는 맞지 않아 key : value 로만 구성했습니다.

힘드네요. ㅋㅋ



가장 심플한 기본 코드만 작성해 봤습니다.

json 파일을 불러와 ngui  Labal에 text를 찍는 과정입니다.



-Litjson.dll 넣기-

Litjson.dll 파일을 유니티에 Plugins폴더에 넣어 주셔야 합니다.





-json파일 구조(key : value)-

{

"key" : "value",

"key" : "value",

"key" : "value"

}





-json 예제 파일-

메모장을 열고 아래처럼 적고 유니티에 넣으세요.(txt)

{

   "Lines_00":"멍멍멍"

}






-C# 예제 코드-

using UnityEngine;

using System.Collections;

using LitJson;

using System;



public class LoadLines : MonoBehaviour {


public TextAsset JsonFile;

public GameObject Lines_00;

private string Out_Lines_00;



void Awake(){


LitJson.JsonData getData = LitJson.JsonMapper.ToObject(JsonFile.text);


Out_Lines_00 = getData["Lines_00"].ToString();

Lines_00.GetComponent<UILabel>().text = Out_Lines_00;


}



}



Posted by 프리랜서 디자이너
TA/Unity2016. 3. 22. 01:19

ngui 줄바꿈 문제가 있었습니다.

아래 글 긁어왔습니다.

 

출처 : http://www.tasharen.com/forum/index.php?topic=4114.msg20026#msg20026

 

 

UI Label.cs

From


   public string text
   {
      get
      {
         return mText;
      }
      set
      {
         if (string.IsNullOrEmpty(value))
         {
            if (!string.IsNullOrEmpty(mText))
               mText = "";
            hasChanged = true;
         }
         else if (mText != value)
         {
            mText = value;   <=== Here
            hasChanged = true;
         }
      }
   }


To


   public string text
   {
      get
      {
         return mText;
      }
      set
      {
         if (string.IsNullOrEmpty(value))
         {
            if (!string.IsNullOrEmpty(mText))
               mText = "";
            hasChanged = true;
         }
         else if (mText != value)
         {
            mText = value.Replace("\\n", "\n");  <== Here
            hasChanged = true;
         }
      }
   }

'TA > Unity' 카테고리의 다른 글

XML의 문법  (0) 2016.03.22
Unity json 파싱  (0) 2016.03.22
객체의 자식을 찾을 떄, 객체를 자식으로 넣을때..  (0) 2015.07.12
카메라 이동 영역 제한을 위한 방법(4개 포지션)  (0) 2013.10.25
간단한 씬 로딩  (0) 2013.10.15
Posted by 프리랜서 디자이너

 

 

 

 

 

 

 

 

 

0123

 

Posted by 프리랜서 디자이너
TA/Unity2015. 7. 12. 17:51

 

객체의 자식구조를 찾을 떄

transform.FindChild("이름");

 

 

 

새로 생성한 프리팹을 특정 오브젝트 자식으로 넣을때..

Player = Instantiate(Player_01, Player_pos.transform.position, transform.rotation) as GameObject;
Player.transform.parent = gameObject.transform.FindChild ("Player_pos").transform;

 

 

 

또는....

Player = Instantiate(Player_01, Player_pos.transform.position, transform.rotation) as GameObject;
Player.transform.parent = Player_pos.gameObject.transform;

 

 

'TA > Unity' 카테고리의 다른 글

Unity json 파싱  (0) 2016.03.22
ngui 줄바꿈 버그 수정  (0) 2016.03.22
카메라 이동 영역 제한을 위한 방법(4개 포지션)  (0) 2013.10.25
간단한 씬 로딩  (0) 2013.10.15
VSync - WaitForTargetFPS  (0) 2013.09.27
Posted by 프리랜서 디자이너

 

01234567891011

 

Posted by 프리랜서 디자이너

 

012345

 

Posted by 프리랜서 디자이너

 

0123456789

 

Posted by 프리랜서 디자이너

 

012345678910

 

Posted by 프리랜서 디자이너

 

01234

 

Posted by 프리랜서 디자이너

 

01234567891011121314

 

Posted by 프리랜서 디자이너
TA/Unity2013. 10. 25. 08:42

카메라의 이동 영역을 제한하기 위해 4개의 포지션을 설정하고 에디터에 라인을 그려 눈으로 확인하게 해줌.(중략)


 public float minPosX=-10;
 public float maxPosX=10;
 public float minPosZ=-10;
 public float maxPosZ=10;


 public bool showGizmo=true;
 .

.

.

.

.
  //Camera Area Line- (Editor & Mobile)
  float x=Mathf.Clamp(thisT.position.x, minPosX, maxPosX);
  float z=Mathf.Clamp(thisT.position.z, minPosZ, maxPosZ);
  thisT.position=new Vector3(x, thisT.position.y, z); .

.

.

.

.

.
.
 //Camera Area Line Void
 void OnDrawGizmos(){
  if(showGizmo){
   Vector3 p1=new Vector3(minPosX, transform.position.y, maxPosZ);
   Vector3 p2=new Vector3(maxPosX, transform.position.y, maxPosZ);
   Vector3 p3=new Vector3(maxPosX, transform.position.y, minPosZ);
   Vector3 p4=new Vector3(minPosX, transform.position.y, minPosZ);
   
   Gizmos.color=Color.green;
   Gizmos.DrawLine(p1, p2);
   Gizmos.DrawLine(p2, p3);
   Gizmos.DrawLine(p3, p4);
   Gizmos.DrawLine(p4, p1);
.

.

.


 

Posted by 프리랜서 디자이너
TA/Unity2013. 10. 15. 15:24

using UnityEngine;
using System.Collections;
using UnityEngine.SceneManagement;


public class GameStart : MonoBehaviour {

    public string SceneName;


    // Use this for initialization
    void Start () {
    }
    
    // Update is called once per frame
    void Update () {
        
    }

    public void StartGame() {
        SceneManager.LoadScene (SceneName);
    }

}

Posted by 프리랜서 디자이너
TA/Unity2013. 9. 27. 14:58

VSync를 꺼서 최적화를 시켜보자 Unity3D

2013/07/18 17:35

출처 http://blog.naver.com/dieofflee/40193299588

 

 

 

 

 

VSync는 모니터의 화면 갱신에 맞추는 것인데

이게 생각외로 프로세스를 잡아 먹는다

 

 

첫번째 사진은 VSync옵션을 킨 상태인데 

보시다시피 대부분의 프로세스를 VSync가 사용하고 있다.



 

두번째 사진은 VSync옵션을 끈 상태인데

FPS가 2배로 오르고 그래프자체도 더 보기 편해졌다.

 

 

이제 이 옵션을 끄는 방법인데

Edit > ProjectObtion > QualitySetting 을 눌러보면

아래쪽에 VSync Count라는 항목이 있는데 Dont Sync로 바꿔주면 된다

 

 

 

아이폰의 경우 VSync를 기기에서 자체적으로 해주기 때문에

처음부터 Dont Sync를 해주면 되고

 

안드로이드의 경우 자체적으로 해주지 않아서 

키던 안 키던 그건 개발자가 결정하면 될듯 하다

 

 

참고로 이 옵션을 끄게 되면 베터리 소모가 많아 진다고 한다 ㅇㅇ

 

 

깨알팁 

- 맨위 옵션도 최적화에 도움이 되는 건데 그냥 텍스쳐의 질을 결정하는 거라 보면 된다.

 

Posted by 프리랜서 디자이너
TA/Unity2013. 9. 11. 14:13

http://myevan.cpascal.net/articles/2013/unity_ngui_pixel_perfect.html

 

유니티 NGUI 논리 해상도와 픽셀 퍼펙트

다양한 해상도를 지원하는 가장 간단한 방법 중 하나는 고정된 논리 해상도를 사용하는 것입니다. 레퍼런스 장치 해상도를 기준으로 그래픽 리소스를 만든 다음 다른 장치에서 적당히 확대 혹은 축소해서 렌더링하는 방법입니다.

예를 들어 아이폰3GS 는 320x480 (2:3) 해상도를 사용하는데, 아이폰4는 640x960 (2:3) 해상도이므로 2배 확대해서 그대로 실행할 수 있습니다. 아이패드 768x1024 (3:4) 해상도에서 실행하려면 세로 길이를 1024에 맞추고 가로길이를1024 / 480 * 320 = 682 로 확대하면 리소스나 레이아웃 수정없이 작동시킬 수 있습니다.

NGUI 에서는 2.5.x 버전 기준으로 UIRoot 컴포넌트의 Scaling Style 프로퍼티를FixedSize 로 선택한 다음 ManualHeight 프로퍼티만 설정해주면 고정된 논리 해상도를 사용할 수 있습니다.

문제는 논리 해상도 모드로 아틀라스 위젯들을 사용할 경우 위젯 경계에 검은색 선이 종종 등장한다는 것입니다. 아틀라스 스프라이트 영역 경계에 있는 검은색 컬러가 살짝 보이는 것인데, 해당 위치에 알파를 확인해보면 0으로 보이지 않아야 정상입니다. 더구나 padding 을 아무리 넓게 주어도 검은색 선 문제는 사라지지 않습니다. 선형 보간 기능의 특성상 주변 텍셀을 참조하기 때문인데, 텍스쳐 Filter Mode 를 Point 로 변경해 선형 보간을 사용하지 않으면 검은 색 선이 사라지는 것을 확인 할 수 있습니다.

그럼 이제 UIWidget 의 MakePixelPerfect 코드를 살펴보도록 하겠습니다.

virtual public void MakePixelPerfect ()
{
    Vector3 scale = cachedTransform.localScale;

    int width  = Mathf.RoundToInt(scale.x);
    int height = Mathf.RoundToInt(scale.y);

    scale.x = width;
    scale.y = height;
    scale.z = 1f;

    Vector3 pos = cachedTransform.localPosition;
    pos.z = Mathf.RoundToInt(pos.z);

    if (width % 2 == 1 && (pivot == Pivot.Top || pivot == Pivot.Center || pivot == Pivot.Bottom))
    {
        pos.x = Mathf.Floor(pos.x) + 0.5f;
    }
    else
    {
        pos.x = Mathf.Round(pos.x);
    }

    if (height % 2 == 1 && (pivot == Pivot.Left || pivot == Pivot.Center || pivot == Pivot.Right))
    {
        pos.y = Mathf.Ceil(pos.y) - 0.5f;
    }
    else
    {
        pos.y = Mathf.Round(pos.y);
    }

    cachedTransform.localPosition = pos;
    cachedTransform.localScale = scale;
}

MakePixelPerfect 는 픽셀 좌표를 정수화시킨 후 위젯 길이가 홀수면 버텍스 좌표를 0.5만큼 보정하는 기능입니다. 즉, 픽셀 퍼펙트를 적용 하면 검은색 선은 보여서는 안된다는 말입니다. 그렇다면 어디에 문제가 있는 것일까요?

그렇습니다. 당연하겠지만 이번 글의 주제인 논리 해상도가 문제의 원흉입니다. 논리 해상도에서 힘들여 설정한 픽셀 퍼펙트한 좌표가 실제 해상도에서는 엉뚱한 좌표로 변환되어 버린 것입니다 =ㅁ=)!!

예를 들어 1000x1000 논리 해상도에서 0.5 보정한 좌표는 2000x2000 해상도에서는 1을 변경한 것으로 바뀝니다. 500x500 해상도라면 보정값이 0.25가 됩니다. 1000x1000 해상도에서 보정이 필요한 5픽셀은 2000x2000해상도에서는 10픽셀 짝수 길이가 되어 보정이 불필요해지고, 1000x1000 해상도에서 보정이 필요없는 10픽셀 길이는 500x500 해상도에서 5픽셀 길이가 되어 보정이 필요해집니다.

실제로 검은색 선이 보이는 위젯의 위치를 0.1 단위로 조금씩 변경해보면 어느순간 검은색 선이 사라지는 상황을 만들 수 있습니다. 다만 해당 값이 짐작하기 어려운 수치라는것이 문제죠.

그럼 논리 해상도에서 픽셀 퍼펙트한 좌표를 계산해 내는 방법을 알아보도록 하겠습니다.

일단 논리 좌표와 논리 크기를 사용해 실제 좌표와 실제 크기를 구합니다.

화면 배율 scale =  논리 해상도 / 실제 해상도
논리 크기 lsize = 위젯 transform.localScale
논리 좌표 lpos = 위젯 transform.localPosition
실제 좌표 rpos = lpos / scale
실제 크기 lsize = lsize / scale

실제 크기를 토대로 실제 좌표에 픽셀 퍼펙트를 적용합니다.

보정 좌표 = 실제 크기가 홀수면 실제 좌표 보정

보정된 실제 좌표를 논리 좌표계로 다시 가져오면 완료입니다!

위젯 transform.localPosition = 보정 좌표 * scale
위젯 transform.localScale = 실제 크기  * scale

한가지 주의해야 할 점은 위젯 transform 을 보정된 값으로 가지고 있을 경우 유니티 인스펙터 값이 그로테스크해진다는 것 입니다.

작업 편의를 위해서는 UpdateGeometry 에서만 위젯 transform 변경해 사용한 다음 바로 원래 값으로 복원해주는 것이 좋습니다.

마지막으로 직접 코딩하기 귀찮으신 분들을 위한 NGUI UIWidget.cs 수정 사항입니다.

// BUGFIX
public static int rootManualHeight; // TODO: 외부에서 UIRoot 의 manualHeight 값을 넣어주어야 합니다
// BUGFIX_END

public bool UpdateGeometry (ref Matrix4x4 worldToPanel, bool parentMoved, bool generateNormals)
{
    if (material == null) return false;

    if (OnUpdate() || mChanged)
    {
        mChanged = false;
        mGeom.Clear();
        OnFill(mGeom.verts, mGeom.uvs, mGeom.cols);

        if (mGeom.hasVertices)
        {
            Vector3 offset = pivotOffset;
            Vector2 scale = relativeSize;

            offset.x *= scale.x;
            offset.y *= scale.y;

            mGeom.ApplyOffset(offset);

            // DELETEME: mGeom.ApplyTransform(worldToPanel * cachedTransform.localToWorldMatrix, generateNormals);

            // BUGFIX
            ApplyPixelPerfectGeometryTransform(ref worldToPanel, generateNormals);
            // BUGFIX_END
        }
        return true;
    }
    else if (mGeom.hasVertices && parentMoved)
    {
        // DELETEME: mGeom.ApplyTransform(worldToPanel * cachedTransform.localToWorldMatrix, generateNormals);
        // BUGFIX
        ApplyPixelPerfectGeometryTransform(ref worldToPanel, generateNormals);                  
        // BUGFIX_END
    }
    return false;
}

// BUGFIX
void ApplyPixelPerfectGeometryTransform(ref Matrix4x4 worldToPanel, bool generateNormals)
{
    float screenScale = rootManualHeight / Screen.height;
    float invScreenScale = 1.0f / screenScale;  

    var widgetTransform = this.cachedTransform;
    var oldSize = widgetTransform.localScale;
    var oldPos = widgetTransform.localPosition;
    var realSize = oldSize * invScreenScale;
    var realPos = oldPos * invScreenScale;

    int realWidth  = Mathf.RoundToInt(realSize.x);
    int realHeight = Mathf.RoundToInt(realSize.y);

    if (realWidth % 2 == 1 && (pivot == UIWidget.Pivot.Top || pivot == UIWidget.Pivot.Center || pivot == UIWidget.Pivot.Bottom))
    {
        realPos.x = Mathf.Floor(realPos.x) + 0.5f;
    }
    else
    {
        realPos.x = Mathf.Round(realPos.x);
    }

    if (realHeight % 2 == 1 && (pivot == UIWidget.Pivot.Left || pivot == UIWidget.Pivot.Center || pivot == UIWidget.Pivot.Right))
    {
        realPos.y = Mathf.Ceil(realPos.y) - 0.5f;
    }
    else
    {
        realPos.y = Mathf.Round(realPos.y);
    }

    widgetTransform.localPosition = realPos * screenScale;
    widgetTransform.localScale = realSize * screenScale;

    mGeom.ApplyTransform(worldToPanel * cachedTransform.localToWorldMatrix, generateNormals);

    widgetTransform.localPosition = oldPos;
    widgetTransform.localScale = oldSize;
}
// BUGFIX_END

뭔가 좀 더 최적화가 가능할 것 같지만, 일단 여기까지만 다루도록 하겠습니다.

'TA > Unity' 카테고리의 다른 글

간단한 씬 로딩  (0) 2013.10.15
VSync - WaitForTargetFPS  (0) 2013.09.27
unity 에디터 확장  (2) 2013.09.11
로딩 페이지 및 로딩 프로그래스바 사용하기.  (0) 2013.09.11
효과적인 C# 메모리 관리 기법  (1) 2013.09.09
Posted by 프리랜서 디자이너
TA/Unity2013. 9. 11. 11:31

출처 : http://www.unitystudy.net/bbs/board.php?bo_table=writings&wr_id=146&sca=&sfl=wr_subject&stx=%EC%97%90%EB%94%94%ED%84%B0+%ED%99%95%EC%9E%A5&sop=and

unitystudy.net

 

 

 

 

 

 

 

 

1. 에디터 확장 01 - 커스텀 인스펙터

안녕하세요, 우영씨입니다. 이번에 에디터 확장과 관련된 글 연재를 맡게 되었습니다. 많은 경험이 있는 것은 아니지만 최대한 제가 알고 있는 선에서 지식과 경험을 공유해보고자 합니다. 이 연재가 여러분의 개발을 즐겁게 해줄 수 있기를 기대해봅니다.
문의 사항이 있으신 분은 아래 덧글로 남겨주시거나 whoo24@gmail.com 또는 @whoo24 으로 문의주십시오. 편의상 존칭은 생략하였습니다.

에디터

에셋스토에서 구입한 툴이나 플러그 인들은 대체로 자신들만의 에디터를 가지는 경우가 많다. 툴이 필요한 이유는 실수를 줄여주고 컨텐츠의 빠른 추가가 가능하기 때문이다. 특히 프로그래머가 아닌 기획자나 아트 쪽 작업자가 건드리기에도 무척 유용하다.
유니티의 경우 엔진 소스에 접근하지 않고 유니티 툴을 확장 시켜서 개발 효율을 향상시키는 방법을 제공하고 있다. 아래의 이미지는 2D Toolkit의 스프라이트 콜랙션에 관련된 에디터이다. 

TK2D 에디터 이미지


인스펙터

인스펙터는 게임 오브젝트에 대한 속성값을 보여주고 설정을 변경할 수 있도록 도와주는 창이다. 기본적으로 MonoBehaviour의 public 으로 설정한 멤버 변수들이 인스펙터에 읽기/쓰기가 가능한 상태로 노출된다. 대부분의 경우는 맴버 변수의 타입에 따라서 인스펙터가 자동으로 필드를 생성시켜준다.

카메라의 경우 Clear Flags 같이 콤보 필드와 Background 같은 컬러 필드, 그리고 Culling Mask처럼 여러 값이 선택 가능한 마스크 필드를 비롯한 여러가지 필드들을 볼 수 있다.

Main Camera의 인스펙터



인스펙터를 다시 한번 보면 게임 오브젝트에 추가된 컴포넌트 별로 속성이 정리되어 있다. 우리는 이 인스펙터가 보여주는 뷰 자체를 뜯어고칠 수는 없다. 다만 하나의 컴포넌트에 대해 개별적으로 수정할 수 있을 뿐이다.

컴포넌트 인스펙터 확장

유니티 프로젝트를 하나 만들면 두 가지 프로젝트가 생성된다. Assembly-CSharp 프로젝트와 Assembly-CSharp-Editor 프로젝트가 자동으로 만들어진다.

에디터에 관계된 코드들은 게임 상에서는 실행될 수 없다. 빌드조차 되지 않는다. 그 이유는 대부분의 기능이 UnityEditor 라는 네임스페이스 내에 구현되어 있고 이 UnityEditor 참조는 게임 프로젝트에 포함되지 않는다. 

반드시 루트의 Editor 폴더에 스크립트가 있어야 하는 것은 아니다. 어느 서브 폴더든지 Editor 폴더안에만 있다면 OK.

image2013-2-3 23-18-9.png



씬에 큐브를 하나 만들고 이 큐브를 회전시키는 스크립트를 하나 만들어 보자. 두 스크립트 파일을 만든다.

그리고 이 스크립트를 조절할 수 있도록 인스펙터를 확장해보자. 우선 CubeRotator.cs 파일을 생성한다.

using UnityEngine;
using System.Collections;
public class CubeRotator : MonoBehaviour {
    public float rotationSpeed = 180.0f;
     
    void Update () {
        gameObject.transform.Rotate(Vector3.up, Time.smoothDeltaTime * rotationSpeed);
    }
}

image2013-2-3 21-1-10.png



큐브에 이 컴포넌트를 추가하고 플레이 버튼을 누르면 이 큐브는 그 자리에서 뱅글뱅글 돌 것이다.

컴포넌트를 확장해서 해당 큐브의 회전 방향을 바꿔주는 버튼을 하나 만들어보자. 우선은 인스펙터를 확장하기 위한 뼈대를 만들자. Editor/CubeRotatorInspector.cs 파일을 만든다.

using UnityEngine;
using UnityEditor;
 
[CustomEditor(typeof(CubeRotator))]
public class CubeRotatorInspector : Editor {
    public override void OnInspectorGUI()
    {
        base.DrawDefaultInspector();
    }
}
 
image2013-2-3 21-8-16.png



CubeRorator에 대한 인스펙터가 미묘하게 달라진 것을 볼 수 있다. 코드를 보면 우선 네임스페이스에 UnityEditor를 추가했다. 그리고 CubeRotatorInspector 클래스가 UnityEditor.Editor 를 상속 받았다. 

상속 받은 CubeRotatorInspector CustomEditor클래스 어트리뷰트를 가지고 있다. 이 CustomEditor는 인자값으로 받은 타입의 인스펙터에 대한 처리를 담당하게 된다. 따라서 여기서는 CubeRotator 컴포넌트에 대한 인스펙터를 처리하게 되는 것이다.

OnInspectorGUI() 메쏘드는 UnityEditor.Editor 클래스에서 파생된 메쏘드이다. 이 메쏘드를 오버라이드 해서 우리가 원하는 모습을 만들 수 있다.
UnityEditor.Editor 클래스에 대한 더 자세한 사항은 다음의 자료를 찾아보도록 하자.
 
 계속해서 인스펙터를 바꿔보자.

using UnityEngine;
using UnityEditor;
[CustomEditor(typeof(CubeRotator))]
public class CubeRotatorInspector : Editor
{
    public override void OnInspectorGUI()
    {
        EditorGUILayout.LabelField("Custom Inspector for", "CubeRotator"); 
         
        base.OnInspectorGUI();
         
        if(GUILayout.Button("Change Direction"))
        {
            CubeRotator cube = target as CubeRotator;
            if (cube)
            {
                cube.rotationSpeed = -cube.rotationSpeed;
            }
        }
    }
}

OnInspectorGUI() 메쏘드에서 LabelField를 추가했다. Custom Inspector for 라는 라벨은 왼쪽에, CubeRotator는 오른쪽에 위치하게 된다. 

그리고 기본 인스펙터 데이터를 그리고(스크립트와 Public 변수들이 포함된다) Change Direction 이라는 버튼을 생성한다. 이 버튼을 누르면 Editor.target 변수를 이용해 컴포넌트를 가지고 온다. Editor.target 변수는 UnityEngine.Object 형이므로 적절한 형으로 캐스팅해줘야 한다. target의 rotationSpeed를 반대 방향으로 바꿔준다. 이제 이 버튼을 누르는 순간 큐브는 반대로 회전하게 될 것이다.

위에서 보는 것처럼 EditorGUILayout  GUILayout 모두 사용 가능하다. EditorGUILayout은 Field와 관련있는 메쏘드들이 대부분이다. GUILayout은 기본 필드, 스크롤바, 토글 등이 있다.
자세한 사항은 다음 링크에서 알아보자.

image2013-2-3 22-12-13.png



플레이 버튼을 누르고 플레이 중에 Change Direction을 눌러보자. Rotation Speed 값이 -180 으로 변경될 것이다. 커스텀 인스펙터도 기본 인스펙터와 마찬가지로 플레이 중이 아니더라도 작동한다. 에디트 모드에서 버튼을 눌러보면 마찬가지로 동작한다.

여러 객체 편집하기

우리가 만든 Cube 객체를 복사(Duplicate)해서 하나 더 만들어보자.

image2013-2-3 22-18-56.png



그리고 둘 다 선택하면 인스펙터에 우리가 제작한 CubeRotator 컴포넌트에 해당하는 부분에 Multi-object editing not supported. 라는 메시지가 뜬다.

image2013-2-3 22-20-21.png



이 경우에는 CanEditMultipleObjects 어트리뷰트를 추가해주면 된다. 단 이 경우 Editor.target은 Hierarchy에서 가장 상위에 위치한 오브젝트를 지칭하게 되니 Editor.target 대신 Editor.targets를 써주면 된다.

using UnityEngine;
using UnityEditor;
using System.Linq;
using System;
 
[CanEditMultipleObjects]
[CustomEditor(typeof(CubeRotator))]
public class CubeRotatorInspector : Editor
{
    public override void OnInspectorGUI()
    {
        EditorGUILayout.LabelField("Custom Inspector for", "CubeRotator");
        base.OnInspectorGUI();
        if (GUILayout.Button("Change Direction"))
        {
            CubeRotator[] cubes = Array.ConvertAll(targets, _t => _t as CubeRotator);
            foreach (CubeRotator cube in cubes)
            {
                if (cube)
                {
                    cube.rotationSpeed = -cube.rotationSpeed;
                }
            }
        }
    }
}

보면 알겠지만 Array를 캐스팅할 때 Linq를 사용했다. Linq 구문 대신 foreach 문으로 대신해도 된다. 저 Linq 구문은 Object[]인 targets에서 _t 이름으로 하나씩 꺼낸 후 CubeRotator 로 컨버팅 한 배열을 돌려주는 것이다. 만약 Linq에 대해서 모른다면 최소한 기본 문법 만큼은 배워두도록 하자.
 
image2013-2-3 22-38-56.png



이제 Cube를 두 개 선택해도 인스펙터가 정상적으로 작동한다.

정리

첫 강은 컴포넌트 타입에 대한 인스펙터 확장법에 대해 알아보았다. 인스펙터와 관련된 코드들이 어디에 있어야 하는지에 대해서 이야기 했고, 인스펙터에 GUI 요소를 추가하는 방법도 알아보았다. 하나의 오브젝트 뿐만아니라 여러 오브젝트들도 동시에 편집하는 방법을 알아보았다.
다음번 강좌에서는 더 많은 GUI 요소들과 메뉴 확장에 대해서 알아보도록 하겠다.

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

2. 에디터 확장 02 - 메뉴 확장(1)

메뉴 확장

이전 강에서는 인스펙터를 확장해 보았다. 
유니티를 확장할 수 있는 요소로는 인스펙터 뿐만 아니라 메뉴를 확장할 수도 있다. 
메뉴에는 메뉴바에 추가되는- 일반 메뉴와 인스펙터 옆에서 작동하는 컨택스트 메뉴가 있다. 
이번 연재에는 메뉴바에 새로운 메뉴를 넣어보고 메뉴를 작동하는 방법에 대해서 알아보도록 한다.

주메뉴 추가

메뉴를 추가하는 방법은 매우 간단하다. 
MenuItemAttribute를 이용하면 된다. 
주의할 점은 static 함수에만 가능하다는 점이다. 
대신에 액세스 한정자는 무엇이 되든 상관이 없다. public 이나 protected나 심지어 private 여도 상관없이 지정할 수 있다.
 또 어떤 클래스에 있던지 상관이 없다. 일반 클래스여도 상관이 없고, MonoBehavior를 상속받은 컴포넌트내에 있어도 상관이 없다.
 다만 MenuItemAttribute는 UnityEditor 네임 스페이스 안에 있으므로 Editor 폴더 밖이라면 적당한 전처리기(#if UNITY_EDITOR ... #endif) 로 묶어줘야 한다.

02-1.png

using UnityEngine;
using UnityEditor;
public class CustomMenu
{
    [MenuItem("Custom Menu/public custom Menu")]
    static public void CustomMenuItem()
    {
        Debug.Log("public customMenuItem");
    }
    [MenuItem("Custom Menu/protected custom Menu")]
    static protected void CustomMenuItemProtected()
    {
        Debug.Log("protected customMenuItem");
    }
    [MenuItem("Custom Menu/private custom Menu")]
    static private void customMenuItemPrivate()
    {
        Debug.Log("private customMenuItem");
    }
}

MenuItemAttribute는 다음과 같은 생성자를 가진다.
public MenuItem(string itemName);
public MenuItem(string itemName, bool isValidateFunction);
public MenuItem(string itemName, bool isValidateFunction, int priority);

itemName은 메뉴의 경로를 나타낸다. 위에서 사용한 것 처럼 '/' 를 구분자로 이용해 서브 메뉴를 자동으로 생성한다. 
유니티에서 MenuItem의 메뉴 경로를 바꾸면 자동으로 새로운 경로로 메뉴를 생성하여 준다.
 다만 루트 경로를 바꾼 경우에는 에디터를 껐다가 켜야 한다.

itemName 인자를 이용해 단축키를 지정할 수도 있다.
02-2.png


using UnityEngine;
using UnityEditor;
public class CustomMenu
{
    [MenuItem("Custom Menu/public custom Menu %1")]
    static public void CustomMenuItem()
    {
        Debug.Log("public customMenuItem");
    }
    [MenuItem("Custom Menu/protected custom Menu #1")]
    static protected void CustomMenuItemProtected()
    {
        Debug.Log("protected customMenuItem");
    }
    [MenuItem("Custom Menu/private custom Menu %&1")]
    static private void customMenuItemPrivate()
    {
        Debug.Log("private customMenuItem");
    }
}

%는 Ctrl키(맥은 cmd키)를 나타낸다. #는 Shift 키를 나타내고 _는 옵션키를 아무것도 지정하지 않은 키를 나타낸다. &는 alt 키를 나타낸다.
%& 처럼 두 가지 이상의 키를 조합할 수도 있다.

isValidateFunction 인자는 메뉴의 유효성 검사를 할 수 있는 옵션이다. 
유효성 검사를 통해 해당 메뉴를 켜거나 끄거나 할 수 있다.

02-3.png


using UnityEngine;
using UnityEditor;
public class ValidateMenu
{
    [MenuItem("Custom Menu/Validate/Get GameObject Id", validate = true)]
    static public bool GetGameObjectIdValidator()
    {
        bool selected = (Selection.activeGameObject != null);
        Debug.Log(string.Format("Validate({0})", selected));
        if (selected)
            return true;
        return false;
    }
    [MenuItem("Custom Menu/Validate/Get GameObject Id", validate = false)]
    static public void GetGameObjectId()
    {
        Debug.Log(string.Format("GetGameObjectId({0})", Selection.activeInstanceID));
    }
}

유효성 평가 함수가 되려면 bool 형을 리턴하는 함수여야 한다. 
만약 void 형이나 다른 형이 들어온다면 false로 평가된다.

현재 아무 오브젝트도 선택하지 않았기 때문에 유효성 평가 함수인 GetGameObjectIdValidator() 는 false를 리턴한다.
로그를 보면 Validate(false) 를 볼 수 있는데, 매번마다 메뉴를 보여질 때 마다 평가하게 된다.

02-4.png



이제 씬의 아무 오브젝트나 선택하고 메뉴를 열면 Get GameObject Id 메뉴가 활성화 된 것을 볼 수 있다.
로그를 보면 Validate가 두 번 불려진 것을 확인할 수 있는데, 메뉴가 보여질 때 한 번, 메뉴가 눌려저서 실행될 때 한 번 불려지는 것을 알 수 있다.

priority 인자는 메뉴의 배치 순서를 나타낸다.
Custom Menu를 보면 public, protected, private 사이에 Validate 메뉴가 있는 것을 볼 수 있다. 
이 메뉴를 정렬하는 법을 알아보자.

priority 인자를 설정하지 않으면 priority 인자를 설정한 메뉴들과 구분선이 생기게 된다.
priority를 양수로 설정하면 메뉴 하단에 위치하게 되고, 음수로 설정하면 메뉴 상단에 위치하게 된다. 
validate가 true 인 함수는 표시 되지 않으므로 의미가 없고 validate가 false로 설정된 함수에 priority를 설정해두면 우선순위 설정이 정상적으로 된다.

02-5.png


using UnityEngine;
using UnityEditor;
public class CustomMenu
{
    [MenuItem("Custom Menu/public custom Menu", priority = 0)]
    static public void CustomMenuItem()
    {
        ...
    }
    [MenuItem("Custom Menu/protected custom Menu", priority = -1)]
    static protected void CustomMenuItemProtected()
    {
        ...
    }
    [MenuItem("Custom Menu/private custom Menu", priority = 1)]
    static private void customMenuItemPrivate()
    {
        ...
    }
}

정리

이번 강의는 조금 짧게 진행이 되었다. 
이번에는 메뉴바에 MenuItemAttribute를 이용해 새로운 메뉴를 추가하는 방법을 알아보았고 MenuItemAttribute의 다양한 속성에 대해서 알아보았다.
다음 번에는 컨택스트 메뉴에 대해서 자세히 알아보는 시간을 가져보도록 하겠다.

 

 

 

 

 

 

 

 

 

 

에디터 확장 03 - 메뉴 확장(2) Context Menu와 Generic Menu

컨텍스트 메뉴

컨텍스트 메뉴는 주로 마우스 오른쪽 버튼을 눌렀을 때 동작하는 메뉴를 뜻하는데, 유니티에서는 타겟이 존재하는 컴포넌트에 행하는 메뉴라고 보는 편이 더 적합하다고 본다.

컨텍스트 메뉴를 만드는 방법은 두 가지가 있다.

먼저 첫 번째 방법은 주 메뉴를 만드는 것 처럼 MenuItemAttribute를 사용하는 방법이 있다.

그리고 두 번째 방법은 ContextMenuAttribte를 사용하는 것이다.

image2013-3-6 9-50-17.png



우선 예제 씬을 구성한다.

녹색 큐브를 놓고 그 아래에 터레인을 깔았다.

Hierarchy는 다음과 같다.

image2013-3-6 9-52-19.png



NewContext.cs 파일과 Editor/ContextEditor.cs 파일을 만들었고, 지형 데이터와 씬, 머테리얼 애셋을 미리 만들어서 설정해두었다.

 

유니티에서 컨택스트 메뉴는 주 메뉴를 만들었을 때 처럼 특정한 attribute 를 이용해서 설정하면 된다.

주 메뉴를 만들듯이 MenuItemAttribute를 이용해서 컨택스트 메뉴를 만들어 보자.

NewContext.cs
using UnityEngine;
using System.Collections;
 
public class NewContext : MonoBehaviour {
#if UNITY_EDITOR
    [UnityEditor.MenuItem("CONTEXT/Rigidbody/Push")]
    static void Push(UnityEditor.MenuCommand command)
    {
        Rigidbody body = (Rigidbody)command.context;
        body.AddForce(Vector3.up * 500f);
    }
#endif
}


이렇게 하면 RigidBody 컴포넌트에 Push 라는 컨택스트 메뉴를 만들어준다. MenuItem은 UnityEditor 네임스페이스 내에 있으므로 전처리기를 이용해 묶어주었다.

플레이를 누르고 RigidBody 컴포넌트의 Push 메뉴를 누르면 큐브가 위로 올라갔다가 내려올 것이다.

image2013-3-10 21-6-16.png



MenuItemAttribute를 사용하는 이 방법은 이미 만들어진 컴포넌트에 메뉴를 붙일 수 있는 유일한 방법이기도 하다.

주 메뉴를 만드는 것과 비슷하게 메뉴의 경로에 CONTEXT/[컴포넌트 이름]/[메뉴명] 순으로 주었다는 것을 눈여겨 보자.

주 메뉴와 다른 점은 MenuCommand 를 인자로 받는다는 점이다.

static 함수는 인스턴스가 없이 호출되므로 어느 컴포넌트에서 불렸는지 알 수가 없다.

커스텀 인스펙터처럼 Editor 를 상속 받은 경우라면 target 정보를 이용해서 처리할 수 있지만, 지금처럼 MonoBehavior를 상속받은 경우에는 static 함수로는 현재의 컨택스트를 가져올 수가 없다.

이를 위해 MenuCommand 인자를 이용해서 처리할 수 있게 배려해놓았다.

MenuCommand.context 멤버는 해당 컴포넌트(선택된 컴포넌트)를 알고 있으므로 이를 이용해 처리할 수 있다.

 

컨텍스트 메뉴라면 좀 더 편하게

위에서 MenuItemAttribute를 이용해 컨택스트 메뉴를 만드는 방법을 알아보았다.

컨텍스트 메뉴를 만들때는 조금 더 편하게 만들 수 있도록 ContextMenuAttribute를 준비해 놓았다.

using UnityEngine;
using System.Collections;
 
public class NewContext : MonoBehaviour {
#if UNITY_EDITOR
    [UnityEditor.MenuItem("CONTEXT/Rigidbody/Push")]
    static void Push(UnityEditor.MenuCommand command)
    {
        Rigidbody body = (Rigidbody)command.context;
        body.AddForce(Vector3.up * 500f);
    }
#endif
    [ContextMenu("Turn")]
    void Turn()
    {
        gameObject.transform.Rotate(Vector3.up, 30);
    }
}

ContextMenuAttribute는 MenuItemAttribute와 다르게 UnityEngine 네임 스페이스 안에 있다.

그리고 static 함수가 아닌 일반 메서드에 attribute를 설정할 수 있다.
MenuCommand 인자로 받지 않지만 일반 클래스 메소드이기 때문에 this를 컨택스트 정보로 사용하면 된다.

MenuItemAttribute처럼 어느 컴포넌트에 위치할 지 결정하지 못한다.

image2013-3-10 22-7-2.png



따라서 ContextMenuAttribute 가 달린 컴포넌트는 해당 컴포넌트에 컨택스트 메뉴를 만든다.

해당 컨텍스트 메뉴를 선택하면 게임 오브젝트를 30도 회전한다.

 

Generic Menu


이제 메뉴의 마지막으로 제너릭 메뉴에 대해서 알아보도록 하자.

제너릭 메뉴는 커스텀 메뉴를 만들 수 있도록 도움을 준다.

일반적인 드롭다운 방식의 메뉴 뿐만 아니라 컨텍스트 메뉴까지 만들 수 있다.

Generic Menu를 설명하기 위해서는 EditorWindow 클래스를 이용하는 편이 설명하기 쉬우므로 다음 강에 배우게 될 EditorWindow 클래스를 미리 이용해보도록 하겠다.

Editor/ContextEditor.cs
using UnityEngine;
using System.Collections;
using UnityEditor;
public class ContextEditor : EditorWindow {
    [MenuItem("Window/Show Context Editor Window")]
    static void Init()
    {
        EditorWindow.GetWindow<ContextEditor>();
    }
 
    void OnGUI() {
        Event evt = Event.current;
        Rect contextRect  = new Rect (10, 10, 100, 100);
         
        if (evt.type == EventType.ContextClick)
        {
            Vector2 mousePos  = evt.mousePosition;
            GenericMenu menu = new GenericMenu();
            menu.AddItem(new GUIContent("MenuItem1"), false, new GenericMenu.MenuFunction2(MenuArg1), "Menu Item 1");
            menu.AddItem(new GUIContent("MenuItem2"), false, new GenericMenu.MenuFunction2(MenuArg1), "Menu Item 2");
            menu.AddSeparator("");
            menu.AddItem(new GUIContent("SubMenu/MenuItem3"), false, new GenericMenu.MenuFunction(MenuNoArg));
            menu.ShowAsContext();
            evt.Use();
        }
    }
 
    void MenuNoArg()
    {
        Debug.Log("Selected menu");
    }
 
    void MenuArg1(object obj)
    {
        Debug.Log ("Selected: " + obj);
    }
}


Init() 함수를 이용해 커스텀 윈도우를 생성한다.

Window/Show Context Editor Window 메뉴를 만들었고, 이 메뉴를 선택하면 새로운 윈도우가 생성된다.

image2013-3-10 22-25-48.png

image2013-3-10 22-26-54.png



아무것도 없는 윈도우지만 마우스 오른쪽 버튼을 누르면 메뉴가 생성된다.

image2013-3-10 22-29-48.png





이제 코드를 살펴볼 시간이다.

OnGUI() 에서 제너릭 메뉴를 생성하는데 마우스 오른쪽 버튼을 누르면 ContextClick 이라는 이벤트가 발생한다.

GenericeMenu를 만들 때 AddItem 으로 메뉴 항목을 추가하게 된다.

AddItem은 다음과 같은 방식으로 선언되어 있다.

function AddItem (content : GUIContent, on : boolean, func : MenuFunction) : void

function AddItem (content : GUIContent, on : boolean, func : MenuFunction2, userData : object) : void



3번째 인자로 MenuFunction에 대한 델리게이트를 전달해줘야 하는데 2가지 방법이 있다.

MenuFuction은 아무런 인자가 없는 델리게이트이고 MenuFunction2는 1개의 인자를 가지는 델리게이트이다.

image2013-3-10 22-42-33.png



위 그림은 메뉴를 하나씩 순서대로 클릭한 결과이다.

MenuFunction2 의 인자는 object 타입이므로 아무거나 넘길 수 있다. 이 경우 userData로 문자열 "Menu Item 1"과 문자열 "Menu Item 2"를 넘겨주었고 해당 델리게이트에서 인자로 userData를 받아서 처리한 것이다.

그리고 메뉴 추가 아랫줄에 menu.ShowAsContext() 함수가 있다.

메뉴를 추가한 후에는 메뉴를 표시해주어야 하는데 ShowAsContext() 메서드 또는 DropDown() 메서드로 메뉴를 표시할 수 있다.

ShowAsContext()는 마우스 클릭한 위치에 메뉴를 표시해준다.

반면 DropDown()은 Rect 인자를 하나 받는다.

Rect.left와 Rect.top에 메뉴를 펼쳐보이는데, 3.5.6f 버전에서는 y 위치에 Rect.height 를 더해서 표시해준다. x 위치는 Rect.width에 영향을 받지 않는 것으로 보인다.

결론

컨택스트 메뉴를 만드는 방법 3가지를 알아 보았다.

MenuItemAttribute를 이용해서 컨택스트 메뉴를 만들어 보았고, MonoBehaviour에 ContextMenuAttribute를 추가해서 간단하게 만들어보기도 했다.

그리고 GenericMenu를 이용해서 커스텀 메뉴를 만들어보기도 했다.

이 예제에서는 GenericMenu를 컨택스트 클릭 이벤트를 받아서 컨택스트 메뉴처럼 사용했지만, 처리 방식에 따라서 어떠한 메뉴라도 만들 수 있다.

다음 강에서는 에디터 확장의 꽃 EditorWindow 클래스를 이용하는 방법에 대해서 알아보도록 하겠다.

 

 

 

 

 

 

 

 

 

 

 

4. 에디터 확장 04 - EditorWindow

 

 

EditorWindow

이번에는 EditorWindow에 대해 알아보겠다.

EditorWindow는 유니티를 툴로 만들기 위해서 사용해야하는 클래스이다.

인스펙터와 달리 EditorWindow는 프로그래머가 원하는 데로 자유롭게 꾸밀 수 있게 해준다.

기본적인 사용법은 인스펙터와 거의 비슷하지만 훨씬 자유도가 있고 다양한 응용이 가능하다.

인스펙터는 컴포넌트가 반드시 게임오브젝트에 포함되어 있어야 하고 컴포넌트를 가진 게임 오브젝트를 선택해야만 보여진다.

그런 반면 EditorWindow는 언제든 창을 띄워둘 수 있어서 그야말로 툴에 적합한 클래스라고 할 수 있다.

이번에는 CanvasWindow 라는 EditorWindow를 확장시킨 클래스를 만들어보도록 한다.

캔버스를 만들자


image2013-4-11 20-58-14.png



 

위 Window는 우리가 이번 시간에 만들 EditorWindow의 확장 클래스인 CanvasWindow의 샘플이다.

펜의 색상과 크기를 고를 수 있고 팔레트에서 다양한 펜을 고를 수 있다(하지만 펜의 적용을 구현하지는 않을 것이다. 내용이 너무 길어지기 때문이다).

단순하게 메뉴를 생성하고 캔버스를 지우고 출력하는 간단한 구현만 해보도록 하겠다.

우선은 Editor\CanvasWindow.cs 파일을 생성한다.

그리고 윈도우를 띄우기 위해서 메뉴를 생성한다.

using UnityEngine;
using UnityEditor;
using System;
 
public class CanvasWindow : EditorWindow {
 
    [MenuItem("Canvas/Show")]
    public static void ShowWindow()
    {
        CanvasWindow window = (CanvasWindow)EditorWindow.GetWindow(typeof(CanvasWindow));
    }
}

이 코드는 Canvas/Show 메뉴를 만든다.

EditorWindow.GetWindow() 를 이용해서 CanvasWindow의 인스턴스를 만든다. 만약에 창이 띄워져 있는 경우라면 해당 창을 가져오고, 생성한 창이 없다면 새로 창을 생성한다.


image2013-4-11 21-4-51.png



 


위처럼 Canvas 메뉴가 생겼고 Show를 누르면 아무것도 없는 윈도우가 생성된다.

이제 이 윈도우에 컨트롤을 배치한다.

여기에 배치할 컨트롤은 펜의 크기, 펜의 색상, 그리고 가운데 표시할 텍스쳐이다.

...
	Texture2D mytexture;
    Color penColor;
    int penSize;
    readonly Rect texSize = new Rect(0, 0, 300, 400);
    Rect windowRect = new Rect(3, 3, 100, 400);

    void OnGUI()
    {
	}
}

가운데 그려지는 캔버스는 Texture가 되고 나머지는 int형 펜 크기, Color형 펜 색상이다.

나머지 rect 들은 각각 텍스처 크기와 팔렛트박스의 크기이다.

...
	void OnGUI()
	{
		if (mytexture == null)
            mytexture = new Texture2D((int)texSize.width, (int)texSize.height, TextureFormat.RGBA32, false);
        Rect canvasPos = new Rect((position.width - texSize.width) / 2, (position.height - texSize.height) / 2, texSize.width, texSize.height);
        
        EditorGUI.DrawPreviewTexture(canvasPos, mytexture);
        penColor = EditorGUILayout.ColorField("Pen Color", penColor);
        penSize = EditorGUILayout.IntSlider("Pen Size", penSize, 1, 10);
	}
}

우선 GUI 로직에 들어와서 texture가 null 이라면 texture를 생성한다.

캔버스의 위치는 화면의 가운데이고, 텍스처를 먼저 그리고 나머지 컨트롤을 그린다.


image2013-4-11 21-23-18.png



 


만약 텍스처를 나중에 그리면 윈도우를 줄였을 때 컨트롤이 텍스처 아래로 들어가서 보이지 않는다.

텍스처를 먼저 그리면 컨트롤이 위에 그려져서 화면이 작을 때에도 불편함 없이 사용할 수 있다.

image2013-4-11 21-24-40.png



 

이제 팔레트 윈도우를 그려보자.

...
	void OnGUI()
    {
        ...
        
        BeginWindows();
        windowRect = GUILayout.Window(1, windowRect, DoWindow, "Palette");
        EndWindows();
	}
 
	enum DrawMode
    {
        point,
        line,
        rectangle,
        ellipse,
    }
    DrawMode drawmode;
    void DoWindow(int id)
    {
        EditorGUILayout.BeginVertical();
        drawmode = (DrawMode)GUILayout.SelectionGrid((int)drawmode, Enum.GetNames(typeof(DrawMode)), 1);
        if (GUILayout.Button("clear"))
        {
            mytexture = new Texture2D((int)texSize.width, (int)texSize.height, TextureFormat.RGBA32, false);
        }
        EditorGUILayout.EndVertical();
    }
}

이제 그럴싸하게 기본 뷰는 다 나왔다.

팔레트 윈도우를 만드는 방법은 EditorWindow.BeginWindow() 와 EditorWindow.EndWindows() 사이에 GUILayout.Window() 함수를 이용해서 새로운 윈도우를 생성하였다.

GUILayout.Window() 에서는 윈도우 GUI에 대한 콜백을 받는다. 여기서는 DoWindow(window_id) 함수를 콜백으로 지정했고 이 안에서 GUILayout.SelectionGrid를 이용해서 여러 메뉴가 선택가능하게 만들었다. 다만 각 메뉴의 처리는 우리의 주제와 맞지 않기 때문에 point만 처리하도록 했다.

image2013-4-11 21-28-26.png



 

그런데 문제가 발생했다. 팔렛트 윈도우가 마우스 드래그로 창의 위치를 바뀌지가 않는다.

윈도우의 메시지 콜백에 GUI.DragWindow() 를 불러주면 이 창은 움직일 수 있게 된다.

...
	void DoWindow(int id)
	{
		...
		GUI.DragWindow();
	}
}

이제 텍스처에 그리기를 구현할 차례다.

다시 OnGUI() 함수의 가장 끝으로 돌아가서 마우스 이벤트에 대한 처리를 추가한다.

...
    void OnGUI()
    {
		...
        
        if (Event.current.type == EventType.MouseDrag)
        {
            int bx = (int)(Event.current.mousePosition.x - canvasPos.x);
            int by = (int)(texSize.height - (Event.current.mousePosition.y - canvasPos.y));
                
            for (int x = 0; x < penSize; ++x)
            {
                for (int y = 0; y < penSize; ++y )
                    mytexture.SetPixel(bx - penSize / 2 + x, by - penSize / 2 + y, penColor);
            }
            mytexture.Apply(false);
            Repaint();
        }
    }
...

Event.current 객체에 마우스의 이벤트가 넘어온다.

MouseDrag 뿐만 아니라 다운이나 업에 관련된 이벤트도 있다.

MouseDrag 이벤트가 오면 캔버스에 그림을 그린다.

캔버스로 사용하고 있는 mytexture에  Texture2D.SetPixel() 함수를 이용해 텍스처를 편집하고 마지막으로 Texture2D.Apply() 를 이용해 텍스처에 적용해준다. 이때 인자로 들어가는 Boolean 값은 밉맵을 갱신 시킬지에 관련된 함수다.

텍스처에 대한 접근이 끝난 후에는 EditorWindow.Repaint() 함수를 불러서 윈도우를 갱신시켜야 한다.

이제 모든 작업이 완료되었다.

정리

이번에 살펴본 EditorWindow는 우리가 지금까지 배워온 것들을 응용해보는 과정에 불과했다.

그래서 하나의 예제를 구현하는 방식으로 진행하였고, 자잘한 처리들은 패스했다.

우리는 EditorwWindow를 상속받은 새로운 윈도우를 생성하는 법과 텍스처를 직접 편집하고 그려보는 일도 해보았다.

이것을 조금 활용하면 다양한 툴을 만들 수 있을 것이다.

이 강좌의 전체 코드는 여기에 있다: https://gist.github.com/whoo24/5334402

다음 강에서는 Handles와 Gizmos에 대해서 배울 것이다.

 

 

 

 

 

Posted by 프리랜서 디자이너
TA/Unity2013. 9. 11. 10:32

 출처 : http://www.unitystudy.net/bbs/board.php?bo_table=tip&wr_id=52

 

 

안녕하세요 유니티스터디에서 도움만 받는 잉여 회원 입니다.
 
로딩 페이지 구현? 에 고생해서 정식으로 유니티스터디를 통해서 올립니다.
 
 AsyncOperation.progress라는 놈이 pc에서는 제대로된 값을 얻을 수 없어서 삽질을 많이 하셨습니다 ㅠㅠ
 
using UnityEngine;
using System.Collections;
public class EnterLoadPage : MonoBehaviour {
 
 public UISlider  Script;
 public UISlider  ScriptPercent;
 public UILabel  textScript;
 
 AsyncOperation   async;
 
 bool IsLoadGame = false;
 
 public IEnumerator StartLoad( string strSceneName )
 {
        if (IsLoadGame == false) 
        {
            IsLoadGame = true;
            
            AsyncOperation async = Application.LoadLevelAsync ( strSceneName );
        
            while(async.isDone == false) 
            {
                float p = async.progress *100f;
                int pRounded = Mathf.RoundToInt(p);
    
                textScript.text = pRounded.ToString();
 
                //progress 변수로 0.0f ~ 1.0f로 넘어 오기에 이용하면 됩니다.
                ScriptPercent.sliderValue = async.progress;
                
                yield return true;
           }
        }
    }
 
 void Start()
 {
       StartCoroutine( "StartLoad", "SceneGame" );
 }
 
 float fTime = 0.0f;
 
//로딩 페이지에서 연속으로 애니메이션 만들때 Update 함수 내에서 만들면 됩니다.
 void Update () {
  fTime += Time.deltaTime;
  
      if( fTime >= 1.0f )
      {
            fTime = 0.0f; 
      }
  
        Script.sliderValue = fTime;
  }
}
 
 
사용법은
 
Scene과  로딩 부하가 심한 Scene 사이에 해당 ScenePage를 추가 하여 사용하시면 됩니다.
 
ex)
 
SceneMenu -> SceneLoadingPage -> SceneGame
 
 
읽어 주셔서 감사합니다.
Posted by 프리랜서 디자이너
TA/Unity2013. 9. 9. 17:56

http://www.imaso.co.kr/?doc=bbs/gnuboard.php&bo_table=article&wr_id=40743

 

조명근 narlamy@ndoors.net|엔도어즈 기술 지원팀 팀장으로 근무하고 있으며, MMORPG ‘아틀란티카’를 개발했다. 덴마크에 있는 유니티 개발팀과 긴밀히 협조해 메모리 문제 등 여러 가지 이슈들을 처리하고 있으며 현재 유니티 3D엔진을 사용해 웹과 모바일 플랫폼을 지원하는 MMORPG ‘삼국지를 품다’를 개발하고 있다.

C#은 상당히 좋은 언어다. 가장 많이 알려진 C#의 특징 중 하나는 메모리 관리에 부담이 없다는 점이다.

So Cool~ C# 메모리
C/C++를 사용하면서 포인터 때문에 괴로워 해본 적이 있는가? 그렇다면 C#에 관심을 가져보는 것이 좋다. C#은 다음과 같은 특징들을 제공하기 때문이다.

- 메모리 해제에 신경 쓰지 않아도 된다.
- 이미 삭제된 메모리에 접근하는 실수를 방지해준다.
- 잘못된 캐스팅으로 엉뚱한 메모리에 접근하지 않게 한다.
- 배열 크기보다 큰 메모리에 접근하지 못한다.
- 메모리 단편화에 대해 신경 쓰지 않아도 된다.

편한 C#, 마구잡이로 사용하면 낭패
골치 아픈 메모리 관리를 신경 쓰지 않아도 된다는 점은 사용자들에게 무척 편리하게 다가온다. 하지만 C#에서도 메모리를 다루기 위해서는 세심한 주의가 필요하다. 마음 놓고 개발하다 당황했던 과거 필자의 경험을 살펴보도록 하자. 

개발 초창기, 게임 플레이 중에 주기적으로 랙이 발생했다. 로직을 확인해 봤지만 특별히 로딩 중이거나 초기화된 부분이 없어 의아했다. 유니티 엔진에서 제공하는 프로파일러로 한 프레임에 걸리는 시간을 측정해봤다. 측정 결과, System.GC.Collect() 호출에 오랜 시간이 걸린다는 점이 발견됐다. 플레이 중에 프레임마다 소모되는 시간을 그래프로 보여주는 <그림 1>을 보면 System.GC.Collect() 호출 시 그래프가 크게 튀어 오른 모습이 확인된다. C#에서 사용하지 않는 메모리를 정리하면서 가비지 컬렉션(Garbage collection) 랙이 발생한 것이다.


< 그림 1> 프로파일러 이미지

이때는 가비지 컬렉션이 동작하는 횟수를 줄여서 랙 발생을 줄이면 된다. 가비지 발생을 줄이면 가비지 컬렉션이 호출되는 시간을 늦출 수 있어 동작 횟수가 줄어든다. 가비지란 프로그램이 실행되면서 어디에서든 더 이상 참조되지 않는 메모리를 의미하므로 가능한 한 메모리를 할당했다 금방 버려지는 상황을 만들지 않는 것이 좋다. 몇 가지 사례들을 살펴보자.

‘+’ operator를 통한 문자열 조합 
C#은 문자열 조합이 쉽다. <리스트 1>에 보이는 것처럼 ‘+’로 연결하면 간단히 문자열 조합이 이뤄진다. 모든 객체가 ToString()을 지원하기 때문에 문자열끼리만 조합되는 게 아니라 int, float 등의 값도 알아서 문자열로 변환·조합된다.

<리스트 1> ‘+’로 연결한 문자열 조합

class Names
{
    public string[] name = new string[100];
    public void Print()
    {
        for (int index = 0; index < name.Length; index++)
        {
            string output = "[" + index + "]" + name;
            Console.WriteLine(output);
        }
    }
}

 

문제는 <리스트 1>에서 가비지가 많이 발생한다는 점이다. ‘+’ 연산자로 두 값을 연결할 때마다 새로운 string 인스턴스가 생성된다. 연이어 ‘+’ 연산자가 나오기 때문에 다시금 새로운 string 인스턴스가 생성되고, 이전에 만들어진 string 인스턴스는 가비지가 된다. string 조합을 위해 ‘+’ operator 호출이 많아질수록 많은 가비지가 만들어지는 것이다.

그래서 문자열을 조합하는 동안 새로운 객체를 생성하지 않는 System.Text.StringBuilder 객체를 소개한다. ‘+’ operator가 아닌 Append() 메소드를 통해 문자열을 추가하며, string 객체를 만들어내는 게 아니라 이미 잡아놓은 메모리 공간에 문자열만 복사해 뒀다가 한번에 ToString()으로 string 객체를 생성해낸다.

<리스트 2> System.Text.StringBuilder 객체 사용

class NewNames
{
    public string[] name = new string[100];
    private StringBuilder sb = new StringBuilder();

    public void Print()
    {
        sb.Clear();     // sb.Length = 0;
        for (int index = 0; index < name.Length; index++)
        {
            sb.Append("[");
            sb.Append(index);
            sb.Append("] ");
            sb.Append(name);
            sb.AppendLine();
        }
        Console.WriteLine(sb.ToString());
    }
}

 

과다한 Append() 메소드 호출이 필요해 ‘+’ 코드보다 깔끔하지 못하다고 생각된다면 AppendFormat()을 사용하는 것도 좋다.

 <리스트 3> AppendFormat() 활용

class NewNames
{
    public string[] name = new string[100];
    private StringBuilder sb = new StringBuilder();

    public void Print()
    {
        sb.Clear();     // sb.Length = 0;
        for (int index = 0; index < name.Length; index++)
        {
            sb.AppendFormat("[{0}] {1}", index, name.ToString());
        }
        Console.WriteLine(sb.ToString());
    }
}

 

string처럼 Immutable pattern을 사용한 객체들의 값에 접근할 때는 기존 메모리를 수정하지 않고 새로운 메모리를 만들어 반환하거나 입력받으므로 사용 시 주의가 필요하다.

메소드 안에서 생성한 객체
C#은 C++과 달리 클래스를 인스턴싱하려면 반드시 new를 해줘야 한다. 이때 heap에서 메모리 할당이 일어난다. 

< 리스트 4>와 같이 메소드 안에서 new로 생성된 인스턴스는 메소드를 빠져나오면 더 이상 사용하지 않게 돼 가비지로 처리된다. 이런 패턴의 메소드가 자주 호출될수록 가비지도 많이 발생한다.

<리스트 4> new로 생성된 인스턴스

public class MyVector
{
    public float x, y;
    public MyVector(float x, float y) { this.x = x; this.y = y; }
    public double Length() { return System.Math.Sqrt(x * x + y * y); }
}

static class TestMyVector
{
    public static void PrintVectorLength(float x, float y)
    {
        MyVector v = new MyVector(x, y);
        Console.WriteLine("Vector=({0},{1}), lenght={2}", x, y, v.Length());
    }
}

 

Vector 클래스를 구조체로 바꿔보면, new 연산자로 인스턴스를 만들어도 heap 영역에 메모리가 할당되지 않는다. 구조체 역시 Value type이기 때문에 stack 영역에 메모리가 할당되며, 메소드를 빠져나갈 경우 자동으로 삭제된다. 물론 heap 영역에 생성된 메모리가 아니기 때문에 가비지 컬렉션의 대상이 되지도 않는다. 

<리스트 5> Vector 클래스를 구조체로 변환

public struct MyVector
{
    public float x, y;
    public MyVector(float x, float y) { this.x = x; this.y = y; }
    public double Length() { return System.Math.Sqrt(x * x + y * y); }
}

static class TestMyVector
{
    public static void PrintVectorLength(float x, float y)
    {
        MyVector v = new MyVector(x, y);
        Console.WriteLine("Vector=({0},{1}), lenght={2}", x, y, v.Length());
    }
}

 

구조체로 바꿀 수 없다면, <리스트 6>처럼 멤버변수 사용을 권장한다.

<리스트 6> 멤버변수 사용

public class MyVector
{
    public float x, y;
    public MyVector() { x = .0f; y = .0f; }
    public MyVector(float x, float y) { this.x = x; this.y = y; }
    public double Length() { return System.Math.Sqrt(x * x + y * y); }
}

static class TestMyVector
{
    private static MyVector m_cachedVector = new MyVector();
    public static void PrintVectorLength(float x, float y)
    {
        m_cachedVector.x = x;
        m_cachedVector.y = y;

        Console.WriteLine("Vector=({0},{1}), lenght={2}",
x, y, m_cachedVector.Length());
    }
}

 

속도 저하가 큰 Boxing
Boxing이란 Value type 객체를 Reference type 객체로 포장하는 과정을 뜻한다. C#의 모든 객체는 object로부터 상속되는데, 심지어 상속받지 못하는 int, float 등의 Value type 조차도 object로부터 상속된 것처럼 사용할 수 있다. 하지만 가비지 컬렉션에 의한 부하 못지않게 boxing으로 인한 부하도 크다. 무심코 만든 코드에서 boxing 과정이 일어나는 경우가 있으니 잘 이해하고 사용해야 한다.

< 리스트 7>을 보면 리스트에 서로 다른 type의 값을 추가했지만, loop 안에서 추가 값을 object type으로 받아 하나의 코드로 처리할 수 있음을 알 수 있다.

<리스트 7> 서로 다른 type 값 추가

class MyClass
{
    public override string ToString() { return "다섯"; }

    static public void Sample()
    {
        ArrayList list = new ArrayList();
        list.Add(1);
        list.Add(1.5f);
        list.Add(‘3’);
        list.Add("four");
        list.Add(new MyClass());

        foreach (object item in list)
            Console.WriteLine(item.ToString());
    }
}

 


< 그림 2> <리스트 7>의 실행 결과

매력적인 C#이지만 Value type의 값을 Reference type인 object로 바꿔주는 과정에는 많은 시간이 걸리며 변환 시에는 System.Object 내부에 랩핑하고 관리되는 heap에 저장된다. 즉 새로운 객체가 만들어지는 셈이다. MSDN에서 발췌한 <그림 3>을 참조하길 바란다.


< 그림 3> boxing과 unboxing의 비교

따라서 한 번에 다양한 type을 처리하는 경우가 아니라면 collection에 사용된 값의 type을 명시해주는 Generic collection 사용을 권한다. Generic은 C++의 template와 비슷하다. 그래서 Generic collection들은 C++의 STL container들과 비슷하게 생겼다. <리스트 8>을 참고하자.

<리스트 8> Generic collection

class Example
{
    static public void BadCase()
    {
        ArrayList list = new ArrayList();
        int evenSum = 0;
        int oddSum = 0;

        for (int i = 0; i < 1000000; i++)
            list.Add(i);

        foreach (object item in list)
        {
            if (item is int)
            {
                int num = (int)item;
                if(num % 2 ==0) evenSum += num;
                else oddSum += num;
            }
        }
           
        Console.WriteLine("EvenSum={0}, OddSum={1}", evenSum, oddSum);
    }

    static public void GoodCase()
    {
        List<int> list = new List<int>();
        int evenSum = 0;
        int oddSum = 0;

        for (int i = 0; i < 1000000; i++)
            list.Add(i);

        foreach (int num in list)
        {
            if (num % 2 == 0) evenSum += num;
            else oddSum += num;
        }
           
        Console.WriteLine("EvenSum={0}, OddSum={1}", evenSum, oddSum);
    }
}

 

메모리가 계속 늘어나는 또 다른 문제의 발생!
이 글을 시작하며 C#에서 사용자는 메모리 해제에 신경 쓸 필요가 없다고 했지만 의도하지 않게 메모리가 늘어나기도 한다. C#에는 delete 같은 메모리 해제 명령이 없기에 메모리 릭(memory leak) 현상이 발생하면 당혹스러울 수 있다. 여기서 C# 메모리의 특징을 다시 한 번 떠올려보자.

시스템에서 더 이상 참조가 없는 메모리를 알아서 해제하는 것을 우리는 가비지 컬렉션이라 부른다. 가비지는 더 이상 참조가 없는 메모리다. C# 애플리케이션이 메모리가 해제되지 않고 계속 증가되고 있다면 어디선가 의도하지 않는 참조가 일어나고 있다고 보면 된다. 그렇다면 어디에서 의도하지 않은 참조가 일어나는 것일까? 예를 통해 확인해 보자.


< 그림 4> #1 - 케릭터 매니저에서 케릭터를 생성한다


<그림 5> #2 - 누군가 디버깅을 위해 '캐릭터 위치 표시' 객체를 만들고 캐릭터 매니저에 접근해 등록된 캐릭터를 모두 참조한다


<그림 6> #3 - 캐릭터 매니저에서 필요없는 캐릭터를 삭제한다


<그림 7> #4 - 캐릭터 매니저에서 삭제됏지만 '캐릭터 위치 표시' 객체에서는 여전히 참조 중이다. 가비지가 아니기 때문에 메모리에 계속 남아있으며, 구현에 따라서는 의도하지 않게 화면에 남을 수도 있다.

WeakReference로 의도하지 않은 참조를 없애보자
System.WeakReference는 가비지 컬렉션에 의한 객체 회수를 허용하면서 객체를 참조한다. 인스턴스를 참조하려면 Weak Reference.Target으로 접근해야 하는데 원본 인스턴스가 가비지 컬렉터에 의해 회수되면 WeakReference.Target은 null이 반환된다.

<리스트 9> WeakReference.Target

public class Sample
{
    private class Fruit
    {
        public Fruit(string name) { this.Name = name; }
        public string Name { private set; get; }
    }

    public static void TestWeakRef()
    {
        Fruit apple = new Fruit("Apple");
        Fruit orange = new Fruit("Orange");
           
        Fruit fruit1 = apple;   // strong reference
        WeakReference fruit2 = new WeakReference(orange);
        Fruit target;
           
        target = fruit2.Target as Fruit;
        Console.WriteLine(" (1) Fruit1 = \"{0}\", Fruit2 = \"{1}\"",
            fruit1.Name, target == null ? "" : target.Name);

        apple = null;
        orange = null;

        System.GC.Collect(0, GCCollectionMode.Forced);
        System.GC.WaitForFullGCComplete();

        // fruit1과 fruit2의 값을 바꾼 적은 없지만, fruit2의 결과가 달라진다.
        target = fruit2.Target as Fruit;
        Console.WriteLine(" (2) Fruit1 = \"{0}\", Fruit2 = \"{1}\"",
            fruit1==null ? "" : fruit1.Name,
            target == null ? "" : target.Name);
    }
}

 

<리스트 9>의 실행으로 <그림 8>을 확인할 수 있다. Fruit2가 참조하고 있던 orange 인스턴스는 가비지 컬렉터에 의해 회수돼 null이 됐다.


< 그림 8> <리스트 9>의 실행 결과

‘캐릭터 매니저’처럼 객체의 생성·삭제를 직접 관여하는 모듈이 아닌 곳에서는 가능한 WeakRefernce를 사용하는 것이 좋다. ‘객체 위치 표시 객체’처럼 인스턴스를 참조하는 모듈에서 WeakReference를 사용하면, 의도하지 않은 참조로 메모리가 해제되지 않는 실수를 방지할 수 있다. 주의할 점은 Weak Reference.Target 값을 보관하면 안 된다는 것이다. 만약 그대로 보관하고 있으면 강한 참조(strong reference)가 일어나 이를 인식한 가비지 컬렉터는 회수를 실행하지 않게 된다.

C/C++처럼 원하는 시점에 객체를 삭제하고 싶다면
C#에서는 할당된 메모리를 임의로 해제할 수 없다. 컬렉션에 보관된 인스턴스를 제거하거나 인스턴스를 담고 있던 변수에 null을 넣어 더 이상 참조하지 않는 방법이 있지만 실제 인스턴스가 삭제되는 시점은 가비지 컬렉션 동작 이후가 되므로, 언제가 될 지 정확히 알 수 없다. 의도한 시점에 맞춰 정확히 삭제할 수 없다는 점이 그렇게 중요하지는 않다. 하지만 캐릭터 매니저에서 캐릭터를 제거했는데도 여전히 캐릭터 인스턴스가 남아서 화면에 한동안 계속 나타나는 경우가 발생할 수 있다.

Dispose pattern 소개C#에서는 관리되지 않는 메모리(리소스)를 해제하는 용도로 System.IDisposable이라는 인터페이스를 제공한다. IDisposable 인터페이스를 상속받은 클래스라면 용도에 맞게 Dispose()를 구현해줘야 하는데 이는 FileStream 관련 객체들에서 많이 볼 수 있다.

리소스를 강제로 해제시키려면 직접 Release(), Delete(), Destroy(), Close() 등의 메소드를 만들어 사용하면 되는데 굳이 IDisposable을 사용할 필요가 있을까? 서로 다른 type의 객체여도 IDisposable 인터페이스를 상속받고 있다면, 하나의 코드로 다양한 type의 메모리를 정리할 수 있기 때문에 IDisposable을 사용할 필요가 있다. 또한 Dispose() 메소드만 보고도 “아, 이 클래스는 사용이 끝나면 Dispose()를 호출해서 메모리를 정리해야 하는구나” 라고 금방 알 수 있다.

캐릭터 객체에서 IDisposable 인터페이스를 구현해보자. 업데이트 목록에서도 제외시키고 렌더링 정보도 다 지우자. 캐릭터의 Dipsose()를 호출한 이후에 캐릭터는 어떠한 동작도 하지 못하게 된다. 물론 Dispose()를 호출한다고 캐릭터가 가비지 컬렉터에 의해 메모리 해제되는 것은 아니다.

WeakReference과 IDisosalbe의 조합원하는 시점에 메모리를 해제하려면 앞서 설명한 Weak Reference와 IDisposable을 개별적으로 사용하는 것으로는 부족하다. 둘을 함께 사용하는 것이 좋다. <리스트 10>을 보자.

<리스트 10> Disposable 인터페이스를 상속받아 구현된 캐릭터 클래스

namespace MyApp
{
    public class SampleChar : IDisposable
    {
        private IRenderObject m_Render = Renderer.CreateRenderObject();

        public void Dispose()
        {
            SampleCharManager.Remove(this);
            m_Render = null;
        }

        public bool isRemoved { get { return m_Render == null; } }

        public void Render()
        {
            if (m_Render == null) return;
            // 렌더링
        }

        public void Update() { }
    }
}

 

예제로 만들어 본 캐릭터 클래스는 Disposable 인터페이스를 상속받아 구현된다. Dispose 후에는 더 이상 업데이트가 되지 않도록 SampleCharManager에서 제거되며, 렌더링 객체를 null로 만들어 화면에 그려지지 않도록 했다.

IRenderObject 인터페이스는 <리스트 11>과 같이 구현된다.

<리스트 11> IRenderObject 인터페이스

namespace MyApp
{
    public interface IRenderObject
    {
        void Render();
    }

    public static class Renderer
    {
        public static IRenderObject CreateRenderObject()
{
return new DumyRenderObject(); // IRenderObject를 상속받은 더미 객체
}
    }
}

 

<리스트 12>의 캐릭터 매니저 클래스는 등록된 캐릭터들을 일괄적으로 업데이트시키고 렌더링한다.

<리스트 12> 등록 캐릭터 일괄 업데이트 및 렌더링

namespace MyApp 
{
    static class SampleCharManager
    {
        private static List<SampleChar> m_list = new List<SampleChar>();

        public static void Update()
        {
            foreach (SampleChar obj in m_list) 
                obj.Update();
        }

        public static void Render()
        {
            foreach (SampleChar obj in m_list)
                obj.Render();
        }

        public static void Add(SampleChar obj)
        {
            m_list.Add(obj); 
        }

        public static void Remove(SampleChar obj)
        {
            m_list.Remove(obj);
        }
    }
}

 

<리스트 13>의 디버깅을 위한 ‘캐릭터 위치 표시 객체’는 WeakReference를 통해 SampleChar 객체를 참조하도록 구현돼 있고, SampleCharManager에서 캐릭터를 삭제하더라도 안전하게 가비지가 회수된다. 업데이트 시 DisplayCharInfo는 삭제된 캐릭터를 스스로 판단해 목록에서 제거한다.

<리스트 13> 디버깅을 위한 캐릭터 위치 표시 객체

namespace MyDebug
{
    static class DisplayCharInfo
    {
        private static List<WeakReference> m_list = new List<WeakReference>();
        private static Queue<WeakReference> m_removeQueue =
new Queue<WeakReference>();

        public static void Update()
        {
            foreach (WeakReference item in m_list)
            {
                MyApp.SampleChar obj = (item.Target != null) ?
 item.Target as MyApp.SampleChar : null;

                if (obj == null || obj.isRemoved)
                {
                    m_removeQueue.Enqueue(item);
                }
                else 
                { 
                    /* 캐릭터 정보 표시 */ 
                }
            }

            while(m_removeQueue.Count > 0)
            {
                WeakReference item = m_removeQueue.Dequeue();
                m_list.Remove(item);
            }
        }

        public static void Add(MyApp.SampleChar obj)
        {
            m_list.Add(new WeakReference(obj));
        }
    }
}

 

C#에서 메모리를 관리하는 데 도움되길 바라며, 지금까지 설명한 내용을 요약하면 다음과 같다.

- string 조합이 많다면, StringBuilder 활용
- Immutable 객체의 값 접근 시 매번 메모리가 생성될 수 있으므로 주의
- 매번 호출되는 메소드 안에서 반복해서 일회성 인스턴스가 생성되지 않도록 주의
- Boxing / unboxing이 가능한 일어나지 않도록 주의
- WeakReference를 사용해서 의도하지 않은 참조 줄이기
- IDisposable 인터페이스를 사용해 사용자가 원하는 시점에 객체 삭제하기

Value type과 Reference type 비교

Value type은 stack 영역에 할당되며 값이 통째로 복사된다.

 

유니티 3D엔진에서의 메모리 관리
유니티 3D엔진으로 개발하면서 주의할 내용을 알아보자. 유니티 3D엔진은 크게 모노 메모리와 엔진에서 관리하는 메모리로 나뉜다. 둘 다 메모리가 부족하면 내부에 관리하는 heap 영역을 늘려 메모리를 할당한다. 이렇게 한 번 늘어난 heap은 줄어들지 않는 특징을 가진다. 물론 늘어난 heap 안에서 메모리가 재사용되므로, 무턱대고 늘어나진 않는다. 하지만 가비지를 너무 많이 생성시키면 GC.Collect()로 인한 성능저하와 더불어 최대 메모리가 늘어날 수도 있으니 주의해야 한다. 가능한 가비지가 덜 생성되도록 코드를 구현하는 게 좋다. 메모리는 한 번에 잡는 것이 좋고, caching이나 memory pool을 사용하는 것도 도움이 된다.

 

 

<리스트 14> Value typepublic static class Sample
{
    public static void TestValueType()
    {
        int a = 100;
        int b = a;
       
        a = 200;
        Console.WriteLine(" a={0}, b={1}", a, b);
    }
}

<리스트 14>를 실행하면 <그림 9>와 같은 결과를 확인할 수 있다. a와 b는 서로 다른 메모리 공간을 가지고 있다.


< 그림 9> <리스트 14>의 실행 결과

Reference Type은 heap 영역에 할당되며, C/C++의 포인터나 레퍼런스처럼 new로 생성한 인스턴스를 참조한다.

<리스트 15> Reference type

public class MyInt
{
    public int Value { get; set; }
    public MyInt(int val) { this.Value = val; }

    public static void TestReferenceType()
    {
        MyInt a = new MyInt(100);
        MyInt b = a;

        a.Value = 200;
        Console.WriteLine(" a={0}, b={1}", a.Value, b.Value);
    }
}

<리스트 15>의 실행 결과로 <그림 10>을 확인할 수 있다. a와 b는 같은 메모리를 참조한다.


< 그림 10> <리스트 15>의 실행 결과

Posted by 프리랜서 디자이너
TA/Unity2013. 9. 9. 17:46

출처 : http://egohim.blog.me/70170987774

 

 

 

참고 : Practical Guide to Optimization for Mobiles - Future & High End Devices

참고 : Practical Guide to Optimization for Mobiles - Graphics Methods

참고 : Practical Guide to Optimization for Mobiles - Scripting and Gameplay Methods

참고 : Practical Guide to Optimization for Mobiles - Rendering Optimizations

참고 : Practical Guide to Optimization for Mobiles - Optimizing Scripts

 

// Practical Guide to Optimization for Mobiles 

참고 : http://docs.unity3d.com/Documentation/Manual/iphone-PracticalScriptingOptimizations.html

1.Class vs Struct

Classes are objects and behave as references. If Foo is a class and

 Foo foo = new Foo();

 MyFunction(foo); 

then MyFunction will receive a reference to the original Foo object 

that was allocated on the heap. 

Any changes to foo inside MyFunction will be visible anywhere foo is referenced.

-C#에서 클래스는 함수 인자로 넘길 대 레퍼런스화 되서 작동 한다만약 Foo클래스가 Foo foo = new Foo();로 선언되었다면

  MyFunction(foo);에서 MyFunction은 힙 메모리 영역에 선언된 Foo 오브젝트의 레퍼런스로 받게 된다.

  만약 foo MyFunction안에서 어떤 변화가 있다면 foo의 참조를 받는 그 어디에서라도 그것이 적용되게 된다.

 

If Foo is a struct and

 Foo foo = new Foo();

 MyFunction(foo); 

then MyFunction will receive a copy of foo. 

foo is never allocated on the heap and never garbage collected. 

If MyFunction modifies it's copy of foo, the other foo is unaffected.

-만약 Foo가 구조체(Struct)로 선언되어있다면 Foo foo = new Foo(); MyFunction(foo);로 선언된 MyFunction(foo); 에서

  MyFunction함수는 foo가 복사 되어 받게 된다.

  foo는 절대 힙메모리 영역에 할당되지 않으며또한 절대 가비지 콜렉트되지 않는다.

  만약 MyFunction에서 foo가 넘어가면 foo가 복사되기 때문에 함수내에서 변경해도 다른 참조된 foo에 영향을 주지 않고,

  스코프를 벗어나면 자동 소멸 된다.

 

 

// Understanding Automatic Memory Management

참고 : http://docs.unity3d.com/Documentation/Manual/UnderstandingAutomaticMemoryManagement.html

 

2. String 배열의 메모리 단편화 문제 

function ConcatExample(intArray: int[]) {

 var line = intArray[0].ToString();

 for (i = 1; i < intArray.Length; i++) {

  line += ", " + intArray[i].ToString();

 }

 return line;

}

 

-> String Builder 사용을 추천.

StringBuilder sb = new StringBuilder();

ShowSBInfo(sb);

sb.Append("This is a sentence.");

ShowSBInfo(sb);

for (int ctr = 0; ctr <= 10; ctr++) {

 sb.Append("This is an additional sentence.");

 ShowSBInfo(sb);

 

3. 가변 시에만 메모리 할당 처리

 var scoreBoard: GUIText;

 var score: int;

 

 function Update() {

  var scoreText: String = "Score: " + score.ToString();

  scoreBoard.text = scoreText;

 }

...will allocate new strings each time Update is called and generate a constant trickle of new garbage. Most of that can

be saved by updating the text only when the score changes:-
매 업데이트 할때 마다 새로운 String 생성으로 가비지가 생성되는 비효율적인 문제로 텍스트를 가변 상황에만 String할당.

 var scoreBoard: GUIText;

 var scoreText: String;

 var score: int;

 var oldScore: int;

 

 function Update() {

  if (score != oldScore) {

   scoreText = "Score: " + score.ToString();

   scoreBoard.text = scoreText;

   oldScore = score;

  }

 }

 

 

4. 배열 생성 반환 함수의 잠재적인 문제
potential problem occurs when a function returns an array value:-

 function RandomList(numElements: int) {

  var result = new float[numElements];

  for (i = 0; i < numElements; i++) {

   result[i] = Random.value;

  }

 

  return result;

 }

This type of function is very elegant and convenient when creating a new array filled with values. However, if it is called repeatedly
then fresh memory will be allocated each time. Since arrays can be very large, the free heap space could get used up rapidly,
resulting in frequent garbage collections. One way to avoid this problem is to make use of the fact that an array is a reference type. An array passed into a function as a parameter can be modified within that function and the results will remain after the function returns. A function like the one above can often be replaced with something like:-

 function RandomList(arrayToFill: float[]) {

  for (i = 0; i < arrayToFill.Length; i++) {

   arrayToFill[i] = Random.value;

  }

 }

This simply replaces the existing contents of the array with new values. Although this requires the initial allocation of the array to be done in the calling code (which looks slightly inelegant), the function will not generate any new garbage when it is called.

4. Garbage Collection 
호출 전략 
4-1. Small heap with fast and frequent garbage collection (빠르고 적은 힙 그리고 빈번한 가비지 콜렉션 방식)
This strategy is often best for games that have long periods of gameplay where a smooth framerate is the main concern. A game like this will typically allocate small blocks frequently but these blocks will be in use only briefly. The typical heap size when using this strategy on iOS is about 200KB and garbage collection will take about 5ms on an iPhone 3G. If the heap increases to 1MB, 

the collection will take about 7ms. It can therefore be advantageous sometimes to request a garbage collection at a regular frame interval. This will generally make collections happen more often than strictly necessary but they will be processed quickly and with minimal effect on gameplay:-
- iOS iPhone 3G 
에서 200KB garbage collection하는 데 5ms 가 걸리고,
                                 1MB
   garbage collection하는데 7ms 가 걸린다. - 

 if (Time.frameCount % 30 == 0)

 {

    System.GC.Collect();

 }

 

However, you should use this technique with caution and check the profiler statistics to make sure that it is really reducing collection time for your game.
그러나 위 방식을 체택 할 경우 프로파일러 통계로 명확하게 체크 할 것을 요한다.

4-1. Large heap with slow but infrequent garbage collection (큰 힙과 느리지만빈번하지 않은 가비지 콜렉션 방식)

This strategy works best for games where allocations (and therefore collections) are relatively infrequent and can be handled during pauses in gameplay. It is useful for the heap to be as large as possible without being so large as to get your app killed by the OS due to low system memory. However, the Mono runtime avoids expanding the heap automatically if at all possible. You can expand the heap manually by preallocating some placeholder space during startup (ie, you instantiate a "useless" object that is allocated purely for its effect on the memory manager):-
-
최고의 게임에서 빈번하지 않게 가비지 콜렉트를 호출하는 전략은 게임 플레이가 멈춘 동안에 다루는 것이다. ... 

 function Start() {

  var tmp = new System.Object[1024];

 

  // make allocations in smaller blocks to avoid them to be treated in a special way, which is designed for large blocks

         for (var i : int = 0; i < 1024; i++)

   tmp[i] = new byte[1024];

 

  // release reference

         tmp = null;

 }

 

A sufficiently large heap should not get completely filled up between those pauses in gameplay that would accommodate a collection. When such a pause occurs, you can request a collection explicitly:-
System.GC.Collect();
Again, you should take care when using this strategy and pay attention to the profiler statistics rather than just assuming it is having the desired effect.

// Practical Guide to Optimization for Mobiles - Optimizing Scripts
http://docs.unity3d.com/Documentation/Manual/iphone-PracticalScriptingOptimizations.html#Object%20Pooling
 

// Optimization for Mobiles - Scripting and Gameplay Methods

http://docs.unity3d.com/Documentation/Manual/iphone-OptimizedScriptingMethods.html

 

 

// 마소 C# 메모리 최적화 글 -good

http://www.imaso.co.kr/?doc=bbs/gnuboard.php&bo_table=article&wr_id=40743

 

// 마소 유니티 메모리 최적화 글

http://www.imaso.co.kr/?doc=bbs/gnuboard.php&bo_table=article&wr_id=41284

 

이하 인용....

유니티 스크립트 중에 최적화란 관점에서는 중요한 두 가지 함수가 존재한다명시적으로 GC를 호출하는 System.gc.collect와 

현재 사용하지 않는 리소스를 해제하는 Resources.Unload UnusedAssets란 함수다보통 scene이 바뀌거나 메모리를 

한번 비우고 가고 싶을 때 호출하게 되는데 두 함수만 열심히 호출한다고 해서 모든 문제가 해결되는 것은 아니다

 

...

개발하던 게임에서는 GUI를 위한 여러 텍스처들이 사용되고 있었는데 몇 개의 텍스처가 NPOT(non power of two)로 제작돼 있었다

OpenGL ES2를 지원하는 최근의 디바이스들은 NPOT 텍스처를 제한적으로 지원하고 있으나 유니티는 호환성을 위해 

NPOT 텍스처의 경우 내부적으로 별도의 처리를 하는 것으로 보인다

프로파일링 결과 POT 대비 2배의 메모리를 사용하는 것으로 생각되는데비디오 메모리를 많이 사용하는 게임에서는 특히 

유념해야 할 부분이다.

 

Posted by 프리랜서 디자이너
TA/Unity2013. 9. 9. 17:36

그래픽 성능 최적화

Desktop

게임이 끊김 없이 부드럽게 동작하는 것은 게임의 성공 여부에 매우 중요 합니다. 유니티가 이를 위해 존재합니다. 저희들은 다양한 하드웨어에 유니티 iOS가 빨리 동작하도록 많은 시간과 노력을 기울였습니다. 아래에는 게임의 스피드를 최대화 하기 위한 몇몇 간단 한 안내서를 소개합니다.

줄여서 얘기하면 – 통합(combine), 통합, 통합

  • 성능을 신경 쓰신다면, 메쉬들을 통합하세요.
  • 성능을 신경 쓰신다면, 통합된 메쉬들이 같은 재료(material)와 질감을 공유하도록 확인하세요.
  • ProfilerRendering Statistics 창이 유용할 것입니다!

자세히 살펴보면:

현대의 그래픽 카드들은 많은 다각형을 표현해 내는데 익숙하지만, 그래픽 카드에 모든 일괄처리(batch)들은 많은 과부하를 야기 합니다. 만약, 100개의 삼각형 오브젝트가 있는 것은 1500개의 삼각형을 표현해 내는 것만큼 많은 성능 소모가 일어 납니다. 최적의 표현 성능을 내는 이상적인 장소는 메쉬당 1500-4000개의 삼각형입니다.

Mesh Renderer가 부탁된 오브젝트들을 표현하는 것은 성능의 일정한 대가를 치러야 합니다. 보기 절두체(view frustum)내에서 그 대가를 치릅니다. 장면(scene)내에서 빈 GameObjects들을 많이 가지고 있는 것은 표현 비용이 없습니다.

  • 표현 성능을 향상 시키는 가장 좋은 방법은 오브젝트들을 결합해서 각 메쉬가 1500개 또는 더 이상의 오브젝트를 가지고, 전체 메쉬를 위해 하나의 Material를 사용하는 것입니다.

재료를 공유하지 않는 두개의 오브젝트들을 결합하는 것은 성능의 부하를 주지 않는다는 것을 이해해야 합니다. 효율적으로 결합하려면, 메쉬가 통합 후에 하나의 재료를 쓴다는 것을 확인하시기 바랍니다.

오브젝트들을 결합 할 때 한가지는 알아야 합니다. 장면에 많은 조그마한 빛들을 사용하면, 근접한 오브젝트들을 결합 하는 것이 더 합리적입니다.

많은 재료를 가지는 메쉬를 표현하는 비용은, 각 재료로 다수의 렌더러(renderer)를 가지는 것과 같은 비용 입니다. 다수의 재료들을 가지는 가장 보편화된 이유는 두 개의 메쉬들은 같은 질감들을 공유하지 않습니다. 표현 성능을 최적화 시키려면, 통합하는 오브젝트들이 같은 질감을 공유하도록 하셔야 합니다.

  • 유니티는 많은 다각형을 밀어 표현 하는것에 강점이 있다. 유니티는 모든 기하를 그래픽 카드로 업로드 하는데, 이는 좋은 캐쉬(cache) 효율과 최적화된 데이터 배열을 위해서이다.
  • 단지 그래픽 카드는 많은 수의 일괄처리를 다룰 필요가 없음을 확인해야 한다.
  • 전면 렌더링 통로 Forward rendering path를 사용하면, 오브젝트에 영향을 주는 Pixel Lights의 개수는 성능에 많은 영향을 준다.

전면 렌더링 통로에서의 픽셀 불빛들

주의: 이는 오직 전면 렌더링 통로 Forward rendering path 에서만 적용이 됩니다.

픽셀 불빛을 사용하면, 각각의 GameObject는 오브젝트에 영향을 주는 픽셀 불빛의 갯수 만큼 표현되어야 합니다. 멀리 떨어져 있는 두 오브젝트를 결합하면, 오브젝트의 크기가 증가할 것이고, 이 큰 오브젝트에 영향을 주는 많은 물빛들을 가지게 될 것입니다. 오브젝트들이 분리되어있으면, 멀리 떨어진 메쉬의 부분에 빛들이 적용될 필요는 없습니다.이는 통합된 메쉬를 표현하는데 통합되지 않는 메쉬(따라서 아무것도 저장하지 않는) 의 개수 만큼 표현하는 결과가 생깁니다. 이런 이유로, 멀리 떨어진 GameObjects들을 각각의 메쉬들로 보존해야 합니다.

메쉬를 표현할때, 유니티는 메쉬 주위의 모든 불빛을 찾습니다. 그리고, 어떤 빛들이 메쉬에 가장 영향을 미치는지 알아냅니다. QualitySettings은 얼마나 많은 빛이 픽셀 빛과 정점 (vertex) 빛으로 도래되는지를 변경할 때 사용합니다.

모든 빛들은 메쉬로부터 얼마나 떨어지고, 얼마나 강도가 있는지에 기반하여 중요성을 계산합니다.

어떤 빛들은 다른 빛들보다 게임의 맥락에서 더 중요합니다. 이러한 이유로, 모든 빛은 Render Mode 설정을 가집니다. 이 설정은 ImportantNot Important로 나눠집니다.

헤드 라이트를 가진 게이머의 차가 밤에 운전하는 것을 상상해 보십시오. 헤드 라이트들은 게임에서 가장 중요한 불빛들입니다. 이러한 이유로, 헤드라이트의 렌더 상태는 Important로 설정되어야 합니다.

별로 중요하지 않는 불빛을 가지고, 픽셀 빛으로부터 시각적으로 얻는 것이 없다면 렌더 상태는 Not Important로 설정합니다. 이런 방식으로 렌더링 성능이나 시각적 성능을 낭비하지 않습니다.

레이어당 누락 거리

그리기 호출의 개수를 줄이기 위해 작은 오브젝트들을 누락시키고 싶을 때가 있습니다. 예를 들어, 조그만 바위나 잔재는 커다란 빌딩보다 작은 거리에서는 보이지 않습니다. 이를 위해서는, 작은 오브젝트들을 분리된 레이어에 놓고, 레이어당 누락거리를 Camera.layerCullDistances 스크립트 함수를 사용하여 설정하십시오.

그림자들

데스크톱 플랫폼에서 배치한다면, 그림자들에 신경을 써야 합니다. 그림자들은 일반적으로 (성능상의)비용이 많이 듭니다. 그림자들이 정확하게 사용되지 않으면 게임의 성능에 많은 부하를 줍니다. 그림자들에 대한 더 많은 정보는 Shadows page를 읽으시길 바랍니다.

주의: 그림자들은 현재 iOS 나 Android 디바이스들에는 지원되지 않습니다.

더 살펴보기

iOS

iOS를 위해서 컨텐트의 최적화를 원한다면 learn more about iOS hardware devices 페이지를 참조 하십시오.

알파 테스팅

데스크톱과는 반대로, 알파테스팅 (또는 픽셀 쉐이더에서 discard$ / clip$$ 연산)은 iOS에서 성능상 비용이 많이 듭니다. 알파 테스트 쉐이더를 알파 블렌드로 교체를 원하시면, 그렇게 하십시오. 알파 테스트가 정말 필요하시면, 보이는 알파 테스트 픽셀 지역을 최소로 유지 시키십시오.

정점 성능

일반적으로 iPhone 3GS 나 새로운 디바이스들을 타겟으로 할때, 프레임당 40K나 더 적은 정점을 목표로 합니다. MBX GPU가 장착된iPhone, iPhone 3G, 1세대 2세대 iPod Touch 경우 프레임당 10K나 더 적은 정점을 목표로 합니다.

라이팅 성능

픽셀당 동적 라이팅(lighting)은 모든 영향 받는 픽셀에 중요한 비용이 추가 되고, 다중 패스들에 렌더링 오브젝트들을 이끌 수 있습니다. 하나의 오브젝트에 영향을 주는 하나의 Pixel Light보다 픽셀 라이트를 더 가지는 것을 피하시고, 방향성 빛을 선호하시길 바랍니다. Pixel LightRender Mode 설정이 Important로 설정되어 있음을 명심하시기 바랍니다.

정점당 동적 라이팅은 정점 변환에 많은 비용이 더 추가 됩니다. 하나의 오브젝트에 영향을 줄때는 다중 불빛을 피하시기 바랍니다. 정적 오브젝트들을 위해서는 베이크 라이팅 (bake lighting)을 사용하십시오.

모델 기하 최적화

모델 기하를 최적화 할 때, 두 가지 기초 룰이 있습니다:

  • 필요하지 않다면, 더 많은 추가 면을 사용하지 마세요.
  • UV매핑솔기(seam)의 수를 유지하고, 하드 모서리(hard edge)를 가능한 적게 하십시오.

그래픽 하드웨어가 프로세싱하는 실질적인 정점의 개수는 3D 어플리케이션에 표시되는 것과는 같지 않습니다. 모델링 어플리케이션들은 모델을 이루는 포인트들, 즉 기하 정점 카운트를 표시합니다.

그래픽 카드들은, 몇몇 정점들은 구분 되어야 합니다. 정점이 다중의 법선(normal)들 (하드 모서리 위에서) 을 가지거나, 다중의 UV 좌표를 가지거나, 다중의 꼭지점 색상을 가지면, 이는 분리되어야 합니다. Unity에서 보는 정점의 숫자는 3D 어플리케이션에서 진열 되는 것과 항상 다릅니다.

질감 압축

iOS 의 네이티브 PVRT compression formats 압축 포맷을 사용하십시오. 질감의 사이즈를 감소시킬 뿐만 아니라(더 빠른 불러오기 시간과 더 작은 메모리 사용), 렌더링의 성능을 증가 시킵니다. 압축된 질감은32bit RGBA에 비교해서 메모리 대역폭의 아주 일부분만 필요합니다. 성능 비교를 위해서는 iOS Hardware Guide를 참조하십시오.

몇몇 이미지는 PVRT 압축 질감의 알파 채널에서 시각적 조형물로 되기 싶습니다. 그런 경우 이미지 소프트웨어에 PVRT 압축 변수들의 직접적인 변경을 원할 수 있습니다. PVRT 포맷을 만든 Imagination Tech의 PVRTexTool을 사용하여 PVR export plugin을 설치해서 직접적인 변경이 가능합니다. ..pvr 확장을 가진 압축 이미지는 유니티 에디터에 가져오기 될 것이며, 수동으로 설정된 압축 변수들은 보존 될 것입니다.

PVRT 압축 포맷이 충분한 시각적 질을 전달하지 않는다면, UI 질감과 같은 추가의 이미지 작업이 필요합니다. 이 경우 32bit RGBA 질감대신 16bit 질감의 사용을 고려해야 합니다. 최소한, 메모리 대역폭이 반으로 줄 것입니다.

성능 좋은 쉐이더를 작성하기 위한 팁들

iPhone3GS 이후로 GPU들은 픽셀과 정점 쉐이더(shader)들을 충분히 지원하지만, 복잡한 픽셀당 기능을 가진 데스코톱 쉐이더의 성능과 초당 30프레임들을 동작하는 iOS디바이스의 성능을 추월하는 기대는 하면 안됩니다. 대부분 쉐이더 들은 편리하게 최적화 되어있고, 연산과 질감은 좋은 프레임 비율을 달성하기 위해서는 최소화 하여야 합니다.

복잡한 수식 연산들

수식 연산들(pow,, exp, log, cos, sin, tan)은 GPU에 많은 부하를 줍니다. 최고의 방법은 부분당(per fragment) 하나의 연산 이상을 가지지 않는 것입니다. 때로는 질감 찾기(lookup)가 더 좋은 대안입니다.

자신만의 규격화된 normalize, dot, inversesqrt 사용을 회피하십시오. 항상 내장되어있는 연산들을 사용하십시오. 이것이 더 좋은 코드를 생성할 것입니다.

Disscard 연산은 부분들(fragments)을 느리게 만들것입니다.

부동소수 연산들

사용자 쉐이더들을 작성할 때 부동 소수 변수들의 소수점 자리를 명시하십시오. 좋은 성능을 위해서는 작은 포맷이 더 중요하다는 것은 매우 중요합니다.

쉐이더가 GLSL ES로 쓰여지면, 소수점 자리는 다음과 같습니다:

  • highp - full 32 비트의 부동소수 포맷입니다. 정점변환에 적합하지만 가장 느립니다
  • mediump - 16비트의 부동소수 포맷입니다. 질감 UV 좌표에 적합합니다. 대략 highp보다 x2 빠릅니다.
  • lowp - 비트의 부동소수 포맷입니다. 색상과 빛 연산, 그리고 고성능 연산에 적합합니다. highp보다는 __x4 배 정도 빠릅니다.

쉐이더가 GG또는 표면 쉐이더로 쓰여지면, 소수점은 다음과 같습니다:

  • float - - GLSL ES 에서 highp 와 유사, 가장 느림
  • half - GLSL ES 에서 mediump와 유사, float보다 _x2__ 빠름
  • fixed - GLSL ES 에서 lowp와 유사, float보다 4x4 빠름

일반적인 쉐이더 성능을 알아보시려면 Shader Performance page 페이지를 참조하세요.

하드웨어 문서

Apple hardware 문서를 공부하고 쉐이더를 작성best practices for writing shaders하는데 시간을 가지시길 바랍니다. 부동 소수점의 힌트를 더 공격적으로 사용하길 권유합니다.

Lightmaps으로의 Bake Lighting

정적 라이팅의 장면을 Unity내의 내장된 Lightmapper를 사용해서 베이크(bake)하십시오. lightmapped 환경을 생성하는 과정은 Unity에서 장면에 빛을 배치하는 것보다 더 시간이 걸립니다. ¬¬그러나:

  • 더 많이 빠릅니다 (2-3 배, 2 픽셀 라이트)
  • 더 좋게 보입니다. 왜냐하면, 전체적 조명을 베이크 할수 있고, lightmapper는 결과들을 부드럽게 할 수 있습니다

재료들 공유

다수의 오브젝트들이 같은 카메라로 표현되면, 유니티 iOS는 더 다양한 내부적 최적화를 할 수 있을 것입니다. 예:

  • 다양한 렌더 상태들을 OpenGL ES로 하는 것을 피함.
  • 정점과 픽셀 프로세싱에 필요한 다양한 종류의 변수들 연산을 피함
  • 그리기(draw)호출들의 감소를 위한 작은 오브젝트들의 일괄처리
  • 그리기 호출을 줄이기 위한 정적 속성을 가능하게 하는 크고 작은 오브젝트들의 일괄 처리

모든 이런 최적화들은 CPU 사이클들을 줄여줄 것입니다. 그러므로, 질감들을 하나의 지도로 합하고 같은 재료(material)을 사용하기 위한 다수의 오브젝트를 만드는 것은 비용을 줄이므로, 권유합니다!

게임을 빠르게 하기 위한 간단한 체크리스트

  • 정점 카운트를 아래로 유지하세요:
    • iPhone 3GS와 더 최신의 디바이스를 타겟으로 할때는 프레임당40K 를 하세요 (SGX GPU를 사용).
    • 더 오래된 디바이스들은 프레임당10K 로 하세요 (MBX GPU를 사용)
  • 내장 쉐이더를 사용한다면, Mobile 카테고리를 살펴보세요. Mobile/VertexLit 는 현재 가장 빠른 쉐이더입니다.
  • 장면당 다른 재료(material)의 개수를 낮게 유지하세요- 가능하면 다른 오브젝트들 사이에서 더 많은 재료를 공유하세요.
  • 내부 최적화를 위해서 움직이지 않는 오브젝트들에게 Static 속성을 설정하세요.
  • 가능한 질감들을 위해서는 PVRTC 포맷을 사용하세요. 아니면, 32bit 대신 16bit 질감을 선택하세요.
  • 통합기(combiners)또는 픽셀 쉐이더를 사용하세요. 이는 다중 패스 접근법 대신에 부분당 몇몇 질감을 혼합하기 위함입니다.
  • 사용자 설정 쉐이더를 작성시에는, 항상 가장 작은 부동 소수점을 사용하세요:
    • fixed / lowp -- 칼라와 라이팅 정보, 법선들을 위해서는 완벽합니다,
    • half / mediump -- 질감 UV 좌표들을 위해서 쓰입니다,
    • float / highp -- 픽셀 쉐이더에서는 피합니다. 정점 쉐이더에서 정점 위치 계산을 위한 사용에 괜찮습니다.
  • 픽셀 쉐이더에서 복잡한 수식 연산(pow, sin, cos 등)을 최소화 합니다.
  • 필요하지 않으면 Pixel Lights는 쓰지 않습니다. – 기하에 영향을 주는 (가능한 방향성의) 하나의 픽셀 라이트를 선택하세요.
  • 필요하지 않으면 동적 라이트를 사용하지 않습니다 – 베이크 라이트를 대신 선택하세요.
  • 부분당 (per fragment) 더 작은 질감을 사용하세요.
  • 알파 테스팅을 피하세요. 알파 블렌딩(alpha-blending)을 대신 사용하세요.
  • 필요하지 않으면 안개를 사용하지 마세요.
  • 폐색 누락(Occlusion culling) 의 장점을 알아보시고, 복잡한 정적 장면에 많은 폐색이 있을 시에는 가시 기하와 그리기 호출의 양을 줄이기 위해서 사용하세요. 폐색 누락으로부터 어떤 이득의 단계를 얻을 것인지 계획하세요.
  • 원거리 기하를 “위장(fake)” 위해서는 스카이박스(skybox)들을 사용하세요.

더 살펴보기

Android

라이팅 성능

픽셀당 동적 라이팅(lighting)은 모든 영향 받는 픽셀에 중요한 비용이 추가 되고, 다중 패스들에 렌더링 오브젝트들을 이끌 수 있습니다. 하나의 오브젝트에 영향을 주는 하나의 픽셀 라이트 보다 Pixel Light를 더 가지는 것을 피하시고, 방향성 빛을 선호하시길 바랍니다. Pixel LightRender Mode 설정이 Important로 설정되어 있음을 명심하시기 바랍니다.

정점당 동적 라이팅은 정점 변환에 많은 비용이 더 추가 됩니다. 하나의 오브젝트에 영향을 줄때는 다중 불빛을 피하시기 바랍니다. 정적 오브젝트들을 위해서는 베이크 라이팅 (bake lighting)을 사용하십시오.

모델 기하 최적화

모델 기하를 최적화 할 때, 두 가지 기초 룰이 있습니다:

  • 필요하지 않다면, 더 많은 추가 면을 사용하지 마세요
  • UV매핑솔기(seam)의 수를 유지하고, 하드 모서리(hard edge)를 가능한 적게 하십시오

그래픽 하드웨어가 프로세싱하는 실질적인 정점의 개수는 3D 어플리케이션에 표시되는 것과는 같지 않습니다. 모델링 어플리케이션들은 모델을 이루는 포인트들, 즉 기하 정점 카운트를 표시합니다.

그래픽 카드들은, 몇몇 정점들은 구분 되어야 합니다. 정점이 다중의 법선(normal)들 (하드 모서리 위에서) 을 가지거나, 다중의 UV 좌표를 가지거나, 다중의 꼭지점 색상을 가지면, 이는 분리되어야 합니다. Unity에서 보는 정점의 숫자는 3D 어플리케이션에서 진열 되는 것과 항상 다릅니다.

질감 압축

모든OpenGL ES 2.0 를 지원하는 안드로이드 디바이스들은 ETC1 compression format을 지원합니다. 그러므로, ETC1을 선호하는 질감 포맷으로 사용하길 권유합니다. 압축된 질감은 질감의 사이즈를 줄이는데 중요할 뿐만 아니라(빠른 불러오기 시간과 더 작은 메모리 사용), 렌더링 성능의 향상도 크게 기여합니다. 압축된 질감은32bit RGBA에 비교해서 메모리 대역폭의 아주 일부분만 필요합니다.

만약Nvidia Tegra 나 Qualcomm Snapdragon과 같은 그래픽 구조를 타겟으로 하면, 그러한 구조에 전속적인 압축 포맷을 사용하는 것도 고려해 볼만 합니다. 안드로이드 마켓은질감 압축 포맷의 지원의 필터링을 허락합니다. DXT compressed textures와 같은 .apk와 같은 배포파일은 지원하지 않는 디바이스에서는 압축이 허용되지 않을 수 있습니다.

Mip Maps 허용

항상 Generate Mip Maps 를 허용하는 것이 좋습니다. 질감 압축이 GPU 렌더링시 질감 데이터의 전송의 한계를 돕듯이, mip mapped 질감은 GPU가 더 작은 삼각형으로 낮은 해상도를 사용하게 하는 것을 가능하게 합니다. 이 규칙의 예외는 texel(texture pixel)이 UI 요소들이나 순수 2D게임에서 렌더된 스크린 픽셀에 1:1 매핑하는 것입니다.

성능 좋은 쉐이더를 작성하기 위한 팁들

모든 안드로이드OpenGL ES 2.0 GPU들은 픽셀과 정점 쉐이더(shader)들을 충분히 지원하지만, 복잡한 픽셀당 기능을 가진 데스코톱 쉐이더의 성능과 초당 30프레임들을 동작하는 안드로이드 디바이스의 성능을 추월하는 기대는 하면 안됩니다. 대부분 쉐이더 들은 편리하게 최적화 되어있고, 연산과 질감은 좋은 프레임 비율을 달성하기 위해서는 최소화 하여야 합니다.

복잡한 수식 연산들

수식 연산들(pow, exp, log, cos, sin, tan)은 GPU에 많은 부하를 줍니다. 최고의 방법은 부분당(per fragment) 하나의 연산 이상을 가지지 않는 것입니다. 때로는 질감 찾기(lookup)가 더 좋은 대안입니다.

자신만의 normalize, dot, inversesqrt 연산들의 사용을 회피하십시오. 항상 내장되어있는 연산들을 사용하십시오. 이것이 더 좋은 코드를 생성할 것입니다.

Discard 연산은 부분들(fragments)을 느리게 만들 것입니다.

부동소수 연산들

사용자 쉐이더들을 작성할 때 부동 소수 변수들의 소수점 자리를 명시하십시오. 좋은 성능을 위해서는 작은 포맷이 더 중요하다는 것은 매우 중요합니다.

쉐이더가 GLSL ES로 쓰여지면, 소수점 자리는 다음과 같습니다:

  • highp - 32비트의 부동소수 포맷입니다. 정점변환에 적합하지만 가장 느립니다
  • mediump - 16비트의 부동소수 포맷입니다. 질감 UV 좌표에 적합합니다. 대략 highp보다 x2 배 빠릅니다
  • lowp - 10비트의 부동소수 포맷입니다. 색상과 빛 연산, 그리고 고성능 연산에 적합합니다. highp보다는 x4 배 정도 빠릅니다

쉐이더가 GG또는 표면 쉐이더로 쓰여지면, 소수점은 다음과 같습니다:

  • float - GLSL ES 에서highp와 유사, 가장 느림
  • half - GLSL ES 에서 mediump와 유사, float보다 x2 빠름
  • fixed - GLSL ES 에서 lowp와 유사, ffloat보다 x4

일반적인 쉐이더 성능을 알아보시려면 Shader Performance page 페이지를 참조하세요. 인용된 성능 그림들은즉 삼성 Nexus S와 같은 기기들에 사용가능한PowerVR 그래픽 환경을 기반으로 합니다. 다른 하드웨어 환경들은 레지스터 정밀(precision)의 감소로부터 덜 혹은 더 수혜를 받을 것입니다.

Lightmaps 으로의 Bake Lighting

정적 라이팅의 장면을 Unity내의 내장된 Lightmapper를 사용해서 베이크(bake)하십시오. lightmapped 환경을 생성하는 과정은 Unity에서 장면에 빛을 배치하는 것보다 더 시간이 걸립니다, 그러나:

  • 더 많이 빠릅니다 (2-3 배, 2 픽셀 라이트)
  • 더 좋게 보입니다. 왜냐하면, 전체적 조명을 베이크 할수 있고, lightmapper는 결과들을 부드럽게 할 수 있습니다

재료들 공유

다수의 오브젝트들이 같은 카메라로 표현되면, 유니티 안드로이드는 더 다양한 내부적 최적화를 할 수 있을 것입니다. 예:

  • 다양한 렌더 상태들을 OpenGL ES로 하는 것을 피함.
  • 정점과 픽셀 프로세싱에 필요한 다양한 종류의 변수들 연산을 피함
  • 그리기(draw)호출들의 감소를 위한 작은 오브젝트들의 일괄처리
  • 그리기 호출을 줄이기 위한 정적 속성을 가능하게 하는 크고 작은 오브젝트들의 일괄 처리

모든 이런 최적화들은 CPU 사이클들을 줄여줄 것입니다. 그러므로, 질감들을 하나의 지도로 합하고 같은 재료(material)을 사용하기 위한 다수의 오브젝트를 만드는 것은 비용을 줄이므로, 권유합니다!

게임을 빠르게 하기 위한 간단한 체크리스트

  • 내장 쉐이더를 사용한다면, Mobile 카테고리를 살펴보세요. Mobile/VertexLit 는 현재 가장 빠른 쉐이더입니다.
  • 장면당 다른 재료(material)의 개수를 낮게 유지하세요- 가능하면 다른 오브젝트들 사이에서 더 많은 재료를 공유하세요.
  • 내부 최적화를 위해서 움직이지 않는 오브젝트들에게 Static 속성을 설정하세요.
  • 가능한 질감들을 위해서는 ETCI 포맷을 사용하세요. 아니면, 32bit 대신 16bit 질감을 선택하세요.
  • mipmaps를 사용하세요.
  • 통합기(combiners)또는 픽셀 쉐이더를 사용하세요. 이는 다중 패스 접근법 대신에 부분당 몇몇 질감을 혼합하기 위함입니다.
  • 사용자 설정 쉐이더를 작성시에는, 항상 가장 작은 부동 소수점을 사용하세요:
    • fixed / lowp -- 칼라와 라이팅 정보, 법선들을 위해서는 완벽합니다,
    • half / mediump -- 질감 UV 좌표들을 위해서 쓰입니다,
    • float / highp -- 픽셀 쉐이더에서는 피합니다. 정점 쉐이더에서 정점 위치 계산을 위한 사용에 괜찮습니다
  • 픽셀 쉐이더에서 복잡한 수식 연산(pow, sin, cos등)을 최소화 합니다.
  • 필요하지 않으면 Pixel Lights 는 쓰지 않습니다. – 기하에 영향을 주는 (가능한 방향성의) 하나의 픽셀 라이트를 선택하세요.
  • 필요하지 않으면 동적 라이트를 사용하지 않습니다 – 베이크 라이트를 대신 선택하세요.
  • 부분당 (per fragment) 더 작은 질감을 사용하세요.
  • 알파 테스팅을 피하세요. 알파 블렌딩(alpha-blending)을 대신 사용하세요
  • 필요하지 않으면 안개를 사용하지 마세요.
  • 폐색 누락(Occlusion culling) 의 장점을 알아보시고, 복잡한 정적 장면에 많은 폐색이 있을 시에는 가시 기하와 그리기 호출의 양을 줄이기 위해서 사용하세요. 폐색 누락으로부터 어떤 이득의 단계를 얻을 것인지 계획하세요.
  • 원거리 기하를 “위장(fake)” 위해서는 스카이박스(skybox)들을 사용하세요.

더 살펴보기

Posted by 프리랜서 디자이너
TA/Unity2013. 9. 9. 17:33

출처 : http://blog.naver.com/dfsin2/100188770656

 

최근에 모바일 프로젝트를 진행하면서 최적화에 대해 많은 필요성을 느끼게 되었습니다.

유니티 모바일 및 스크립트 관련해서 최적화 관련 부분을 하나씩 포스팅 해나갈까 합니다.

출처 : http://unitykoreawiki.com/index.php?n=KrMain.iphone-Optimizing-Scripts

스크립트 성능 최적화

이 페이지는 iOS에서 어떻게 스크립트 성능을 향상시킬수 있는지에 대한 일반 힌트를 제공합니다.

감소된 고정 델타 타임

15-25fps의 고정 델타 타임을 사용하세요. 사용자는 Edit->Project Settings->Time에서 이것을 바꿀수 있습니다. 이것은 FixedUpdate가 불리고 물리 엔진이 충돌감지(collision detection)와 rigidbody 업데이트하는 빈도수를 줄여줍니다. 주요 캐릭터에 rigidbody를 사용한다면 Rigidbody컴포넌트에서 낮은 고정 델타 타임 스텝을 부드럽게하기 위해 interpolation을 활성화 할 수 있습니다.

GetComponent콜수 줄이기

GetComponent나 BuiltIn 컴포넌트를 액세서를 사용하는 것은 주목할만한 오버헤드를 생기가 할 수 있습니다. 이것은 컴포넌트의 직접적인 참조를 캐싱하여 줄일수 있습니다.

예를 들어:

function Update () {
    transform.Translate(0, 1, 0);
}

대신 아래와 같이 최적화 할수 있습니다:

myTransform : Transform;
function Awake () {
   myTransform = transform;
}
function Update () {
    myTransform.Translate(0, 1, 0);
}

 >> 직접 gameObject 나 transform 과 같은 BuiltIn 컴퍼넌트의 사용을 자제하고

변수로 만들어서 사용하라는 의미인거 같네요.


메모리 할당을 피하세요

  • 스크립트에서 할당을 최소화하세요.
  • Structs를 사용하세요. Structs는 메모리를 할당하지 않습니다. 대신 스택에 놓이게되며 값으로 전달을하며 이것이 더 빠릅니다.

GUI 를 줄이세요

  • GUILayout대신 GUI functions을 사용하고 화면에서 GUI의 양을 최소화 하세요
  • GUI 오버헤드를 줄이기 위해MonoBehaviour.useGUILayout = false를 사용하세요
function Awake () {
    useGUILayout = false;
}

>> 그냥 유니티 내장 GUI를 안 쓰는게 답입니다...

    에셋 스토어에서 판매하는 GUI 플러그인 (EZGUI 및 NGUI) 등을 구매하셔서 사용하는게 좋을듯 하네요.
    불가피하게 내장 GUI를 써야 한다면 저런 방법도 좋겠네요.

iOS 스크립트 콜 최적화를 사용하세요

UnityEngine 내임스페이스에 있는 대부분의 함수는 C/C++로 구현되어 있습니다. 그런 함수를 스크립트에서 부르는 것은 성능에 추가적인 오버헤드가 있습니다. 프레임당 추가의 몇 밀리세컨드를 얻기 위해서는 Edit->Project Settings->Player 에 있는 iOS스크립트 콜 최적화를 사용하세요:

  • Slow and Safe - 기본적인 단일의 내부 콜 핸들링 (익셉션 지원함) .
  • Fast and Exceptions Unsupported - 빠른 단일 내부 콜 구현, 그러나 익셉션이 지원되지 않으므로 조심히 사용하세요. 이것이 유용한 전형적인 경우:프로그램이 유니티엔진을 많이 사용하면서 익셉션이 생기지 않을때 입니다. 이 옵션은 1-4ms/frame을 줄일 수 있습니다.

>> 올ㅋ 요건 몰랐네 한번 써봐야겠다.


Garbage Collection 최적화

  • 위에 서 말한 것처럼 어떤 할당도 피하도록 하세요
  • 만약 피할수 없다면 두가지 할당/수집 방법이 있습니다:
    • 작은 힙 빠르고 자주 일어나는 가비지 콜렉션 전략
      이 방법은 아주 긴 액션 게임플레이가 있고 부드러움 framerate가 필요한 게임에서 잘 작동합니다.
      짧은 시간동안 작은 블락을 할당하는 유니티 iOS게임에서 힙은 보통 ~5ms(iPhone 3G에서)걸립니다. 1MB힙에서는 가비지 컬력션은 ~7ms정도 더 걸립니다.
      가끔은 매 N번 프레임마다 강제 가비지 콜렉션을 하는것도 유용합니다:
if (Time.frameCount % 30 == 0)
{
   System.GC.Collect();
}
  • 그러나 유의해서 사용하세요. 내부 프로파일러 통계를 보고 그것을 바탕으로 결정을 내리세요.
  • 큰 힙 느리고 빈도수가 낮은 가비지 콜렉션
    이 방법은 게임이 요구하는 부드러운 프레임 속도가 짧은 것과 중간 사이의 길이일 때 사용을 합니다. 아이디어는 게임이 실행될때는 가비지 컬렉션을 피하고 게임이 중단 되었을 때하는 것입니다. 게임 시작에 힙에 어느 정도의 공간을 미리 할당하는 것도 좋은 생각인데 그 공간은 게임 플레이 세션이 잘 돌아갈수 있을 만큼 크고 OS의 낮은 메모리 시스템으로 인해 프로그램이 멈추지 않을 만큼의 작은 공간을 말합니다. 모노는 필요할 때문 힙을 늘리기 때문에 가끔 시작시에 강제적으로 확장하는 것도 좋은 생각입니다:
function Start() {
 var tmp = new System.Object[1024];
 // 할당은 작은 블락에해서 큰 블락은 위하 특별한 처리 방법을 피하도록 합니다
        for (var i : int = 0; i < 1024; i++)
  tmp[i] = new byte[1024];
 // 참조를 풀어 줍니다
        tmp = null;
}

나중에 게임 플레이가 중단 되었을때 강제적으로 가비지 콜렉션을 할 수 있습니다:

System.GC.Collect();

주의해서 사용하셔야 합니다. 내주 프로파일러 통계에 주목하시고 실제 숫자를 바탕으로 결정을 내리세요.

 

>> 가비지 컬렉터 주의해서 사용해야죠.

    저희는 주기적으로 (며느리도 안알려줌) GC.Collect 를 호출하는데

    요때 부하가 좀 걸리게 됩니다. 고려하시고 사용해야할듯...

 

Posted by 프리랜서 디자이너
TA/Unity2013. 9. 5. 14:51

어이없지만 자꾸 잊어 버린다... 하아~

 

문자 -> 숫자 =      int.Parse()

숫자 -> 문자 =     .ToString()

Posted by 프리랜서 디자이너
TA/Unity2013. 8. 29. 14:39

유니티게임 구글 인앱 결제 붙이기!!d Unity

2013/03/11 19:16

복사 http://blog.naver.com/psd0217/10162792878

전용뷰어 보기

1. 먼저 만들어진 유니티게임을 빌드 하여 APK파일을 만들어서 구글 플레이 개발자 콘솔 사이트 들어가서 어플을 등록해 주세요~!

물론 여기까지 들어오려면 개발자 등록을 해야 한다.

돈이 듭니다...ㅠㅠ

우선 매인은 이게 아니니까 패스 하고

새 애플리케이션 추가 클릭 클릭

 

이런화면이 뜨는데 1번 에 당연히 어플이름을 적어주시면 되고

APK업로드 누르시면 업로드가 됩니다.

 

여기서도 잠깐 업로드가 안된다고요??

그렇다면 유니티 빌드할때 키를 넣지 않았다는 뜻인데요

이것도 인터넷 쪼금만 찾아보면 나오는 것이니까 패스 할게요~!

 

자!! 여기서 궁금증?? 왜 인앱 시스템을 집어 넣지도 않았는데 어플을 업로드 했나요??

그건 밑에 설명해 드릴께요 차근차근 합니다

 

아 그리고 업로드 한다고 바로 어플 다운이 되고 구글 플레이에서 검색이 되고 이러는거 아니니까 걱정 마세요

어플 활성화 버튼을 누르지 않는 이상 어플은 완전히 등록 된것이 아니기 때문에 상관 없습니다.

 

자 다음은~유니티로 돌아가서~~

 

이부분은 다들 아시겠죠???

파일 -> 빌드 셋팅 -> 플레이어 셋팅

을 하시면~ 밑에처럼

이렇게 나오죠 ㅋㅋ

당연히 빌드를 했다면 저기 번들 이름을 지정하셧겟죠???

이거 복사 해 두세요~! ㅋ

 

자 다음은 이클립스에서 안드로이드 프로젝트를 생성해 볼까요?

자 프로젝트를 생성하다보면 패키지 이름을 지정하라고 나오죠???

방금 유니티에서 복사한 번들 이름 집어 넣어 주세요

자자 다음은 

생성된 프로젝트에서 오른쪽 마우스 클릭

그리고 properties를 눌러 주세요~!

이런화면이 나오는데 순서대로 눌러 주세요~!


자 그럼 저 파일을 찾아야 하는데 어딧을까요??ㅋ

경로는 Program File->Unity->Editor->Data->PlaybackEngines->androidplayer->bin

이경로에 있습니다 ㅋ

없다고요?? 그럴리가

유니티가 설치되어 있다면 당연히 있어야 정상입니다

없으면...구글링 혹은 유니티 재설치 ㄱㄱ
무튼 열기를 누르면

이번에도 순서대로 누르시기만 하면 됩니다.



위처럼 프로젝트 안에 classes.jar파일이 생성 되었다면 완료~!

이로써 이클립스에서 유니티 함수들을 불러다 쓸수 있게 되었습니다~~~짝짝짝~!

끝낫냐고요???

아직 멀었으니 따라 오세요


자 그러면 다음은 아래의 파일을 다운받아서 압축 풀고 임포트를 시켜 주세요

BillingTest.zip

위 그림처럼 src폴더에 복사해 주세요

그리고 다시 구글 플래이 개발자 콘솔로 돌아와서

여기서 왜 인앱결제 시스템을 붙이지 않고 어플을 등록 시켰는지 이해를 하게 될거에요

왜냐면 위의 그림처럼 인앱시스템을 구축하려면 구글에서 주는 키를 등록해 주어야 하는데

이 소스에서는 이부분을 소스 코드 안에서 처리를 해주기 때문이죠~! ㅋ

위 소스를 복사해서~

조기에 붙여넣어 주세요

잘 안보이시나???

babyaba.Test.billing 밑에 있는Security 클래스에 base64EncodedPublicKey부분에 붙여 넣어주시면 됩니다

 

그림으로는 안나와 있지만 MainActivity 소스를 아래처럼 바꿔 줍니다

 

 

 

 

 

import bayaba.test.billing.BillingService;

import bayaba.test.billing.Consts;

import bayaba.test.billing.PurchaseObserver;

import bayaba.test.billing.ResponseHandler;

import bayaba.test.billing.BillingService.RequestPurchase;

import bayaba.test.billing.BillingService.RestoreTransactions;

import bayaba.test.billing.Consts.PurchaseState;

import bayaba.test.billing.Consts.ResponseCode;

import com.Bumblebee.CatsIsland.MainActivity;

 

import com.unity3d.player.UnityPlayer;

import com.unity3d.player.UnityPlayerActivity;

 

import android.os.Bundle;

import android.os.Handler;

import android.os.Message;

import android.app.AlertDialog;

import android.content.DialogInterface;

 

public class MainActivity extends UnityPlayerActivity {

 

    private GamePurchaseObserver mPurchaseObserver;

    private Handler mHandler;

    private BillingService mBillingService;

  

    private class GamePurchaseObserver extends PurchaseObserver

    {

        public GamePurchaseObserver(Handler handler) {

            super(MainActivity.this, handler);

        }

 

@Override

public void onBillingSupported(boolean supported, String type){

if (type == null || type.equals(Consts.ITEM_TYPE_INAPP)) {

if (supported) {

mBillingService.restoreTransactions();

}

}

}

 

        @Override

        public void onPurchaseStateChange(PurchaseState purchaseState, String itemId, int quantity, long purchaseTime, String developerPayload)

        {

        switch (purchaseState)

        {

case PURCHASED:

UnityPlayer.UnitySendMessage("AndroidManager", "CharacterPurchaseState", itemId );

// 현재 구매 확인

break;

case CANCELED:

//UnityPlayer.UnitySendMessage("AndroidManager", "LabelLog", "CANCELED" + itemId );

// 취소 ..

break;

/*

case REFUNDED:

UnityPlayer.UnitySendMessage("AndroidManager", "LabelLog", "REFUNDED" + itemId );

// 환불 ..

break;

*/

default:

break;

}

        }

 

        @Override

        public void onRequestPurchaseResponse(RequestPurchase request, ResponseCode responseCode)

        {

            if (responseCode == ResponseCode.RESULT_OK) 

            {

            // 확인 메시지

            UnityPlayer.UnitySendMessage("AndroidManager", "PurchaseResult", "ok" );

            // 확인된 아이템 아이디

            UnityPlayer.UnitySendMessage("AndroidManager", "PurchaseOk", "" + request.mProductId );

           

            if (Consts.DEBUG) 

            {

                    //Log.i(TAG, "purchase was successfully sent to server");

                }

            }

            else if (responseCode == ResponseCode.RESULT_USER_CANCELED) 

            {

            // 취소 메시지

            UnityPlayer.UnitySendMessage("AndroidManager", "PurchaseResult", "cancle" );

            // 취소된 아이템 아이디

            UnityPlayer.UnitySendMessage("AndroidManager", "PurchaseCancle", "" + request.mProductId );

           

                if (Consts.DEBUG) 

                {

                    //Log.i(TAG, "user canceled purchase");

                }

            } 

            else 

            {

                if (Consts.DEBUG) {

                    //Log.i(TAG, "purchase failed");

                }

            }

        }

 

        @Override

        public void onRestoreTransactionsResponse(RestoreTransactions request, ResponseCode responseCode)

        {

        if (responseCode == ResponseCode.RESULT_OK)

        {

// Log.d(TAG, "completed RestoreTransactions request : " + request.getStartId());

}

        else 

        {

// Log.d(TAG, "RestoreTransactions error: " + responseCode);

}

        }

    }

    

@Override

protected void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);

        

        InitBiling();

}

private void InitBiling()

{

    mHandler = new Handler();

 

    mPurchaseObserver = new GamePurchaseObserver(mHandler);

        mBillingService = new BillingService();

        mBillingService.setContext(this);

        

        ResponseHandler.register(mPurchaseObserver);

        if ( !mBillingService.checkBillingSupported() )

   {

        //showDialog(DIALOG_CANNOT_CONNECT_ID);

  }

}

public void BuyItem( String item_name )

{

if ( !mBillingService.requestPurchase(item_name, Consts.ITEM_TYPE_INAPP, "REQUEST_PURCHASE") )

{

UnityPlayer.UnitySendMessage("AndroidManager", "LabelLog", "PurchaseError");

}

}

//public void initActivity(String tagFromUnity, String messageFromUnity)

    //{

    // UnityPlayer.UnitySendMessage("AndroidManager", "AndroidLog", "[" + tagFromUnity + "]" + messageFromUnity);

    //}

public void quitApplication()

    {

handler.sendEmptyMessage(0);

    }

public Handler handler = new Handler()

{

public void handleMessage(Message msg)

{

switch(msg.what)

{

case 0:

finishMessage();

}

}

};

public void finishMessage()

{

AlertDialog.Builder builder = new AlertDialog.Builder(this);

builder.setMessage("프로그램을 종료 하시겠습니까?");

builder.setCancelable(false);

builder.setPositiveButton("예", new DialogInterface.OnClickListener() {

@Override

public void onClick(DialogInterface dialog, int which)

{

UnityPlayer.UnitySendMessage("AndroidManager", "ApplicationQuit","");

}

});

builder.setNegativeButton("아니요", new DialogInterface.OnClickListener(){

@Override

public void onClick(DialogInterface dialog, int which)

{

dialog.cancel();

}

});

builder.show();

}

@Override

public void onDestroy()

{

super.onDestroy();

mBillingService.unbind();

}

   

@Override

protected void onStart() {

super.onStart();

ResponseHandler.register(mPurchaseObserver);

}

 

@Override

protected void onStop() {

super.onStop();

ResponseHandler.unregister(mPurchaseObserver);

}

 

}




이 클래스로 유니티와 함수 호출을 왔다갔다 해줍니다.


 


이대로 프로젝트를 익스포트 해주시구요

 

jar파일을 선택해서

1번 처럼 체크를 해주시고 

2번처럼 경로와 파일이름을 지정해 주시고

finish!!!

이로서 플러그인 완전 완성~!!!!!

안된다구요??

끝~!!!! 이었으면 좋았으나 아직 좀 남았습니다~!

다시 유니티로 돌아와서

빌드를 해주시는데~

이미 빌드했다면 안해 줘도 됨 ㅋ

프로젝트 경로로 쭉쭉 들어가서 Temp폴더를 살펴 보시면 위 폴더가 보일거에요~!

다 필요 없고

그안에 있는 매니 패스트만 복사 해서 다른 폴더에 넣어주세요

아까 생성한 jar파일 있는곳에 같이 넣어주면 좋구요

자 다시 유니티에서 Plugins 폴더와 Android폴더를 만들어서 그 밑에

두파일을 집어 넣어 주세요

위 경로는 무조건 저렇게

한글자라도 틀리면 못읽어 오니까 주의 하시구요~!!!

 

그리고~! 매니페스트 파일을 열어 보시면

밑에 처럼 뭐라뭐라 솰랴 솰랴 하는데

다 필요 없고요

1번 젤위 의 페키지 명을 플러그인으로 가져온(이클립스에서 가져온) jar파일에있는 패키지명..com.어쩌고.어쩌고 했던 그거 적어 주세요~!

2번 부분에는 3번에 있는 MAIN이랑 LANCHER 이부분이 있을탠대요 싹 지워 주세요

이유는 뭐랄까 저부분은 다른 패키지 명으로 열어줘야 해서?? 라고 답해드리죠  매인이 두번 올순 없으니까요

3번은 그냥 그대로 적어주세요

대략 내용은 위에서 지운 매인부분 넣어주고 결제 시스템 클래스들을 같이 써주기 위함이죠

아 그리고 깜빡하고 위의 그림에서 그리지 않은 부분이 있는데요

<uses-permission android:name="com.android.vending.BILLING"/>

그림 밑에 쪽에 보시면 이 소스 보이시죠

꼭 써줘야 결제가 됩니다~!!!

 

다시 유니티로 돌아와서~!!!

 

 

위 처럼 안드로이드매니져 클래스를 만들어 주세요

당연 클래스 이름 저거랑 같아야 됩니다. 왜냐면 아까 이클립스에서 호출하는 클래스 이름을 저걸로 해뒀거든요 쭉 살펴 보시면 아실거에요

이 클래스에는

 

 

using UnityEngine;

using System.Collections;

 

public class AndroidManager : MonoBehaviour

{

    private static AndroidManager _instance;

 

    public string androidLog = "No Log";

public string fps = "No fps";

public string purchaseOkItemID = "";

public string purchaseCancleItemID = "";

public bool character2State = false;

public bool character3State = false;

public bool character4State = false;

public bool character5State = false;

public bool purchaseOk = false;

public bool purchaseCancle = false;

 

//#if UNITY_ANDROID && !UNITY_EDITOR

 

    public AndroidJavaObject activity;

 

    void Awake()

    {

 

        AndroidJavaClass jc = new AndroidJavaClass("com.unity3d.player.UnityPlayer");

        activity = jc.GetStatic<AndroidJavaObject>("currentActivity");

    }

 

//#endif

 

void OnGUI()

{

GUI.Label( new Rect( 10, 10, 200, 20 ), "fps : " + fps );

GUI.Label( new Rect( 10, 30, 600, 20 ), "" + androidLog );

GUI.Label( new Rect( 10, 50, 600, 20 ), "character2State : " + character2State );

GUI.Label( new Rect( 10, 70, 600, 20 ), "character3State : " + character3State );

GUI.Label( new Rect( 10, 90, 600, 20 ), "character4State : " + character4State );

GUI.Label( new Rect( 10, 110, 600, 20 ), "character5State : " + character5State );

GUI.Label( new Rect( 10, 130, 600, 20 ), "purchaseOk : " + purchaseOk );

GUI.Label( new Rect( 10, 150, 600, 20 ), "purchaseCancle : " + purchaseCancle );

}

public void Buy( string ItemID )

{// 이클립스에서 작성한 클래스에서 BuyItem함수를 호출하는겁니다. 물론 뒤에는 인자값임

activity.Call("BuyItem", "" + ItemID );

}

public void Exit()

{// 위와 같이 함수를 호출하는거입니다

activity.Call("quitApplication");

}

// 이 밑에 부분은 신경 안쓰셔도 되고 쓰셔도 되고

// 대강 설명하자면 이클립스에서 만든 클래스에서 불려 쓰이는 함수들입니다.

 

void PurchaseOk( string ItemID )

{// 현재 무슨 아이템이 구매가 되었는지를 알아오는함수

purchaseOkItemID = ItemID;

}

void PurchaseCancle( string ItemID )

{// 현재 무슨 아이탬이 취소가 되었는지 알아오는 함수

purchaseCancleItemID = ItemID;

}

void PurchaseResult( string result )

{// 현재 아이템을 결재 했는지 취소했는지 알아오는함수

if( "ok" == result )

{

purchaseOk = true;

}

else if( "cancle" == result )

{

purchaseCancle = true;

}

}

void CharacterPurchaseState(string itemID)

{// 현재까지 결재된 아이템이 무었인지 알려주는 함수 (당연히 수정해서 쓰시겟죠?)

if( "character2" == itemID )

{

character2State = true;

}

if( "character3" == itemID )

{

character3State = true;

}

if( "character4" == itemID )

{

character4State = true;

}

if( "character5" == itemID )

{

character5State = true;

}

}

void ApplicationQuit(string message)

{// 이함수가 불리는순간 게임 종료

Application.Quit();

}

void LabelLog(string message)

{// 에러 매시지 같은 로그를 받아옵니다. 

// 인앱부분은 핸드폰 기기에서만 사용할수 있기 때문에 애러같은것을 라벨로 로그를 남겨줌

androidLog = "Log : " + message;

}

// 당연 어떤 클래스에서 불릴수 있는 싱글톤의 매력~!!!

    public static AndroidManager Instance

    {

        get

        {

            if (_instance == null)

            {

                _instance = FindObjectOfType(typeof(AndroidManager)) as AndroidManager;

                if (_instance == null)

                {

                    _instance = new GameObject("AndroidManager").AddComponent<AndroidManager>();

                }

            }

 

            return _instance;

        }

    }

}

 

대략 요딴식으로 적어주시면 되는대요

설명은 주석으로 달려있으니까 확인해 보세요

결재 되는 부분(클래스 or 함수)에서

AndroidManager.Instance.Buy( _itemID );

이런식으로만 써주면 바로 결재 들어갑니다.ㅋㅋ

 

자 다음은 다시 구글 플래이 게발자 콘솔로 돌아와서

인앱제품 부분으로 들어가서

새 제품 추가를 하시면~~~


 

위 그림처럼 나오는데요

관리되는 제품 관리되지 않은 제품으로 나뉘게 됩니다.

뭐 인터넷 조금만 확인해 보시면 되실꺼지만

관리되는 제품은 구글 서버에서 이아이템을 가지고 있는지 아닌지를 알아서

단 한번만 제품을 구매하게끔 만들어 줍니다.

관리되지 않는 제품은 

예를 들면 게임에서 게임머니같은 건대요

여러번 구매 할수 있습니다.

제품ID는 자신이 짠 소스 itemID와 일치 시켜야 합니다

뭐 간단한 설명과 아이템 이름을 적어주고

활성화를 시켜줍니다.

 

이렇게 모든게 끝이 났습니다.

그러면 다시 유니티 빌드를 해주시고

구글 콘솔에 APK파일을 다시 업로드 해주시고(당연히 전버전과는 버전코드와 번호가 달라야 올라가겠죠??)

테스트 아이디도 입력해 주시고~!!!(이 아이디는 테스트 대상자 폰의 구글 계정과 일치 해야 합니다.)

테스트 아이디는 개발자 콘솔 왼쪽에 설정 누르면 나옵니다~!!!!

두세시간후에 구매를 해보시면~!!

짜잔 결제가 됩니다~!!!!

물론 진짜로 결제 되 버림 ㅠㅠㅠ

이거 환불 받으세요 이부분은 인터넷 참조해 주쌔요~!ㅋㅋㅋㅋ

 

 

Posted by 프리랜서 디자이너
TA/Unity2013. 8. 29. 14:29

유니티 페이스북 연동 - 안드로이드(로그인&아웃,내사진&글 올리기&정보,친구목록) Unity

2013/06/10 15:56

복사 http://blog.naver.com/psd0217/10170072555

전용뷰어 보기

이번 포스팅은 유니티3D 네이티브 안드로이드에 페이스북 SDK 연동을 정리해봅니다. 원래는 Facebook SDK for .NET으로 연동할까 했는데 추후에 시간이 있을 때 다시 해볼까 합니다. 포스팅 많이하기 시작했던 cocos2d-x 때와는 다르게 하나에 쭉 다 포스팅이 아닌 먼저 포스팅했던 내용들은 그 링크들로 대신합니다.
  • 페이스북 개발자 및 앱 등록
  • 유니티3D 안드로이드 JAR 플러그인 프로젝트 생성
  • 페이스북 SDK 설치 및 빌드
  • 연동에 필요한 관련 파일 복사 및 AndroidManifest.xml 수정
  • 테스트를 위한 페이스북 해시키 생성 및 등록
 위와 같은 내용들을 준비과정으로 정리했습니다.


1. 페이스북 개발자 등록

 먼저 페이스북 개발자 등록을 합니다.


2. 페이스북 앱 등록

 만들어 서비스할 앱을 페이스북에 등록합니다.


3. 유니티3D 프로젝트 및 안드로이드 페이스북 Jar 플러그인 만들기

 유니티3D에서 안드로이드용 외부 SDK 연동을 위해서는 플러그인 JAR 파일을 만들어야하죠. 링크에 있는 내용을 보시고 폴더 설정등 기본 준비작업을 하시면 됩니다. 저는 스샷과 같이 UnityAndroidFacebookJar라고 만들었습니다.


4. 페이스북 안드로이드 SDK 설치 및 임포트

 페이스북 개발자 페이지의 안드로이드 SDK를 다운로드합니다. v3.0.1이 현재 최신입니다.

 적당한 곳에 다운로드 받은 페이스북 안드로이드 SDK를 압축해제합니다. 제 프로젝트 구성은 위와 같습니다. UnityFacebook은 유니티3D 프로젝트고 AndroidJar에는 위에서 생성한 이클립스 프로젝트가 들어있습니다.

 이클립스에서 페이스북 SDK를 임포트합니다. facebook-android-sdk-3.0.1\facebook 에 들어있습니다. 그러면 스샷과 같이 느낌표가 있고 에러가 발생할 듯합니다.

Project 'FacebookSDK' is missing required source folder: 'gen'
The project cannot be built until build path errors are resolved
Unable to resolve target 'android-8'

 위와 같은 에러인데 따로 정리를 했으니 링크를 참고하시기 바랍니다.

 이제 안드로이드 JAR 프로젝트에서 페이스북 SDK를 스샷과 같이 Library로 추가해줍니다. 그러면 Jar mismatch! Fix your dependencies 에러가 발생합니다. android-support-v4.jar가 중복되서 발생한 문제로 링크에 정리해놨습니다.

 이제 페이스북 SDK를 빌드합니다.


5. 페이스북 SDK 및 Res 복사

 유니티3D Plugins\Android 디렉터리에 페이스북 SDK의 bin 디렉터리에 생긴 facebooksdk.jar파일과 libs에 있는 android-support-v4.jar 파일을 복사합니다. android-support-v4.jar를 복사하지 않으면 나중에 java.lang.NoClassDefFoundError: android.support.v4.content.LocalBroadcastManager 에러가 발생할 수 있습니다.

 AndroidManifest와 UnityAndroidFacebook.jar는 위에서 만든 이클립스 프로젝트의 것을 복사했습니다.

 페이스북 SDK의 res 디렉터리를 마찬가지로 복사해줍니다. 역시나 res를 복사하지 않으면 android.content.res.Resources$NotFoundException: Resource ID #0x7f030001 에러가 발생합니다.


6. AndroidManifest.xml 수정

<uses-permission android:name="android.permission.INTERNET" />

        <activity
            android:name="com.facebook.LoginActivity"
            android:label="@string/app_name" />

 페이스북을 위해 AndroidManifest.xml에 위와 같은 내용을 추가합니다. Resource re-package failed 에러에 대비해서 drawable/ic_launcher을 drawable/app_icon 으로 바꾸는 것도 잊지마시기 바랍니다.


7. 페이스북 해시키 생성 및 등록

 페이스북을 연동한 안드로이드 앱을 테스트하려면 페이스북 개발자 페이지에서 등록한 앱에 해시키 값을 위 스샷과 같이 등록해야합니다. 해시키 생성하는 것은 따로 정리 했으니 링크를 참고하시고 만들어진 해시키를 각자가 만드신 앱의 정보 페이지에서 네이티브 Android 앱 부분을 클릭해 Key Hashes 부분에 기입하신 후 변경 내용 저장하면 됩니다.

 다음 포스팅에서는 페이스북 SDK 초기화와 로그인, 로그아웃 코드 연동 작업을 정리하겠습니다.

 

 

 

 

 

 

 

 

 

 

 

 유니티3D 안드로이드 프로젝트에 페이스북 연동을 위한 준비작업을 정리했었습니다. 이번에는 위 링크 내용을 기준으로 페이스북 초기화부터 로그인, 로그아웃 연동 작업에 대해 정리해봅니다.

 Prime31이라는 곳에서 각종 플러그인을 유료로 팔고 있네요. 그중에 당연히 페이스북 플러그인도 있긴합니다. 제가 정리하는건 Prime31이 필요없는 직접 구현하는 것에 대한 정리입니다. 즉, without Prime31 이죠. 아래는 소스입니다.

1. Android

public class MainActivity extends UnityPlayerActivity {
///< 페이스북 앱 등록용 해시키값
private String strHash;
///< 페이스북에 등록한 앱 ID
private String strAppId;
///< 인스턴스 상태 값
private Bundle savedInstanceState;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
///< 인스턴스 상태를 저장한다.
this.savedInstanceState = savedInstanceState;
}

public void onActivityResult(int requestCode, int resultCode, Intent data) {
Session.getActiveSession().onActivityResult(this, requestCode, resultCode, data);
super.onActivityResult(requestCode, resultCode, data);
}

protected void onSaveInstanceState(Bundle outState) {
Session session = Session.getActiveSession();
Session.saveSession(session, outState);
super.onSaveInstanceState(outState);
}

///< 페이스북 초기화. 유니티에서 호출하는 함수
public void InitFacebook_U(String strAppId)
{
this.strAppId = strAppId;

///< 해쉬키를 찾는다. 패키지 이름은 각자 다를것이다.
try {
PackageInfo info = this.getPackageManager().getPackageInfo("com.Test.unityandroidfacebookjar", PackageManager.GET_SIGNATURES);
for (Signature sig : info.signatures) {
MessageDigest md = MessageDigest.getInstance("SHA");
md.update(sig.toByteArray());
strHash = Base64.encodeToString(md.digest(), Base64.DEFAULT);
}
}
catch (NameNotFoundException e) {
Log.d("UnityFacebookTest", "Error Hashkey not found");
}
catch (NoSuchAlgorithmException e) {
Log.d("UnityFacebookTest", "Error Hashkey no such algorithm");
}
///< 해시키를 출력. 이 값을 페이스북 개발자 센터의 등록한 앱에 기입
Log.d("UnityFacebookTest", "Hash : " + this.strHash);

Session session = Session.getActiveSession();
///< 세션이 없다면 생성을 하자.
if (session == null) {
///< 세션 복구 시도
if (this.savedInstanceState != null) {
session = session.restoreSession(this, null, statusCallback, this.savedInstanceState);
Log.d("UnityFacebookTest", "Session restoreSession");
}
///< 세션 생성
if (session == null) {
session = new Session.Builder(this).setApplicationId(this.strAppId).build();
Log.d("UnityFacebookTest", "Session Builder");
}

///< 세션 저장하고 오픈
Session.setActiveSession(session);
if (session.getState().equals(SessionState.CREATED_TOKEN_LOADED)) {
session.openForRead(new Session.OpenRequest(this).setCallback(statusCallback));
Log.d("UnityFacebookTest", "Session openForRead");
}
}
}

///< 로그인. 유니티에서 호출하는 함수
public void Login_U() {
     Session session = Session.getActiveSession();
     if (session.isOpened() == false && session.isClosed() == false) {
     session.openForRead(new Session.OpenRequest(this).setCallback(statusCallback));
     Log.d("UnityFacebookTest", "Login openForRead");
     } else if (session != null) {
     Session newSession = new Session.Builder(this).setApplicationId(this.strAppId).build();
     Session.setActiveSession(newSession);
     newSession.openForRead(new Session.OpenRequest(this).setCallback(statusCallback));
     Log.d("UnityFacebookTest", "Login new Session");
     } else {
     Log.d("UnityFacebookTest", "Error Session is null");
     }
    }
///< 로그아웃
public void Logout_U() {
Session session = Session.getActiveSession();
if (session.isClosed() == false) {
session.closeAndClearTokenInformation();
Log.d("UnityFacebookTest", "Logout ClearTokenInfo");
}
}
///< 세션 처리 콜백
private Session.StatusCallback statusCallback = new SessionStatusCallback();
    private class SessionStatusCallback implements Session.StatusCallback {
        @Override
        public void call(Session session, SessionState state, Exception exception) {
            if (state.isOpened())
            {
                Log.d("UnityFacebookTest", "Session state opened");
            }
            else
            {
                Log.d("UnityFacebookTest", "Session state closed");
            }
        }
    }
}

 준비과정에서 만들었던 UnityAndroidFacebookJar 프로젝트의 MainActivity.java 소스를 위와같이 수정 및 저장하고 빌드 후 jar 파일로 export합니다. export된 jar 파일은 유니티 프로젝트의 플러그인 디렉터리에 복사해주시구요.

 지난번에는 페이스북 해시키 생성을 위해 콘솔에서 openssl도 설치하고 keytool로 추출을 하는 복잡한 과정을 거쳤는데 이번에는 위와같이 코드로 처리했습니다. 해당 소스는 처음 추출할 때만 필요하고 추출하셨다면 지우셔도 무관합니다.


2. Unity3D

public class FacebookManager : MonoBehaviour
{
static FacebookManager _instance;
private AndroidJavaObject curActivity;

public static FacebookManager GetInstance()
{
if( _instance == null )
{
_instance = new GameObject("FacebookManager").AddComponent<FacebookManager>();
}

return _instance;
}

void Awake()
{
AndroidJavaClass jc = new AndroidJavaClass("com.unity3d.player.UnityPlayer");
curActivity = jc.GetStatic<AndroidJavaObject>("currentActivity");
}

public void Init(string strAppId)
{
curActivity.Call("InitFacebook_U", strAppId);
}

public void Login()
{
curActivity.Call("Login_U");
}

public void Logout()
{
curActivity.Call("Logout_U");
}
}

 간단한 FacebookManager 싱글턴 컴포넌트입니다. 이제 이것을 사용하는 TestGUI 컴포넌트는 아래와 같습니다.

public class TestGUI : MonoBehaviour
{
void Start()
{

}

// Update is called once per frame
void Update ()
{
if (Application.platform == RuntimePlatform.Android)
        {

            if (Input.GetKey(KeyCode.Escape))
            {
                Application.Quit();
                return;
            }
}
}

void OnGUI()
{
float fYpos = 0;
GUI.Label(new Rect(0, fYpos, 400, 100), "Unity3D Android Facebook Test");

fYpos += 50;
if (GUI.Button (new Rect(0, fYpos, 100, 50), "Init") == true)
{///< 자신의 페이스북 앱 ID를 넣어준다.
FacebookManager.GetInstance().Init("442544649174747");
}

fYpos += 50;
if (GUI.Button (new Rect(0, fYpos, 100, 50), "Login") == true)
{
FacebookManager.GetInstance().Login();
}

fYpos += 50;
if (GUI.Button (new Rect(0, fYpos, 100, 50), "Logout") == true)
{
FacebookManager.GetInstance().Logout();
}
}
}

 페이스북 초기화, 로그인 등의 버튼을 만드는 GUI 컴포넌트입니다. Main Camera에 붙여줍니다.


3. Facebook R.java Build
 

 마지막으로 유니티에서 빌드한 후 임시 디렉터리인 Temp/StagingArea/gen/com/Test/unityandroidfacebookjar에 생성되는 Facebook의 R.java를 플러그인에 위 스샷과 같이 jar로 만들어 넣어줘야합니다. 그런데 이 부분에서 java.lang.NoClassDefFoundError: com.facebook.android.R$layout 이슈가 있습니다. 이슈 해결은 링크에도 있지만 완벽한 해결이 아니라는게 좀 걸리네요. 페이스북 SDK 버전이라도 올라간다면 나중에 또 이슈가 생길 수 있습니다.


4. Build and Run
 

 빌드하고 에이서 A500에서 실행한 스샷입니다.
 

 Init을 눌러 Facebook을 초기화 한 후 Login을 누르면 위와같이 페이스북 로그인 페이지가 뜹니다. 참고로 전 이 테스트를 위해 태블릿에 깔린 페이스북 앱에서 로그아웃을 한 후 진행했습니다. 이미 로그인이 된 상태라면 조금 다를 수 있습니다. 아마 바로 로그인 되거나 잠깐 로딩 화면이 나오고 다시 유니티 화면으로 전환될 것입니다.
 

 로그인이 되면 위와같이 UnityFacebook에서 공개 프로필, 친구 리스트에 액세스 권한을 요청하고 있습니다. 라고 뜹니다. 확인을 눌러줍니다.

 이것으로 유니티3D에 페이스북 SDK 안드로이드 연동 기본사항을 정리해봤습니다. 다음에는 자신의 담벼락에 게시하는 것과 자신의 정보를 얻어와 프로필 사진을 출력해보는 것을 정리해보겠습니다.

 

 

 

 

 

 

 

 

 유니티3D 안드로이드에 페이스북 초기화, 로그인 로그아웃 연동을 정리했었습니다. 이번에는 아래와 같은 것들을 정리해봅니다. 

  • 자신의 정보를 얻기
  • 자신의 담벼락에 글을 게시
  • 자신의 프로필 사진 이미지 출력

1. 자신의 담벼락에 글 게시

public void PostToMeWall_U(final String strMsg) {
runOnUiThread(new Runnable(){
public void run() {
Session session = Session.getActiveSession();
final List<String> PERMISSIONS = Arrays.asList("publish_stream");
if (session != null)
{
NewPermissionsRequest reauthRequest = new Session.NewPermissionsRequest(UnityPlayer.currentActivity, PERMISSIONS);
session.requestNewPublishPermissions(reauthRequest);
}

Bundle postParams = new Bundle();
postParams.putString("message", strMsg);

        Request request = new Request(session, "/me/feed", postParams,
                              HttpMethod.POST, requestCallback);
       
        RequestAsyncTask requestAsyncTask = new RequestAsyncTask(request);
        requestAsyncTask.execute();
}
});
}

 유니티에서 호출하는 자바 함수입니다. publish_stream 권한은 사실 자신의 담벼락에 게시할 때는 필요하지 않은 듯합니다. 자신 이외에 친구들에게 할 때 필요한 듯보이네요. graph api중 하나에 속하는 message를 추가하고 유니티로부터 받은 스트링을 추가합니다. 요청할때는 /me/feed 라고 했는데 밑에서 알아볼 자신의 숫자로된 페이스북 id를 넣어줘도 됩니다. 친구의 id를 넣어주면 친구의 담벼락에 게시가 되겠죠.

Request.Callback requestCallback = new Request.Callback() {
      public void onCompleted(Response response) {
          JSONObject graphResponse = response.getGraphObject().getInnerJSONObject();

          String postId = null;

          try {
              postId = graphResponse.getString("id");
          } catch (JSONException e) {
              Log.i("UnityFacebookTest", "JSON error " + e.getMessage());
          }

          FacebookRequestError error = response.getError();

          if (error != null) {
           Log.d("UnityFacebookTest", error.getErrorMessage());
          } else {
           Log.d("UnityFacebookTest", "success post score to wall. " + postId);
          }
      }
};

 요청에 대한 콜백입니다. 로그 정도만 처리하고 있습니다.


public string strPostMsg = string.Empty;

public void PostToMeWall(string strMsg)
{
curActivity.Call("PostToMeWall_U", strMsg);
}

 FacebookManager 컴포넌트의 추가된 내용입니다. 스트링 변수를 하나 추가해주고 자바 호출을 추가해줍니다.


fYpos += 50;                                                                                                                   
FacebookManager.GetInstance().strPostMsg = GUI.TextField(new Rect(0, fYpos, 300, 50), FacebookManager.GetInstance().strPostMsg);
                                                                                                                              
fYpos += 50;                                                                                                                   
if (GUI.Button (new Rect(0, fYpos, 100, 50), "Post") == true)                                                                 
{                                                                                                                             
FacebookManager.GetInstance().PostToMeWall( FacebookManager.GetInstance().strPostMsg );                                     
}

 GUI 쪽 추가된 내용이구요. 텍스트 입력을 위해 텍스트필드를 추가하고 위에 추가했던 변수에 넣어줍니다. Post를 누르면 페이스북메니저를 통해 자바가 호출되겠지요.
 

 test facebook post by unity3d라고 입력했습니다.
 

 제 페이스북 담벼락에 게시된 내용입니다. curl로 담벼락에 게시할 때는 한글이 깨졌었는데 유니티에서는 잘 되네요.


2. 자신의 정보를 얻기

public void requestMe_U() {
runOnUiThread(new Runnable(){
public void run() {
Session session = Session.getActiveSession();
Request.executeMeRequestAsync(session, new Request.GraphUserCallback() {

@Override
public void onCompleted(GraphUser user, Response response) {
// TODO Auto-generated method stub
if (user != null) {
Log.d("UnityFacebookTest", user.getId());
Log.d("UnityFacebookTest", user.getName());

JSONObject jsonObj = new JSONObject();
try {
jsonObj.put(user.getId(), user.getName());
} catch (JSONException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
            });
}
});
}

 자신의 정보를 요청해서 간단히 id와 이름을 출력하는 내용입니다. Request.executeMeRequestAsync를 안쓰고 위 처럼 Request와 Bundle에 직접 graph api 인자들을 넣어서 요청해도 가능합니다. 한가지 접근 api만 있는건 아닌거죠.

///< 페이스북 메니져 컴포넌트에 추가
public void RequestMe()         
{                               
curActivity.Call("requestMe_U");
}                               

///< GUI 컴포넌트에 추가
fYpos += 50;                                                         
if (GUI.Button (new Rect(0, fYpos, 100, 50), "Me") == true)     
{                                                               
FacebookManager.GetInstance().RequestMe();           
}


3. 자신의 프로필 사진 이미지 출력

private Texture2D curDownTex = null;
private Texture2D texMe = null;


fYpos += 50;
if (GUI.Button (new Rect(0, fYpos, 100, 50), "Me") == true)
{
//FacebookManager.GetInstance().RequestMe();
///*
StartCoroutine(MyPicDown("wwforever80"));
//*/
}

fYpos += 50;
if ( texMe != null )
{
GUI.DrawTexture(new Rect(0, fYpos, texMe.width, texMe.height), texMe);
}


private IEnumerator MyPicDown(string strId)
{
yield return StartCoroutine( DownPic(strId) );
texMe = curDownTex;
}


private IEnumerator DownPic(string strFacebookId)
{
string strPicUrl;
strPicUrl = System.String.Format("http://graph.facebook.com/{0}/picture", strFacebookId);
WWW www = new WWW(strPicUrl);
Debug.Log(strPicUrl);

yield return www;

if (www.error != null)
{
Debug.Log("Picture error");
}
else
{
curDownTex = www.texture;
//www.LoadImageIntoTexture(curDownTex);
Debug.Log(www.url);
}
}

 페이스북 프로필 이미지를 코루틴과 WWW로 다운받아 Texture2D로 저장하고 출력하는 부분입니다. 전부 GUI 컴포넌트쪽이구요. graph api중 picture인 http://graph.facebook.com/{0}/picture 부분에서 전 제 영문 ID를 바로 넘겨줬는데 위에서 알아낸 숫자 id를 넘겨줘도 됩니다.
 

 이제 빌드 후 실행해보면 위와 같이 페이스북 프로필 사진이 출력됩니다.

 다음에는 친구 목록을 얻어와서 친구들의 프로필 사진을 랜덤하게 출력하는 것을 정리해보겠습니다.

 

 

 

 

 

유니티3D 페이스북 SDK 안드로이드 연동 관련 자신의 담벼락 게시 및 자신의 프로필 이미지 출력까지 해봤습니다. 이번 포스팅은 페이스북 친구 목록을 얻어와서 친구의 프로필 사진을 유니티3D로 렌더링하는 것까지 정리해보겠습니다.


1. 친구 목록 가져오기

public void requestFriends_U() {
runOnUiThread(new Runnable(){
public void run() {
Session session = Session.getActiveSession();
            Request.executeMyFriendsRequestAsync(session, new Request.GraphUserListCallback() {

@Override
public void onCompleted(List<GraphUser> users,
Response response) {
// TODO Auto-generated method stub
if (users != null) {
JSONArray jsonArr = new JSONArray();

for (GraphUser user : users) {
Log.d("UnityFacebookTest", user.getName() + " " + user.getId());
try {
JSONObject jsonObj = new JSONObject();
jsonObj.put("FacebookId", user.getId());
jsonObj.put("Name", user.getName());
jsonArr.put(jsonObj);
} catch (JSONException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}

UnityPlayer.UnitySendMessage("FacebookManager", "ResultFriends_J", jsonArr.toString());
}
}
            });
}
});
}

 먼저 자바쪽입니다. Request.executeMyFriendsRequestAsync를 통해 친구 목록을 요청합니다. Request.GraphUserListCallback의 onCoompleted에서 친구 목록을 JSONObject로 만들어 JSONArray에 쌓고있죠. 마지막에는 유니티3D에 json을 스트링형으로 넘겨줍니다.
 

 결과부터 보면 위와같이 로그로 잘 찍혔습니다. 이제 유니티3D부분을 보겠습니다.

using LitJson;

private Dictionary<string, string> dicFriends;

void Awake()
{
  ...
  this.dicFriends = new Dictionary<string, string>();
}


public void RequestFriends()
{
curActivity.Call("requestFriends_U");
}

public void ResultFriends_J(string jsonFriends)
{
JsonData jData = JsonMapper.ToObject(jsonFriends);
for (int i = 0 ; i < jData.Count ; i++)
{
string strFacebookId = jData[i]["FacebookId"].ToString();
string strFacebookName = jData[i]["Name"].ToString();
Debug.Log("Friend id " + strFacebookId + " Name " + strFacebookName);
this.dicFriends.Add(strFacebookId, strFacebookName);
}
Debug.Log("Friend Count = " + this.dicFriends.Count.ToString());
}

 FacebookManager 컴포넌트에 추가된 부분입니다. 친구 목록을 스트링으로 받아와서 JSON 객체로 만든 후 Dictionary에 추가하고 있습니다. C#에서 JSON을 사용하기 위해 저는  LitJson을 사용했습니다. 링크를 참고하세요.


class Friend                         
{                                 
public Texture2D texFriend = null;
public Rect rtTexPos;           
}                                 
                                   
List<Friend> listFriend = null;   

void OnGUI()
{
...
fYpos += 50;                                                             
if (GUI.Button (new Rect(0, fYpos, 100, 50), "Get Friends\nList") == true)
{                                                                       
FacebookManager.GetInstance().RequestFriends();
}                                                                       
}

 TestGUI 컴포넌트 부분입니다.


2. 친구 프로필 사진 출력하기
 

 위 내용과 아래에 나올 GUI쪽 작업을 하면 위와 같이 생겼습니다. 이번에는 자바쪽은 없고 유니티3D쪽 C# 작업만 있습니다.

public Dictionary<string, string> GetFriends()
{
return this.dicFriends;
}

 쌓아둔 친구 목록을 통째로 가져오기 위해 FacebookManager 컴포넌트에 위와같은 함수를 추가합니다.


fYpos += 50;
if (GUI.Button (new Rect(0, fYpos, 100, 50), "Down Friends\nPic") == true)
{
StartCoroutine(DownFriendPic());
}

if (this.listFriend != null)
{
for ( int i = 0 ; i < this.listFriend.Count ; i++)
{
if (this.listFriend[i].texFriend != null)
GUI.DrawTexture(this.listFriend[i].rtTexPos, this.listFriend[i].texFriend);
}
}

 TestGUI 컴포넌트 OnGUI에 위와같이 친구의 사진을 다운로드할 버튼을 추가하고 친구 프로필 사진을 그려주는 GUI.DrawTexture를 추가합니다.


private IEnumerator DownFriendPic()                                                     
{                                                                                       
Dictionary<string, string> dicFriends;                                                 
dicFriends = new Dictionary<string, string>(FacebookManager.GetInstance().GetFriends());
if (dicFriends.Count > 0)                                                             
{                                                                                     
Debug.Log ("Friends num " + dicFriends.Count.ToString());                           
                                                                                     
this.listFriend = new List<Friend>();                                               
                                                                                       
foreach (KeyValuePair<string, string> pair in dicFriends)                           
{                                                                                   
Friend friend = new Friend();                                                     
                                                                                   
yield return StartCoroutine( DownPic(pair.Key) );                                 
                                                                                   
friend.rtTexPos = new Rect(                                                       
Random.Range(0, Screen.width)                                                   
, Random.Range(0, Screen.height)                                                 
, 50, 50);                                                                       
friend.texFriend = curDownTex;                                                     
this.listFriend.Add(friend);                                                       
}                                                                                   
}                                                                                     


 코루틴을 사용해서 친구의 프로필 사진을 WWW로 다운받아 Texture2D에 저장합니다. 위치는 랜덤하게 잡아줬습니다.
 

 버튼을 누르면 친구 사진이 다운로드가 됩니다. 코루틴을 사용하기 때문에 비동기로 하나씩 렌더링이 되죠.
 

 친구 프로필 사진이 모두 그려진 상태입니다.

 간단히? 유니티3D에 페이스북 안드로이드 연동 포스팅을 해봤습니다. 여기서 더 해나갈지는 두고봐야겠네요.

 

 

 

 

 

출처 : http://westwoodforever.blogspot.kr/

Posted by 프리랜서 디자이너
TA/Unity2013. 7. 23. 17:37

http://xamarin.com/

 

 

 

자마린 스튜디오
자마린 스튜디오(Xamarin Studio)는 멀티 플랫폼 네이티브 모바일 앱을 설계, 개발, 디버깅, 배치할 수 있는 간소화된 IDE다. 음악 스트리밍 업체 알디오(Rdio)는 자마린 스튜디오와 자마린의 안드로이드, iOS 개발 기술을 이용해 개발 작업을 최소화할 수 있었다.
알디오의 수석 안드로이드 개발자 브렛 던카비니는 “우리는 안드로이드와 iOS 두 플랫폼 모두를 위한 대규모 팀을 구성하지 않고도 앱의 기능을 동등하게 만들 수 있는 방법을 찾고 있었다"며 "현재는 안드로이드와 iOS 사이에 대략 60% 정도 코드를 공유하고 있다"고 말했다 "이어 "앱 개발은 주로 C# 언어를 이용하고 있다"며 "자마린 스튜디오는 훌륭한 코드 완성, 디버깅, 개발 능력을 제공하지만 여전히 성숙도가 부족한 면이 있다"고 지적했다.

Posted by 프리랜서 디자이너