슈팅게임으로 만들었어요.

소스 다운로드
강의자료

요즘 집필중이라서 정신이 하나도 없습니다. 
부실한 자료지만 참고하시기 바랍니다.


2017/01/04 15:30 2017/01/04 15:30

많은 인디 개발자 또는 프로개발자 조차도 모바일 UX를 지키지 않고 그냥 개발하고 있어 안타깝더군요.

PC 마우스 UX의 경우는

- Hover In / Out
- Click
- Double Click 

이렇게 이벤트를 먹이지만, 

모바일의 경우는 
- Pressed 
- Unpressed 로

- Press 됐을때 버튼을 확대하고
- 만약 사용자가 버튼 영역을 벗어나서 손을 떼면 원상복귀되면서 명령이 취소됩니다.
- 만약 사용자가 버튼 영역을 벗어나지 않고 손을 떼면 원상복귀되면서 명령이 실행됩니다.

일전에 작성했던 이벤트 통합하기로 구현하였으니 참고하시고 다음 소스를 보시면 이해가 쉬우실거 같네요.

참고 링크 : http://www.wolfpack.pe.kr/909


먼저 버튼을 2개 만들어 놓으시고 이벤트 통합용 스크립트는 다음과 같습니다.

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

public class UIManager_Intro : MonoBehaviour {

    const float workTerm = 0.2f;

    public static UIManager_Intro instance;
    public void Awake()
    {
        UIManager_Intro.instance = this;
     }

     private void SetScaleUp(GameObject _obj, bool isPressed)
     {
         if (isPressed)
         {
             iTween.ScaleTo(_obj, new Vector3(1.1f, 1.1f, 1.1f), workTerm);
         }
         else
         { 
             iTween.ScaleTo(_obj, new Vector3(1f, 1f, 1f), workTerm);
         }
     }

     private bool DetectHoverSameObject(GameObject _obj)
     {
         RaycastHit hit;
         Ray ray = NGUITools.FindCameraForLayer(_obj.layer).ScreenPointToRay(Input.mousePosition);
         if (Physics.Raycast(ray, out hit, Mathf.Infinity))
         {
             return (hit.collider.gameObject == _obj);
         }
         return false;
     }

     public void Button1_OnPress(GameObject _obj, bool isPressed)
     {
         SetScaleUp(_obj, isPressed);
         if (!isPressed && DetectHoverSameObject(_obj)) StartCoroutine(LoadingScene("usingAnchor"));
    }
 
    public void Button2_OnPress(GameObject _obj, bool isPressed)
     {
         SetScaleUp(_obj, isPressed);
         if (!isPressed && DetectHoverSameObject(_obj)) StartCoroutine(LoadingScene("inputField"));
     }

     IEnumerator LoadingScene(string _sceneName)
     {
         yield return new WaitForSeconds(workTerm);
         SceneManager.LoadScene(_sceneName);
     }
}


버튼1에만 우선 적용합니다. 스크립트는 다음과 같습니다. 
using UnityEngine;
using System.Collections;

public class Behavior_Button1 : MonoBehaviour {

    void Start () {
        UIEventListener.Get(gameObject).onPress += UIManager_Intro.instance.Button1_OnPress;
    }
}



결과 1. 마우스를 클릭하여 Press 시뮬레이팅
사용자 삽입 이미지
결과2. 취소 (드레그하여 다른곳에서 손 뗌)
사용자 삽입 이미지
결과 3. 명령실행 (원상복귀후 다른씬으로 이동)
사용자 삽입 이미지


2016/09/22 09:23 2016/09/22 09:23
간만에 유니티3D 관련 글을 쓰네요.

몇 번의 프로젝트에서 사용했던 티끌 같은 팁인데 많은 분들이 모르시는 것같아 소스 하나 남겨둡니다.

using UnityEngine;
using System.Collections;

public class Test : MonoBehaviour {

    // Use this for initialization
    IEnumerator Start () {

        //for Lambda
        StartCoroutine(ReturnValue( (x) => 
        {
            Debug.Log(x);
        } 
        ));

         //for local value
        int a = 0;
        yield return StartCoroutine(ReturnValue( (x) => 
        {
            a = x;
        }
        ));
        Debug.Log(a);
    }
 
    IEnumerator ReturnValue(System.Action<int> callback)
    {
        yield return null;
        callback(10);
    }
}


