약 35개 의 객체에 itween을 적용해서 이동시켰을때 프로파일러상에서 GC.collect의 메모리 할당량이 140kb 정도로 부하가 발생함..

해외 사이트를 검색해보니 itween의 경우 부하가 좀 발생한다고 함..

테스트로 NGUI에서 제공하는 tweenPosition 기능으로 변경해서 적용한 결과, 5~60kb 사이로 반 이상 줄어들었음.

타겟 디바이스가 모바일인 경우 이정도 부하면 민감하므로 적용시 참고.


WRITTEN BY
빨강꼬마

,


ABS(A.x - B.x)  <= (A.Scale.x - B.Scale.x) / 2 

'Unity > 스크립트' 카테고리의 다른 글

에셋 Android Native Plugins에 대해  (0) 2015.03.02
String -> enum 변환  (0) 2015.02.17
기본 변수 저장시 암호화  (0) 2014.10.16
Playerprefs 암호화.  (0) 2014.10.14
2D상에서의 각도를 기반으로 회전값 구하기  (4) 2014.06.11

WRITTEN BY
빨강꼬마

,

타겟을 향해 바라봐야한다던가.. 다리같이 뭔가 타겟과 이어주어야 할때 


void Start()

{

// z축 +180은 이미지 방향에 따라 수정하여 적용.

// target1은 자신의 객체가 아닌 비교할 해당 객체.

transform.eulerAngles = new Vector3(0, 0, -getAngle(transform.position.x, transform.position.y, target1.position.x, target1.position.y) + 180.0f);

}


private float getAngle(float x1, float y1, float x2, float y2)

