[Unity] 프리팹 속의 프리팹

Filed under tutorials, unity | Comments Off

(Nested Prefab)

유니티를 이용한 개발에서 프리팹은 빼놓을 수 없는 핵심적 요소다. 특히 팀 단위 작업에서 프리팹은 더욱 중요하다. 여러 작업자가 하나의 씬을 가지고 동시에 작업할 경우, 씬의 저장과 별개로 프리팹 단위에서 개별 작업자의 작업을 반영, 취합할 수 있기 때문이다. 그런데 이런 프리팹에도 큰 한계가 있다. 현재 유니티(4.3)는 프리팹 속의 프리팹, 즉 네스티드 프리팹(Nested Prefab)을 지원하지 않는다.

PrefabTree

예를 들어 위의 구조처럼, 프리팹을 자식으로 가진 게임오브젝트를 프리팹으로 만들 수 있다. 그런데 문제는 나중에 Prefab A를 변경하고 반영(Apply)해도, 그 내용이 Parent Prefab 하위에 있는 Prefab A에는 반영되지 않는다. 실제로 이 문제를 해결하기 위해서 유니티 애셋 스토어에는 유료 미들웨어가 출시돼 있다(유니티 애셋스토어 링크).

Nested

직접 사용해보지 않아서 이 미들웨어의 성능을 논할 수는 없다. 그러나 $35이라는 가격과 더불어, 조만간 네스티드 프리팹을 유니티에서 공식적으로 지원할 거라는 루머가 이 미들웨어의 구매, 사용을 주저하게 만든다. 이런 이유에서 기능에는 약간의 제약이 있지만 간단한 스크립트를 통해서 네스티드 프리팹을 사용할 수 있는 우회적 방법을 소개한다 (출처).

우선 다음 스크립트를 저장한다.

using UnityEngine;
#if UNITY_EDITOR
using UnityEditor;
using UnityEditor.Callbacks;
#endif
using System.Collections.Generic;

[ExecuteInEditMode]
public class PrefabInstance : MonoBehaviour
{
	public GameObject prefab;

	#if UNITY_EDITOR
	// Struct of all components. Used for edit-time visualization and gizmo drawing
	public struct Thingy {
		public Mesh mesh;
		public Matrix4x4 matrix;
		public List<Material> materials;
	}

	[System.NonSerializedAttribute] public List<Thingy> things = new List<Thingy> ();

	void OnValidate () {
		things.Clear();
		if (enabled)
			Rebuild (prefab, Matrix4x4.identity);
	}

	void OnEnable () {
		things.Clear();
		if (enabled)
			Rebuild (prefab, Matrix4x4.identity);
	}

	void Rebuild (GameObject source, Matrix4x4 inMatrix) {
		if (!source)
			return;

		Matrix4x4 baseMat = inMatrix * Matrix4x4.TRS (-source.transform.position, Quaternion.identity, Vector3.one);

		//foreach (MeshRenderer mr in source.GetComponentsInChildren(typeof (Renderer), true))
		foreach (MeshRenderer mr in source.GetComponentsInChildren<Renderer>(true))
		{
			things.Add(new Thingy () {
				mesh = mr.GetComponent<MeshFilter>().sharedMesh,
				matrix = baseMat * mr.transform.localToWorldMatrix,
				materials = new List<Material> (mr.sharedMaterials)
			});
		}

		foreach (PrefabInstance pi in source.GetComponentsInChildren(typeof (PrefabInstance), true))
		{
			if (pi.enabled && pi.gameObject.activeSelf)
				Rebuild (pi.prefab, baseMat * pi.transform.localToWorldMatrix);
		}
	}

	// Editor-time-only update: Draw the meshes so we can see the objects in the scene view
	void Update () {
		if (EditorApplication.isPlaying)
			return;
		Matrix4x4 mat = transform.localToWorldMatrix;
		foreach (Thingy t in things)
			for (int i = 0; i < t.materials.Count; i++)
				Graphics.DrawMesh (t.mesh, mat * t.matrix, t.materials[i], gameObject.layer, null, i);
	}

	// Picking logic: Since we don't have gizmos.drawmesh, draw a bounding cube around each thingy
	void OnDrawGizmos () { DrawGizmos (new Color (0,0,0,0)); }
	void OnDrawGizmosSelected () { DrawGizmos (new Color (0,0,1,.2f)); }
	void DrawGizmos (Color col) {
		if (EditorApplication.isPlaying)
			return;
		Gizmos.color = col;
		Matrix4x4 mat = transform.localToWorldMatrix;
		foreach (Thingy t in things)
		{
			Gizmos.matrix = mat * t.matrix;
			Gizmos.DrawCube(t.mesh.bounds.center, t.mesh.bounds.size);
		}
	}

	// Baking stuff: Copy in all the referenced objects into the scene on play or build
	[PostProcessScene(-2)]
	public static void OnPostprocessScene() {
		foreach (PrefabInstance pi in UnityEngine.Object.FindObjectsOfType (typeof (PrefabInstance)))
			BakeInstance (pi);
	}

	public static void BakeInstance (PrefabInstance pi) {
		if(!pi.prefab || !pi.enabled)
			return;
		pi.enabled = false;
		GameObject go = PrefabUtility.InstantiatePrefab(pi.prefab) as GameObject;
		Quaternion rot = go.transform.localRotation;
		Vector3 scale = go.transform.localScale;
		go.transform.parent = pi.transform;
		go.transform.localPosition = Vector3.zero;
		go.transform.localScale = scale;
		go.transform.localRotation = rot;
		pi.prefab = null;
		foreach (PrefabInstance childPi in go.GetComponentsInChildren<PrefabInstance>())
			BakeInstance (childPi);
	}

	#endif
}

 

부모가 될 빈 게임오브젝트를 생성한다. 하위에 프리팹을 직접 연결하는 대신, 빈 게임오브젝트를 생성한다. 그리고 프리팹을 대신하는 하위의 각 게임오브젝트에 위 스크립트를 연결한다.

 

prefab_01

 

이제 Prefab Instance 컴포넌트에 있는 Prefab 항목에서 자식으로 사용하려는 프리팹을 참조한다. 여기서는 기본 프리미티브를 프리팹으로 만들어 사용했다.

 

prefab_03

 

 

부모인 ParentPrefab을 프리팹으로 만든다. 이제 하위 프리팹의 수정하고, 그 내용이 ParentPrefab에도 반영되는지 확인한다. 씬에 하위 프리팹 중 하나인 캡슐 프리팹을 꺼내서 스케일을 바꾸고 그 내용을 적용(Apply)한다.

 

prefab_04

 

참조된 하위 프리팹을 수정했지만 아직까지 ParentPrefab에 있는 캡슐은 그래도 남아있다. 에디터를 플레이해서 내용을 갱신한다. 참조된 프리팹의 수정된 내용이 부모 프리팹의 하위에 반영된 모습을 확인할 수 있다.

 

prefab_05

Comments are closed.