Skip to content

Instantly share code, notes, and snippets.

@fangzhangmnm
Last active March 1, 2022 04:51
Show Gist options
  • Save fangzhangmnm/bdb16f3970c2158c3bb829bf2685bb94 to your computer and use it in GitHub Desktop.
Save fangzhangmnm/bdb16f3970c2158c3bb829bf2685bb94 to your computer and use it in GitHub Desktop.
Unity3D paint prefabs on grid
//https://gist.github.com/fangzhangmnm/bdb16f3970c2158c3bb829bf2685bb94
// credits: https://www.synnaxium.com/en/2019/01/unity-custom-map-editor-part-1/
#if UNITY_EDITOR
using UnityEngine;
using UnityEditor;
using System.Collections;
using System.Collections.Generic;
using System.IO;
public class GridPaintEditor : EditorWindow
{
private bool paintMode = false;
private string path = "Assets/GridPaintPalette";
[SerializeField]
private List<GameObject> palette = new List<GameObject>();
private List<GUIContent> paletteIcons = new List<GUIContent>();
[SerializeField]
private int paletteIndex;
private Vector2 scrollPosition;
private Transform container;
private int selectedY;
private float cellHeight = 1f;
private float cellSize = 1f;
private int rotate = 0;
private bool halfIntegerX = false;
private bool halfIntegerZ = false;
private GameObject previewObject;
private Bounds lastPlace = new Bounds();
private Vector3 oldA = Vector3.zero;
private bool mouseMoved = true;
[MenuItem("Window/GridPaint")]
private static void ShowWindow()
{
EditorWindow.GetWindow(typeof(GridPaintEditor));
}
void OnFocus()
{
SceneView.duringSceneGui -= this.OnSceneGUI;
SceneView.duringSceneGui += this.OnSceneGUI;
}
void OnDestroy()
{
SceneView.duringSceneGui -= this.OnSceneGUI;
}
private void LoadPalette()
{
palette.Clear();
paletteIcons.Clear();
string[] prefabFiles = System.IO.Directory.GetFiles(path, "*.prefab");
foreach (string prefabFile in prefabFiles)
{
GameObject prefab = AssetDatabase.LoadAssetAtPath(prefabFile, typeof(GameObject)) as GameObject;
if (prefab.GetComponentInChildren<Renderer>() != null)
{
palette.Add(prefab);
var guic = new GUIContent(prefab.name);
guic.image = null;
paletteIcons.Add(guic);
}
}
RefreshPaletteIcons(0, 40);
paletteIndex = 0;
EnterPaintModeAndFocus();
}
private void RefreshPaletteIcons(int begin, int end)
{
for (int i = Mathf.Max(0, begin); i < Mathf.Min(palette.Count, end); ++i)
paletteIcons[i].image = AssetPreview.GetAssetPreview(palette[i]);
}
private void OnGUI()
{
GUILayout.BeginHorizontal();
container = EditorGUILayout.ObjectField("Container", container, typeof(Transform), true) as Transform;
/*if (GUILayout.Button("Clear"))
{
for (int i = container.childCount - 1; i >= 0; --i)
{
Undo.DestroyObjectImmediate(container.GetChild(i));
}
}*/
if (container == null)
if (GUILayout.Button("Create"))
container = new GameObject("container").transform;
GUILayout.EndHorizontal();
cellSize = EditorGUILayout.FloatField("Grid Size", cellSize);
cellHeight = EditorGUILayout.FloatField("Grid Height", cellHeight);
selectedY = EditorGUILayout.IntField("Height Layer[Alt+MouseWheel]", selectedY);
GUILayout.BeginHorizontal();
GUILayout.Label("Align to Center?[C]");
halfIntegerX = GUILayout.Toggle(halfIntegerX, "");
halfIntegerZ = GUILayout.Toggle(halfIntegerZ, "");
GUILayout.Label("Rotation[R]");
rotate = EditorGUILayout.IntSlider("", rotate, 0, 3);
GUILayout.EndHorizontal();
bool oldPaintMode = paintMode;
paintMode = GUILayout.Toggle(paintMode, (paintMode ? "End Paint[1]" : "Start Paint[1]"), "Button", GUILayout.Height(60f));
if (container == null) paintMode = false;
if (!oldPaintMode && paintMode)
EnterPaintModeAndFocus();
GUILayout.Space(10);
GUILayout.Label("Prefab Palette Folder");
GUILayout.BeginHorizontal();
path = GUILayout.TextField(path);
if (GUILayout.Button("Load")) LoadPalette();
GUILayout.EndHorizontal();
if (paletteIndex < palette.Count)
{
GUILayout.Label(palette[paletteIndex].name);
}
else
{
GUILayout.Label("");
}
scrollPosition = GUILayout.BeginScrollView(scrollPosition);
int xCount = Mathf.Max(1, Mathf.FloorToInt((EditorGUIUtility.currentViewWidth - 30) / 128));
RefreshPaletteIcons(Mathf.FloorToInt(scrollPosition.y / 135) * xCount - 10, Mathf.FloorToInt(scrollPosition.y / 135) * xCount + 30);
GUIStyle guis = new GUIStyle(GUI.skin.button);
guis.fixedWidth = 128;
guis.fixedHeight = 135;
guis.wordWrap = true;
guis.imagePosition = ImagePosition.ImageAbove;
guis.padding = new RectOffset(3, 3, 3, 3);
int newIndex = GUILayout.SelectionGrid(paintMode ? paletteIndex : -1, paletteIcons.ToArray(), xCount, guis, GUILayout.Width(xCount * 128));
if (newIndex != -1)
{
paintMode = true;
if (paletteIndex != newIndex)
EnterPaintModeAndFocus();
paletteIndex = newIndex;
}
HidePreviewObject();
GUILayout.EndScrollView();
}
void Rotate(int rotate, ref float x, ref float z)
{
float x0 = x, z0 = z;
switch (rotate)
{
case 0: x = x0; z = z0; break;
case 1: x = z0; z = -x0; break;
case 2: x = -x0; z = -z0; break;
case 3: x = -z0; z = x0; break;
}
}
static Bounds getCompoundGameobjectBound(GameObject g)
{
var a = g.GetComponentsInChildren<Renderer>();
if (a.Length == 0) return new Bounds();
Bounds b = a[0].bounds; //new Bounds contains original point dont use that
for (int i = 0; i < a.Length; ++i)
b.Encapsulate(a[i].bounds);
return b;
}
bool ShowPreviewObject(out Vector3 placePos,out Quaternion placeRot, out Bounds bounds)
{
placePos = Vector3.zero;
placeRot = Quaternion.identity;
bounds = new Bounds();
if (paletteIndex >= palette.Count) { paintMode = false; return false; }
if (container == null) return false;
if (previewObject == null)
{
previewObject = PrefabUtility.InstantiatePrefab(palette[paletteIndex]) as GameObject;
previewObject.name = "[PREVIEW]";
}
Bounds prefabBounds = getCompoundGameobjectBound(palette[paletteIndex]);
float biasX = halfIntegerX ? 0.5f : 0;
float biasZ = halfIntegerZ ? 0.5f : 0;
float plx = (Mathf.RoundToInt(prefabBounds.min.x / cellSize + biasX - 0.1f) - biasX + 0.1f) * cellSize;
float plz = (Mathf.RoundToInt(prefabBounds.min.z / cellSize + biasZ - 0.1f) - biasZ + 0.1f) * cellSize;
float phx = (Mathf.RoundToInt(prefabBounds.max.x / cellSize + biasX + 0.1f) - biasX - 0.1f) * cellSize;
float phz = (Mathf.RoundToInt(prefabBounds.max.z / cellSize + biasZ + 0.1f) - biasZ - 0.1f) * cellSize;
int pwx = Mathf.RoundToInt((phx - plx) / cellSize);
int pwz = Mathf.RoundToInt((phz - plz) / cellSize);
if (pwx == 0) { pwx = 1; phx += cellSize; }
if (pwz == 0) { pwz = 1; phz += cellSize; }
float pmx = (plx + phx) / 2;
float pmz = (plz + phz) / 2;
float prmx = pmx, prmz = pmz; Rotate(rotate, ref prmx, ref prmz);
//Debug.Log(palette[paletteIndex].transform.position);
//Debug.Log($"{prefabBounds.min.x} {plx} {prefabBounds.min.z} {plz}");
int paintWidthX = pwx, paintWidthZ = pwz;
if (rotate == 1 || rotate == 3) { paintWidthX = pwz; paintWidthZ = pwx; }
Ray ray = HandleUtility.GUIPointToWorldRay(Event.current.mousePosition);
Plane plane = new Plane(container.up, container.position + container.up * cellHeight * selectedY);
float enter;
if (!plane.Raycast(ray, out enter))
{
HidePreviewObject();
return false;
}
Vector3 intersection = ray.origin + enter * ray.direction;
Vector3 coord = container.InverseTransformPoint(intersection);
int x0 = Mathf.RoundToInt(coord.x / cellSize - paintWidthX / 2f);
int z0 = Mathf.RoundToInt(coord.z / cellSize - paintWidthZ / 2f);
Vector3 newA = container.TransformPoint(new Vector3(x0 * cellSize, 0, z0 * cellSize));
if (mouseMoved) oldA = newA;
Vector3 a = oldA + container.up * cellHeight * selectedY;
Vector3 b = container.right * cellSize * paintWidthX;
Vector3 c = container.forward * cellSize * paintWidthZ;
Vector3 d = container.right * prmx+ container.forward * prmz;
Handles.DrawLine(a, a + b);
Handles.DrawLine(a + b, a + b + c);
Handles.DrawLine(a + b + c, a + c);
Handles.DrawLine(a + c, a);
bounds.min = new Vector3(x0 * cellSize, 0, z0 * cellSize);
bounds.max = new Vector3((x0 + paintWidthX) * cellSize, 1, (z0 + paintWidthZ) * cellSize);
bounds.size = bounds.size - Vector3.one * cellSize * 0.01f;
Vector3 e= palette[paletteIndex].transform.position;
Rotate(rotate, ref e.x, ref e.z);
placePos = a + (b + c) / 2 - d + e;
placeRot= container.rotation * Quaternion.Euler(0, 90 * rotate, 0) * palette[paletteIndex].transform.rotation;
previewObject.transform.position = placePos;
previewObject.transform.rotation = placeRot;
previewObject.transform.parent = null;
return true;
}
void HidePreviewObject()
{
if (previewObject != null)
{
DestroyImmediate(previewObject);
previewObject = null;
}
}
bool rightMouseDown = false;
private void OnSceneGUI(SceneView sceneView)
{
Event ev = Event.current;
int sca = (ev.shift ? 4 : 0) + (ev.control ? 2 : 0) + (ev.alt ? 1 : 0);
if (ev.type == EventType.MouseMove)
mouseMoved = true;
if (ev.type == EventType.MouseDown && ev.button == 1) rightMouseDown = true;
if (ev.type == EventType.MouseUp && ev.button == 1) rightMouseDown = false;
if (paintMode)
{
if (ev.type == EventType.Layout)
HandleUtility.AddDefaultControl(0); // Consume the event
if (true || sca == 0)
{
if (ShowPreviewObject(out Vector3 placePos,out Quaternion placeRot, out Bounds bounds))
{
if ((ev.type == EventType.MouseDown && ev.button == 0 && sca == 0)
||(ev.type == EventType.MouseDrag && ev.button == 0 && sca == 0 && !bounds.Intersects(lastPlace)))
{
GameObject gameObject = PrefabUtility.InstantiatePrefab(palette[paletteIndex]) as GameObject;
gameObject.transform.position = placePos;
gameObject.transform.rotation = placeRot;
gameObject.transform.parent = container;
lastPlace = bounds;
Undo.RegisterCreatedObjectUndo(gameObject, "");
ev.Use();
}
}
else
HidePreviewObject();
}
else
HidePreviewObject();
if (ev.type == EventType.KeyDown && ev.keyCode == KeyCode.R && sca == 0)
{
rotate = (rotate + 1) % 4;
ev.Use();
Repaint();
}
if (ev.type == EventType.KeyDown && ev.keyCode == KeyCode.C && sca == 0)
{
halfIntegerX = !halfIntegerX;
if (halfIntegerX) halfIntegerZ = !halfIntegerZ;
ev.Use();
Repaint();
}
if (ev.type == EventType.KeyDown && (ev.keyCode == KeyCode.Alpha1 || Tools.current!=Tool.None) && sca == 0)
{
paintMode = false;
HidePreviewObject();
ev.Use();
Repaint();
}
if (ev.type == EventType.ScrollWheel && sca == 1)
{
if (ev.delta.y < 0)
selectedY += 1;
if (ev.delta.y > 0)
selectedY -= 1;
mouseMoved = false;
ev.Use();
Repaint();
}
}
else
{
HidePreviewObject();
if (ev.type == EventType.KeyDown && ev.keyCode == KeyCode.Alpha1)
{
EnterPaintModeAndFocus();
ev.Use();
Repaint();
}
}
string path1 = AssetDatabase.GetAssetPath(Selection.activeObject);
if (path1 != "")
{
if (Path.GetExtension(path1) != "")
path1 = path1.Replace(Path.GetFileName(AssetDatabase.GetAssetPath(Selection.activeObject)), "");
if (path != path1)
{
path = path1;
Repaint();
}
}
}
void EnterPaintModeAndFocus()
{
(SceneView.sceneViews[0] as SceneView).Focus();
Selection.SetActiveObjectWithContext(null, null);
Tools.current = Tool.None;
paintMode = true;
}
}
#endif
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment