Sinossi:
Sto lavorando ad un editor di livelli per un gioco scritto usando Unity , il gioco è in realtà un nuovo motore per un vecchio gioco. Questo è un gioco di +20 anni e le sue strutture dati sono conformi agli standard attuali, difficili da gestire in un ambiente come Unity. Quindi, perché sto scrivendo un editor di livelli per alleviare il dolore.
Fondamentalmente il ruolo di questo editor di livelli è di facilitare il processo di creazione di entità amichevoli da riferimenti a primitive grafiche come modelli e poligoni. Le principali funzionalità sono gli helper che semplificano il prelievo per creare entità amiche (ad esempio, selezionare i modelli 9 e 10 e creare un'entità "billboard").
Problemi:
Ora sono sulla fase di correggere i bug cattivi principalmente a causa di eventi che si verificano all'interno di Unity, ad esempio quando alcuni valori devono essere ripristinati a causa di modifiche esterne e così via.
Quindi ho deciso di dare un'occhiata alla classe usando Mappa codici in Visual Studio e sta diventando sempre più difficile seguire le strutture grazie alle correzioni sopramenzionate:
(i membri in rosso sono Hub trovati dalla Mappa del codice)
Codice:
Stopostandoilcodice(~500righe)quicomeriferimento,quindipuoidareun'occhiatasevuoi.Nesonoabbastanzasoddisfatto,nelsensocheimetodisonocortienonèancoraandatofuoricontrollo.
usingSystem;usingSystem.Collections.Generic;usingSystem.Linq;usingAssets.Scenes;usingAssets.Source.Game.Core;usingJetBrains.Annotations;usingUnityEditor;usingUnityEditor.SceneManagement;usingUnityEngine;usingObject=UnityEngine.Object;namespaceAssets.Source.Game.Scene.Editor{publicsealedclassSceneEditorWindow:EditorWindow{#regionFields/propertiesprivateconststringSceneInputPath="Assets/Scenes/SceneEditorInput.unity";
private const string SceneOutputPath = "Assets/Scenes/SceneEditorOutput.unity";
private bool _highlight;
private GameObject[] _highlightObjects;
private Dictionary<int, ScenePart> _partsInHierarchy;
private Dictionary<int, ScenePrimitive> _primitivesInHierarchy;
private Dictionary<int, ScenePrimitive> _primitivesNotReferenced;
private Dictionary<int, ScenePrimitive> _primitivesReferenced;
private SceneRenderer _sceneRenderer;
private Track _sceneRendererTrack;
private SceneRoot _sceneRoot;
/// <summary>
/// Dictionary of parts in hierarchy.
/// </summary>
private Dictionary<int, ScenePart> PartsInHierarchy
{
get
{
if (_partsInHierarchy == null)
{
var parts = FindObjectsOfType<ScenePart>();
_partsInHierarchy =
parts.ToDictionary(k => k.gameObject.GetInstanceID(), v => v);
}
return _partsInHierarchy;
}
set { _partsInHierarchy = value; }
}
/// <summary>
/// Dictionary of primitives in hierarchy.
/// </summary>
private Dictionary<int, ScenePrimitive> PrimitivesInHierarchy
{
get
{
if (_primitivesInHierarchy == null)
{
var primitives = FindObjectsOfType<ScenePrimitive>();
_primitivesInHierarchy =
primitives.ToDictionary(k => k.gameObject.GetInstanceID(), v => v);
}
return _primitivesInHierarchy;
}
set { _primitivesInHierarchy = value; }
}
private Dictionary<int, ScenePrimitive> PrimitivesNotReferenced
{
get
{
if (_primitivesNotReferenced == null)
{
_primitivesNotReferenced = PrimitivesInHierarchy
.Except(PrimitivesReferenced)
.OrderBy(s => s.Value.Reference)
.ToDictionary(p => p.Key, p => p.Value);
}
return _primitivesNotReferenced;
}
set { _primitivesNotReferenced = value; }
}
/// <summary>
/// Dictionary of primitives referenced in hierarchy.
/// </summary>
private Dictionary<int, ScenePrimitive> PrimitivesReferenced
{
get
{
if (_primitivesReferenced == null)
{
var comparer = new SceneReferenceEqualityComparer();
_primitivesReferenced = PrimitivesInHierarchy
.Where(s => ScenePrimitiveReferenced(s.Value, comparer))
.ToDictionary(k => k.Key, v => v.Value);
}
return _primitivesReferenced;
}
set { _primitivesReferenced = value; }
}
/// <summary>
/// Scene renderer in use.
/// </summary>
private SceneRenderer SceneRenderer
{
get
{
if (_sceneRenderer == null)
_sceneRenderer = FindObjectsOfType<SceneRenderer>().Single();
return _sceneRenderer;
}
}
/// <summary>
/// Scene root element.
/// </summary>
private SceneRoot SceneRoot
{
get
{
if (_sceneRoot == null)
_sceneRoot = FindObjectsOfType<SceneRoot>().Single();
return _sceneRoot;
}
}
#endregion
#region Unity
[MenuItem("WXX-REBIRTH/Scene editor")]
[UsedImplicitly]
private static void Init()
{
var window = GetWindow<SceneEditorWindow>("Scene editor", true);
window.minSize = new Vector2(360.0f, 225.0f);
}
[UsedImplicitly]
private void OnEnable()
{
EditorApplication.hierarchyWindowChanged += OnHierarchyWindowChanged;
EditorApplication.hierarchyWindowItemOnGUI += OnHierarchyWindowItemOnGUI;
EditorApplication.update += OnUpdate;
Repaint();
HierarchyReset();
// TODO this currently de-selects, shouldn't (renderer does rebuild on enable)
HighlightReset();
}
private void OnUpdate()
{
// reset scene-object-related stuff after a track change
var track = SceneRenderer.Track;
if (track != _sceneRendererTrack)
{
HighlightReset();
HierarchyReset();
HighlightReset();
_sceneRendererTrack = track;
}
}
[UsedImplicitly]
private void OnGUI()
{
if (GUIWorkspace())
{
GUIActions();
GUIOptions();
GUIStats();
}
HelpBox.Draw();
}
private void OnHierarchyWindowChanged()
{
// TODO is this one really needed ?
HierarchyReset(); // let's keep it simple by simply doing this
Repaint();
}
private void OnHierarchyWindowItemOnGUI(int id, Rect rect)
{
// if a primitive is referenced by a part, add a green icon, else add a red icon
if (!PrimitivesInHierarchy.ContainsKey(id))
return;
var primitive = PrimitivesInHierarchy[id];
var comparer = new SceneReferenceEqualityComparer(); // won't work otherwise thanks to Unity IDs :)
var referenced = ScenePrimitiveReferenced(primitive, comparer);
var iconType = referenced ? IconType.CircleOkGreen16 : IconType.CircleErrorRed16;
Texture2D[] icons = { IconHelper.GetIcon(iconType) };
var rect1 = rect;
foreach (var icon in icons)
{
rect1.x = rect1.xMax - 16.0f;
rect1.width = 16.0f;
GUI.DrawTexture(rect1, icon);
}
}
[UsedImplicitly]
private void OnSelectionChange()
{
Highlight();
Repaint(); // for actions
}
#endregion
#region GUI
/// <summary>
/// Workspace screen in GUI.
/// </summary>
/// <returns></returns>
private bool GUIWorkspace()
{
// already open ?
var scenes = new[]
{
new SceneSetup {path = SceneInputPath, isLoaded = true, isActive = true},
new SceneSetup {path = SceneOutputPath, isLoaded = true, isActive = false}
};
var setup = EditorSceneManager.GetSceneManagerSetup();
if (scenes.All(s => setup.Any(t => t.path == s.path && t.isLoaded == s.isLoaded)))
return true;
// try open it, ensure current dirty scenes are saved first if any
HelpBox.LogError("Workspace is not opened.");
if (!GUIHelper.GUIButtonLargeCenter("Open workspace"))
return false;
if (!EditorSceneManager.SaveCurrentModifiedScenesIfUserWantsTo())
return false;
EditorSceneManager.RestoreSceneManagerSetup(scenes);
HierarchyReset(); // they're all "null" at this point
return true;
}
/// <summary>
/// Actions section in GUI.
/// </summary>
private void GUIActions()
{
GUIHelper.GUILabelLarge("Actions");
// here an action will be disabled
// - if there is no targetted type in selection
// - if there are unrelated objects in selection
var gameObjects = Selection.gameObjects;
var primitives = SelectedPrimitives();
var parts = SelectedParts();
var objects = SelectedObjects();
var n = !gameObjects.Any();
var n1 = !primitives.Any();
var n2 = !parts.Any();
var n3 = !objects.Any();
var m1 = gameObjects.Length != primitives.Length;
var m2 = gameObjects.Length != parts.Length;
var m3 = gameObjects.Length != objects.Length;
var d1 = n || n1 || m1 || primitives.Select(s => s.Reference.Type).Distinct().Count() > 1;
ActionNewPartFromSelection(d1, primitives.Select(s => s.Reference).ToArray());
var d2 = n || n2 || m2;
ActionNewObjectFromSelection(d2, parts);
var d3 = n || n3 || m3;
ActionNewGroupFromSelection(d3, objects);
var d4 = n || n1 || m1 || primitives.Any(s => s.Reference.Type != SceneReferenceType.Polygon);
ActionSelectParentModels(d4, primitives);
var d5 = PrimitivesNotReferenced == null || !PrimitivesNotReferenced.Any();
ActionSelectFirstNotReferenced(d5);
}
/// <summary>
/// Options section in GUI.
/// </summary>
private void GUIOptions()
{
GUIHelper.GUILabelLarge("Options");
// highlighting
var highlight = _highlight;
if (highlight != (_highlight = EditorGUILayout.ToggleLeft("Highlight selection", highlight)))
Highlight(); // make it react at an editor change instead of a selection change
EditorGUILayout.Space();
}
/// <summary>
/// Stats section in GUI.
/// </summary>
private void GUIStats()
{
GUIHelper.GUILabelLarge("Stats");
var count1 = PrimitivesInHierarchy.Count;
EditorGUILayout.LabelField("Primitives in hierarchy", count1.ToString());
var count2 = PrimitivesReferenced.Count;
var percent1 = 1.0d / count1 * count2 * 100.0d;
EditorGUILayout.LabelField("Primitives referenced", string.Format("{0} ({1:F}%)", count2, percent1));
var count3 = PrimitivesNotReferenced.Count;
var percent2 = 1.0d / count1 * count3 * 100.0d;
EditorGUILayout.LabelField("Primitives not referenced", string.Format("{0} ({1:F}%)", count3, percent2));
var count4 = PartsInHierarchy.Count;
EditorGUILayout.LabelField("Parts in hierarchy", count4.ToString());
// TODO objects
EditorGUILayout.Space();
}
#endregion
#region Everything else
/// <summary>
/// Action that creates an object from a selection of parts.
/// </summary>
/// <param name="disabled"></param>
/// <param name="parts"></param>
private void ActionNewObjectFromSelection(bool disabled, [NotNull] ScenePart[] parts)
{
if (parts == null)
throw new ArgumentNullException("parts");
// realize the button
var tooltip = "To create an object, select one or more parts.";
if (!GUIHelper.GUIButtonAction(disabled, new GUIContent("+ New object from selection", tooltip)))
return;
// create new object with these parts
using (new SceneEditorObjectScope("Object", SceneRoot, parts))
{
}
}
/// <summary>
/// Action that creates a part from a selection of references (primitives).
/// </summary>
/// <param name="disabled"></param>
/// <param name="references"></param>
private void ActionNewPartFromSelection(bool disabled, [NotNull] SceneReference[] references)
{
if (references == null)
throw new ArgumentNullException("references");
// realize the button
var tooltip = "To create a part, select either models or polygons.";
var content = new GUIContent("+ New part from selection", tooltip);
if (!GUIHelper.GUIButtonAction(disabled, content))
return;
// create new part with these references
using (new SceneEditorPartScope("Part", SceneRoot, references))
{
}
}
/// <summary>
/// Action that creates a group from a selection of objects.
/// </summary>
/// <param name="disabled"></param>
/// <param name="objects"></param>
private void ActionNewGroupFromSelection(bool disabled, [NotNull] SceneObject[] objects)
{
if (objects == null)
throw new ArgumentNullException("objects");
// realize the button
var tooltip = "To create a group, select one or more objects.";
var content = new GUIContent("+ New group from selection", tooltip);
if (!GUIHelper.GUIButtonAction(disabled, content))
return;
// create new group with these objects
using (new SceneEditorGroupScope("Group", SceneRoot, objects))
{
}
}
/// <summary>
/// Action that selects parent models of selected primitives.
/// </summary>
/// <param name="disabled"></param>
/// <param name="primitives"></param>
private void ActionSelectParentModels(bool disabled, [NotNull] ScenePrimitive[] primitives)
{
if (primitives == null)
throw new ArgumentNullException("primitives");
// realize the button
var tooltip = "To select parents, select one or more polygons.";
if (!GUIHelper.GUIButtonAction(disabled, new GUIContent("+ Select parent model(s)", tooltip)))
return;
// get polygon sources
var polygons = primitives
.Where(s => s.Reference.Type == SceneReferenceType.Polygon)
.ToArray();
// get polygons parents
var collection = PrimitivesInHierarchy.Values;
var parents = polygons
.Select(polygon => collection.SingleOrDefault(s =>
{
var r = s.Reference;
var b = r.Type == SceneReferenceType.Model &&
r.Container == polygon.Reference.Container &&
r.Model == polygon.Reference.Model;
return b;
}))
.Where(parent => parent != null)
.Distinct()
.Select(s => (Object)s.gameObject)
.ToArray();
Selection.objects = parents;
// make it easy for single selection
if (parents.Length == 1) SelectionHelper.SelectFramePing(parents.First());
}
private void ActionSelectFirstNotReferenced(bool disabled)
{
string tooltip = "TODO";
if (!GUIHelper.GUIButtonAction(disabled, new GUIContent("+ Select first not referenced", tooltip)))
return;
var primitive = this.PrimitivesNotReferenced.Values.First();
SelectionHelper.SelectFramePing(primitive);
}
/// <summary>
/// Resets hierarchy-related data, this will force repopulation on next query.
/// </summary>
private void HierarchyReset()
{
// re-populate these
PrimitivesInHierarchy = null; // when user disable GOs
PrimitivesReferenced = null;
PrimitivesNotReferenced = null;
PartsInHierarchy = null;
}
/// <summary>
/// Highlights currently selected primitives.
/// </summary>
private void Highlight() // todo rename all once 2nd highlight is in place
{
// color all childs of all objects
Action<GameObject[], Color> action = (gameObjects, color) =>
{
foreach (var o in gameObjects)
{
var renderers = o.GetComponentsInChildren<MeshRenderer>();
foreach (var renderer in renderers)
{
renderer.sharedMaterial.color = color;
}
}
};
// NOTE let's just stick to null pattern even though zero length arrays are returned
// clear highlighting for previous objects
if (_highlightObjects != null)
action(_highlightObjects, Color.white);
// pick scene primitives in selection (polygons might be hit twice but that'll do)
var primitives = SelectedPrimitives();
_highlightObjects = primitives.Any() ? primitives.Select(s => s.gameObject).ToArray() : null;
// highlight currents if any
if (_highlight && _highlightObjects != null)
action(_highlightObjects, Color.magenta);
}
/// <summary>
/// Resets highlighting by clearing cache.
/// </summary>
private void HighlightReset()
{
_highlightObjects = null;
}
/// <summary>
/// Gets if a primitive is referenced.
/// </summary>
/// <param name="primitive"></param>
/// <param name="comparer"></param>
/// <returns></returns>
private bool ScenePrimitiveReferenced([NotNull] ScenePrimitive primitive,
[NotNull] SceneReferenceEqualityComparer comparer)
{
if (primitive == null)
throw new ArgumentNullException("primitive");
if (comparer == null)
throw new ArgumentNullException("comparer");
var r = primitive.Reference;
var b = PartsInHierarchy.Any(s => s.Value.References.Contains(r, comparer));
// if a polygon is not referenced but parent is, then it is too
if (r.Type == SceneReferenceType.Polygon)
{
b |= PartsInHierarchy.Any(
s => s.Value.References.Any(
t => t.Container == r.Container && t.Model == r.Model && t.Type == SceneReferenceType.Model));
}
return b;
}
/// <summary>
/// Gets currently selected primitives in hierarchy.
/// </summary>
/// <returns></returns>
private ScenePrimitive[] SelectedPrimitives()
{
var primitives = Selection.GetFiltered(typeof(ScenePrimitive), SelectionMode.Unfiltered)
.Cast<ScenePrimitive>()
.ToArray();
return primitives;
}
/// <summary>
/// Gets currently selected parts in hierarchy.
/// </summary>
/// <returns></returns>
private ScenePart[] SelectedParts()
{
var parts = Selection.GetFiltered(typeof(ScenePart), SelectionMode.Unfiltered)
.Cast<ScenePart>()
.ToArray();
return parts;
}
/// <summary>
/// Gets currently selected objects in hierarchy.
/// </summary>
/// <returns></returns>
private SceneObject[] SelectedObjects()
{
var objects = Selection.GetFiltered(typeof(SceneObject), SelectionMode.Unfiltered)
.Cast<SceneObject>()
.ToArray();
return objects;
}
#endregion
}
}
Domanda:
Che tipo di approccio / strategia adottare quando vengono fatte sempre più chiamate tra membri di una classe e si vuole ridurre la quantità?