Game/Unity2016.08.12 16:52

실은 매우 간단한 것이지만, 의외로 정보가 없을 수도있어서, 정리해 보겠습니다.

어딘가 잘못하고 있으면 미안합니다.

 

참고로 아래 공식 매뉴얼 방식이 일본어로 번역 된 때문에 이쪽에서도 확인 해보세요!

https://developers.google.com/mobile-ads-sdk/docs/games

 

1. SDK를 DL하자

먼저 위의 URL에서 admob SDK를 DL합시다!

페이지 중간 쯤에있는 "Download the plugin"에서 unitypackage을 DL 해주십시오.

sdk

github

 

GoogleMobileAds.unitypackage "라는 파일이 DL 있으면 OK입니다!

 

2. Unity에 가져 오려고!

Unity의 Assets> Import Package> Custom Package ...에서 방금 DL 한 unitypackage을 가져!

custom

 

Importing_package_과 _Untitled _-_ admob _-_ PC__Mac___Linux_Standalone

 

Unity 프로젝트에 아래의 폴더가 추가됩니다!

(버전이나 환경에 따라 약간 다를 가능성이 있습니다)

project

 

그러면 각 OS마다 대응합시다.

 

3. Android 대응

Android 대응하려면 아래에 조심할 필요가 있습니다.

· 「Google Play services」되는 것을 Android 폴더에 넣는다

(· AndroidManifest 편집) ← admob 이외 Manifest를 사용하는 경우

 

 

3-1. Google Play Services를 가져 오기

먼저 AndroidSDK에있는 "Google Play services"을 가져 봅시다.

AndroidSDK의 DL 방법 이라든지는 구글 선생님에게 각자 들어요 ... (미안)

※ Admob의 Unity 플러그인 버전 3.0.0에서 Google Play Services를 가져올 필요가 없어진 것 같습니다. 3.0.0 이후의 SDK를 사용하는 경우는 건너 뜁니다.

 

Google Play services 설치 방법이지만, Android SDK manager를 시작하여 설치합니다.

다음은 mac 버전의 Android SDK manager 위치. (android라는 녀석을 시작)

androidsdk_android

 

manager를? 시작되면 Extras 폴더에있는 "Google Play services"를 설치하십시오.

(Google Play services for Froyo라는 것도 있습니다 만, 이번은 필요 없습니다)

androidsdk

 

설치가 끝나면,

<android_sdk> / extras / google / google_play_services / libproject / google-play-services_lib /

에 설치되어 있다고 생각하기 때문에,

Unity의 Plugin 폴더> Android 폴더에 ぶち込み합니다.

project2

이것으로 OK!

 

3-2. AndroidManifest에 대해

기본적으로 admob 이외의 광고에서 Manifest를 사용하거나 직접 편집하는 것이 아니라면이 항목을 건너 뛰고 괜찮습니다.

Manifest를 변경하고있는 분은

1
2
<meta-data android:name="unityplayer.ForwardNativeEventsToDalvik"
        android:value="true" />

위와 같이,

unityplayer.ForwardNativeEventsToDalvik의 value를 true로합니다.

이것만으로 android 버전은 OK입니다.

4. iOS 버전 지원

iOS 버전은 admobSDK7.0.0에서 xcode 프레임 워크로 변경되어 있기 때문에,

xcode에서 작업을 수행해야합니다.

 

이 단계에서는 광고 표시 처리를 추가하지 않지만 とりえあず xcode 프로젝트를 빌드하라.

 

먼저 프레임 워크를 DL합시다.

https://developers.google.com/mobile-ads-sdk/download

iossdk

 

다음 프로젝트에 프레임 워크를 추가합니다.

방법으로 xcode에서 설정하는 방법과 Unity 측에서 설정하는 방법이 있습니다.

Unity5 이상을 사용하고있는 분은 Unity로 설정하는 것이 편하다고 생각합니다.

 

4-1. xcode에서 framework을 설정하는 경우

 

xcode

xcode의 "linkd Framework and Libraries"에서 admob에 필요한 프레임 워크를 추가합시다.

아래쪽에있는 + 버튼을 누르면 추가 할 수 있습니다.

필요한 것은 아래 (2015/2/25 현재)

  • AdSupport
  • AudioToolbox
  • AVFoundation
  • CoreGraphics
  • CoreTelephony
  • EventKit
  • EventKitUI
  • MessageUI
  • StoreKit
  • SystemConfiguration
  • GoogleMobileAds.framework (방금 DL 한 것)

입니다.

 

GoogleMobileAds.framework 이외는 xcode에 있으므로 검색 양식에서 찾아보세요.

 

GoogleMobileAds.framework는 "Add Other"를 선택합니다.

xcodeframework

frameworkopen

 

※ Unity5.3.1p2 현재 Unity에서 xcode 프로젝트를 덮어 (Append) 할 때 외부 Framework의 연결이 끊어지는 현상이 발생하고 있습니다. 
 해결 방법으로 Append 할 때마다 Add Other에서 .framework을 다시 추가해야합니다

 

4-2. Unity에서 framework을 설정하는 경우

 

Unity의 Plugins> iOS 아래에 넣고있는 .h 또는 .m 파일을 선택하면 
Inspector에서 사용하는 framework을 설정할 수 있습니다.

Untitled _-_ test _-​​_ Android__Personal___OpenGL_4_1_

여기에서 사용하는 framework에 체크를 넣어두면 
빌드 할 때 자동으로 xcode 측에서 설정되므로 매우 편리합니다.

admob에서 사용하는 framework은 아래와 같습니다 때문에, 체크합시다.

(2015/2/25 현재)

  • AdSupport
  • AudioToolbox
  • AVFoundation
  • CoreGraphics
  • CoreTelephony
  • EventKit
  • EventKitUI
  • MessageUI
  • StoreKit
  • SystemConfiguration

 

다음은 DL 한 GoogleMobileAds.framework를 Plugins> iOS 다음에 넣어보세요.

Untitled _-_ test _-​​_ Android__Personal___OpenGL_4_1_2

 

이제 Unity에서 빌드 한 것만으로 framework가 자동으로 설정됩니다.

※ windows에서 Unity를 사용하는 경우이 방법을 사용하면 xcode 측에서 잘 인식 해주지 않는 경우가 있습니다. Windows 분들은 솔직하게 xcode에서 .framework을 넣어 보자.

 

4-3. @improt에서 오류가 나오지 않게하기

 

