Tag Archives: Unity

Unity/ 셰이더/ Surface Shader 작성법

Surface Shader

Vertex-Fragment Shader보다 좀 더 높은 수준에서 작성되는 셰이더. Vertex-Fragment 에서 번거롭고 복잡하게 작성해야 하는 것 –라이팅 계산 등– 들을 프로그램 내부에서 자동으로 처리하고, 프로그래머는 직관적인 속성들만 제어하면 되도록 정의되어 있다. –물론 그 자동으로 처리되는 부분을 커스터마이징 하고자 한다면 할 수 있다.

Surface Shader는 유니티에서 주로 사용하는 셰이더이다. 처음 셰이더 파일을 만들면 Default 로 들어가 있는 코드가 서피스 셰이더로 작성되어 있음.

파이프라인


http://www.alanzucconi.com/2015/06/17/surface-shaders-in-unity3d/

Surface Shader에서는 주로 Surface Function을 이용하여 재질을 제어하는 코드를 작성하지만, 필요하다면 Vertex를 제어하거나 Lighting을 제어하는 코드도 만들 수 있다. 위 이미지는 그 3가지 프로세스의 순서를 나타내고 있다.

Continue reading

Unity/ 셰이더/ Vertex-Fragment Shader 기본 작성법

Vertex-Fragment Shader

Vertex Shader + Fragment Shader. 절차상 2개이지만 보통 함께 쓰이기 때문에 Vertex-Fragment Shader라고 한다. 참고로 HSLS(DirectX)에서는 Fragment 대신 Pixel이라는 표현을 쓴다.

유니티에서는 Surface Shader를 주로 사용하지만, 이전의 DirectX 기반으로 게임을 만들 때는 Vertex-Pixel Shader를 주로 사용하였다.

파이프라인


https://www.khronos.org/opengl/wiki/Rendering_Pipeline_Overview

Continue reading

Unity/ 셰이더/ Fixed Function Shader 기본 작성법

Fixed Function Shader

고정 함수 셰이더. 이름 그대로 고정되어 있기 때문에 커스터마이징이 불가능하지만, 그 반대 급부로 처리 속도가 빠르고 낮은 성능의 기기에서도 잘 동작한다는 장점이 있다. 보다 정확한 특징은 아래 참조.

  • 버텍스당 라이팅 연산이 이루어진다.
  • 커스터마이징이 불가능하다
  • 처리 속도가 빠르다.
  • 구형 디바이스에서도 잘 동작한다.
  • 작성이 간편하다.

성능 상 좋지만 고급 효과를 구성하기에는 한계가 있으므로 보통은 잘 쓰지 않는다. 이 페이지에서는 간단한 내용만 이해하고 넘어가기로 한다.

Continue reading

Unity/ 셰이더/ 기본 작성법

셰이더란

프로그래머가 자신이 원하는 그래픽 효과를 구현하기 위해 GPU에게 명령하는 것. GPU는 그래픽 데이터를 처리하는 파이프라인 중간에 프로그래머의 명령을 받아 효과를 반영한다.

셰이더의 종류 자체는 여러가지인데 유니티에서는 Fixed Function Shader, Vertex-Fragment Shader, Surface Shader를 많이 사용한다. –이 외에 Geometry Shader 같은 것도 있지만 잘 쓰이지 않으니 생략.

셰이더를 작성하는 언어도 여러가지인데, OpenGL에서 사용하는 GLSL과 DirectX에서 사용하는 HLSL, NVIDIA에서 개발한 CG 등이 있다. 유니티에서는 보통 CG를 이용하여 작성하는데, CG가 OpenGL과 DirectX 모두 호환되기 때문.

Continue reading

Unity/ Web View

설치

사용

webView = transform.GetComponent<WebViewObject>();
webView.Init();
webView.SetMargins(left: 0, top: 150, right: 0, bottom: 0);
webView.LoadURL("https://www.google.co.kr");
webView.SetVisibility(true);
  • 사용 방법은 대단히 간단하다.
  • 유니티 5.3 버전부터 iOS에서 http는 보안 문제가 있으므로 주의.

Unity/ SQLite Unity Kit

설치

아래의 링크에서 파일을 받을 수 있다.

파일을 열면 readme 파일을 제외하고 3개의 파일이 있다

  • DataTable.cs
  • SqliteDatabase.cs
  • libsqlite3.so

3개의 파일 중에 DataTable.csSqliteDatabase.cs 파일은 플러그인 폴더에 넣고 –플러그인 폴더에 SQLiteUnityKit 폴더를 만들어서 넣는다– libsqlite3.so 파일은 안드로이드 빌드용 파일이므로 plugin/android 폴더 아래에 넣는다.

DB 파일 저장 위치

게임 내에서 사용할 DB 파일을 만들었다면 Assets/StreamingAssets 폴더 아래에 둔다.

이렇게 해두면 플러그인의 코드는 StreamingAssets 폴더 아래에 있는 db 파일을 원본으로 보고, 그 파일을 유니티에서 지원하는 persistentDataPath 에 복사해서 게임 내에서 사용할 것이다.

게임 내에서 DB 파일 불러오기

게임 내에서 DB 파일을 불러 올 때는 SqliteDatabase 생성자에 db 파일의 이름을 string으로 넘기면 된다.

static SqliteDatabase vocaDB;
static SqliteDatabase quizEngDB;

static public void InitQuizDictionary () 
{   
    quizEngDB = new SqliteDatabase("quiz_english.sqlite");
    vocaDB = new SqliteDatabase("vocabulary.sqlite");
}

게임 내에서 사용되는 DB 파일은 persistentDataPath에 들어가게 되는데, 만일 persistentDataPath에 DB 파일이 없다면, 플러그인 코드는 streamingAssetsPath –Assets/StreamingAssets– 에서 DB 파일을 persistentDataPath에 복사해 와서 사용할 것이다.

때문에 streamingAssetsPath에도 파일이 없으면 에러가 난다.

DB 파일 최신 버전으로 변경하기

사용하는 DB가 업데이트 되어 DB 파일을 교체 해야 하는 경우 아래와 같이 DB 파일이 변경된 것을 확인하여 갈아 치우는 로직을 사용한다.

// 일단 vocaDB 를 생성한 후에 vocaDB이 업데이트 되었는지 확인한다.
vocaDB = new SqliteDatabase("vocabulary.sqlite");
vocaDB.OverwriteOnSourceMismatch("vocabulary.sqlite");

// OverwriteOnSourceMismatch가 DB 파일 업데이트를 체크하는 부분. MD5를 이용해서 버전이 바뀌었는지를 확인한다.
public bool OverwriteOnSourceMismatch(string dbName) {
    string sourceDbPath  = System.IO.Path.Combine (Application.streamingAssetsPath, dbName);
    string currentDbPath = System.IO.Path.Combine (Application.persistentDataPath, dbName);

    if (System.IO.File.Exists(currentDbPath) && System.IO.File.Exists(sourceDbPath)) {
        String sourceDbChecksum = CreateMD5HashOfFile(sourceDbPath);
        String currentDbChecksum = CreateMD5HashOfFile(currentDbPath);

        if (currentDbChecksum != sourceDbChecksum) {
            CopyDB (sourceDbPath, currentDbPath);
            return true;
        }
    }

    return false;
}

// 버전이 바뀌었는지를 체크하는 MD5 부분.
private string CreateMD5HashOfFile(string filename)
{
    // Use input string to calculate MD5 hash
    System.Security.Cryptography.MD5 md5 = System.Security.Cryptography.MD5.Create();
    System.IO.FileStream stream = System.IO.File.OpenRead(filename);
    byte[] hashBytes  = md5.ComputeHash(stream);

    // Convert the byte array to hexadecimal string
    System.Text.StringBuilder sb = new System.Text.StringBuilder();
    for (int i = 0; i < hashBytes.Length; i++)
    {
        sb.Append (hashBytes[i].ToString ("X2"));
        // To force the hex string to lower-case letters instead of
        // upper-case, use he following line instead:
        // sb.Append(hashBytes[i].ToString("x2")); 
    }
    return sb.ToString();
}

DB 파일 사용하기

DB에 값을 읽거나 쓸 때는 그것을 하기 전에 먼저 Open을 하고 쿼리를 날린 후에는 Close를 해야 하는데, 이는 SQLite Unity Kit 플러그인 코드에 구현되어 있다. 따라서 그냥 값을 읽거나 쓰기를 시도하면 알아서 open하고 처리가 끝나면 자동으로 close 해줌.

DB에서 값을 읽어 오기

DB에 쿼리를 날려 값을 읽어올 때는 SELECT 구문을 사용하는 ExecuteQuery를 사용한다.

DataTable maxLevelResult = vocaDB.ExecuteQuery("SELECT MAX(level) FROM vocabulary");

DB에 쿼리를 날리면 DataTable 형태의 값을 리턴해 준다. DataTable은 아래와 같이 생겼다.

public DataTable()
{
    Columns = new List<string>();
    Rows = new List<DataRow>();
}

Row를 구성하는 DataRow는 아래와 같이 생겼다.

public class DataRow : Dictionary<string, object>
{
    public new object this[string column]
    {
        get
        {
            if (ContainsKey(column))
            {
                return base[column];
            }

            return null;
        }
        set
        {
            if (ContainsKey(column))
            {
                base[column] = value;
            }
            else
            {
                Add(column, value);
            }
        }
    }
}

DB에 값을 쓰기