{

        float dx = x2 - x1;

        float dy = y2 - y1;

        

        float rad = Mathf.Atan2(dx, dy);

        float degree = rad * Mathf.Rad2Deg;


        return degree;



----


WRITTEN BY
빨강꼬마

,

유니티 문서를 보면 언급되는 Managed Memory란 단서에서 흔히 추측하기로 유니티가 알아서 모든 메모리를 잘 관리하고 있을 것 처럼 생각되지만 사실은 그렇지 않다는 것이 함정.

사실은 유니티 시스템은 메모리를 어떻게 처리할 것인가에 대한 단서를 당신이 만드는 코드내에서 제공해주기를 원한다. 따라서 잘못된 코드는 줄줄 새는 메모리로 당신의 앱을 디바이스에서 뻗어 버리게 만들것이다. 요즘은 모바일 디바이스에서 조차 64bit 시스템(iPhone 5s)가 올라가고 기본 장착 메모리가 2Gb 이상이 되는 등 모바일 앱으로서는 무한한 자원이 있는것 같지만 사실상 아직도 지구상의 대부분의 유저는 허접한 디바이스로 연명하......


링크 - http://la-stranger.blogspot.kr/2013/09/blog-post_25.html

'Unity > 메모리관리' 카테고리의 다른 글

GC 로깅 (메모리 단편화 추정될때)  (0) 2018.05.15
itween은 되도록 자제...  (0) 2014.08.12
유니티 메모리 관리 4 (마지막)  (2) 2013.10.16
유니티 메모리 관리 3  (0) 2013.10.16
유니티 메모리 관리 2  (0) 2013.10.16

WRITTEN BY
빨강꼬마

,

유니티 <-> MS-SQL 연동.

Unity 2014. 2. 12. 11:05

유니티안 - http://cafe.naver.com/xgdn/10336


WRITTEN BY
빨강꼬마

,

결제모듈 붙이는 작업때문에 그간 고생한걸 생각하면..-,.-... 3분간 묵념을....


음음.. 각설하고 검색을 해봐도 한글로 참고할만한 글이 없어서 정리할겸 기록함..





포인트는 데이터의 흐름을 파악하는거다.

결제를 요청할때 어떤 함수를 호출해야 하고.. 결제가 완료되면 어떤 함수가 호출되고 이런것들


사실 Prime31가져다 쓰기 전에 구글에서 제공하는 결제API를 가져다 쓰기위해 샘플을 가져와서 디벼봤지만..

이건 언어는 자바가 맞는거 같은데 자바처럼 안보이는 그런느낌이랄까-_-;;


유니티로 개발하면서 네이티브 코드를 안만질수가 없다는 걸 다시한번 새삼 느끼는 계기가 된 거 같음..

원래는 Prime31을 알고는 있었지만 구매문제보다는 검색해봤을때 뭔가 사용에 문제가 있는거 같아서 사용을 안하다가 한 강사분께서 현업에서도 많이 쓰이는 에셋이라 하시길래 바로 구매후 사용해본 결과..


안드로이드를 모르는 사람이라 하더라도 엄청 쉽게 구글플레이 결제모듈 붙이는데 가능할것 같다.


자 그럼 시작해보자.

※ 구글인앱빌링 v3 버젼이 적용된 Prime31임을 참고.

※ Prime31을 임포트시킨후 빌드하여 구글 개발자 콘솔에서 apk를 업로드하면 Prime31의 구조상 플러그인안에 있는 매니페스트에서 안드로이드 접근 권한에 결제와 관련된 권한이 부여되어 있기 때문에 인앱상품 등록이 가능하다. 이는 따로 설명하지 않아도 검색하면 많이 있음. 기본적으로 인앱상품 등록이 가능하고, 구글개발자콘솔에서 하나 이상의 결제 테스트 계정이 있다고 가정하고 설명함을 참고.



1. 우선 필요한 것은 Prime31을 구매하는 것.. 구매후 임포트시키면 아래와 같이 구성된다.



2. 저기서 나에게 필요한 것은 demo 폴더안에 GoogleIABEventListener.cs 파일. 에셋의 특성상 소스 자체를 올리진 못하겠고 함수만 설명!


※ 먼저 결제 프로세스를 알아야하는데.. 일반적으로 구글플레이에서의 관리되지 않는 품목.. 즉 여러번 구매가 가능한 코인, 물약 같은 인앱상품의 경우 사용자가 결제를 하면 구입이 되고 이후 이를 소비를 시켜줘야 한다. 그렇지 않으면 한번 구매한 이후 해당 상품을 구매할 수 가 없다. 


- OnEnable()과 OnDisable()의 특징은 따로 설명하진 않겠음.. 검색하면 무수히 많은 정보가 있으니..ㅇ_ㅇ


billingSupportedEvent() : 결제를 진행할때 결제와 관련된 씬에 진입시 가장 처음에 초기화를 할텐데..(이 초기화 작업이 정확하게 왜 필요한지.. 하지 않을 경우 발생되는 일이야 추측은 가능하지만 디테일한 부분에 있어서 정확히 이해는 못했음 ㅠㅠ) 그 초기화가 완료된 이후 호출되는 함수다. 따라서 그에 따르는 처리를해야한다면 이 함수내에서 처리하면 될 거다.


billingNotSupportedEvent() : 이 함수명을 보면 알겠지만 위의 함수와는 반대의 이미다.. 초기화 실패시 호출된다.


- queryInventorySucceededEvent(), queryInventoryFailedEvent() : 개인적으로 쿼리는 DB에서만 만나본 개념이라-_-;;; 뭐 소비되지 않은 구입목록을 불러온 결과에 따라 자동 호출되는 개념으로 이해할 수 있을것 같다..


- purchaseCompleteAwaitingVerificationEvent() : 아이템을 구매한 이후 호출되는 함수다. 매개변수로 구입과 관련된 데이터와 서명(?)을 string 형식으로 받는데 이와 비슷한 다른 함수를 사용할 것이므로 일단 이 함수는 제껴두기로 ...(지금 함수명을 다시보니 인증과 관련되어 추가적인 액션이 가능한 것 같다.. 일단 쉽게쉽게 가기위해 패스하지만 결제인증과 관련된 부분이 있다면 다시 파고들 부분이다.)


- purchaseSucceededEvent() : 이 함수는 위의 함수와 동일하게 아이템을 구매한 이후 성공적으로 구매되었을 경우 호출되는 함수로 결제정보를 매개변수로 받는다. 이 결제정보엔 제품아이디, 영수증번호와 같은 정보가 있다.


- purchaseFailedEvent() : 함수명 보면 눈치 까야함..


- consumePurchaseSucceededEvent() : 소비형 아이템을 구매했다면 아이템을 구매한 다음 소비를 시켜야 하는데 이 소비가 성공적으로 되었다면 호출되는 함수로 만약 골드를 증가시켜줘야 한다 뭐 이런 아이템이라면 이 함수내에서 처리하면 되겠다.. 매개변수로 결제정보를 받는데, 이 결제정보안에 제품id가 있으므로 이 id를 비교해서 처리하면 되겠다..


- consumePurchaseFailedEvent() : 이거 역시 함수명으로 설명끝..



3. 위에서 설명한 GoogleIABEventListener.cs파일의 내용이 중요하므로 그 파일 그대로 가져다 써도 되고 파일의 내용을 복사해서 별도의 cs파일로 생성해도 된다. 여기선 별도의 파일로 뺀다.. 그냥 이건 내 성격상..ㄱ-;;; 위의 파일의 내용과 100%동일하게 BillingEventListener.cs라는 파일로 대체하였음..



4. BillingEventListener.cs를 결제와 관련된 씬에 빈 게임오브젝트를 만들고 BillingEventListener.cs 을 attach. (이 역시 결제와 관련된 씬 하이라키 내에 만드는 것은 기본..)



5.  결제와 관련된 씬을 로비라고 가정하고 설명한다면 로비 진입시 Start() 하나에서 결제를 초기화 시키는 작업을 해야한다.

내 경우는 결제와 관련된 액션은 BillingEventListener 라는 게임오브젝트에서 전부 관리할 거라 아까 만들었던 BillingEventListener.cs에 Start() 함수를 만들고 초기화 작업을 했음. 


초기화 작업은 GoogleIAB.init(publicKey)를 호출하면 되는데 여기서 publicKey는 구글 앱 상품관리 페이지에서 라이센스키를 의미함.



6. 초기화 작업이 정상적으로 되었을 경우 호출되는 함수에서 이를 판별해서 결제를 진행시키거나 결제를 막거나 혹은 게임을 종료시켜서 다시 시도하게 하거나 .. 이런 액션을 유도해야 하겠다..



7. 해당 작업이 완료되면 이제 실제 결제를 요청해볼 차례. 개발자 콘솔에서 인앱상품을 등록했다면 해당 페이지에서 상품번호를 사용해 아래와 같이 요청. (상품번호가 "0001" 이라고 가정)

GoogleIAB.productPurchase("0001");



8. 그럼 구글플레이 결제 프로세스가 진행되고, 결제완료시 purchaseSucceededEvent() 함수호출되는 것을 이용하여 아래와 같이 해당 아이템을 바로 소비시켜줘야한다.

GoogleIAB.consumeProduct("0001");



9. 소비 완료가 되면 그때 소비된 이후 프로세스를 각자 처리하면 되겠다..



사실 좀 더 정리하려고했는데 일단 이정도라도 이해가 가능할 정도라서.. 작업완료 후 결제인증과 관련된 추가적인 처리를 하고 나서 다시 업데이트를 하던가 해야할듯..




ps. 사실 5번 항목까지 적다가.. 결제 인증을 아무래도 붙여야 겠다 싶어서.. GoogleIAB.productPurchase()를 호출할때 매개변수로 developerPayLoad라는 스트링으로 함수오버로딩이 되어 있었다.. 원한 액션은 결제시 상품번호와 함께 토큰값을 전달하고 결제완료시 해당 토큰값이 정상인지 확인하는 절차를 넣고싶었으나.. 이부분은 테스트가 필요해서 일단 위에 기술하진 않았음..


사실 결제라는 부분이 민감한 부분이기때문에 인증은 반드시 필요한 부분이라.. 추후 이부분을 반드시 체크하고 넘어가야 하겠음..


이내용을 정리하기위해 참고한 일본쪽 사이트를 링크한다.

번역기 돌려서 보면 대충 이해할만하다..


http://westhillapps.blog.jp/archives/25210270.html



추가..

결제초기화 이후 purchaseCompleteAwaitingVerificationEvent() 함수와 연계하여 결제 인증을 처리할때 GoogleIAB.cs에 보면 setAutoVerifySignatures(bool a) 라는 함수와 함께 처리하면 될것 같다...



WRITTEN BY
빨강꼬마

,

public Camera _MyCamera;


void Start ()

 {

        UIRoot root = gameObject.GetComponent<UIRoot>();

  // 자동 조절

        root.automatic = true;

        

        root.manualHeight = 480;    

        root.minimumHeight = 480;

        root.maximumHeight = 1280;        

}


void Update ()

{

        float perx = 1024.0f / Screen.width;

        float pery = 768.0f / Screen.height;

        float v = (perx > pery) ? perx : pery;

  m_pCam.GetComponent<Camera>().orthographicSize = v;

}


WRITTEN BY
빨강꼬마

,

이글은 핸드폰번호를 유니티에서 써먹기 위해 이틀간의 삽질 및 구글 검색등등으로 해결한 나의 노고를-_-

은 개뿔 안잊어먹으려고 정리할 겸 남겨둔다.

(참고로 난 기본 자바는 알고있고, 안드로이드쪽은 하나도 모르는 유니티 개발자다.)






1. 기본적으로 핸드폰 번호와 같은 기계 정보(혹은 유니티에서 제어되지 않는 디테일한 것들..)를 가져오기 위해서는 유니티에서는 불가능하다고 한다. 그런고로 JDK, 안드로이드 SDK, 이클립스 그리고 안드로이드 프로젝트를 생성할 수 있는 안드로이트 개발 툴킷 설정을해야한다.

당연히 유니티는 설치되어있다고 가정하고... 아무튼 이부분은 인터넷 찾아보면 열나 많으니 문제가 되지 않을것으로 본다.




2. 이클립스에서 먼저 프로젝트를 만들던 유니티에서 먼저 작업을해서 AndroidManifest.xml을 만들던 순서가 중요하지는 않은 것 같다. 난 이클립스로 먼저 플젝을 만들기로 함.




3. 난 이클립스 케플러 버젼으로 쓰는데 .. 검색해보면 대부분 이전 이클립스 버젼을 사용하고 있어서 약간 메뉴에서 보이는게 다를 수 있는데, 아래의 이미지로 안드로이드 프로젝트를 만들면 된다.





어플리케이션 이름과 플젝 이름은 그냥 Test와 같은 단어식으로 기재하면 되고(이부분은 딱히 무슨단어를 쓰건 상관없다), 중요한건 패키지 이름인데.. 이부분은 나중에 유니티에서 설정할 부분하고 동일하게 쓰면 AndroidManifest.xml에서 수정할일이 없어진다. 수정자체가 간단하긴 하지만ㅋㅋ SDK 버젼은 각자가 적절하게 셋팅.





아래 이미지에서는 건드릴거 없이 그냥 넥스트 ㄱㄱ





아래 이미지 역시 그냥 넥스트 ㄱㄱㄱ





아래 이미지 역시 그냥 넥스트..





이부분은 그냥 넘어가도 되고 약간 변경해도 되는데 나중에 AndroidManifest.xml에서 진입할 부분을 지정할때 사용할 Activity name이라고 생각하믄 된다. 그런고로 변경없이 그냥 넥스트해도 아무런 무리가 없음.








4. 그러면 아래와 같이 플젝이 만들어지면서 에디터 창이 열릴텐데 MainActivity.java에서 필요없는 부분(붉은색 처리한 두부분)은 삭제를 과감하게 !!







5. 그다음 MainActivity가 유니티로 정보를 보내줄수 있도록 유니티에서 사용하는 클래스를 상속받아야 함. 그런고로 유니티를 기본 경로로 설치했다면 아래 폴더에 있는 classes.jar 파일을 이클립스로 복사해야한다. (사실 굳이 가져오지 않아도 나중에 jar 파일 만들때는 뺄꺼라 상관없긴 하지만 이클립스 상에서 저걸 복사 안해두고 코딩하면 붉은색 라인들이 축제를 벌이는걸 볼수가 있음. 개인적으로 노란줄 뜨는것도 엄청 싫어해서ㅋㅋ 그리고 복사하는게 어려운게 아니라 코딩도 수월해지므로 꼭 복사하도록~)

복사방법은 classes.jar 파일을 드래그앤 드랍으로 이클립스 왼쪽 창에 보이는 libs 로 옮겨도 되고 컨트롤 c / v 도 먹음. 복사할땐 당연히 카피!


C:\Program Files\Unity\Editor\Data\PlaybackEngines\androiddevelopmentplayer\bin




6. 난 핸드폰 번호만 가져올 생각임. 코딩시 순서대로 코딩할텐데 코딩하다가 스펠링도 정확히 적었음에도 빨간줄이 가면 마우스 오버했을때 툴팁을 보고 import해도 되고, 이클립스의 컨트롤 쉬프트 m 을 사용해도 알아서 임포트함 ㅇㅇ; 쨌던 임포트는 코딩 다하고 해도 문제없으니까 ..


* onCreate 메소드는 아마 진입 시점에서 가장 먼저 호출되는 메소드로 추정함.. 아마 맞을껀데 귀찮아서 찾아보진 않았음.

* 캐스팅 연산자 나 기타 등등 자바 문법 해석은 안하겠음.. 여기에 기재된 클래스및 메소드들이 어떤 역할을 하는지가 중요함.

* UnitySendMessage

   첫번째 인자는 해당 메소드가 실행되면 유니티로 값을 전달할때 유니티의 하이라키 상에서의 오브젝트 이름으로 찾아 해당 오브젝트로 값을 전달한다는    의미고,

   두번째 인자는 해당 오브젝트에 추가된 스크립트들중 전달하고자 하는 데이터를 처리할 함수를 지정하는 것이고,

   세번째 인자는 당연히 전달할 데이터를 의미함.



// 패키지 명은 뺐음.. 코딩시 당연히 패키지는 기재되어 있어야함.

import com.unity3d.player.UnityPlayer;

import com.unity3d.player.UnityPlayerActivity;

import android.content.Context;

import android.os.Bundle;

import android.telephony.TelephonyManager;


public class MainActivity extends UnityPlayerActivity {

public static String PhoneNumber = "";

TelephonyManager tm;


@Override

protected void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);

tm = (TelephonyManager)getSystemService(Context.TELEPHONY_SERVICE);

PhoneNumber = tm.getLine1Number();

}