framework를 추가하는 것만으로는 오류가 빌드 수 없기 때문에 xcode의 Build Settings> Apple LLVM 6.1 - Language - Modules 에있는 Enable Modules (C and Objective-C) 을 Yes 로 변경하십시오.



Unity-iPhone_xcodeproj

 

이상에서 Xcode 설정이 완료됩니다.

 

5. 광고를 보려고

광고를 표시하기위한 스크립트를 만듭시다.

"Scripts"폴더에 "admobManager"인 파일을 만듭니다.

아래 같은 느낌합니다.

비교적 적당한 때문에, 제대로 수정 사용 해주세요!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
using UnityEngine;
using System.Collections;
using GoogleMobileAds.Api;
 
public class AdMobManager : MonoBehaviour {
    public string Android_Banner;
    public string Android_Interstitial;
    public string ios_Banner;
    public string ios_Interstitial;
 
    private InterstitialAd interstitial;
    private AdRequest request;
 
    bool is_close_interstitial = false;
 
    // Use this for initialization
    void Awake () {
        // 起動時にインタースティシャル広告をロードしておく
        RequestInterstitial ();
        // バナー広告を表示
        RequestBanner ();
    }
 
    // Update is called once per frame
    void Update () {
 
    }
 
    public void RequestBanner()
    {
        #if UNITY_ANDROID
        string adUnitId = Android_Banner;
        #elif UNITY_IPHONE
        string adUnitId = ios_Banner;
        #else
        string adUnitId = "unexpected_platform";
        #endif
 
        // Create a 320x50 banner at the top of the screen.
        BannerView bannerView = new BannerView(adUnitId, AdSize.Banner, AdPosition.Bottom);
        // Create an empty ad request.
        AdRequest request = new AdRequest.Builder().Build();
        // Load the banner with the request.
        bannerView.LoadAd(request);
    }
 
    public void RequestInterstitial()
    {
        #if UNITY_ANDROID
        string adUnitId = Android_Interstitial;
        #elif UNITY_IPHONE
        string adUnitId = ios_Interstitial;
        #else
        string adUnitId = "unexpected_platform";
        #endif
 
        if (is_close_interstitial == true) {
            interstitial.Destroy ();
        }
 
        // Initialize an InterstitialAd.
        interstitial = new InterstitialAd (adUnitId);
        // Create an empty ad request.
        request = new AdRequest.Builder ().Build ();
        // Load the interstitial with the request.
        interstitial.LoadAd (request);
 
        interstitial.AdClosed += HandleAdClosed;
 
        is_close_interstitial = false;
    }
 
    // インタースティシャル広告を閉じた時に走る
    void HandleAdClosed (object sender, System.EventArgs e)
    {
        is_close_interstitial = true;
    }
}

이 스크립트를 카메라 나름대로 빈 객체 나름대로 연결합니다.

admob_id

위 admob에서 발급 된 ID를 입력하십시오.

※ ID는 "광고 단위 ID '를 입력하십시오 (게시자 ID가 없습니다)

 

각각에 더 맛있는 느낌에 정의 해 주셨으면 합니다만, 쉽게 해설.

RequestBanner에서 배너 설정 RequestInterstitial에서 삽입 광고의 설정을 실시하고 있습니다.

Awake 2 개를 부르는 것으로, 실행시에 광고를로드하도록하고 있습니다.

 

RequestInterstitial에서

1
2
3
if (is_close_interstitial == true) {
    interstitial.Destroy ();
}

하고 있습니다 만, admob의 삽입 광고는 한번 표시하면 다시 사용할 수없는 일회용 타입 을 위해 사용한 후에 Destroy하도록하고 있습니다.

Destroy하지 않고 다시로드하면 메모리 사용량이 점점 증가하고 가므로주의하십시오.

 

HandleAdClosed은 삽입 광고가 닫힐 때 호출됩니다.

 

덧붙여서, 삽입 광고를 볼 때,

1
2
3
if (interstitial.IsLoaded()) {
    interstitial.Show();
}

로 표시 할 수 있으므로 표시하고 싶은 곳에서 전화하십시오.

 

이상.

네, 간단 하죠?

 

※ admob 구현하고 즉시 광고가 게재되지 않습니다. 아래 테스트를 시도하거나 여러 번 응용 프로그램을 다시 시작하려고합니다.

※ 또한, 테스트 버전도 빨리 나타나지 않는 경우가 있습니다. 어째서 나 자지 않아.

 

덧붙여서, 시뮬레이터로 표시 싶기도 테스트에서 보려는 경우

1
2
3
4
AdRequest request = new AdRequest.Builder()
    .AddTestDevice(AdRequest.TestDeviceSimulator)       // Simulator.
    .AddTestDevice("テストデバイスID"// My test iPod Touch 5.
    .Build();

처럼, .Build () 앞에 AddTestDevice로 쓰고 있습니다.

장치 ID하지만 xcode 나름 Android Device Monitor 나름으로 로그를 보면 장치 ID가 포함 된 로그가 뱉어 있기 때문에 그것을 보면 좋다고 생각합니다.

 

그 정도 변은 다른 자세하게 써주고있는 사이트가 많이 있으므로, 그 쪽을 봐 주시면 다행입니다!

 

이상입니다!

 

여담

덧붙여서이지만, admob 관리 화면에서 중재 (admob 기능을 사용하여 여러? 광고 회사의 광고를 표시하는 기능)를 활성화하면 쉽게 중재 사용할 수 있습니다.

 

각 광고 회사마다 필요한 SDK와 프레임 워크는 다르기 때문에 거기는 각각 조사 할 필요가 있지만,

그 필요한 파일을 Plugins 폴더에 넣어두면, 다음은 admob 관리 화면에서? 사용자 정의 할 수 있으므로 편리합니다.

간단한 비해서 효과가 크다 때문에 꼭 도전 해보세요!


출처 : http://games.genieus.co.jp/unity/admob_unity/


저작자 표시
신고
Posted by Namseungil
TAG AdMob, Unity
Game/Unity2016.01.06 17:20

https://github.com/namseungngil/UnityGangOfFour


저작자 표시
신고
Posted by Namseungil
Game/Unity2015.12.18 15:28

<마우스 좌표에 오브젝트 붙이기>   


IEnumerator OnMouseDown()

    {

        Vector3 scrSpace = Camera.main.WorldToScreenPoint(transform.position);//객체의 스크린좌표를 구하고       

        Vector3 offset = transform.position - Camera.main.ScreenToWorldPoint(new Vector3(Input.mousePosition.x, Input.mousePosition.y, scrSpace.z));


        while (Input.GetMouseButton(0))

        {

            Vector3 curScreenSpace = new Vector3(Input.mousePosition.x, Input.mousePosition.y, scrSpace.z);//현재 스크린좌표를 구함

            Vector3 curPosition = Camera.main.ScreenToWorldPoint(curScreenSpace) + offset; //마우스좌표를 월드좌표로 + 

            transform.position = curPosition;

            yield return null;

        }

    }


 


픽킹


RaycastHit m_Hit;


if ( Input.GetMouseButtonDown(0) == true ) //마우스를 눌르면

{

Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition); //현재 마우스클릭한 위치


if ( Physics.Raycast(ray,out m_Hit,100)) // 피킹이 되면 m_Hit에 피킹된 오브젝트정보가 달려온다.

{

Debug.Log(m_Hit.transform.tag); 

}

}


 