핵심은 Lambda 식으로 callback 함수를 구현하는 겁니다. =)
로컬 변수에 값을 할당할때는 반듯이 yield return 명령으로 Coroutine 함수가 종료되고나서 값이 할당됐는지 확인되어야 합니다. (그럼 코루틴 쓰는 의미가.. -_-;;)


2016/04/04 17:00 2016/04/04 17:00
일전에 Mesh생성하는 방법에 대해 써 놓은 적이 있는데,

Link : http://www.wolfpack.pe.kr/853

문득 Hexa로 만드는것도 가능하지 않을까? 고민했습니다.
그래서, 만들어 봤습니다.

먼저 Mesh 만든 절차를 그대로 따라 했습니다.
1. 좌표를 Vector3 array로 만든다.
* Mesh의 경우는 단 4개의 Vertex로 구성되면 되기 때문에, 좌표는 총 4개를 만들면 되죠.
예를 들어, pivot point가 Center에 있는 4각의 Mesh라면,
(-1, -1, 0), (-1, 1, 0), (1, 1, 0), (1, -1, 0) >> 각각 0, 1, 2, 3 번 이라고 합시다.

2. Polygon Array를 만듭니다. 하나 중요한건 Unity3D는 왼손 좌표계이므로 Surface가 -Z축에서 보여지려면 시계방향으로 Array를 생성해야 합니다.
* 0, 1, 2, 0, 2, 3 / 3개씩 잘라서 보면 0,1,2 폴리곤과 0, 2, 3 폴리곤 2개가 있어, 4각으로 보이죠.

자세한건 위의 Link 참조바랍니다.

Hexa의 경우는 간단히 다음과 같이 하면 되겠지하고 덤볐다가.. -_-;; 개피봤습니다.
사용자 삽입 이미지
1개 일때는 문제없이 0, 1, 3, 1, 2, 3, 0, 3, 4, 0, 4, 5 로 폴리곤 셋을 잘라주면 되는데.. 2개 이상만 되도 일정한 규칙을 발견하기 어려웠습니다.
사용자 삽입 이미지
슬슬 멘붕오기 시작하지요. 여기에 Y축까지 추가되면...
사용자 삽입 이미지
규칙은 사라지고 Chaos 만 남습니다. -_-;;
만약, Pivot이 Center에 있다면? 이라는 질문을 하게 되었고 Vetex를 정리해보니 다음과 같이 그런대로 규칙이 생깁니다.
사용자 삽입 이미지
정리하자면 2*2짜리 Hexa는 가로로 n개의 Vertex를 가집니다.
이걸 수식으로 정리하면 Hexa갯수가 x라면 Vertex수는
1 - 3
2 - 5
3 - 7
4 - 9
n - 2 * 헥사갯수 + 1

입니다만, Hexa는 항상 위에서 보는바와 같이 왔다 갔다 하므로 Y축 확장을 위해 우로 Vertex열이 1줄 더 있어야 하고 좌로도 1줄 더 있어야 하는 상황이 됩니다.

그리고, 또 하나 만난 문제는 저렇게 Vertex를 배열하는게 문제였습니다.
6각형은 60도로 나누어져 있는데 소숫점 오차로 인해 제대로 배열이 안되는것도 문제여서...
그냥 다음과 같이 간단히 일렬로 배열한뒤에...
사용자 삽입 이미지
홀수 열만 0.5정도 올려주면...
사용자 삽입 이미지
근사하게 육각으로 바뀌는 군욤. ㅋ

* 여기까지 코드는 다음과 같습니다.

using UnityEngine;
using System.Collections;
using System.Collections.Generic;

public class Hexa : MonoBehaviour {

    private Vector3[] vertexs; 

    void Awake()
    {
        gameObject.AddComponent<MeshFilter>();
        gameObject.AddComponent<MeshRenderer>();
    }

    void Start () {
        SetVertex(5, 6);
    }
    