public void GetPhoneNumber(){

UnityPlayer.UnitySendMessage("DataObject", "CheckPhoneNumber", PhoneNumber);

}

}





6. 위와 같이 코딩이 끝났다면 이제 작업한 것들을 jar로 빼야 한다.

왼쪽에 보이는 src 하위 에 MainActivity.java 가 포함된 패키지에서 오른쪽 클릭을 하고 아래의 이미지 순서대로 고고~





아래 이미지에서 붉은색부분에 jar파일을 생성할 위치 지정하고 피니쉬하면 해당 경로에 jar가 생성됨.






7. 이제 이클립스에서 작업하는건 다 끝났으므로 이클립스는 종료. 유니티에서 새프로젝트를 만든 후 빈 오브젝트를 하나 만든다.

빈 오브젝트 이름은 DataObject 로 MainActivity에 있는 메소드 매개변수와 동일하게 만든다.




8. 빈 스크립트를 만들고 DataObject 에 드래그앤드랍으로 추가한다.




9. 스크립트 내용을 아래와 같이 작성한다. 보통 웹에 있는 다른 예제들은 OnGUI 함수를 이용하는데, 난 NGUI로 좀이따 버튼을 만들 생각임을 참고.



using UnityEngine;

using System.Collections;


public class DataObjectScript : MonoBehaviour {


private AndroidJavaObject currentActivity;

private string _PhoneNumber;


public UILabel _ViewLabel;


void Awake()

{

// 현재 활성화된 액티비티를 얻어와 미리 준비한 변수에 저장.

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

currentActivity = AJC.GetStatic<AndroidJavaObject>("currentActivity");


if (currentActivity != null)

{

// MainActivity에 있는 GetPhoneNumber 메소드를 데이터를 전달받기 위해 호출함.

currentActivity.Call("GetPhoneNumber");

}

}


// NGUI에서 사용할 함수.

void ViewPhoneNumver()

{

_ViewLabel.text = _PhoneNumber;

}


// MainActivity에 있는 GetPhoneNumber 메소드가 실행되어 데이터를 받을 함수.

void CheckPhoneNumber(string arg)

{

_PhoneNumber = arg;

}

}