//////////////////////////////////////////////////////////////////////////////


using UnityEngine;

using System.Collections;


public class picking : MonoBehaviour

{


    protected Ray ray;

    protected RaycastHit hit;

   


    // Use this for initialization

    void Start()

    {


    }


    // Update is called once per frame

    void Update()

    {


        if (Input.GetMouseButton(0) == true)

        {

            ray = Camera.main.ScreenPointToRay(Input.mousePosition);


            if (Physics.Raycast(ray, out hit))

            {

                Vector3 offset;

                Vector3 scrSpace = Camera.main.WorldToScreenPoint(hit.transform.position);

                offset = hit.transform.position - Camera.main.ScreenToWorldPoint(new Vector3(Input.mousePosition.x, Input.mousePosition.y, scrSpace.z));


                Vector3 curScreenSpace = new Vector3(Input.mousePosition.x, Input.mousePosition.y, scrSpace.z);

                Vector3 curPosition = Camera.main.ScreenToWorldPoint(curScreenSpace); // +offset;

                hit.transform.position = curPosition;


                Debug.Log(curScreenSpace.x);

                Debug.Log(curPosition.x);

            }

        }

    }


저작자 표시
신고
Posted by Namseungil
Game/Unity2015.11.02 17:23


http://gamecodingschool.org/2015/05/18/unity%EA%B0%80-%EC%A7%81%EB%A9%B4%ED%95%9C-%EA%B8%B0%EC%88%A0%EC%A0%81-%EB%AC%B8%EC%A0%9C%EB%93%A4/

저작자 표시
신고

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

[Unity] Gang Of Four (GOF)  (0) 2016.01.06
[Unity] Picking  (0) 2015.12.18
Unity가 직면한 기술적 문제들  (0) 2015.11.02
[unity3d]게임 최적화 기법  (0) 2015.10.21
Unity에서 화면 외의 위치를​​ 검색하는 방법  (0) 2015.09.09
[unity3d] swipe  (0) 2015.03.27
Posted by Namseungil
Game/Unity2015.10.21 12:54

프로젝트를 하고 완성이 될 무렵, 게임이 굉장히 괴랄한 프레임을 뿌려대며 제대로 굴러가지 않기 시작했다.

필자의 컴퓨터는 사양이 굉장히 좋다고 자부할 수 있는 컴퓨터였다. 유니티 에디터가 잘못인지 소스코드 상에서 문제가 있는 것인지 보기 위해 탐색을 시작하게 되었고. 프로파일러를 통해 개선할 부분을 다수 개선하였다.


이 밑부터는 내가 경험한 것을 바탕으로 최적화를 한 방법에 대해 서술한다.

 - 프로젝트 진행시 참고했던 박민근 님의 유니티 최적화 테크닉을 참고하였다.

 - http://www.slideshare.net/agebreak/141206-42456391 [링크]

 - 아마 위 파워포인트를 정리한거라고 보면 될 듯 싶다. (그 외에 추가한 것들도 존재한다. 이것들은 오리지날..)


1. 소스코드 병목의 파악.


그 누구나 먼저 볼 곳이 아마도 자신의 소스코드에 대한 문제가 아닐까 싶다.

리소스가 문제라고 해서 리소스를 바꿧는데, 그럼에도 불구하고 문제가 있으면 그 것은 좀 아니다 싶지 않은가?


- DP Call

- 복잡한 연산

- 3D의 수많은 버텍스, 연산

- 픽셀, 오버 드로우

- 셰이더의 많은 연산

- 압축되지 않고 큰 텍스쳐들

- 고 해상도 프레임 버퍼


2. 스크립트 최적화


- 유니티 객체들은 멤버변수에 저장하여, 캐싱을 이용하는게 좋다.

 -> 예를 들자면, Find 라는 검색이 붙은 것들은 왠만하면 지속적으로 사용하지 않는게 좋다. 왜냐하면 Find 들은 프로젝트안의 모든 오브젝트를 순환하며 그 안의 클래스의 스트링을 비교하기 때문이다.


- Instantiate, Destroy 이 두 놈의 프리팹 생성/해제는 비용이 상당히 크다.

 -> 프리펩은 미리 생성해둔후 오브젝트를 풀에서 쓰도록 하자.


- Update 함수 보다는 Coroutine을 사용하는게 좋다.

 -> Update 함수를 쓰지말고, Coroutine을 무한 루틴시켜서 사용하도록 하자. 필자같은 경우에는 게임 관련은 전부 Coroutine에서 사용을 했으며 UI 같은 경우 메인 업데이트에서 돌렸다.


- 나눗셈 말고 곱셈의 사용.

 -> 나눗셈은 곱셈보다 연산속도가 월등히 느리다 100 / 10 이런식으로 사용하지말고 100 * 0.1 을 사용하라는 소리.


- 박싱과 언박싱은 부하가 큰 작업 이므로 많이 사용하지 말자.

 -> 박싱과 언 박싱이란. 

 --> http://vallista.tistory.com/entry/C-%EB%B0%95%EC%8B%B1%EA%B3%BC-%EC%96%B8%EB%B0%95%EC%8B%B1 [링크]


- Magnitude 보다 sqrMagnitude를 사용하여 비교해서 쓴다. (제곱근 계산 X)

 -> Unity 공식 문서에도 써져있다. 