DB에 쿼리를 날려 값을 쓸 때는 INSERT/UPDATE/DELETE 구문을 사용하는 ExecuteNonQuery를 사용한다.

quizEngDB.ExecuteNonQuery("INSERT INTO WordActivity (word, level) VALUES ('" + Word + "', " + Level + ")");

quizEngDB.ExecuteNonQuery("UPDATE WordActivity SET " + 
                            "quized_count = " + totalQuizedCount + ", " + 
                            "incorrect_count = " + totalIncorrectCount + ", " + 
                            "unhinted_correct_count = " + totalUnhintedCorrectCount + ", " + 
                            "hinted_correct_count = " + totalHintedCorrectCount + " " + 
                            "WHERE word = '" + Word + "'");

Unity/ Spine Skin

Attachment와 Skin

  • Slot은 캐릭터 의상이 들어갈 부위를 의미한다. 머리, 몸통, 팔, 다리, 모자 등등
  • Attachment는 하나의 텍스쳐를 의미한다. Attachment는 각 Slot에 지정된다.
  • Skin은 Attachment들의 모음이다. 캐릭터 의상 전체 Set를 의미한다.

Attachment 바꾸기

skeletonAnimation = transform.GetComponent<SkeletonAnimation>();
skeletonAnimation.skeleton.SetAttachment(SlotName, AttachName);
  • Attachment를 바꾸려면 Attachment의 이름과 Attachment가 들어갈 Slot의 이름이 필요하다.
  • SkeletonAnimation 아래의 skeleton 아래에 SetAttachment()를 사용하면 된다.

Skin 바꾸기

skeletonAnimation = transform.GetComponent<SkeletonAnimation>();
skeletonAnimation.skeleton.SetSkin(SkinName);
  • Skin은 의상 전체 –Attachment 모음– 이기 때문에 만들어진 Skin을 그대로 사용하면 된다.
  • SkeletonAnimation 아래의 skeleton 아래에 SetSkin()를 사용하면 된다.

Skin에서 특정 부위만 바꾸기

  • 캐릭터 커스터마이징이 없는 게임이라면 SetSkin을 이용해서 전체 Skin을 바꾸면 되지만, 캐릭터 커스터마이징을 한다면 SetSkin을 이용할 수 없다. 바지만 갈아입히려는데 바지 외의 것들이 한번에 날아가기 때문이다.
  • 이럴 때는 기존에 입고 있는 스킨에서 특정 Attachment만 바꿔야 되는데, 이게 Spine-Unity 기본에는 없다. 어느 훌륭한 개인 사용자가 만든 방식이 있으니 그것을 이용한다.
  • 기본 개념은 기존에 입고 있는 옷들과 새로 바꿀 부위를 이용해서 새로운 Skin을 만든 후에 그것을 입히는 식으로 진행된다.

1. Skin.cs 클래스 수정하기

public void AddFromSkin(Skin other)
{
    foreach (var a in other.attachments)
    {
        attachments[a.Key] = a.Value;
    }
}
  • spine의 Skin.cs 클래스에 위의 메서드를 추가한다.

2. Combined Skin 만들기

public void SetEquip(List<string> EquipList)
{
    skeletonAnimation = transform.GetComponent<SkeletonAnimation>();
    Skin combined = new Skin("combined");

    foreach (var equip in EquipList)
    {
        Skin skin = skeletonAnimation.skeleton.data.FindSkin(EquipList[i]);

        if (skin != null)
        {
            combined.AddFromSkin(skin);
        }
    }

    ani.skeleton.Skin = null;
    ani.skeleton.SetSkin(combined);
}
  • Skin.cs에 AddFromSkin 메서드를 추가했다면 다음과 같이 옷 갈아입기를 한다.
    1. combined 이라는 이름으로 새로운 Skin을 만든다.
    2. 새로 입힐 코스튬 리스트를 돌면서 skeletonAnimation.skeleton.data의 FindSkin을 통해 Skin 정보를 가져온다.
    3. 가져온 스킨 정보를 combined 스킨에 AddFromSkin()한다.
  • 최종적으로 combine 한 Skin을 적용한다.
    • 정확한 이유는 모르겠는데 여기서 바로 SetSkin을 하면 스킨이 제대로 입혀지지 않는다. Skin에 null을 대입한 후에 SetSkin을 하면 잘 되므로 그렇게 한다.

Unity/ NGUI

UI Camera

UICamera가 Input을 처리하는 방식