10. 기본적인 NGUI 버튼 및 라벨 설정 부분은 설명없이 넘어감..ㅇㅇ;




11. 스크립트에 View Label로 사용할 라벨을 드래그앤드랍으로 연결한다.




12. 이제 거의 막바지 작업으로 넘어가는데... 컨트롤 쉬프트 B 입력해서 빌드 셋팅으로 간다음. 


 - 안드로이드로 스위치 플랫폼

 - 플레이어 셋팅

 - Other settings 에 있는 Bundle Identifier 부분에 이클립스에서 만든 패키지명 그대로 설정해준다.

 - 그리고 폰으로 테스트 해볼건데 테스트라 하더라도 세로모드면 보기도 이쁘지 않음. 

   Resulution and Presentation 에서 Default Orientation을 Landscape Left 로 변경한다. (이부분은 주로 게임만들때 많이 변경되는 부분인데 좀이따 AndroidManifest.xml에서 맞춰줘야 앱이 정상동작함).




13. 빌드 설정에서 Scenes In Build 부분에 씬을 추가하지 않아도 된다고 했는데 일단 난 씬은 추가함.. 안해도 상관없긴 하더라...




14. 이제 빌드를 해서 AndroidManifest.xml 을 유니티에서 생성해줘야 한다.

이클립스에서 생성된걸로 작업하면 좀 번거로운 부분이 있어서 이렇게 하는게 개인적으로는 편했음)

그러면 빌드하기 직전 셋팅은 아래 그림과 같게 되겠음.


* Assets에 Plugins 폴더를 만들고 그안에 Android 폴더를 만들어 둔다. (여기에 좀이따 파일을 넣을 예정임)






15. 빌드! 이때 apk를 어디다 만들던, 어떤 이름으로 만들던 상관없음.




16. 빌드를 하고 나면 프로젝트가 생성된 폴더로 접근하고 해당 프로젝트 안에 Temp / StagingArea 폴더안에 있는 AndroidManifest.xml을 아까 만든 Plugins/Android 폴더안에 드래그 앤 드랍으로 넣고 이클립스에서 Export 한 Jar 파일도 넣는다. 그럼 총 두개의 파일이 해당 폴더 안에 위치하게 됨.

AndroidManifest.xml이나 jar파일을 이 폴더에 넣어야만 나중에 빌드할때 요 안에 있는 파일로 빌드가 됨.. 폴더명 틀리면 앙댐..




17. 이제 마지막 단계임. AndroidManifest.xml을 아래와 같이 수정할 예정임. 제일 중요함 ㅋㅋ


* 변경점은 UnityPlayerProxyActivity 쪽에 선언되있던 Intent filter를 삭제하고, 작업 초기에 이클립스쪽에서 만든 MainActivity를 먼저 인식하게 만들어야 하므로,activity 추가 및 intent 필터 설정. 그리고 폰 데이터 접근권한을 줘야 데이터를 사용할 수 있으므로, <uses-permission android:name="android.permission.READ_PHONE_STATE" /> 부분을 추가함. 화면 회전이 AndroidManifest.xml에 별도로 설정되어 있지 않은 경우 화면회전과 동시에 앱이 종료가 되는 싱기한 현상을 목격할 수 있음. 그러므로 이부분을 주의해서 보고 수정이 필요하다면 꼭 수정해야함.



<?xml version="1.0" encoding="utf-8"?>

