This script which enables users to work with “nested prefabs” seems to be a saver. I read a lot of good comments on it around the forums and it’s for FREE.
Now, my problem is that I want to use it for a 2D game, while this is written for 3D (meshes and stuff). I don’t have the knowledge of Unity’s internal Graphics system in order to make the neccesary changes to the script (if possible) but maybe somebody has already done it, or might be able to check it out and share some thoughts about it.
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 (Renderer mr in source.GetComponentsInChildren(typeof (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
}
I know there are assets on the store for doing this, but I don’t believe I should pay for something that Unity is currently trying to solve. This one works and should be easily adapted for 2D.
I have this 2D project, where I randomly pick a prefab and Instantiate it on runtime, so I managed to make it work with these (crude) changes:
(please, bear in mind that I’m a total noob with unity yet, and with c# for that matter)
using UnityEngine;
#if UNITY_EDITOR
using UnityEditor;
using UnityEditor.Callbacks;
#endif
using System.Collections.Generic;
[ExecuteInEditMode]
public class PrefabInstance2D : MonoBehaviour
{
public GameObject prefab;
#if UNITY_EDITOR
// Struct of all components. Used for edit-time visualization and gizmo drawing
public struct Thingy {
public Sprite sprite;
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 (SpriteRenderer mr in source.GetComponentsInChildren(typeof (SpriteRenderer), true))
{
things.Add(new Thingy () {
sprite = mr.GetComponent<Sprite>(),
matrix = baseMat * mr.transform.localToWorldMatrix,
materials = new List<Material> (mr.sharedMaterials)
});
}
foreach (PrefabInstance2D pi in source.GetComponentsInChildren(typeof (PrefabInstance2D), true))
{
if (pi.enabled && pi.gameObject.activeSelf)
Rebuild (pi.prefab, baseMat * pi.transform.localToWorldMatrix);
}
}
// I don't need editor scene view rendering of the prefabs, so I just commented this out
// Editor-time-only update: Draw the meshes so we can see the objects in the scene view
// void Update () {
// ...
// }
// Picking logic: Since we don't have gizmos.drawmesh, draw a bounding cube around each thingy
// ...
// Baking stuff: Copy in all the referenced objects into the scene on play or build
[PostProcessScene(-2)]
public static void OnPostprocessScene() {
foreach (PrefabInstance2D pi in UnityEngine.Object.FindObjectsOfType (typeof (PrefabInstance2D)))
BakeInstance (pi);
}
public static void BakeInstance (PrefabInstance2D 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 (PrefabInstance2D childPi in go.GetComponentsInChildren<PrefabInstance2D>())
BakeInstance (childPi);
}
#endif
}
and on a script Instantiated on the randomly picked prefab, I call this on the start method (so I can do stuff to the nested prefab)
foreach (PrefabInstance2D pi in UnityEngine.Object.FindObjectsOfType (typeof (PrefabInstance2D)))
PrefabInstance2D.BakeInstance (pi);
#endif
this works when I play on editor mode, but not when I build the project on android
Okay, now I could make it work and build on android with the following very simplified version, I removed everything used to execute it on editor edit mode
using UnityEngine;
public class PrefabInstance2D : MonoBehaviour
{
public GameObject prefab;
public static void BakeInstance (PrefabInstance2D pi) {
if(!pi.prefab || !pi.enabled)
return;
pi.enabled = false;
GameObject go = Instantiate(pi.prefab);
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 (PrefabInstance2D childPi in go.GetComponentsInChildren<PrefabInstance2D>())
BakeInstance (childPi);
}
}
it makes it harder to work without a preview, but maybe we can start implementing it from here…
I could make it show up the sprite on the scene view with Gizmos.DrawGUITexture, you need to set the sprite Texture type to Advanced and check the read/write enabled
then, you need to update the script to:
using UnityEngine;
#if UNITY_EDITOR
using UnityEditor;
using System.Collections.Generic;
#endif
[ExecuteInEditMode]
public class PrefabInstance2D : MonoBehaviour
{
public GameObject prefab;
public static void BakeInstance (PrefabInstance2D pi) {
if(!pi.prefab || !pi.enabled)
return;
pi.enabled = false;
GameObject go = Instantiate(pi.prefab);
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 (PrefabInstance2D childPi in go.GetComponentsInChildren<PrefabInstance2D>())
BakeInstance (childPi);
}
#if UNITY_EDITOR
public struct Thingy {
public Sprite sprite;
public Transform thingyTransform;
}
[System.NonSerializedAttribute] public List<Thingy> things = new List<Thingy> ();
void OnValidate () {
things.Clear();
if (enabled)
Rebuild (prefab);
}
void OnEnable () {
things.Clear();
if (enabled)
Rebuild (prefab);
}
void Rebuild (GameObject source) {
if (!source)
return;
foreach (SpriteRenderer mr in source.GetComponentsInChildren(typeof (SpriteRenderer), true))
{
things.Add(new Thingy () {
sprite = mr.sprite,
thingyTransform = transform
});
}
foreach (PrefabInstance2D pi in source.GetComponentsInChildren(typeof (PrefabInstance2D), true))
{
if (pi.enabled && pi.gameObject.activeSelf)
Rebuild (pi.prefab);
}
}
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;
foreach (Thingy t in things){
var spriteTexture = new Texture2D ((int) t.sprite.rect.width, (int) t.sprite.rect.height);
var pixels = t.sprite.texture.GetPixels(
(int) t.sprite.textureRect.x,
(int) t.sprite.textureRect.y,
(int) t.sprite.textureRect.width,
(int) t.sprite.textureRect.height
);
spriteTexture.SetPixels(pixels);
spriteTexture.Apply();
Gizmos.DrawGUITexture(
new Rect(
t.thingyTransform.position.x,
t.thingyTransform.position.y,
t.thingyTransform.localScale.x,
t.thingyTransform.localScale.y
),
spriteTexture
);
}
}
#endif
}
I wanted a simpler way to achieve this, so I wrote this:
using UnityEngine;
using System.Collections;
using UnityEditor;
public class PrefabCreator : MonoBehaviour {
public GameObject prefab;
public GameObject go;
// Use this for initialization
void Start ()
{
go = Instantiate<GameObject>(prefab);
go.transform.position = transform.position;
go.transform.parent = transform.parent;
}
void OnDrawGizmos()
{
if (gameObject.transform.Find(prefab.name + "(Clone)") == null)
{
go = Instantiate<GameObject>(prefab);
go.transform.parent = gameObject.transform;
}
else if (null != go)
{
go.transform.position = transform.position;
}
}
// Update is called once per frame
void Update ()
{
Destroy (gameObject);
}
}