직접 구현하면 번거로운 것을 대신해 줘서 대단히 편리한데 이를 처리하는 방식은 아래와 같다.

  • 모든 Input 이벤트는 NGUI의 UICamera가 처리한다.
    • 사실 이는 당연한 방식이다. 카메라는 항상 떠 있으니까
  • Input이 발생하면 UICamera는 그 Input의 성격을 파악해서 이게 어떤 이벤트인지 –클릭, 더블클릭, 드래그, 드롭 등등– 파악한다.
    • 이는 UICamera.cs의 ProcessEvents 메소드에서 일어난다.
    • Touch인지, Mouse인지 파악하고, Press인지 Release인지 파악하고 관련한 메소드를등등.
  • 이벤트 타입 –3D World/ 3D UI/ 2D Word/ 2D UI– 과 충돌 타입 –Rigidbody/ Collider– 을 기준으로 Ray를 쏴서 타입에 맞는 GameObject를 가져온다.
  • 해당 GameObject에 발생한 이벤트 –OnClick, OnPress, OnDrag 등등– 를 SendMessage로 보낸다.
    • SendMessage는 Unity의 함수
    • Input Event는 실체가 있는 것에서 발생하는 것이기 때문에 충돌한 GameObject에 메소드를 실행하게 하는 것은 매우 당연하다.
    • GameObject에게 SendMessage를 보내기 때문에, 딱히 UIWidget을 달고 있지 않아도 Input 처리가 가능하다.

UICamera가 처리해주는 Input 목록

  • OnHover
  • OnPress
  • OnClick
  • OnDoubleClick
  • OnSelect
  • OnDrag
  • OnDrop
  • OnInput
  • OnTooltip
  • OnScroll
  • OnKey

Tip

  • UICamera는 UI 뿐만 아니라 모든 Input 이벤트를 처리할 수 있다. 기본적으로 UICamera는 UIRoot 아래의 Camera에 달려있지만, Main Camera에 추가로 UICamera를 달면, 월드 상의 Input 이벤트 처리도 가능하다.
    • 이러면 UICamera 클래스가 2개가 되는 셈인데, NGUI가 알아서 메인과 서브를 조절해 주므로 신경쓰지 않아도 된다.

UI Wrap Content

Wrap Content가 무한 스크롤을 구성하는 방식

  • Wrap Content 하위 요소들을 List에 담는다.
    • transform.childCount
  • OnMove가 –스크롤– 발생하면 WrapContent를 실행한다.
    • 하위 transform들의 크기와 갯수를 기준으로 전체 크기를 구한다.
    • 하위 transform들의 리스트를 모두 순회하면서 해당 transform 포지션과 Center의 포지션을 비교하여 거리를 구한다.
    • 거리가 전체 크기를 벗어났다면 –음수 or 양수– 해당 transform의 위치를 조정한다.
      • 수평 스크롤에 스크롤이 ‘우 -> 좌’ 로 이루어진다면 화면 왼쪽 바깥으로 빠진 transform을 화면 오른쪽 시작에 넣는다. 스크롤이 반대거나 수직인 경우엔 그에 맞게 적용한다.
      • 만일 스크롤이 무한이 아닌 경우, 시작과 끝이 분명하다면 시작점과 끝점에서는 더 스크롤이 되면 안되므로 시작이나 끝점에서 스크롤이 막힌다.
    • UpdateItem()을 통해 real 데이터를 기준으로 transform의 내용을 채운다.

주의 사항

  • 무한 스크롤에 속하는 구성 요소들은 반드시 그 크기가 같아야 한다.
  • Wrap Content 하위의 transform index와 transform에 들어가는 데이터의 real index를 구분해야 한다.
    • 100개의 리스트를 100개의 transform을 만들지 않고 무한 스크롤을 통해 보여주게 하는게 Wrap Content 이므로, 화면을 채우고 약간 남는 정도의 transform 만 가지고 스크롤을 구성하는게 성능상 좋다.

유니티로 배우는 게임 수학

3D 세계를 이루는 수학 원리들에 대해 다루고, 유니티로 확인해 볼 수 있게 하는 책. 사실 유니티는 사용자가 3D 에 대한 지식이 깊지 않아도 충분히 3D 게임을 만들 수 있도록 만들어져 있기 때문에 –내부적으로 잘 감싸져 있다– 책에서 다루는 수식들을 유니티 상에서 접하기는 쉽지 않다.

그러나 상급 프로그래머가 되려면 하드웨어에 대한 지식이 필수적이다는 생각과 같은 맥락에서 그래픽을 다루는 프로그래머라면 그 그래픽을 구성하는 수학 원리를 이해해야 한다고 생각하기 때문에 공부할 필요가 있다고 생각 함.

다만 책이 다루는 수학적 원리에 대한 설명이 매끄럽지는 않은데, 무엇(what)을 봐야 하는 지에 대한 이해는 좀 됐는데, 그래서 그게 정확히 어떻게(how) 돌아가는 것인지에 대한 이해는 쉽지 않았음. 수학 원리를 좀 더 이해하면 수월할지 모르겠으나, 여튼 이 책만으로는 좀 부족한 듯 하고 다른 책도 함께 봐야겠다는 생각이 들었다.