<manifest xmlns:android="http://schemas.android.com/apk/res/android"

  package="kr.co.bandus"

  android:theme="@android:style/Theme.NoTitleBar"

  android:versionName="1.0"

  android:versionCode="1"

  android:installLocation="preferExternal">

  <supports-screens

  android:smallScreens="true"

  android:normalScreens="true"

  android:largeScreens="true"

  android:xlargeScreens="true"

  android:anyDensity="true" />

  <application android:icon="@drawable/app_icon"

   android:label="@string/app_name"

   android:debuggable="false">

    <activity android:name="com.unity3d.player.UnityPlayerProxyActivity"

  android:launchMode="singleTask"

  android:label="@string/app_name"

  android:configChanges="fontScale|keyboard|keyboardHidden|locale|mnc|mcc|navigation|orientation|screenLayout|screenSize|smallestScreenSize|uiMode|touchscreen"

  android:screenOrientation="landscape">

    </activity>

    <activity android:name="com.unity3d.player.UnityPlayerActivity"

  android:launchMode="singleTask"

  android:label="@string/app_name"

  android:configChanges="fontScale|keyboard|keyboardHidden|locale|mnc|mcc|navigation|orientation|screenLayout|screenSize|smallestScreenSize|uiMode|touchscreen"

  android:screenOrientation="landscape">

    </activity>

    <activity android:name="com.unity3d.player.UnityPlayerNativeActivity"

  android:launchMode="singleTask"

  android:label="@string/app_name"

  android:configChanges="fontScale|keyboard|keyboardHidden|locale|mnc|mcc|navigation|orientation|screenLayout|screenSize|smallestScreenSize|uiMode|touchscreen"

  android:screenOrientation="landscape">

      <meta-data android:name="unityplayer.ForwardNativeEventsToDalvik" android:value="false" />

    </activity>

<activity

        android:name="kr.co.bandus.MainActivity"

        android:label="@string/app_name"

android:screenOrientation="landscape">

<intent-filter>

<action android:name="android.intent.action.MAIN" />

<category android:name="android.intent.category.LAUNCHER" />

</intent-filter>

</activity>

  </application>

  <uses-sdk android:minSdkVersion="6" android:targetSdkVersion="18" />

  <uses-feature android:glEsVersion="0x00020000" />

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

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

  <uses-feature android:name="android.hardware.touchscreen" />

  <uses-feature android:name="android.hardware.touchscreen.multitouch" android:required="false" />

  <uses-feature android:name="android.hardware.touchscreen.multitouch.distinct" android:required="false" />

</manifest>




이제 모든 설정이 끝났으므로, 빌드앤런을 통해 확인하면 01098987575 식과같은 번호를 스트링형식으로 뿌려주는 걸 확인할 수 있음!!


WRITTEN BY
빨강꼬마

,

void Awake()
 {
  // Play 버튼을 누르거나 객체가 생성될 때 호출된다. (딱 1번만)
  // 즉 씬에 배치되어있는 컴포넌트라면 Play버튼을 눌렀을 경우 호출되고
  // 실행중에 동적으로 생성되는 경우에는 Instantiate() 함수를 호출 했을 때 호출된다.
  // 반드시 필요한 초기화는 이곳에서 하는게 좋으며 부하가 큰 처리는 피하는게 좋다. 
 }
  
 void Start()
 { 
  // Play 버튼을 누르거나 객체가 생성된 이후 첫 Update 가 호출되는 타이밍일 때
  // Start()가 호출된 적이 없다면 Start()를 먼저 호출하고 이후에 Update()를 호출한다. (딱 1번만) 
  // 초기화 작업중 반드시 생성과 동시에 할 필요 없는 내용은 이곳에서 처리한다.  
 }

 void Reset()
 {
  // 스크립트가 게임오브젝트에 컴포넌트로 등록될 때 혹은 컴포넌트 우측 상단에 있는
  // Gear 모양의 아이콘을 클릭하여 Reset메뉴를 실행할 때 호출된다.
  // 주로 초기화 관련 처리를 해주는 용도로 사용하며 Awake()나 Start()와 달리
  // 컴포넌트로 등록할 때 한번만 호출되기 때문에 초기화에 대한 부하를 줄이고 싶다면 유용하다.
 }
  
 void Update()
 {
  // 매 프레임 렌더링시 호출되며 최대 호출 간격은 기본적으로 초당 60번 정도이다.(완전 일치하지는 않음)
  // 만약 렌더링 프레임 레이트가 호출 간격 제한인 60번을 넘길경우 무시되며,
  // 렌더링 프레임 레이트가 60번 미만인 경우 그 만큼만 호출된다.
  // 만약 최대 호출 간격을 조종하고 싶다면 "Time.captureFramerate = 초당 호출 수" 를 이용하면 된다.
  // 주로 게임의 갱신에 사용되며 불규칙적인 호출이 되도 문제가 없거나 없도록 처리해야한다.
  // 즉 렌더링을 할 때만 호출되므로 렌더링 관련이나 애니메이션 등의 보여지는거에 관계있는 것들의
  // 갱신 처리가 주가 된다.
  // 캐릭터의 이동 등을 Time.deltaTime을 이용해서 구현하기도 함.
 }

 void FixedUpdate()
 {
  // 정해진 주기에 따라 고정적으로 호출된다.
  // 호출 주기는 Edit -> Project Settting -> Time에서 Fixed Timestep 항목을 조종하면 된다.
  // 기본값은 0.02초로 초당 50번 호출하도록 되어있고 초당 60번을 호출하고 싶다면 0.016666666을 넣으면 된다.
  // 불규칙하게 호출되면 안되는 내용은 이곳에서 처리한다. (주로 물리, 이동, 네트웍, 입력 등을 처리)
 }

 void OnDestroy()
 {
  // 객체가 소멸 되거나 Destroy() 함수를 이용해서 소멸 시킬 때 호출된다.
  // 캐릭터가 죽으면서 처리할 내용, 예를들어 레그돌이나 폭발효과, 이벤트 발생 등의 일을 처리하기 좋다.
 }
}


- 데브코리아 펌


WRITTEN BY
빨강꼬마

,

Static Variables
정적 변수


많은 초보자들은 이 간단한 실수를 한다:

1
2
3
4
public static int health;
void Start(){
    health=100;
}

다른 스크립트에서 우리는 다음과 같은 코드를 발견할 것이다.

1
2
3
4
5
6
void OnCollisionEnter(Collision other){
    if (other.gameObject.tag == “Enemy”){
        EnemyScript.health-=10;
        if(EnemyScript.health <=0)Destroy(other);
    }
}

그들은 적을 죽이려고 할 것이고, 그를 죽이는데 성공한다. 그러나 그때 더 많은 적들이 추가되고, 모두 갑자기 죽는것에 의아해 한다.

이유는 간단하다. 당신은 많은 enemy 클래스 인스턴스를 가지고 있다.(그것은 static이 아니다) 그러나 그들은 모든 적에 대해 오직 하나의 health 인스턴스를 가진다. 하나를 죽이는 것은 모두를 죽이는 것이다!

사실, 클래스에 선언된 정적 변수는 객체들에 속해 있는 것이 아니라 클래스 그 자체에 속해 있다.
이것이 바로 우리가 정적 변수에 접근할 때 인스턴스 이름이 아닌 클래스 이름을 사용하는 이유이다.

그것은 적의 체력에 대해서 올바르게 실행되지 않았다. 왜냐하면 각각의 적들은 그들 스스로의 health 변수를 가져야 하기 때문이다.

정적 클래스와 같은 방법으로, 정적변수를 선언한 클래스가 첫번째로 접근될 때 정적 변수는 인스턴스화된다.
이것은, 게임에서 클래스의 어떤 인스턴스도 없더라도, 우리가 정적변수에 접근하려고 할 때 정적 변수는 존재한다는 것을 의미한다.

적 카운터를 고려할 때, 우리는 게임 안에서 적들의 숫자를 추적하기를 원한다.

static var counter;
 
public void CreateEnemy(){
    GameObject obj=Instantiate(enemyPrefab, new Vector3(0,0,0).Quaternion.identity);
 
    if(obj)counter++;
}
 
public void DestroyEnemy(){
    Destroy(gameObject);
    counter--;
}

이 스크립트는 각 적들에게 첨부될 것이다. 모든 적들이 다른 스크립트를 가지고 있더라도, 모든 적들은 같은 counter 변수를 공유한다

우리의 게임에 이 함수를 사용하는 것은 우리가 계속해서 적들의 숫자를 추적 가능하게 한다.
Instantiate가 객체를 만드는 것을 실패했을 경우에(예를 들면, 힙 파편화로 인한), Instantiate는 null 참조를 반환하기 때문에, 참조 변수 사용에 주의해야 한다. 

만약 어떤 이유로 인스턴스 생성이 실패되었을 경우, 다음과 같은 if문의 확인없이 적 카운트를 증가시킨다면, 우리는 잘못된 counter를 가질 것이다.

지금 만약 다음과 같이, counter변수를 사용하여 레벨을 끝내고, 새로운 레벨을 불러오려고 한다면,
if(counter<=0)Application.LoadLevel(nextLevel);
만약 확인을 하지 않고 만약 인스턴스화를 실패했다면, 모든 적을 죽인 후에, 우리가 counter가 1이고 우리의 게임은 버그가 생긴 것이다.

우리의 플레이어는 어떤가, 정적 health를 사용할 수 있는가? 그렇다. 우리는 우리의 플레이어의 체력을 위해서 정적 변수를 사용할 수 있다. 사실 정적 변수에 접근하는 것은 심지어 인스턴스 맴버보다 더 효율 적이다.

인스턴스의 멤버를 호출할 때, 컴파일러는 객체의 존재를 확인하기 위한 작은 함수 요청이 필요하다. 왜냐하면 존재하지 않은 데이터를 수정할 수 없기 때문이다.


Static Functions
정적 함수

정적 함수는 비정적 클래스에서 구현될 수 있다. 이 경우에 있어서, 오직 그 클래스의 정적 변수만이 정적함수안에서 접근될 수 있다. 정적 함수는 인스턴스가 아닌 클래스를 통해 호출되기때문에, 
존재하는 것이 확신되지 않는 맴버에 접근하는 것은 문제가 될 수 가 있다.

반면에, 정적 함수에 매개변수를 전달할 수 있고, 당신은 아마 이런 함수를 꽤 많이 사용하고 있을 것이다.
1
2
3
4
5
6
Vector3.Distance(vec1.vec2);
vec1.Normalize();
 
Application.LoadLevel (1);
 
if (GUI.Button(Rect(10,10,50,50),btnTexture)){}

그 목록들을 다 말하기에는 너무 많다. 두번째 라인은 정적 함수가 아니다. 그것은 vector3타입의 vec1 인스턴스를 통해 호출된다. 그 밖의 예는, 클래스가 정적 함수를 호출하기위해 사용되는 것을 보여 준다.

정적 함수는 비정적함수보다 더 빠른 경향이 있다. 컴파일러는 인스턴스함수의 호출이후에 인스턴스가 존재하는지 확인을 하기위해 작은 오버헤드가 발생하기 때문이다. 정적 함수는 존재의 유무가 확실한 클래스를 사용한다. 그렇지만 시간의 이득은 작다.


정적 클래스의 또 다른 이점은 그들의 존재가 영구적이고, 컴파일러에게 알려져 있으며,
따라서 C#에서 정적 클래스는 확장메소드 를 정의하기 위해 사용되어 질 수 있다.
확장메소드는 다른 타입의 변수에 첨가된 추가적인 함수처럼 보인다.
이는 syntactic sugar이다. 이런 식으로 확장된 클래스는 사실 열려있지 않지만, 
(그들의 private과 protected 변수는 확장메소드에서 접근이 불가능하다.)
이는 겉보기에 기존의 객체에 새로운 함수를 주는 깔끔한 방법 - 코드의 가독성을 향상시키고, 개발자들에게 더 쉬운 API를 제공하는 - 이다.

예를 들어 표면상으로 다음과 같이 Transform에 새로운 함수를 추가할 수 있다.

transform.MyWeirdNewFunction(x);
확장 메소드의 예:


기존의 함수(OrderBy, Sort,...)를 확장하는 배열에 함수를 만들기를 원한다고 생각해보자.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
using UnityEngine;
using System.Collections;
 
public static class ExtensionTest {
 
    public static int BiggestOfAll(this int[] integer){
        int length = integer.Length;
        int biggest = integer[0];
        for(int i = 1;i<length;i++){
            if(integer[i]>biggest)biggest = integer[i];
        }
        return biggest;
    }
}


우리는 정적 함수를 정의한다. 전달된 매개변수가 어떻게 함수를 호출하는 인스턴스가 되는지를 봐라.
매개변수의 타입을 수정하는 것은 다른 객체에서 작동하도록 한다.


이제 선언해서 배열을 채워보자:

01
02
03
04
05
06
07
08
09
10
11
using UnityEngine;
using System.Collections;
 
public class Test : MonoBehaviour {
 
        int[] array = {1,25,12,120,12,6};
 
    void Start () {
        print(array.BiggestOfAll());
        }
}

배열 이름 뒤에 .(점)을 입력하면, 위에서 입력한 새로운 함수가 나타나는 것을 보게 될 것이다.

그들의 타입과 상관없는 다른 배열들을 받아들이는 더 제너릭한 함수를 만들기를 원한다면, 
정적 제너릭 확장 메소드를 구현해야 할 것이다.


01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
 
public static class ExtensionTest {
 
    public static T TypelessBiggestOfAll<T>(this T[] t){
        int length = t.Length;
        var biggest = t[0];
        for(int i = 1;i<length;i++){
            biggest =((Comparer<T>.Default.Compare(t[i], biggest)>0)?t[i]:biggest);
        }
        return biggest;
    }
}


using System.Collections.Generic의 추가를 주목해라. 컴파일러가 T타입이 무슨타입인지 알지 못하기 때문에, 우리는 간단하게  < 또는 >를 통해서 값을 비교할 수 없다. 우리는 Comparer<T>를 사용해야 한다.
예를 들어,  삼항연산자가 사용된다. 우선 약간 혼란스러운 코딩을 만드는 경향이 있지만, 또한 약간의 라인을 줄여준다.


삼항 연산자는 다름과 같에 생겼다.  a ? b : c;

a는 수행된다. 그리고 만약 참이면 b가 명령문에서 내보내진다. 만약 거짓을 경우 c가 보내진다. 
일반적인 if문과 비교해봤을 때 장점은 비교후 값을 리턴한다는데 있다. 
result = a ? b : c; result는 a에 의존하여 b 또는 c값을 받는다.
이제 다른 타입의 배열을 선언한 뒤 같은 함수를 사용하는 것이 가능하다.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
using UnityEngine;
using System.Collections;
 
public class Test : MonoBehaviour {
 
    int[] arrayInt = {1, 25, 12, 120, 12, 6};
    float[] arrayFl = {0.5f, 52.456f, 654.25f, 41.2f};
    double[]arrayDb = {0.1254, -15487.258, 654, 8795.25, -2};
 
    void Start () {
        print(arrayInt.TypelessBiggestOfAll());
        print (arrayFl.TypelessBiggestOfAll());
        print (arrayDb.TypelessBiggestOfAll());
       }
}

이는 값 120, 654.25, 8798.25를 출력한다.

이제 나는 당신에게 이것이 유용하다는 것을 보여주고싶다.  많은 유니티 유저는 객체 내부의 지역을 통제하는 방법에 대해서 묻는다. 예를 들어, 화면 내부에 객체를 유지하고 싶기를 원한다면, 당신은 이 함수를 사용하여 그 객체의 Transform을 고정해야 한다.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
using UnityEngine;
using System.Collections;
 
public static class GameManager{
 
    public static void ClampTransform(this Transform tr,Vector3 move, Vector3 min, Vector3 max){
        Vector3 pos = tr.position + move;
        if(pos.x < min.x ) pos.x = min.x;
        else if(pos.x > max.x) pos.x = max.x;
 
        if(pos.y  < min.y ) pos.y = min.y;
        else if(pos.y> max.y) pos.y = max.y;
 
        if(pos.z < min.z) pos.z = min.z;
        else if(pos.z > max.z) pos.z = max.z;
 
        tr.position = pos;
    }
}

그리고 당신이 필요한 어떤 곳이든 사용하면 된다:

1
2
3
Vector3 minVector = new Vector3(-10,0,-10);
Vector3 maxVector = new Vector3(10,0,10);
transform.ClampTransform(move,minVector,maxVector);

이 예에서,  객체는 (0, ,0, 0)을 중심으로한 20*20 사각형의 2D 환경에 제약이 걸려 있다.

Heap Fragmentation, Garbage Collection and Object Pooling
힙 파편화, 가비지 컬렉션, 오브젝트 풀링


힙은 데이터가 무작위로 저장되어 있는 큰 메모리 공간이다.  사실 그렇게 무작위는 아니다. OS는 메모리를 조사할 것이고, 요구된 데이터에 적합한 크기의 첫번째 메모리 지역을 찾을 것이다. 
힙의 크기와 위치는 당신이 선택한 플랫폼에 따라 달라진다.

예를 들어, 정수에 대해 요청할 때, OS 연속적인 4바이트의 메모리공간을 찾는다. 사실은, 프로그램이 실행될 때, 메모리 지역은 실제 규칙없이 사용되고, 해제된다. 그리고 당신의 프로그램은 힙 파편화가 발생될 것이다.


이 사진은 힙 파편화의 예를 보여준다. 명백하게 이것은 좀 지나치게 강조하고 있지만, 본질은 틀리지 않다.

어떤 데이터도 추가 되지 않았다는 것을 명심해라. state1에서의 같은 양의 데이터가 있다.
오직 움직임이 힘 파편화를 만들었다.



유니티는 .NET managed 메모리를 사용한다. C 프로그램에서 힙 파편화는 메모리 블럭을 할당하는 것이 불가능해지는 상황을 초래할 수 있다. 왜냐하면 사실 충분한 메모리 공간이 있더라도 적합한 크기의 인접한 블록이 없기 때문이다. managed 메모리는 이 제한으로 부터 영향을 받지 않는다. 만약 메모리 블록 발견되지 않는다면, 실제 .NET 메모리 관리와 가비지 컬렉션 시스템은 메모리를 움직임으로써 힙의 파편화를 제거할 수 있다. 유니티에서는 가비지 컬렉션의 실행으로 힙 파편화 현상이 일어나지 않는다.
한번에 하나 이상의 객체가 제거됨으로써 공간을 찾을 것이다. 그러나 이것은 그들이 적합할 때까지 블록을 움직이는 것만큼 효율적이지는 않다.
프로그래머의 해답은 오브젝트 풀링을 사용하는 것이다. state1에서 data3를 파괴하는 대신에 우리는 간단하게 나중의 사용을 위해서 그것을 비활성화 시키면 어떻게 될까? 아마 힙 파편화를 피했을 것이다.