 --> If you only need to compare magnitudes of some vectors, you can compare squared magnitudes of them using sqrMagnitude (computing squared magnitudes is faster).



- 삼각함수의 값은 상수로 저장하고 사용하는게 좋다.


- 문자열은 readonly 혹은 const 키워드를 사용하여, 가비지 컬렉션으로부터 벗어나도록 한다.


3. 가비지 컬렉터


- 우리가 쓰고있는 MonoBehavior는 메모리 관리에 GC가 자동호출되도록 설계되어 있는데, 이 GC가 프로그래머들한테는 좋을 수도 있고, 안좋을 수도 있다. GC가 실행되는 동안 유저들은 게임에서 갑자기 렉이 걸리며, 그렇지 않기위해 우리는 게임을 GC를 피해 만들어야 한다. (필자한테는 상당히 거리낌이 있다.) 이제 이 GC와 친하게 지내기위해 우리도 몇가지 방법을 써서 길들여야하는데, 그 방법에 대해 서술한다. 


- 무엇이든 동적 생성 및 해제는 부하가 굉장히 큰 작업이다. 그래서 위에 언급했다시피 오브젝트 풀 기법을 사용하여 메모리를 관리하도록 하자.


- 오브젝트가 해제되면 다음과정으로는 GC가 동작되서 렉이 걸릴수 밖에 없다. 즉 오브젝트를 만들어 둔 후 활성화 또는 비 활성화를 이용하여 사용하도록 하자.


- 문자열 병합은 StringBuilder의 Append를 사용하면 된다. 왜냐하면 string + string은 임시 문자열을 뱉기 때문에 가비지 컬렉션이 일어나는 환경을 제공한다.


- foreach 대신에 for를 이용하도록 한다. Foreach는 한번 돌리면 24byte의 쓰레기 메모리를 생성시키며 수많이 돌면 더 많은 메모리를 생성시키므로 for을 이용하도록 한다.


- 태그 비교에서는 CompareTag() 를 사용하도록 한다.

객체의 tag 프로퍼티를 호출하는 것은 추가 메모리를 할당하며 복사를 하게된다.


- 모든 비교문에서 .equals()를 사용하도록 하자. == 구문으로 사용하면 임시적인 메모리가 나오게 되며 가비지 컬렉션의 먹이를 준다.


- 데이터 타입은 Class 대신 Struct 를 사용하여 만들어 주면 메모리 관리가 된다. 구조체는 메모리 관리를 Stack에서 하므로 GC에 들어가지 않는다.


- 즉시 해제시에는 Dispose를 수동으로 호출하게 되면 즉시 클린업된다.


- 임시 객체를 만들어내는 API를 조심해야한다.

GetComponents<T>, Mesh, Vertices, Camera.allCameras, 이런것들..


- 객체의 변경 사항에 대해 캐싱한다. 객체의 이동과 변형에 대한 처리를 캐싱해서 매프레임당 한번만 처리


- 컴포넌트 참조를 캐싱한다.

GetComponent()는 한번만 호출되며, 객체를 캐싱해서 사용한다.


- 콜백함수 안쓰는 것들을 제거해야한다. Start(), Update(), OnDestroy() 같은 것들은 비어있어도 성능에 영향을 끼치므로 지워주도록 하자.


4. 텍스쳐


- 텍스쳐를 압축 할때는 권작 압축 텍스쳐를 사용하도록 하자.

 -> 아이폰 : PVRCT

 -> 안드로이드 (Tegra) : DXT

 -> 안드로이드 (Adreno) : ATC

 -> 안드로이드 (공통) : ETC1


- 텍스쳐 사이즈는 2의 제곱이어야 한다.

 -> POT(Power of Two)

 -> POT가 아닌경우 POT 텍스쳐로 자동 변환 로딩이 된다.

 -> 900x900은 실제로는 1024 x 1024로 된다.


- 텍스쳐 아틀라스를 활용

 -> 텍스쳐 아틀라스로 최대한 묶어 사용 (NGUI Atlas 같은 사용법)

 -> UI 만 아니라, 같은 재질의 오브젝트를 묶어 사용하게 된다.


- 압축된 텍스쳐와 밉맵을 사용한다. (대역폭 최적화)


- 32bit가 아닌 16bit 텍스쳐 사용도 상황에 맞게 고려한다.




[사진 1] 보통 이렇게 최적화 하면 된다. (안드로이드)


5. Mesh


- Import 시에 언제나 "Optimize Mesh" 옵션을 사용한다.

 -> 변환 전/ 변환 후 버텍스 캐쉬를 최적화 해준다.


- 언제나 optimize Mesh Data 옵션을 사용한다.

 -> Player Setting > Other Settings

 -> 사용하지 않는 버텍스 정보들을 줄여 준다. (tangents, Normal, Color, ETC...)


6. 오디오


- 모바일에서 스테레오는 의미 없음

 -> 모두 92kb, 모노로 인코딩


- 사운드 파일을 임포트하면 디폴트로 3D 사운드 설정

 -> 2D 사운드로 변경


- 압축 사운드 (mp3, ogg), 비압축 사운드 (wav) 구별

 -> 비압축 사운드 : 순간적인 효과음, 이펙트

 -> 압축 사운드 : 배경 음악


7. 폰트 리소스 최적화


- Packed Font를 사용

 -> R,G,B,A 채널에 저장하는 기법으로 메모리 용량을 1/4로 절약하도록 하자.

 -> 단 Packed Font는 단점이 너무 많다. 일반적으로 글씨에 그림자도 못 넣을 뿐더러 알파도 적용이 안된다. 그리고 NGUI Atlas 적용도 안됨.


8. 리소스


- ResourceLoadAsync() 함수는 엄청 느리다.

 -> 게임 레벨 로드시에 사용했을 경우, 일반 함수에 비해 수십배나 느리다.


9. 컬링


- Frustum Culling (프러스텀 컬링)

 -> Layer 별로 컬링 거리 설정이 가능 (NGUI 의 경우 Panel 에서 Smooth Culling 도 먹일 수 있다.)

 -> 멀리 보이는 중요한 오브젝트는 거리를 멀게 설정하고 중요도가 낮은 풀이나 나무등은 컬링 거리를 짧게 설정하여 컬링한다.


- Occlusion Culling (오클루젼 컬링)

 -> Window->Occlusion Culling 메뉴에서 설정 가능

 -> Occlusion Culling 이란 카메라에서 보이는 각도의 오브젝트 들만 렌더링 하는 기법을 뜻한다.


- Combine (오브젝트 통합)

