새소식

개발/유니티3D

[Unity] 귀차니즘이 이끄는 유니티 Sprite Packing Editor

  • -

가끔 아트와 협의 없이 작업하다보면 (여기서부터 잘못이다.) 
생각지도 못 한 결과가 나오곤 한다.

예를 들어, 파티클 시스템의 Texture Sheet Animation 모듈에 89개의 분리되어있는 스프라이트 이미지를 박아야 하거나,

(아아.. 유니티님은 여러개의 이미지를 한 번에 할당하는 기능을 만들어주지 않아, 일일이 하나씩 할당해줘야 한다...)


캐릭터의 노말맵을 생각하지 않고 있다가, 디자인 기획이 바뀌어 분리 되어 제작되어 있는 캐릭터 이미지를 하나의 스프라이트 시트로 묶어야 하는 경우가 이렇다... 

이런 문제를 아트에게 다시 부탁하기도 뭐하고, 내가 해결하기도 귀찮아 그냥 유틸성을 위해 에디터 확장 스크립트를 작성하기로 했다.

확장 기능의 목표 루틴은 다음과 같다.
1. 한 폴더 내에 있는 이미지를 하나로 묶어야 한다.
2. 왼쪽 상단을 0,0으로 시작해 이미지들이 나열되어야 한다.
3. 원하는 만큼의 열에서 다음 행으로 넘어가야 한다.
4. 사용하기 편해야 한다.

첫 번째, 사용자가 이미지가 들어있는 폴더를 선택하고 폴더 내 이미지를 풀러오는 것 부터 해결해보자.

 var absFolderPath = EditorUtility.OpenFolderPanel("Select Image Folder", "", "");
 var d = new DirectoryInfo(absFolderPath);
 var files = d.GetFiles("*.png"); 
 var originTexSet = new Texture2D[files.Length];

첫 번째 라인은 EditorUtility.OpenFolderPanel을 통해 폴더를 선택하는 코드로, 절대 경로값을 반환한다.
이후 DirectoryInfo 객체의 GetFiles 함수를 사용하면 원하는 확장자의 파일을 로드할 수 잇다.
이 파일들은 모두 유니티 에셋 내 Texture2D로 지정되어있다 가정해, Texture2D Array를 파일의 수 만큼 할당했다.

여기서 주의할 점이 있다. 위와 같이 GetFiles를 통해 로드하면, C#의 경우 {'abc-1-abc', 'abc-11-abc', 'abc-2-abc'}와 같은 순서로 객체가 할당된다.
이에 우리는 숫자를 찾아, 정렬 순서를 바꿔줄 필요가 있다.

var counter = 0;
foreach (var file in files.OrderBy(n =>
	{
      int result = -1;
      var numbers = n.Name.Where(char.IsDigit).ToArray();
      var numbString = new string(numbers);
     Int32.TryParse(numbString, out result);
     return result;
   	}))
{
     var absFilePath = absFolderPath + "/" + file.Name;
     var startIdx = absFilePath.IndexOf("Assets");
     var assetPath = absFilePath.Substring(startIdx);
     var texture = AssetDatabase.LoadAssetAtPath(assetPath);
     originTexSet[counter] = texture;
     counter++;
}

LINQ를 통해 간단하게 구현해봤다. 먼저 files에는 아까 로드한 FileInfo 객체들이며, 각 객체의
이름에 포함되어있는 정수(Digit)을 찾아, 추출한 다음 해당 값들을 기준으로 크기값을 비교해 정렬한다.
이는 OrderBy에서 이뤄지며, OrderBy의 리턴 값은 IEnumerable 객체다 따라서 해당 객체를 foreach를 통해 반복하며
아까 할당한 Texture 배열에 할당한다.

자 이제 필요한 텍스쳐는 모두 불러왔으니 두번째에서 세번째, 하나의 텍스쳐로 합치는 작업을 해야 한다.

코드는 우선 아래와 같다.

 

//	첫번재 텍스쳐의 너비를 캐싱한다.
var originWidth = originTexSet[0].width;
//	첫번째 텍스쳐의 높이를 캐싱한다.
var originHeight = originTexSet[0].height;
//	아틀라스의 너비다.
var width = originWidth * row;
//	나열하는데 필요한 행의 갯수를 캐싱한다.
col = Mathf.RoundToInt((float) originTexSet.Length / row);
//	아틀라스의 높이다.
var height = originHeight * col;
//	텍스쳐 세팅을 유지한다.
var wrap = originTexSet[0].wrapMode;
var filter = originTexSet[0].filterMode;
var atlasTex = new Texture2D(width, height)
{
	filterMode = filter, wrapMode = wrap
};
//	새로 만들어질 아틀라스 텍스쳐의 픽셀 컬러 배열을 할당한다.
var texColorMap = new Color[originWidth*originHeight*col*row];
var padY = 0;
for (var i = 0; i < originTexSet.Length; i++)
{
	//	inverY 변수가 TRUE면, 왼쪽 상단부터 나열, FALSE면 왼쪽 하단부터 나열한다.
    var curCol = invertY ? (originTexSet.Length - i) / row : i / row;
    var offsetX = (i % row) * originWidth;
    var offsetY = curCol * width * originHeight;
    if (offsetY - padY * width >= 0) 
        offsetY -= padY * width; 
    var offset = offsetY + offsetX; 
    var tex = originTexSet[i]; 
    for (var y = 0; y < originHeight; y++) 
    { 
      for (var x = 0; x < originWidth; x++)
      {
        var t = offset + x + y * width;
        texColorMap[t] = tex.GetPixel(x,y);
      }
    }
    padY = 1;
}
//	생성할 아틀라스 텍스쳐에 Color값을 할당한다.
atlasTex.SetPixels(texColorMap);
//	아틀라스 텍스쳐를 적용한다.
atlasTex.Apply();

자 이제 만들어진 아틀라스 텍스쳐를, 저장하는 일만 남았다.

코드는 아래와 같이 작성했다.

var absSavePath = EditorUtility.SaveFilePanelInProject("Save Path", "atlas", "png", "Save Atlas");
var bytes = atlasTex.EncodeToPNG();
File.WriteAllBytes(absSavePath, bytes);

이제 아틀라스 텍스쳐를 어디에 저장할 것인지 물어보는 대화창이 나오고, 지정한 위치에 png 파일이 저장되는 것을 확인할 수 있을 것 이다.

ImagesToSheet.unitypackage
0.00MB

 

 

Contents

포스팅 주소를 복사했습니다

이 글이 도움이 되었다면 공감 부탁드립니다.