    void SetVertex(int xSize, int ySize)
    {
        //사전처리 부분입니다. 예외처리를 위해 가로 헥사수는 2이상, 세로 헥사수는 짝수로 고정합니다.
        if (xSize < 2) xSize++; //가로 헥사수, 예외 처리를 위해 2이상으로 강제 처리
        if (ySize % 2 == 1) ySize++; //세로 헥사수  //예외 처리를 위해 짝수로 강제 처리

        // 가로와 세로의 버텍스 수를 구합니다.
        int xDotSize = 2 * xSize + 2; //가로 버텍스 수
        int yDotSize = (ySize % 2 == 0) ? (3 * ySize / 2) + 1 : 3 * (ySize / 2 + 1); //세로 버텍스 수

        //For 문으로 자동으로 찍되 홀수 줄은 0.5f만큼 올립니다.
        MeshFilter MF = GetComponent<MeshFilter>();
        vertexs = new Vector3[xDotSize * yDotSize];
        for (int i = 0, y = 0; y > yDotSize * -1 ; y--)
        {
            for (int x = 0; x < xDotSize; x++, i++)
            {
                vertexs[i] = (x % 2 == 0) ? new Vector3(x, 0f, y) : new Vector3(x, 0f, y + 0.5f);
            }
        }
        MF.mesh.vertices = vertexs;
    }

    private void OnDrawGizmos()
    {
        if (vertexs == null) return;
        Gizmos.color = Color.red;
        for (int i = 0; i < vertexs.Length; i++)
            Gizmos.DrawSphere(vertexs[i], 0.1f);
    }
}


이제 문제는 Center를 찾는 겁니다. 의외로 어렵게 해결된 부분인데,
먼저 좌변의 Center Point들을 먼저 찾고 거기에 가로로 +2를 반복하였습니다.
그 이유는 다음의 그림에서 처럼 첫 Center는 2번째, 2번째 Center는 5번째... 이런식으로 나타 났고 이를 위해 수열 공식 찾느라 오래 걸렸습니다.
사용자 삽입 이미지
암튼 위의 결과를 보인 소스는 다음과 같습니다.

        //SetVertex() 함수 밑부분에 넣었습니다.
        //Find Center
        CenterP = new Vector3[xSize * ySize];
        for (int idx =0, y = 0; y < ySize; y++) {
            int verticesIdx = 0;
            verticesIdx = xDotSize * ((y / 2) + 1 + y) + 1 + (y % 2); //버텍스인덱스는 가로크기* ((y / 2) + 1 + y) + 1 + (y % 2), y는 세로변수 y
            CenterP[idx] = vertexs[verticesIdx];
            CenterIdx.Add(verticesIdx);
            idx++;
            for (int x = 1; x < xSize; x++)
            {
                CenterP[idx] = vertexs[verticesIdx + 2 * x]; //세로에서 구한 센터값에 +2 반복
                CenterIdx.Add(verticesIdx + 2 * x);
                idx++;
            }
        }

좀 복잡하지만, CenterP는 OnGizmo에서 디버깅 용으로 만든 변수이고, 실재 사용하는 변수명은 CenterIdx입니다.
클래스 변수는 다음과 같이 위의 2개 변수를 추가했구요.

    private Vector3[] vertexs;
    private Vector3[] CenterP;
    private List<int> CenterIdx = new List<int>();


마지막으로 OnGizmo를 다음과 같은 최종형태로 만들었습니다.
    private void OnDrawGizmos()
    {
        if (vertexs == null) return;
        Gizmos.color = Color.red;
        for (int i = 0; i < vertexs.Length; i++)
            Gizmos.DrawSphere(vertexs[i], 0.1f);

        if (CenterP == null) return;
        Gizmos.color = Color.yellow;
        for (int i = 0; i < CenterP.Length; i ++ )
            Gizmos.DrawSphere(CenterP[i], 0.1f);

    }


마지막으로 Vertex 연결 순서는 짝수 열과 홀수 열이 조금 다르지만, 정리하면,

                //총 6개의 폴리곤이 있어야 6각형이 나오므로...
                triangles[idx] = CenterIdx[x];
                triangles[idx + 1] = CenterIdx[x] - 1;
                triangles[idx + 2] = CenterIdx[x] - xDotSize;
                
                triangles[idx + 3] = CenterIdx[x];
                triangles[idx + 4] = CenterIdx[x] - xDotSize;
                triangles[idx + 5] = CenterIdx[x] + 1;
                
                triangles[idx + 6] = CenterIdx[x];
                triangles[idx + 7] = CenterIdx[x] + 1;
                triangles[idx + 8] = CenterIdx[x] + xDotSize + 1;

                triangles[idx + 9] = CenterIdx[x];
                triangles[idx + 10] = CenterIdx[x] + xDotSize + 1;
                triangles[idx + 11] = CenterIdx[x] + xDotSize;
                
                triangles[idx + 12] = CenterIdx[x];
                triangles[idx + 13] = CenterIdx[x] + xDotSize;
                triangles[idx + 14] = CenterIdx[x] + xDotSize - 1;
                
                triangles[idx + 15] = CenterIdx[x];
                triangles[idx + 16] = CenterIdx[x] + xDotSize - 1;
                triangles[idx + 17] = CenterIdx[x] - 1;