 -> 드로우콜은 오브젝트에 설정된 재질의 셰이더 패스당 하나씩 일어남.

 -> 렌더러에 사용된 재질의 수 만큼 드로우 콜이 발생함.

 ->> Combine (통합)

  ->>> 성질이 동일한 오브젝트들은 하나의 메쉬와 재질을 사용하도록 통합

  ->>> Script 패키지 - CombineChildren 컴포넌트 제공

   ->>>> 하위 오브젝트를 모두 하나로 통합

  ->>> 통합하는 경우 텍스쳐는 하나로 합쳐서, Texture Atlas를 사용해야함.


- Batch


- Static Batch

 -> Edit > Project Setting > Player 에서 설정한다.

 -> 움직이지 않는 오브젝트들은 static으로 설정해서, 배칭이 되게 함.

 -> Static으로 설정된 게임 오브젝트에서 동일한 재질을 사용 할 경우, 자동으로 통합된다.

 -> 통합되는 오브젝트를 모두 하나의 커다란 메쉬로 만들어서 따로 저장한다. (메모리 사용량 증가)


- Dynamic Batch 

 -> 움직이는 물체를 대상으로 동일한 재질을 사용하는 경우, 자동으로 통합

 -> 동적 배칭은 계산량이 많으므로, 정점이 900개 미만인 오브젝트만 대상이 된다.


10. 라이팅


- 라이트 맵 사용

  -> 고정된 라이트와 오브젝트의 경우(배경) 라이트 맵을 최대한 활용

  -> 아주 빠르게 실행됨 (Per-Pixel Light 보다 2~3배)

  -> 더 좋은 결과를 얻을 수 있는 GI와 Light Mapper를 사용할 수 있다.


- 라이트 렌더 모드

 -> 라이팅 별로 Render Mode : Important / Not Important 설정이 가능하다.

 -> 게임에서 중요한 동적 라이팅만 Important 로 설정 (Per-Pixel Light)

 -> 그렇지 않은 라이트들은 Not Important로 설정한다.


11. Overdraw


- 화면의 한 픽셀에 두 번 이상 그리게 되는 경우 (Fill rate)

 -> DP Call의 문제만큼 Overdraw 로 인한 프레임 저하도 중요한 문제

 -> 특히 2D 게임에서는 DP Call 보다 더욱 큰 문제가 된다.


- 기본적으로 앞에서 뒤로 그린다.

 -> Depth testing 으로 인해서 오버드로우를 방지한다.

 -> 하지만 알파 블렌딩이 있는 오브젝트의 경우에는 알파 소팅 문제가 발생한다.


- 반투명 오브젝트의 개수의 제한을 건다.

 -> 반투명 오브젝트는 뒤에서부터 앞으로 그려야 한다. -> Overdraw 증가

 -> 반투명 오브젝트의 지나친 사용에는 주의해야 한다.


- 유니티의 Render Mode를 통해서 overdraw 확인이 가능하다.


12. 유니티 셰이더


- 기본 셰이더는 모바일용 셰이더 사용

 -> 기본 셰이더를 사용할 경우, 모바일용 셰이더를 사용한다.

  ->> Mobile > VertexLit는 가장 빠른 셰이더


- 복잡한 수학 연산

 -> pow, exp, log, cos, sin, tan 같은 수학 함수들은 고비용

 -> 픽셀별 그런 연산을 하나 이상 사용하지 않는 것이 좋다.

 -> 텍스쳐 룩업 테이블을 만들어서 사용하는 방법도 좋다.

 -> 알파 테스트 연산 (discard)는 느리다.

 -> 기본적인 연산 보다는 최적화 시키고 간략화시킨 공식들을 찾아서 사용할 수 있다.


- 실수 연산

 -> float : 32bit - 버텍스 변환에 사용. 아주 느린 성능 (픽셸 셰이더에서 사용은 피함)

 -> Half : 16bit - 텍스쳐 uv에 적합. 대략 2배 빠름

 -> fixed : 10bit - 컬러, 라이트 계산과 같은 고성능 연산에 적합. 대략 4배 빠르다.


- 라이트 맵을 사용하자.

 -> 고정된 라이트와 오브젝트의 경우 (배경) 라이트 맵을 최대한 활용.

 -> 아주 빠르게 실행됨 (Per-Pixel Light 보다 2~3배)

 -> 더 좋은 결과를 얻을 수 있는 GI와 Light Mapper 사용가능


13. Fixed Update 주기 조절


- FixedUpdate()는 Update와 별도로 주기적으로 불리는 물리엔진 처리 업데이트


- 디폴트는 0.02초 1초에 50번


- TimeManager에서 수정


- 게임에따라 0.2초 정도로 수정해도 문제 없음. 


14. 물리 엔진 설정


- Static Object

 -> 움직이지 않는 배경 물체는, static으로 설정


- 충돌체의 이동

 -> 리지트 바디가 없는 고정 충돌체를 움직이면, CPU 부하 발생

 -> 이럴 경우 리지드 바디를 추가하고, IsKinematic 옵션 사용


- Maximum Allowed Timestep 조정

 -> 시스템에 부하가 걸려, 지정된 시간보다 오래 걸릴 경우, 물리 계산을 건너뛰는 설정


- Solver Iteration Count 조정

 -> 물리 관련 계산을 얼마나 정교하게 할지를 지정. (높을수록 정교)

 -> Edit > Project Setting > Physics


- Sleep 조절

 -> 리지드 바디의 속력이 설정된 값보다 작을 경우, 휴면 상태에 들어감

 -> Physics.Sleep() 함수를 이용하면, 강제 휴면 상태를 만들기 가능


15. 물리 엔진 스크립트


- 래그돌 사용 최소화

 -> 래그돌은 물리 시뮬레이션 루프의 영역이 아니기 때문에, 꼭 필요할 때만 활성화 함.


- 태그 대신 레이어

 -> 물리 처리에서 레이어가 훨씬 유리하다. 성능과 메모리에서 장점을 가진다.


- 메쉬 콜라이더는 절대 사용하지 않는다.


- 레이캐스트와 Sphere Check 같은 충돌 감지 요소를 최소화 한다.


16. Tilemap Collision Mesh


- 2D 게임에서 타일맵의 Collision Mesh를 최적화 하라.

 -> Tilemap을 디폴트로 사용해서, 각 타일별로 충돌 메쉬가 있는 경우, 물리 부하가 커짐.

 -> 연결된 Tilemap을 하나의 Collision Mesh로 물리 연산을 최적화 하라


출처 : http://vallista.tistory.com/entry/Unity-%EA%B2%8C%EC%9E%84-%EC%B5%9C%EC%A0%81%ED%99%94-%EA%B8%B0%EB%B2%95

저작자 표시
신고

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

[Unity] Picking  (0) 2015.12.18
Unity가 직면한 기술적 문제들  (0) 2015.11.02
[unity3d]게임 최적화 기법  (0) 2015.10.21
Unity에서 화면 외의 위치를​​ 검색하는 방법  (0) 2015.09.09
[unity3d] swipe  (0) 2015.03.27
[unity3d] fadein, fadeout  (0) 2015.03.27
Posted by Namseungil
Game/Unity2015.09.09 20:52

Unity에서 화면 외의 위치를​​ 검색하는 방법을 참고.

예를 들어 나는, 스마호게무에서 화면 밖이 보이지 않는 위치에 개체 (적)을 출현시키고 자했습니다. 다만이 상황에서 출현하는 위치를 절대 배치 해 버리면 장치마다 화면 크기가 다른 스마트 폰에서는 차이가 발생할 수 있습니다.

이 문제를 해결하려면 스마트 폰 화면의 위치를 월드 좌표로 변환 하여 그 위치에 객체를 생성하도록합니다.월드 좌표는 장면 뷰에서 객체를 배치 할 때의 위치입니다.

unity-offscreen-position
흰색 테두리가 카메라 그리기 영역입니다

실제 코드는 다음과 같습니다. (C #입니다)

적 개체를 조립식 화하고 그것을 인스펙터에서 멤버 변수로 설정해야합니다. 그 제조를 화면의 오른쪽 상단에 생성합니다.

9 번째 줄의 "1,1"어떤 부분이 각각 x 좌표, y 좌표로되어 있기 때문에 화면 밖을 원한다면는 마이너스 값을 설정하자. 예를 들면 이런 느낌입니다.

생성하는 인스턴스의 스프라이트 크기에 맞게 수치를 조정할 수 있습니다.

저작자 표시
신고
Posted by Namseungil
Game/Unity2015.03.27 12:57

using UnityEngine;
using System.Collections;

public class Swipe : MonoBehaviour
{
    // const
    public const string RIGHT = "Right";
    public const string DOWN = "Down";
    public const string LEFT = "Left";
    public const string UP = "Up";
    // delegate
    public delegate void SwapeDelegate (string way);

    public SwapeDelegate swape {
        set {
            swapeDelegate = value;
        }
    }

    private SwapeDelegate swapeDelegate;
    // variable
    private Vector2 touchStartPos;
    private bool touchStarted;
    private float minSwipeDistancePixels = 100f;

    void Update ()
    {
        if (Input.touchCount > 0) {
            var touch = Input.touches [0];
            
            switch (touch.phase) {
            case TouchPhase.Began:
                touchStarted = true;
                touchStartPos = touch.position;
                break;
            case TouchPhase.Ended:
                if (touchStarted) {
                    Swipe (touch);
                    touchStarted = false;
                }
                break;
            case TouchPhase.Canceled:
                touchStarted = false;
                break;
            case TouchPhase.Stationary:
                break;
            case TouchPhase.Moved:
                break;
            }
        }
    }

    private void Swipe (Touch touch)
    {
        var lastPos = touch.position;
        var distance = Vector2.Distance (lastPostouchStartPos);
        
        if (distance > minSwipeDistancePixels) {
            float dy = lastPos.y - touchStartPos.y;
            float dx = lastPos.x - touchStartPos.x;
            
            float angle = Mathf.Rad2Deg * Mathf.Atan2 (dxdy);
            
            angle = (360 + angle - 45) % 360;

            if (angle < 90) {
                // right
                swapeDelegate ("Left");
            } else if (angle < 180) {
                // down
                swapeDelegate ("Down");
            } else if (angle < 270) {
                // left
                swapeDelegate ("Right");
            } else {
                // up
                swapeDelegate ("Up");
            }
        }
    }
}

저작자 표시
신고
Posted by Namseungil
Game/Unity2015.03.27 12:56

using UnityEngine;
using System.Collections;

public enum FadeStatus
{
    FADEIN,
    FADE,
    FADEOUT,
    FADEOVER
}

public class FadeInOutComponent : MonoBehaviour
{
    // delegate
    public delegate void FinishDelegate ();
    private FinishDelegate fadeIn;
    private FinishDelegate fade;
    private FinishDelegate fadeOut;

    public FinishDelegate FadeIn
    {
        set {
            fadeIn = value;
        }
    }

    public FinishDelegate Fade
    {
        set {
            fade = value;
        }
    }

    public FinishDelegate FadeOut
    {
        set {
            fadeOut = value;
        }
    }

    // component
    public Color color = Color.black;
    private Color currentColor;
    // variable
    public float time = 0.4f;
    private float currentTime;
    private FadeStatus fadeStatus;

    void Start ()
    {
        currentTime = time;
        currentColor = Color.clear;
        fadeStatus = FadeStatus.FADEOVER;
    }

    void Update ()
    {
        if (fadeStatus == FadeStatus.FADEOVER) {
            return;
        }

        currentTime += Time.deltaTime;

        switch (fadeStatus) {
        case FadeStatus.FADEIN :
            currentColor = Color.Lerp (Color.clearcolorcurrentTime / time);
            break;
        case FadeStatus.FADEOUT :
            currentColor = Color.Lerp (colorColor.clearcurrentTime / (time));
            break;
        }

        if (currentTime >= time) {
            currentTime = 0;
            switch (fadeStatus) {
            case FadeStatus.FADEIN :
                fadeStatus = FadeStatus.FADE;

                if (fadeIn != null) {
                    fadeIn ();
                }
                break;
            case FadeStatus.FADE :
                fadeStatus = FadeStatus.FADEOUT;

                if (fade != null) {
                    fade ();
                }
                break;
            case FadeStatus.FADEOUT :
                fadeStatus = FadeStatus.FADEOVER;

                if (fadeOut != null) {
                    fadeOut ();
                }
                break;
            default :
                return;
            }
        }
    }

    void OnGUI ()
    {
        var gColor = GUI.color;
        GUI.color = currentColor;
        GUI.DrawTexture (new Rect (00Screen.widthScreen.height), Texture2D.whiteTexture);
        GUI.color = gColor;
    }

    public void Init ()
    {
        currentTime = 0;
        currentColor = Color.clear;
        fadeStatus = FadeStatus.FADEIN;
    }
}

저작자 표시
신고
Posted by Namseungil
Game/Unity2015.03.18 17:06

안드로이드에서 사용할 수 있는 암호화 팁

첫번째 - PlayerPref 암호화

iOS에서는 그런 이슈가 거의 없는 걸로 알고 있지만, 안드로이드에서는 많은 분들이 보안 관련 팁 또는 방법이 없는지 문의를 많이 해오셔서 준비를 해보았습니다.

여기 나온 팁들은 UNITE Seoul 2013에서 에릭 헤밍 (유니티 본사 개발자)이 발표했던 내용들 중 일부 입니다.

에릭 헤밍이 발표한 원문 ppt 자료는 아래 링크에 가시면 확인 가능합니다. http://www.slideshare.net/williamyang3910/unitekorea2013-protecting-your-android-content-21713675?from_search=1

그 중에서 암호화 관련 내용을 중점적으로 진행을 하려고 합니다.

암호화 진행 대상은 아래와 같습니다.


1. PlayerPrefs

2. 스크립트

3. 에셋

1번은 과정이 간단한 편이지만, 2번 3번은 유니티 외부에서 해야하는 작업들이 있기 때문에, 조금...복잡할 수 있습니다.


그럼 그 중에 제일 간단한 PlayerPrefs 부터 진행을 해보도록 하겠습니다.

간단히 설명부터 드리자면,


PlayerPrefs 에 값을 넣기전에 Key / Value 값의 암호화를 진행하고 값을 저장하고, 빼올 때는 거꾸로 복호화를 해서 값을 꺼내오는 방식입니다.

예제에서 제가 사용할 방법은 MD5 와 3DES(Triple DES) 라는 두 가지 암호화 방식입니다.

암호화 방식은 여러가지가 있기 때문에 입맛에 맛는 걸로 골라서 사용하시면 됩니다.


또한 C# 에서는 위의 두 가지 암호화 방식에 대해서 API를 제공합니다.
따라서 C#에서 API를 제공하는 암호화 방식을 사용하시는 걸 권장합니다.

(직접 코드로 작성을 하셔서 암호화를 하셔도 좋지만...정신 건강에 그리 좋지 않을 수도 있습니다...)


값을 저장하고 / 빼내기위해 두 가지 함수를 만들어서 사용을 할 텐데요.
SetString과 GetString 함수를 만들어서 사용을 했습니다.

코드를 보면서 설명을 드리겠습니다.

public static void SetString(string _key, string _value, byte[] _secret)  
{  
    // Hide '_key' string.  
    MD5 md5Hash = MD5.Create();  
    byte[] hashData = md5Hash.ComputeHash(System.Text.Encoding.UTF8.GetBytes(_key));  
    string hashKey = System.Text.Encoding.UTF8.GetString(hashData);  

    // Encrypt '_value' into a byte array  
    byte[] bytes = System.Text.Encoding.UTF8.GetBytes(_value);  

    // Eecrypt '_value' with 3DES.  
    TripleDES des = new TripleDESCryptoServiceProvider();  
    des.Key = _secret;  
    des.Mode = CipherMode.ECB;  
    ICryptoTransform xform = des.CreateEncryptor();  
    byte[] encrypted = xform.TransformFinalBlock(bytes, 0, bytes.Length);  

    // Convert encrypted array into a readable string.  
    string encryptedString = Convert.ToBase64String(encrypted);  

    // Set the ( key, encrypted value ) pair in regular PlayerPrefs.  
    PlayerPrefs.SetString(hashKey, encryptedString);  
          
    Debug.Log("SetString hashKey: " + hashKey + " Encrypted Data: " + encryptedString);  
}

위의 코드 내용은 SetString 함수 내부 입니다.
주석 내용을 보면 대충 감이 오시겠지만, 내용을 설명해 드리겠습니다.


먼저 인자로 받는 _key 값을 MD5 방식으로 함호화를 해서 실제로 PlayerPref에 저장할 때 이 값을 키 값으로 사용합니다.

암호화가 되었기 때문에 읽을 수 없는 값이 되겠지요.
그리고 실제 저장할 _value 값은 3DES 방식으로 암호화를 합니다.


암호화가 진행이 되면 byte[] 바이트 배열값이 결과로 나오기 때문에 저장을 위해서 문자열로 변환을 해줍니다.

그리고 PlayerPref.SetString 함수에 위에서 암호화한 _key 값과 방금 암호화된 _value 값을 저장합니다.

참고로 함수의 인자 중 마지막에 있는 _secret 값은 3DES 방식으로 암호화가 진행될 때 사용되는 값 입니다.

이 부분이 많이 궁금하신 분들은 3DES 암호화 방식을 좀 더 찾아보시면 좋을 것 같습니다.

======================================================================
이해가 안되는 코드 내용이 있는 부분은 MS의 MSDN 등에서 MD5 / TripleDES 클래스 사용방법을 찾아보시면 이해가 되실겁니다. 그대로 가져다가 사용한 것이니까요.

다른 암호화 방식을 사용하시려는 분들도 사용방법을 먼저 참고하세요.

======================================================================

이제 값을 저장했으니 반대로 빼오는 함수도 필요하겠지요?

public static string GetString(string _key, byte[] _secret)  
{  
    // Hide '_key' string.  
    MD5 md5Hash = MD5.Create();  
    byte[] hashData = md5Hash.ComputeHash(System.Text.Encoding.UTF8.GetBytes(_key));  
    string hashKey = System.Text.Encoding.UTF8.GetString(hashData);  

    // Retrieve encrypted '_value' and Base64 decode it.  
    string _value = PlayerPrefs.GetString(hashKey);  
    byte[] bytes = Convert.FromBase64String(_value);  

    // Decrypt '_value' with 3DES.  
    TripleDES des = new TripleDESCryptoServiceProvider();  
    des.Key = _secret;  
    des.Mode = CipherMode.ECB;  
    ICryptoTransform xform = des.CreateDecryptor();  
    byte[] decrypted = xform.TransformFinalBlock(bytes, 0, bytes.Length);  
          
    // decrypte_value as a proper string.  
    string decryptedString = System.Text.Encoding.UTF8.GetString(decrypted);  
          
    Debug.Log("GetString hashKey: " + hashKey + " GetData: " + _value + " Decrypted Data: " + decryptedString); 
    return decryptedString;  
}

PlayerPref.GetString 함수로 값을 빼오기 위해서는 키 값이 필요한데, 앞서 저장한 키 값은 암호화가 된 키 값이기 때문에 똑같이 암호화를 해줍니다.

_key 값은 암호화 전 키 값으로, 함수 내에서 똑같이 MD5로 암호화를 해줘야 우리가 저장할 때 사용했던 값이 나오겠죠?


그리고 암호화된 키를 이용해서 PlayerPref.GetString 함수를 이용해서 값을 빼냅니다.
문자열이 튀어 나올텐데, 저장할 때 암호화를 했기 때문에 알아볼 수 없는 값이 나올겁니다.


이 값을 앞에서 저장할 때 사용한 3DES 암호화 방식으로 복호화를 해줍니다.


이렇게 해주면 비로소 우리가 저장했던 온전한 _value 값이 나옵니다.
사용 예를 한번 보겠습니다.


void Start()  
{  
    string userName = "Ronnie Jang";  

    MD5 md5Hash = new MD5CryptoServiceProvider();  
    byte[] secret = md5Hash.ComputeHash(System.Text.Encoding.UTF8.GetBytes(userName));  

    // Game progress ( key, value ) pair.  
    string key = "test_key";  
    string _value = "Encrypt_Example";  

    // Insert ( key, value ) pair.  
    CustomFunction.SetString(key, _value, secret);  

    // Retrieve ( key, value ) pair.  
    string ret = CustomFunction.GetString(key, secret);  

    // Output.  
    Debug.Log("userName: " + userName);  
    Debug.Log(key + " : " + ret);  
}

맨 위를 보시면, “Ronnie Jang”으로 “secret” 값을 생성 합니다. 이 부분은 간단히 MD5 방식으로 암호화를 했구요. 이 값은 SetString으로 값을 저장할 때 _value 값을 암호화하는 부분에서 사용됩니다.

(위에서 작성한 SetString 함수 인자 마지막에 들어가는 값입니다.)


저장하고 값을 얻어오는 데 사용되는 키 값과 _value값은 각각 “test_key”와 “Encrypt_Example” 로 해봤습니다.


그리고 이 값들을 이용해서 CustomFunction.SetString / GetString 이 두 함수를 사용한 것이 보일 겁니다. 이 함수는 앞에서 작성한 함수들입니다.

실행 결과를 한번 보겠습니다.



먼저 빨간색 박스 두개부터 보겠습니다.

첫 번째 빨간 박스는 값을 저장할 때 암호화 된 키 값의 형태를 보여줍니다.

그리고 두 번째는 GetString 을해서 값을 얻어올 때 사용된 키 값입니다.

이 값도 역시 암호화 되어 있고, 위의 값과 똑같죠?

그리고 두번째 주황색 박스 두개인데, 이 값은 _value 값으로 암호화 되어 있습니다.

두 번째 줄에 있는 값은 PlayerPref.GetString을 하고나서 바로 찍어본 값인데, 아직은 알아볼 수가 없습니다. 보시면 알겠지만 이 값은 저장할 때 암호화 했던 값과 동일합니다.

그리고 오른쪽에 보이는 보라색 값이 바로 복호화를 끝낸 우리가 원래 저장하려고 했던 값입니다.

처음에 “Encrypt_Example” 저장한 값이 잘 나왔죠?

마지막에 보라색 박스는 점검 차원에서 한번 더 확인을 해보았습니다.

뭔가 복잡하게 지나간 듯 한데, 사실 차근차근 살펴보시면 별것 없습니다.

중요한 요점은 “PlayerPref.SetString / GetString 함수를 사용하기 전에 인자로 사용되는 key / value 값을 모두 암호화해서 사용하자” 입니다.

이 간단한 내용이 실제 코드로 옮겨진 게 위에 지금까지 설명했던 내용들 입니다.


PlayerPref가 보안에는 취약할 수 있다는 점에서 진행을 해본 예제 입니다.

지금까지 설명드린 내용은 어디까지나 여러분에게 아이디어를 드리고자 설명드린 부분이기 때문에 “응용하셔서” 사용하시는 것이 좋을 것 같습니다.

암호화 방식은 어떤걸 사용하셔도 상관이 없지만, 저장할 때 방식과 값을 얻어올 때 방식이 동일해야 한다는 점 주의하시구요.

실제로 해보시면 그리 어렵지 않습니다. 단지, 조금 더 귀찮을 뿐입니다.

그렇기 때문에 여러분께서 만들고 계신 게임에서 PlayerPref를 사용하는데 개발시간이 조금 넉넉하다 싶으시면 응용해서 사용해 보시기 바랍니다.

이런 부분 때문에 여러분의 개발기간이 길어진다면 안되니까요...

시간이 남으셨을 때 진행하시는 게 좋을 것 같습니다.

다음에 이어서 스크립트와 에셋번들 암호화에 대해서 알아보겠습니다.


출처 : http://unitystudy.net/bbs/board.php?bo_table=newwriting&wr_id=355


저작자 표시
신고
Posted by Namseungil
Game/Unity2015.02.24 18:10

Unity에서 Ui를 작성하는데 매우 편리한 NGUI 
많은 유용한 기능이 매우 도움이됩니다. NGUI의 Documentation

오늘은 UILabel의 텍스트 색상을 바꾸는 방법을 소개하고 싶습니다.

색상 코드를 지정하는

다음과 같이 NGUI의 UILabel에 색상 코드를 지정합니다. 
이번에는 빨간색을 지정합니다. 
형식은 색상 코드입니다.
label

위의 설정하면 다음과 같은 표시됩니다.
uiLavelDisp

주의 할 점은 색상 지정한 부분 이후 모든 문자가 색상이되므로 
중간 부분 만 색을 변경하려면, 문자 색상을 복원 [-] 지정을 잊지 않도록!

굵은 글씨체 (Bold)

하는 김에, 두께 문자도! 
[b] [/ b]에서 문자를 묶으면 굵은 글씨체입니다.
bold

N을 굵은 글씨체로 보았습니다.
boldDisp

그 밖에도 다양한 지정을 할 수있을 것 같네요. 
그럼 또.

출처 : http://bribser.co.jp/blog/colorbold/


저작자 표시
신고
Posted by Namseungil

티스토리 툴바