Last active
March 1, 2022 04:51
-
-
Save fangzhangmnm/bdb16f3970c2158c3bb829bf2685bb94 to your computer and use it in GitHub Desktop.
Unity3D paint prefabs on grid
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
//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