위의 코드를 좀 짧게 축약하면 다음과 같습니다.

                triangles[idx] = triangles[idx + 3] = triangles[idx + 6] = triangles[idx + 9] = triangles[idx + 12] = triangles[idx + 15] = CenterIdx[x];
                triangles[idx + 1] = triangles[idx + 17] = CenterIdx[x] - 1;
                triangles[idx + 2] = triangles[idx + 4] = CenterIdx[x] - xDotSize;
                triangles[idx + 5] = triangles[idx + 7] = CenterIdx[x] + 1;
                triangles[idx + 8] = triangles[idx + 10] = CenterIdx[x] + xDotSize + 1;
                triangles[idx + 11] = triangles[idx + 13] =  CenterIdx[x] + xDotSize;
                triangles[idx + 14] = triangles[idx + 16] = CenterIdx[x] + xDotSize - 1;


이제 이걸 돌려 보면... 두둥..
사용자 삽입 이미지
전체 코드는 다음과 같습니다.

using UnityEngine;
using System.Collections;
using System.Collections.Generic;

public class Hexa : MonoBehaviour {

    private Vector3[] vertexs;
    private Vector3[] CenterP;
    private List<int> CenterIdx = new List<int>();

    void Awake()
    {
        gameObject.AddComponent<MeshFilter>();
        gameObject.AddComponent<MeshRenderer>();
    }

    // Use this for initialization
    void Start () {
        SetVertex(5, 6);
    }
    
    void SetVertex(int xSize, int ySize)
    {
        if (xSize < 2) xSize++; //x 헥사수
        if (ySize % 2 == 1) ySize++; //y 헥사수
        int xDotSize = 2 * xSize + 2; //가로 버텍스 수
        int yDotSize = (ySize % 2 == 0) ? (3 * ySize / 2) + 1 : 3 * (ySize / 2 + 1); //세로 버텍스 수
        MeshFilter MF = GetComponent<MeshFilter>();
        vertexs = new Vector3[xDotSize * yDotSize];
        for (int i = 0, y = 0; y > yDotSize * -1 ; y--)
        {
            for (int x = 0; x < xDotSize; x++, i++)
            {
                vertexs[i] = (x % 2 == 0) ? new Vector3(x, 0f, y) : new Vector3(x, 0f, y + 0.5f);
            }
        }
        MF.mesh.vertices = vertexs;

        //Find Center
        CenterP = new Vector3[xSize * ySize];
        for (int idx =0, y = 0; y < ySize; y++) {
            int verticesIdx = 0;
            verticesIdx = xDotSize * ((y / 2) + 1 + y) + 1 + (y % 2);
            CenterP[idx] = vertexs[verticesIdx];
            CenterIdx.Add(verticesIdx);
            idx++;
            for (int x = 1; x < xSize; x++)
            {
                CenterP[idx] = vertexs[verticesIdx + 2 * x];
                CenterIdx.Add(verticesIdx + 2 * x);
                idx++;
            }
        }

        int[] triangles = new int[18 * (xSize * ySize)];

        for (int idx = 0, x = 0; x < CenterIdx.Count; x++)
        {
            if ((x / xSize) % 2 == 0)
            {
                triangles[idx] = CenterIdx[x];
                triangles[idx + 1] = CenterIdx[x] - xDotSize - 1;
                triangles[idx + 2] = CenterIdx[x] - xDotSize;

                triangles[idx + 3] = CenterIdx[x];
                triangles[idx + 4] = CenterIdx[x] - xDotSize;
                triangles[idx + 5] = CenterIdx[x] - xDotSize + 1;

                triangles[idx + 6] = CenterIdx[x];
                triangles[idx + 7] = CenterIdx[x] - xDotSize + 1;
                triangles[idx + 8] = CenterIdx[x] + 1;

                triangles[idx + 9] = CenterIdx[x];
                triangles[idx + 10] = CenterIdx[x] + 1;
                triangles[idx + 11] = CenterIdx[x] + xDotSize;

                triangles[idx + 12] = CenterIdx[x];
                triangles[idx + 13] = CenterIdx[x] + xDotSize;
                triangles[idx + 14] = CenterIdx[x] - 1;

                triangles[idx + 15] = CenterIdx[x];
                triangles[idx + 16] = CenterIdx[x] - 1;
                triangles[idx + 17] = CenterIdx[x] - xDotSize - 1;

            }
            else
            {
                triangles[idx] = CenterIdx[x];
                triangles[idx + 1] = CenterIdx[x] - 1;
                triangles[idx + 2] = CenterIdx[x] - xDotSize;
                
                triangles[idx + 3] = CenterIdx[x];
                triangles[idx + 4] = CenterIdx[x] - xDotSize;
                triangles[idx + 5] = CenterIdx[x] + 1;
                
                triangles[idx + 6] = CenterIdx[x];
                triangles[idx + 7] = CenterIdx[x] + 1;
                triangles[idx + 8] = CenterIdx[x] + xDotSize + 1;

                triangles[idx + 9] = CenterIdx[x];
                triangles[idx + 10] = CenterIdx[x] + xDotSize + 1;
                triangles[idx + 11] = CenterIdx[x] + xDotSize;
                
                triangles[idx + 12] = CenterIdx[x];
                triangles[idx + 13] = CenterIdx[x] + xDotSize;
                triangles[idx + 14] = CenterIdx[x] + xDotSize - 1;
                
                triangles[idx + 15] = CenterIdx[x];
                triangles[idx + 16] = CenterIdx[x] + xDotSize - 1;
                triangles[idx + 17] = CenterIdx[x] - 1;
            }
            idx += 18;
        }

        MF.mesh.triangles = triangles;
        MF.mesh.RecalculateNormals();

    }

