Unity 2017.2에서 작은 ARPG를 작업 중입니다.하위 검사기가있는 Unity Custom Inspector
내 게임의 AbilityBluePrint 클래스에 대한 사용자 지정 편집기를 구현해 보았습니다.
기본적으로 AbilityBluePrints에는 런타임에 능력을 생성하는 데 필요한 모든 정보가 들어 있습니다. 능력이 사용될 때 트리거되는 Effect [] ScritpableObjects의 배열을 포함합니다.
저는 현재 구현 및 작업이 필요한 모든 것을 갖추고 있지만 다음과 같은 이유로 매우 지루한 능력을 창출하려고합니다.
효과 클래스가 있습니다. DamagePlusX : 피해 수정 자 값의 효과. 이 효과가 서로 다른 두 가지 능력에 대해 다른 수식어 값을 갖기를 원하면 자산 디렉토리에 두 개의 인스턴스를 만들고 각각을 해당 능력의 Effect [] 배열에 수동으로 할당해야합니다. 본질적으로 서로 다른 int와 float를 가진 많은 효과의 인스턴스를 가지고 결국 끝나게 될까 우려됩니다.
따라서 Unity에서 나온 the Adventure Tutorial의 것과 같은 사용자 지정 관리자를 사용할 것이라고 생각했습니다.
기본적으로 AbilityBluePrint의 인스턴스를 만든 다음 Custom Inspector를 사용하여 Effects [] 배열에서 Effects를 동적으로 인스턴스화하고 AbilityBluePrint 속성에서 각 효과의 속성을 직접 편집 할 수 있습니다. 기본적으로
내가 뭔가를해야만 조금 그 (가난한 포토샵에 대한 사과)처럼 좀하고 싶습니다 :
내 요구에 맞게 튜토리얼에서 스크립트를 변환하는 시도했지만 내가 가진 유지 어제부터 같은 오류가 :
NullReferenceException: Object reference not set to an instance of an object
AbilityBluePrintEditor.SubEditorSetup (.EffectEditor editor) (at Assets/Scripts/Editor/AbilityBluePrintEditor.cs:90)
EditorWithSubEditors`2[TEditor,TTarget].CheckAndCreateSubEditors (.TTarget[] subEditorTargets) (at Assets/Scripts/Editor/EditorWithSubEditors.cs:33)
나는 내가 뭘하려고 오전 스크립트 객체로 행할 경우 궁금하고 많은 것들을 시도했습니다. 원본 튜토리얼에서 BluePrintAbility와 동일한 것은 Monobehaviour입니다.
내가 가진 코드는 다음과 같습니다 :
내 BluePrintAbility 등급 :
[CreateAssetMenu(fileName = "New Ability BluePrint", menuName = "Ability BluePrint")]
public class AbilityBluePrint : ScriptableObject {
public Effect[] effects = new Effect[0];
public string description;
}
내 Effect 클래스 :
public abstract class Effect : ScriptableObject {
}
내 DamagePlusX 효과 클래스 :
[CreateAssetMenu(fileName = "DamagePlusX",menuName = "Effects/DamagePlusX")]
public class DamagePlusX : Effect
{
[SerializeField]
int modifier;
public void ApplyModifier(){ // some logic}
}
그리고 지금 편집자 (오랫동안 샘플에 대한 사과,하지만 지금은 오류가 거기에없는, 내가 주 수업을 자른) :
이것은 내 오류가 어디서 왔는지 튜토리얼에서 기본 편집기입니다 :
// This class acts as a base class for Editors that have Editors
// nested within them. For example, the InteractableEditor has
// an array of ConditionCollectionEditors.
// It's generic types represent the type of Editor array that are
// nested within this Editor and the target type of those Editors.
public abstract class EditorWithSubEditors<TEditor, TTarget> : Editor
where TEditor : Editor
where TTarget : Object
{
protected TEditor[] subEditors; // Array of Editors nested within this Editor.
// This should be called in OnEnable and at the start of OnInspectorGUI.
protected void CheckAndCreateSubEditors (TTarget[] subEditorTargets)
{
// If there are the correct number of subEditors then do nothing.
if (subEditors != null && subEditors.Length == subEditorTargets.Length)
return;
// Otherwise get rid of the editors.
CleanupEditors();
// Create an array of the subEditor type that is the right length for the targets.
subEditors = new TEditor[subEditorTargets.Length];
// Populate the array and setup each Editor.
for (int i = 0; i < subEditors.Length; i++)
{
subEditors[i] = CreateEditor (subEditorTargets[i]) as TEditor;
SubEditorSetup (subEditors[i]); // ERROR comes inside this function HERE !!!!
}
}
// This should be called in OnDisable.
protected void CleanupEditors()
{
// If there are no subEditors do nothing.
if (subEditors == null)
return;
// Otherwise destroy all the subEditors.
for (int i = 0; i < subEditors.Length; i++)
{
DestroyImmediate (subEditors[i]);
}
// Null the array so it's GCed.
subEditors = null;
}
// This must be overridden to provide any setup the subEditor needs when it is first created.
protected abstract void SubEditorSetup (TEditor editor);
}
[CustomEditor(typeof(AbilityBluePrint)), CanEditMultipleObjects]
public class AbilityBluePrintEditor : EditorWithSubEditors<EffectEditor, Effect>
{
private AbilityBluePrint blueprint; // Reference to the target.
private SerializedProperty effectsProperty; //represents the array of effects.
private Type[] effectTypes; // All the non-abstract types which inherit from Effect. This is used for adding new Effects.
private string[] effectTypeNames; // The names of all appropriate Effect types.
private int selectedIndex; // The index of the currently selected Effect type.
private const float dropAreaHeight = 50f; // Height in pixels of the area for dropping scripts.
private const float controlSpacing = 5f; // Width in pixels between the popup type selection and drop area.
private const string effectsPropName = "effects"; // Name of the field for the array of Effects.
private readonly float verticalSpacing = EditorGUIUtility.standardVerticalSpacing;
// Caching the vertical spacing between GUI elements.
private void OnEnable()
{
// Cache the target.
blueprint = (AbilityBluePrint)target;
// Cache the SerializedProperty
effectsProperty = serializedObject.FindProperty(effectsPropName);
// If new editors for Effects are required, create them.
CheckAndCreateSubEditors(blueprint.effects);
// Set the array of types and type names of subtypes of Reaction.
SetEffectNamesArray();
}
public override void OnInspectorGUI()
{
// Pull all the information from the target into the serializedObject.
serializedObject.Update();
// If new editors for Reactions are required, create them.
CheckAndCreateSubEditors(blueprint.effects);
DrawDefaultInspector();
// Display all the Effects.
for (int i = 0; i < subEditors.Length; i++)
{
if (subEditors[i] != null)
{
subEditors[i].OnInspectorGUI();
}
}
// If there are Effects, add a space.
if (blueprint.effects.Length > 0)
{
EditorGUILayout.Space();
EditorGUILayout.Space();
}
//Shows the effect selection GUI
SelectionGUI();
if (GUILayout.Button("Add Effect"))
{
}
// Push data back from the serializedObject to the target.
serializedObject.ApplyModifiedProperties();
}
private void OnDisable()
{
// Destroy all the subeditors.
CleanupEditors();
}
// This is called immediately after each ReactionEditor is created.
protected override void SubEditorSetup(EffectEditor editor)
{
// Make sure the ReactionEditors have a reference to the array that contains their targets.
editor.effectsProperty = effectsProperty; //ERROR IS HERE !!!
}
private void SetEffectNamesArray()
{
// Store the Effect type.
Type effectType = typeof(Effect);
// Get all the types that are in the same Assembly (all the runtime scripts) as the Effect type.
Type[] allTypes = effectType.Assembly.GetTypes();
// Create an empty list to store all the types that are subtypes of Effect.
List<Type> effectSubTypeList = new List<Type>();
// Go through all the types in the Assembly...
for (int i = 0; i < allTypes.Length; i++)
{
// ... and if they are a non-abstract subclass of Effect then add them to the list.
if (allTypes[i].IsSubclassOf(effectType) && !allTypes[i].IsAbstract)
{
effectSubTypeList.Add(allTypes[i]);
}
}
// Convert the list to an array and store it.
effectTypes = effectSubTypeList.ToArray();
// Create an empty list of strings to store the names of the Effect types.
List<string> reactionTypeNameList = new List<string>();
// Go through all the Effect types and add their names to the list.
for (int i = 0; i < effectTypes.Length; i++)
{
reactionTypeNameList.Add(effectTypes[i].Name);
}
// Convert the list to an array and store it.
effectTypeNames = reactionTypeNameList.ToArray();
}
private void SelectionGUI()
{
// Create a Rect for the full width of the inspector with enough height for the drop area.
Rect fullWidthRect = GUILayoutUtility.GetRect(GUIContent.none, GUIStyle.none, GUILayout.Height(dropAreaHeight + verticalSpacing));
// Create a Rect for the left GUI controls.
Rect leftAreaRect = fullWidthRect;
// It should be in half a space from the top.
leftAreaRect.y += verticalSpacing * 0.5f;
// The width should be slightly less than half the width of the inspector.
leftAreaRect.width *= 0.5f;
leftAreaRect.width -= controlSpacing * 0.5f;
// The height should be the same as the drop area.
leftAreaRect.height = dropAreaHeight;
// Create a Rect for the right GUI controls that is the same as the left Rect except...
Rect rightAreaRect = leftAreaRect;
// ... it should be on the right.
rightAreaRect.x += rightAreaRect.width + controlSpacing;
// Display the GUI for the type popup and button on the left.
TypeSelectionGUI(leftAreaRect);
}
private void TypeSelectionGUI(Rect containingRect)
{
// Create Rects for the top and bottom half.
Rect topHalf = containingRect;
topHalf.height *= 0.5f;
Rect bottomHalf = topHalf;
bottomHalf.y += bottomHalf.height;
// Display a popup in the top half showing all the reaction types.
selectedIndex = EditorGUI.Popup(topHalf, selectedIndex, effectTypeNames);
// Display a button in the bottom half that if clicked...
if (GUI.Button(bottomHalf, "Add Selected Effect"))
{
// ... finds the type selected by the popup, creates an appropriate reaction and adds it to the array.
Debug.Log(effectTypes[selectedIndex]);
Type effectType = effectTypes[selectedIndex];
Effect newEffect = EffectEditor.CreateEffect(effectType);
Debug.Log(newEffect);
effectsProperty.AddToObjectArray(newEffect);
}
}
}
public abstract class EffectEditor : Editor
{
public bool showEffect = true; // Is the effect editor expanded?
public SerializedProperty effectsProperty; // Represents the SerializedProperty of the array the target belongs to.
private Effect effect; // The target Reaction.
private const float buttonWidth = 30f; // Width in pixels of the button to remove this Reaction from the ReactionCollection array.
private void OnEnable()
{
// Cache the target reference.
effect = (Effect)target;
// Call an initialisation method for inheriting classes.
Init();
}
// This function should be overridden by inheriting classes that need initialisation.
protected virtual void Init() { }
public override void OnInspectorGUI()
{
Debug.Log("attempt to draw effect inspector");
// Pull data from the target into the serializedObject.
serializedObject.Update();
EditorGUILayout.BeginVertical(GUI.skin.box);
EditorGUI.indentLevel++;
DrawDefaultInspector();
EditorGUI.indentLevel--;
EditorGUILayout.EndVertical();
// Push data back from the serializedObject to the target.
serializedObject.ApplyModifiedProperties();
}
public static Effect CreateEffect(Type effectType)
{
// Create a reaction of a given type.
return (Effect) ScriptableObject.CreateInstance(effectType);
}
}
[CustomEditor(typeof(DamagePlusXEditor))]
public class DamagePlusXEditor : EffectEditor {}
제안 해 주셔서 감사합니다. 나는 당신의 방법을 사용하여 그것이 작동하도록 바꾸어야 할 것에 대해 생각할 것입니다. 뜻하지 않은 변화가 없다면 답을 받아 들일 것입니다. – Sorade