우리는 data를 파괴하지 않는다. 우리는 그것을 잠자게 할 것이고, 필요할 때 우리는 그것을 깨울 것이다.

이 해법에 많은 장점이 있다. 우선 우리는 위에서 봤던 경우를 피할 것이고, 우리는 또한 Instantiate과 Destroy 함수 호출을 피할 것이다. 우리는 오직 gameobject.SetActiveRecursevely(true/false)를 사용하면 된다.

마지막으로 우리는 파괴하지 않기 때문에, 우리는 비용이 비싼 행위인 가비지 컬렉터를 호출하지 않는다.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
ObjectScript.cs
using UnityEngine;
using System.Collections;
 
public class Test : MonoBehaviour {
    public GameObject prefab;
    GameObject[] objectArray = new GameObject[5]; 
        public static int counter;
 
         void Start(){
        for(int i = 0;i<objectArray.Length;i++){
            objectArray[i] = (GameObject)Instantiate(prefab,new Vector3(0,0,0),Quaternion.identity);
            objectArray[i].SetActiveRecursively(false);
        }
        }
        void Createobject(){
        if(counter < objectArray.Length){   
                // iterate through array
            for(int i = 0;i<objectArray.Length;i++){ 
                                // Find an inactive object                                    
                if(!objectArray[i].active){   
                               // Increase the counter, activate the object, position it                                             
                    counter++;                                             
                    objectArray[i].SetActiveRecursively(true);            
                    objectArray[i].transform.position = new Vector3(0,0,0); 
                    return;
                }
            }return;
        }else return;
    }
}
1
2
3
4
void OnCollisionEnter(CollisionEnter){
    if(other.gameObject.tag=="Object"){
        other.gameobject.SetActiveRecursively(false);       //Deactivate the object
        ObjectScript.counter--;                                             //Decrease counter

이 씬에서는 한번에 절대 5개의 객체를 가지지 않는다. 1개가 비활성화 될 때, 우리 원하는 곳에 다시 재활성화 시킬 수 있다. 가비지 컬렉션은 필요하지 않는다.


만약 당신 앞에 나타나는 적들의 웨이브를 가진다면, 죽은 적들을 파괴하는 대신에, 당신의 적에 대해서 이 트릭을 사용할 수 있고, 그들을 다시 재위치 시킬 수 있다. 


탄약에서도 똑같이, 각각의 총알을 파괴하는 대신에 그것을 비활성화 시킨다.
만약 당신의 총 앞의 위치와 적절한 속도로 다시 발사할 때, 그것을 다시 활성화 시키면 된다.


Note that pooling at the memory level is a different process managed by the OS consisting in reserving always the same amount  of memory whatever size the object is. As a result there will never be tiny memory locations lost all over the memory.
메모리 단계에서 풀링은 객체의 사이즈가 얼마이든 간에, 항상 같은 양의 메모리를 확보하는 OS에 의해 관리되는 다른 프로세스이다.

Array Reuse
배열 재사용

객체를 재사용하는 것에 더하여, 우리는 각 호출 또는 같은 호출에서 반복적인 코드를 재사용하는 버퍼를 생성하는 것은 생각해볼 만한 가치가 있다.

01
02
03
04
05
06
07
08
09
10
11
12
void Update()
{
      if(readyToFire && Input.GetKeyDown(KeyCode.F))
      {
          var nearestFiveEnemies = new Enemy[5];
 
          //Do something to fill out the list finding the nearest
          //enemies
 
          TargetEnemies(nearestFiveEnemies);
      }
}

이 예제에서, 우리는 가장 가까운 5개의 적을 타겟팅 할 것이고, 자동적으로 그들에게 발사할 것이다.
문제는 발사 버튼을 누를 때 마다 메모리가 할당된다는 점이다. 대신에 만약 우리가 오랫동안 유지되는 적들의 배열을 만들었다면, 우리는 코드 어디에서든 사용할 수 있을 것이고, 우리는 메모리를 할당하지 않아도 되며 느려지는 현상을 피할 수 있을 것이다.

이것은 간단한 케이스가 될 수 있다:

01
02
03
04
05
06
07
08
09
10
11
12
Enemy[] _nearestFiveEnemies = new Enemy[5];
 
void Update()
{
      if(readyToFire && Input.GetKeyDown(KeyCode.F))
      {
          //Do something to fill out the list finding the nearest
          //enemies
 
          TargetEnemies(_nearestFiveEnemies);
      }
}

그러나 어쩌면 우리가 종종 적들의 배열이 필요할 때,  우리는 그것을 더 일반적으로 만들 수 있을 것이다.

01
02
03
04
05
06
07
08
09
10
11
12
Enemy[] _enemies = new Enemy[100];
 
void Update()
{
      if(readyToFire && Input.GetKeyDown(KeyCode.F))
      {
          //Do something to fill out the list finding the nearest
          //enemies
 
          TargetEnemies(_enemies, 5);
      }
}

카운트를 가지는 TargetEnemies에 대한 코드를 재작성함으로써, 우리는 효율적으로 우리의 코드 어떤 곳에서든 일반적인 적의 목적 버퍼를 사용할 수 있을 것이고, 훨씬 더 할당의 필요성을 피할 수 있을 것이다.


두번째 예제는 꽤 지나친 경우이다. 그리고 만약 실제로 메모리 문제를 야기시키는 거대한 집합을 가진다면 반드시 이것을 해야한다. - 일반적으로 우리는 언제나 읽기쉽고, 약간의 메모리를 아낄 수 있는 이해할 수 있는 코드를 작성해야 한다.
- 유니티안 Dexter님께서 http://unitygems.com/memorymanagement/ 의 원문을 번역하여 올려주신 걸 따로 가지고 있기 위해 올림.
덱스터님 의 원문은 [여기]를 참고해주세요 ^_^


'Unity > 메모리관리' 카테고리의 다른 글

itween은 되도록 자제...  (0) 2014.08.12
유니티에서의 메모리 관리  (0) 2014.04.28
유니티 메모리 관리 3  (0) 2013.10.16
유니티 메모리 관리 2  (0) 2013.10.16
유니티 메모리 관리 1  (0) 2013.10.16

WRITTEN BY
빨강꼬마

,