    private void OnDrawGizmos()
    {
        if (vertexs == null) return;
        Gizmos.color = Color.red;
        for (int i = 0; i < vertexs.Length; i++)
            Gizmos.DrawSphere(vertexs[i], 0.1f);

        if (CenterP == null) return;
        Gizmos.color = Color.yellow;
        for (int i = 0; i < CenterP.Length; i ++ )
            Gizmos.DrawSphere(CenterP[i], 0.1f);

    }
}


그럼~ 새해 복 많이 받으세요!
2016/02/09 17:17 2016/02/09 17:17
1. 먼저 쉐이더를 다음과 같이 입력하자.

Shader "TextureMask"
{
 Properties
 {
 _Mask("Culling Mask", 2D) = "white" {}
 }
 SubShader
 {
 Tags{ "Queue" = "Background" }
 Blend SrcAlpha OneMinusSrcAlpha
 Lighting Off
 ZWrite On
 ZTest Always
 Alphatest LEqual 0
 Pass
 {
 SetTexture[_Mask]{ combine texture }
 }
 }
}


2. Material 하나 만들어서 TextureMask 로 설정하자.
사용자 삽입 이미지
3. 인스펙터에서 다음과 같이 설정해주자.
사용자 삽입 이미지

3. 박스와 Quad를 생성하여 박스와 카메라 사이에 Quad가 위치하도록 한다.

4. Quad에다 위에서 생성한 메터리얼을 Drag&drop하거나, Texture를 Drag&drop 한후에 Shader를 TextureMask 로 설정한다

5. 끝.
사용자 삽입 이미지
* 블랙영역은 컬링되어 렌더링 되지 않는다. =)
2015/10/22 17:56 2015/10/22 17:56
NGUI에서 사소한 문제(?)는 한글 폰트 사용시 UI Atlas 메터리얼과 Font의 메터리얼이 2개 올라오면서 Drawcall이 1이 아니라 2로 잡히는 문제가 있다.
(Drawcall 줄이려고 뻘짓을 다 해하는 최적화 더쿠에게는 이러한 불편함도 참을 수 없는 것이다!)
이번에는 Font Data와 UI Sprite Atlas를 Texture Packer 로 합쳐보자.
(물론 NGUI Atlas도 불가능한 건 아니겠지만...)

1. BMFONT로 1024 사이즈 한장에 폰트를 다 뽑아주자.
* 상세내용은 http://www.wolfpack.pe.kr/806

2. Texture Packer에 다음과 같이 배치하자.
사용자 삽입 이미지

3. Texture Packer에서 Export하자.
사용자 삽입 이미지

4. Unity3D로 파일을 복사해 놓자.
사용자 삽입 이미지

5. Texture 파일을 다음과 같이 수정하자.
   * Font 파일을 만들 예정이므로 Read/Write Enabled 반듯이 체크!
사용자 삽입 이미지

6. Material을 생성하여 Texture를 추가하자.
   * 같은 폴더에 Material을 생성하고
사용자 삽입 이미지

   * CardUI라는 이름으로 변경, Shader를 Ulite/Transparent Colored 로 변경한 후, Texture를 Drag&drop으로 끌어다 놓았다.
사용자 삽입 이미지

7. 빈 게임오브젝트를 "Atlas"라는 이름으로 만들고 Atlas Script를 Attach하자.
   * 하이라키 탭에서 마우스 우클릭하면 Create Empty 메뉴가 보인다. 이걸 클릭하자.
사용자 삽입 이미지

   * 빈 게임오브젝트가 만들어지면 Atlas라는 이름으로 이름을 변경한다.
사용자 삽입 이미지

   * Component > NGUI > UI > Atlas를 클릭하여 Atlas 게임 오브젝트에 Script를 Attach 하자.
사용자 삽입 이미지

   * Script가 Attach된 모습
사용자 삽입 이미지

   * 아까 만든 Material을 "Material" 속성에 Drag&Drop하자.
사용자 삽입 이미지

   * TP Import라는 속성이 활성화 되면 여기에 "Texture Packer"에서 생성한 Data가 담긴 Text 파일을 Drag&Drop하자.
사용자 삽입 이미지

8. 만들어진 Atlas 게임 오브젝트를 Prefab으로 변환하자. (하이라키 탭에서 Project 탭으로 Drag&Drop하면 자동으로 Prefab으로 변한다.)
사용자 삽입 이미지

9. 빈 게임오브젝트를 "Font"라는 이름으로 만들고 UI Font Script를 Attach하자.
    * Atlas를 만들 때와 같이 빈 게임 오브젝트를 생성하고 이름을 Font로 바꾼다.
    * Component > NGUI > UI > NGUI Font 클릭!
사용자 삽입 이미지

   * UI Font Script가 Attach된 모습
사용자 삽입 이미지

   * Atlas 속성에 아까 만들어 놓은 "Atlas" 게임 오브젝트를 Drag&Drop 한뒤에...
사용자 삽입 이미지

   * Import Data 속성에 BMFont에서 만들어진 Data Text File을 Drag&drop으로 추가하면 끝!
사용자 삽입 이미지


10. 만들어진 Font 게임 오브젝트를 Prefab으로 변환하자.
    * 만드는 방법은 8번과 동일!

테스트!
사용자 삽입 이미지
* 카메라를 키면 올라가는 1과 폰트 Drawcal 1,  Sprite Drawcall 1 총 3개가 잡히던 Drawcall이 2로 줄어 들었다.


2015/10/06 04:27 2015/10/06 04:27
패친이신 Seungjee Baek 님이 알려주신 방법대로 BMFONT에서 RGB에 폰트 값을 넣고 Alpha를 재활용해서 처리하는 방식으로 해봤다.
이름하여 Alpha8bit로 굽기.

1. BM Font에서 다음과 같이 Font 설정 한다.
사용자 삽입 이미지
2. Export 설정을 다음과 같이 하되 포인트는 "White with Alpha"로 설정한다.
사용자 삽입 이미지

3. Export한 Texture를 Import 한 후에  Inspector에서 다음과 같이 설정한다. 중요한건 Format을 Alpha8로 설정하는 것이다.
(4MB짜리 텍스쳐가 1MB로 줄어드는 매직!)
사용자 삽입 이미지
4. 하지만, 쉐이더를 손보지 않고 NGUI Font Maker로 폰트를 만들어보면 이렇게 안나온다. OTL..
사용자 삽입 이미지
5. Material 쉐이더 속성을 NGUI에서 제공하는 Unlit/Text 로 설정하면... 
사용자 삽입 이미지
6. 짜잔~! (이팩트가 먹는다!)
사용자 삽입 이미지

* 이 방법은 2마리 토끼를 절충하는 방법 같다.
지금까지 내용을 정리하자면, 

1. 용량관점에서 "Packed Font" > "Alpha8" > "일반폰트"
2. 이팩트 설정등의 관점에서 "일반폰트" = "Alpha8" >>>넘사벽>>> "Packed Font" 

-_-a 상황에 맞춰서 사용하자.
2015/08/30 17:58 2015/08/30 17:58
BMFONT로 Font를 뽑으면 데이터가 커진하고 믿겠지만, RGBA 4개 채널에 폰트데이터를 넣으면 용량이 매우 작아진다.
단적인 예로 우리가 흔히 쓰는 나눔폰트나 Noto 폰트의 경우 각각 용량이 4M, 15~16M에 달한다.

* 나눔폰트 용량
사용자 삽입 이미지
* Google Noto sans CJK Kor 
사용자 삽입 이미지

그런데, Packed Font로 뽑으면...
사용자 삽입 이미지
1024*1024 텍스쳐 한장으로 딸랑 782KB 이다. 
(주의 : 유니티에 Import 하는순간 True Color Texture는 1024에서 4M가 소요된다. 하지만, 512라면 1M에 불과해진다. 폰트사이즈 32정도면 512에 떨어지니 실망하지 말자)
특히, Font 파일을 앱속에 탑재함으로써 발생하는 기타 여러 잡다한 문제도 없고 형좋고 매부 좋고, 암튼 사용하는 방법에 대해 알아보자.

먼저, BM Font 설치하고 한글 파일을 다운로드받자.

- BMFONT : http://www.angelcode.com/products/bmfont/
- 한글텍스트파일 : http://www.wolfpack.pe.kr/attachment/1142498515.txt

이제 BM Font를 실행하고 다음과 같이 설정한다.
* 자세한 BM Font 사용법은 http://www.wolfpack.pe.kr/806 

1. 폰트 셋팅에서 Size를 62정도 입력했을때 1024사이즈에서 공간이 남았었다. (Super Sampling을 켜서 폰트가 이쁘게 나오게 하자.)
사용자 삽입 이미지
2. 익스포트 옵션에서 Bit Depth를 32로 변경하고 "Pack chars in multiple channels" 옵션을 켜자.
사용자 삽입 이미지
3. 이제 익스포트하고 Unity3d로 옮겨 놓자.
사용자 삽입 이미지
4. Texture Import 옵션을 다음과 같이 Advance로 바꾼후 변경하자.
사용자 삽입 이미지
5. NGUI Font Maker를 열어서 폰트를 만든다.
사용자 삽입 이미지
6. 파일이 생성된 모습. 하지만, 하나 더 남았다.
사용자 삽입 이미지
7. NotoFont 메터리얼을 Inspector 창에서 확인하면 쉐이더가 "Unlit/Transparent Colored"로 되어 있을텐데 이걸 "Unlit/Transparent Packed"로 변경해야 한다.
사용자 삽입 이미지
8. 적용결과
사용자 삽입 이미지

* 물론 Dynamic Font로 적용하면 편하다! 하지만, Texture Resize 될때 종종 스냅드래곤을 채용한 핸드폰에서 폰트가 사라지거나 별별 문제가 다 발생하고 그거 잡는 시간에 BMFont로 뽑는게 더 빠르다.
* Packed Font는 외곽 효과라던가... 이팩트 먹이기가 다음과 같이 잘 안된다. 
사용자 삽입 이미지


2015/08/28 17:11 2015/08/28 17:11
유니티가 지뢀같은건 아마도 많은 개발자들이 느끼겠지만, 소스 관리이다.

기본적으로 유니티는 GameObject라는 껍데기에 Transform / Sound / Animator 등의 Components 구조로 되어 있다보니..
UI 개발시 필연적으로 이런 모습의 UI를 만나면 신나게 개발해놓고 간혹 사운드를 바꿔 달라던가... 액션을 바꿔달라면 개 노가다를 해야 하는 문제가 생긴다.

* 예시 : ArmyAttack2
사용자 삽입 이미지
(저 수많은 버튼들을 보라... 죽음이 기다리고 있다!)

몇 번의 프로젝트에서 출시전 뜯어고치기 노가다 하다가 발견한 방법은 NGUI의 이벤트 핸들러를 직접 컨트롤 하는 것이다.

1. 먼저 UI를 만들고 Sprite 과 Label을 올려서 Button을 만든다.
사용자 삽입 이미지
* Collider를 반드시 붙여 주자.
사용자 삽입 이미지
* Label은 Font만 정의하였다.
사용자 삽입 이미지
* 최종 형태는 다음과 같다.
사용자 삽입 이미지

2. 공통 관리 Class를 다음과 같이 코딩해서 UI Root에 붙여 주자.
using UnityEngine;
using System.Collections;

public class UIManager : MonoBehaviour {

//싱글톤 선언 시작
 public static UIManager instance;
 public void Awake()
 {
 UIManager.instance = this;
 }
//싱글톤 선언 끝

//버튼별 이벤트 Method
 public void SetClickButton(GameObject _obj)
 {
 Debug.Log("Hello World!");
 Debug.Log(_obj.transform.name + " is Click!");
 }
//끝

}


3. 마지막으로 Button에 이벤트를 등록하는 Script를 만들고 Sprite 에 붙여주자.
using UnityEngine;
using System.Collections;

public class Behavior_Button1 : MonoBehaviour {


 void Start() {
 UIEventListener.Get(gameObject).onClick += UIManager.instance.SetClickButton;
 }

}

 

실행결과 버튼을 클릭하면 해당 문자가 출력된다.

이렇게 만드는 이유는 씬에 많은 버튼을 한꺼번에 코드로 관리하기 위함이다.

* 참고 이벤트를 받는 Method의 Arguments 값은 다음과 같이 UIEventListener에 정의되어 있다.

void OnSubmit () { if (isColliderEnabled && onSubmit != null) onSubmit(gameObject); }
void OnClick () { if (isColliderEnabled && onClick != null) onClick(gameObject); }
void OnDoubleClick () { if (isColliderEnabled && onDoubleClick != null) onDoubleClick(gameObject); }
void OnHover (bool isOver) { if (isColliderEnabled && onHover != null) onHover(gameObject, isOver); }
void OnPress (bool isPressed) { if (isColliderEnabled && onPress != null) onPress(gameObject, isPressed); }
void OnSelect (bool selected) { if (isColliderEnabled && onSelect != null) onSelect(gameObject, selected); }
void OnScroll (float delta) { if (isColliderEnabled && onScroll != null) onScroll(gameObject, delta); }
void OnDragStart () { if (onDragStart != null) onDragStart(gameObject); }
void OnDrag (Vector2 delta) { if (onDrag != null) onDrag(gameObject, delta); }
void OnDragOver () { if (isColliderEnabled && onDragOver != null) onDragOver(gameObject); }
void OnDragOut () { if (isColliderEnabled && onDragOut != null) onDragOut(gameObject); }
void OnDragEnd () { if (onDragEnd != null) onDragEnd(gameObject); }
void OnDrop (GameObject go) { if (isColliderEnabled && onDrop != null) onDrop(gameObject, go); }
void OnKey (KeyCode key) { if (isColliderEnabled && onKey != null) onKey(gameObject, key); }
void OnTooltip (bool show) { if (isColliderEnabled && onTooltip != null) onTooltip(gameObject, show); }


2015/08/26 17:56 2015/08/26 17:56
게임의 재미와 게임을 어떻게 만들어야 하는지에 대한 고민을 저도 모르게 오늘 컨설팅을 진행하고 난 후에 보고서를 쓰며 나와버리고 말았다.

= 전략 =

거의 모든 게임은 생존에 대한 위협으로 인한 "공포"와 그것을 극복했을때의 "시원함"임
. 첫째, 공포는 적이 질적 또는 양적, 아니면 둘다가 압도적일때
. 둘째, 적으로부터 심리적 위협이 인지될때
. 셋째, 라스트 찬스일때
. 넷째, 의도적으로 느리지만 한방에 골로 가는 상황일때

또한, 게임은 core Loop에서는 "준비" => "시도"("정보인식" => "전력적 의사결정" => "행위" => "정보인식") => "보상" => "준비"의 Loop
. 정보인식이 실패한 경우 "어? 거기 있었어?" 학습후 "재시도"
. 올바른 정보인식이나 전략적 의사결정이 실패한 경우 "앗!"하는 학습후 "재시도"
. 올바른 정보인식, 올바른 전략적 의사결정후 "행위"가 실패한 경우 "손가락 짤라버리 싶은" 후회후 "재시도"
. 이후 기대한 결과에 충족하지 않으면(버그, 반응 더딤 등) "개발자 병신" 쌍욕하고 포기

= 후략 =

표현이 거칠기는 하나, 그리고 이러한 주장을 한다고 알아주는 사람은 없겠지만 (지랄하네? 하는 인간들은 많겠다)
게임공부한지 6년정도 삽질과 삽질의 연속에서 무언가 손에 잡힌 느낌이랄까?

-_-a
2015/08/26 03:54 2015/08/26 03:54