Skip to content

Instantly share code, notes, and snippets.

@alankent
Created September 25, 2021 22:16
Show Gist options
  • Save alankent/a8df5d39eff57b7db2afcead1185bee0 to your computer and use it in GitHub Desktop.
Save alankent/a8df5d39eff57b7db2afcead1185bee0 to your computer and use it in GitHub Desktop.
using UnityEngine;
using UnityEditor;
using UnityEngine.Timeline;
using UnityEngine.Playables;
using System.Collections.Generic;
using UnityEditor.Timeline;
public class MyAssemblyWindow : EditorWindow
{
[MenuItem("Window/Alan Tools/Assembly")]
public static void ShowWindow()
{
// Find or create window so it appears on the screen.
EditorWindow.GetWindow<MyAssemblyWindow>("Assembly");
}
private enum Positioning { InScene, AtCamera }
private enum Rotation { FacingCamera, SameAsCamera, AsIs }
private enum Gravity { Float, OnGround }
void OnGUI()
{
AddCategory("Characters", Positioning.InScene, Rotation.FacingCamera, Gravity.OnGround);
AddCategory("Props", Positioning.InScene, Rotation.AsIs, Gravity.OnGround);
AddCategory("Lighting", Positioning.InScene, Rotation.AsIs, Gravity.Float);
AddCategory("Photography", Positioning.AtCamera, Rotation.SameAsCamera, Gravity.Float);
}
private void AddCategory(string name, Positioning positioning, Rotation rotation, Gravity gravity)
{
var assets = AssetDatabase.FindAssets("t:Prefab", new[] { "Assets/_SHARED/Assembly Prefabs/" + name, "Assets/_LOCAL/Assembly Prefabs/" + name });
if (assets.Length == 0)
{
return;
}
var buttonStyle = EditorStyles.miniButton;
GUILayout.Label(name, EditorStyles.boldLabel);
var inHoriz = false;
int i = 0;
foreach (var guid in assets)
{
var path = AssetDatabase.GUIDToAssetPath(guid);
// Get a clean name for the button (removing "Prefab Variant" style endings).
var prefabName = path.Substring(path.LastIndexOf('/') + 1);
prefabName = prefabName.Remove(prefabName.LastIndexOf('.'));
if (prefabName.EndsWith(" Variant"))
{
prefabName = prefabName.Remove(prefabName.LastIndexOf(' '));
}
if (prefabName.EndsWith(" Prefab"))
{
prefabName = prefabName.Remove(prefabName.LastIndexOf(' '));
}
// Putting 4 per line because cannot work out how to flow them nicely to pack as many as will fit per line.
// TODO: try https://docs.unity3d.com/ScriptReference/EditorGUIUtility.GetFlowLayoutedRects.html
if (!inHoriz)
{
GUILayout.BeginHorizontal("box");
inHoriz = true;
}
if (GUILayout.Button(prefabName, buttonStyle))
{
AddPrefabInstance(prefabName, path, positioning, rotation, gravity);
}
if ((i % 4) == 3)
{
GUILayout.EndHorizontal();
inHoriz = false;
}
i++;
}
if (inHoriz)
{
GUILayout.EndHorizontal();
}
}
/*
* Add prefab instance
*/
private void AddPrefabInstance(string name, string path, Positioning positioning, Rotation rotation, Gravity gravity)
{
bool refreshNeeded = false;
// Normally only one sequence would be selected, but just in case...
foreach (GameObject obj in Selection.gameObjects)
{
// Don't add if a sequence not currently selected
// (User can end up dropping things in wrong spots by accident)
PlayableDirector director = obj.GetComponent<PlayableDirector>();
if (director == null)
{
Debug.LogWarning("Not dropping instance as current selection is not a sequence node");
}
else
{
// Don't add the same character multiple times
// (This might be a bad idea - e.g. might want multiple instances of a prop)
//var child = obj.transform.Find(name);
//if (child == null)
{
// Does not already exist, lets try and add it.
var prefab = (GameObject)AssetDatabase.LoadAssetAtPath(path, typeof(GameObject));
if (prefab == null)
{
Debug.LogError("Could not load '" + name + "' at path '" + path + "'.");
}
else
{
// Add prefab game object as child.
var inst = PrefabUtility.InstantiatePrefab(prefab) as GameObject;
Undo.RecordObject(obj.transform, "Add " + name + " to sequence");
inst.transform.parent = obj.transform;
refreshNeeded = true;
// Move new instance to where camera is (rather than starting at 0,0)
var sceneView = UnityEditor.SceneView.lastActiveSceneView;
if (sceneView != null)
{
if (positioning == Positioning.InScene)
{
//inst.transform.position = sceneView.pivot;
inst.transform.position = sceneView.camera.transform.position + 4f * sceneView.camera.transform.forward;
}
else
{
inst.transform.position = sceneView.camera.transform.position;
}
if (rotation != Rotation.AsIs)
{
// Default it to look at the scene camera
var offset = (rotation == Rotation.FacingCamera) ? 180f : 0f;
inst.transform.rotation = Quaternion.Euler(0f, sceneView.rotation.eulerAngles.y + offset, 0f);
}
// Try to make it land on ground so don't have to get the Y value correct manually
// https://answers.unity.com/questions/39203/instantiate-object-and-align-with-object-surface.html
if (gravity == Gravity.OnGround)
{
RaycastHit hit;
var down = new Vector3(0, -1, 0);
if (Physics.Raycast(inst.transform.position, down, out hit))
{
// if too far away, its probably best to leave at middle of scene view
var distanceToGround = hit.distance;
if (distanceToGround < 3f)
{
var currentPos = inst.transform.position;
var newY = currentPos.y - distanceToGround;
inst.transform.position = new Vector3(currentPos.x, newY, currentPos.z);
}
}
}
}
// If we find the Timeline, add an animation track to the timeline as well
// if the new object has an Animator component (otherwise we cannot animate it)
TimelineAsset timeline = (TimelineAsset) director.playableAsset;
if (timeline != null && inst.GetComponent<Animator>() != null)
{
AnimationTrack track = timeline.CreateTrack<AnimationTrack>(null, name);
director.SetGenericBinding(track, inst);
}
}
}
}
}
if (refreshNeeded)
{
TimelineEditor.Refresh(RefreshReason.ContentsAddedOrRemoved);
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment