Last active
June 6, 2022 09:11
-
-
Save darbotron/9622723bfbbe983bf0ea5096cd94a427 to your computer and use it in GitHub Desktop.
Super easily (and extensibly) add a project global settings asset to the Unity Editor's project settings panels and/or edit in a floating window
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
// | |
// GenericUnityEditorSettings by Alex 'darbotron' Darby | |
// | |
// License: https://opensource.org/licenses/unlicense | |
// TL;DR: | |
// 1) you may do what you like with it... | |
// 2) ...except blame me for any consequence of acting on rule 1) | |
// | |
using UnityEngine; | |
using UnityEditor; | |
[CreateAssetMenu( fileName = "ExampleSettings.asset", menuName = "Example/Example Settings" )] | |
public class ExampleSettings : GenericEditorSettings< ExampleSettings > | |
{ | |
public bool m_someFlag = true; | |
[Space][Header( "yep, editor attributes work!" )][SerializeField] private bool m_someOtherFlag = true; | |
public TestAsset m_objectReference; | |
[ObjectInspectorDrawer.ShowInlineGUI] public TestAsset m_objectReferenceWithInlineGUI; | |
protected override void DefaultInitialise() | |
{ | |
m_someFlag = true; | |
m_someOtherFlag = true; | |
} | |
} | |
class ExampleSettingsProvider : GenericEditorSettingsProvider< ExampleSettingsProvider, ExampleSettings > | |
{ | |
// IMPORTANT: this registers the settings provider with the editor | |
[SettingsProvider] public static SettingsProvider CreateProvider() => InitialiseSettingsProvider( new ExampleSettingsProvider() ); | |
public ExampleSettingsProvider() : base( $"{ObjectNames.NicifyVariableName( nameof( ExampleSettings ) )}", SettingsScope.Project ) | |
{} | |
} | |
class ExampleSettingsWindow : GenericEditorSettingsEditorWindow< ExampleSettingsWindow, ExampleSettings > | |
{ | |
[MenuItem( "Example/Show Example Settings Editor" )] | |
static void MenuShowWindow() => FindOrCreateWindow(); | |
protected override float VGetRequiredLabelWidth() => 400f; | |
} |
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
// | |
// GenericUnityEditorSettings by Alex 'darbotron' Darby | |
// | |
// License: https://opensource.org/licenses/unlicense | |
// TL;DR: | |
// 1) you may do what you like with it... | |
// 2) ...except blame me for any consequence of acting on rule 1) | |
// | |
using System; | |
using System.Linq; | |
using System.Reflection; | |
using System.Collections.Generic; | |
using UnityEditor; | |
using UnityEngine; | |
////////////////////////////////////////////////////////////////////////////// | |
/// base class for generically handled editor settings asset used by | |
/// GenericEditorSettingsProvider | |
/// | |
/// Inherit from this and implement DefaultInitialise() to create a class | |
/// which can be generically handled as a settings item in the Project | |
/// settings | |
/// | |
/// For editor access, treat GenericEditorSettings< T >.GetOrCreateSettings() | |
/// like a singleton instance function | |
/// | |
/// The asset is created in the root of "Assets/" with the namespace qualified | |
/// name of type T | |
////////////////////////////////////////////////////////////////////////////// | |
public abstract class GenericEditorSettings< T > : ScriptableObject where T : GenericEditorSettings< T > | |
{ | |
//------------------------------------------------------------------------ | |
/// loads an asset of derived type T at AssetPath() | |
/// if not present it's created | |
/// treat this like a singleton instance function | |
//------------------------------------------------------------------------ | |
public static T GetOrCreateSettings() | |
{ | |
var settings = AssetDatabase.LoadAssetAtPath< T >( AssetPath ); | |
if( settings == null ) | |
{ | |
settings = ScriptableObject.CreateInstance< T >() as T; | |
settings.DefaultInitialise(); | |
AssetDatabase.CreateAsset( settings, AssetPath ); | |
AssetDatabase.SaveAssets(); | |
} | |
return settings; | |
} | |
//------------------------------------------------------------------------ | |
/// change this in derived classes to change the location of the singleton | |
/// asset in the project | |
/// | |
/// NOTE: for this to work you will need to use [InitializeOnLoadMethod] | |
/// <example> | |
/// [InitializeOnLoadMethod] | |
/// private static void OnLoad_SetPathForAssetSingleton() => AssetPath = $"Assets/NewDirectory/{nameof( DerivingType )}.asset"; | |
/// </example> | |
//------------------------------------------------------------------------ | |
public static string AssetPath { get; protected set; } = $"Assets/{typeof( T ).FullName}.asset"; | |
//------------------------------------------------------------------------ | |
internal static SerializedObject GetSettingsAssetAsSerialisedObject() => new SerializedObject( GetOrCreateSettings() ); | |
//------------------------------------------------------------------------ | |
/// implement this in derived types to default initialise the settings | |
/// asset on creation | |
//------------------------------------------------------------------------ | |
protected abstract void DefaultInitialise(); | |
} | |
////////////////////////////////////////////////////////////////////////////// | |
/// base class for generically handling editor project settings | |
/// | |
/// You can use this to create settings under either | |
/// * [Edit -> Project Settings...] or | |
/// * [Edit -> Preferences...] | |
/// depending on the params passed to the base constructor in a derived class | |
/// | |
/// Inherit from GenericEditorSettings create an asset class which contains | |
/// the editor settings properties you want. | |
/// | |
/// This generic class uses ObjectInspectorDrawer to draw all fields of the | |
/// serialised settings asset in a customiseable manner | |
/// | |
/// NOTES: | |
/// * the settings won't appear until after the settings asset exists EITHER: | |
/// ** after the 1st call to GenericEditorSettings< T >.GetOrCreateSettings() | |
/// ** if you use [CreateAssetMenu( ... )] on the asset type to create one from the editor menu manually | |
/// | |
/// * You MUST register the settings provider from the deriving type (see | |
/// example below ) | |
/// | |
/// <example> | |
/// class ExampleSettingsProvider : GenericEditorSettingsProvider< ExampleSettingsProvider, ExampleSettings > | |
/// { | |
/// // IMPORTANT: this registers the settings provider with the editor | |
/// [SettingsProvider] public static SettingsProvider CreateProvider() => InitialiseSettingsProvider( new ExampleSettingsProvider() ); | |
/// public ExampleSettingsProvider() : base( $"{ObjectNames.NicifyVariableName( nameof( ExampleSettings ) )}", SettingsScope.Project ) | |
/// {} | |
/// } | |
/// </example> | |
////////////////////////////////////////////////////////////////////////////// | |
public class GenericEditorSettingsProvider< TProvider, TSettings > : SettingsProvider | |
where TProvider : GenericEditorSettingsProvider< TProvider, TSettings > | |
where TSettings : GenericEditorSettings< TSettings > | |
{ | |
//------------------------------------------------------------------------ | |
public static bool SettingsAssetExists() => System.IO.File.Exists( GenericEditorSettings< TSettings >.AssetPath ); | |
//------------------------------------------------------------------------ | |
public static SettingsProvider InitialiseSettingsProvider( GenericEditorSettingsProvider< TProvider, TSettings > provider ) | |
{ | |
if( SettingsAssetExists() ) | |
{ | |
// Automatically extract all keywords from the Styles. | |
var finfoArrayAllSerialised = ReflectionUtils.GetSerialisedFieldsforType( typeof( TSettings ) ); | |
var keywordList = new List< string >( finfoArrayAllSerialised.Length ); | |
foreach( var finfo in finfoArrayAllSerialised ) | |
{ | |
keywordList.Add( ObjectNames.NicifyVariableName( finfo.Name ) ); | |
} | |
provider.keywords = keywordList; | |
return provider; | |
} | |
// Settings Asset doesn't exist yet; no need to display anything in the Settings window. | |
return null; | |
} | |
//------------------------------------------------------------------------ | |
public override void OnActivate( string searchContext, UnityEngine.UIElements.VisualElement rootElement ) | |
{ | |
// This function is called when the user clicks on the MyCustom element in the Settings window. | |
m_GenericEditorSettings = GenericEditorSettings< TSettings >.GetSettingsAssetAsSerialisedObject(); | |
} | |
//------------------------------------------------------------------------ | |
public override void OnGUI( string searchContext ) | |
{ | |
if( m_objectInspectorDrawer is null ) | |
{ | |
m_objectInspectorDrawer = VGetObjectInspectorDrawer(); | |
} | |
using( var checkChanges = new EditorGUI.ChangeCheckScope() ) | |
{ | |
var originalLabelWidth = EditorGUIUtility.labelWidth; | |
EditorGUIUtility.labelWidth = VGetRequiredLabelWidth(); | |
m_objectInspectorDrawer.DrawObjectGUI( m_GenericEditorSettings ); | |
EditorGUIUtility.labelWidth = originalLabelWidth; | |
if( checkChanges.changed ) | |
{ | |
m_GenericEditorSettings.ApplyModifiedProperties(); | |
} | |
} | |
} | |
//------------------------------------------------------------------------ | |
/// constructor must be called from deriving type | |
/// for params see https://docs.unity3d.com/ScriptReference/SettingsProvider-ctor.html | |
/// note: the keywords param is optional and defaults to null | |
//------------------------------------------------------------------------ | |
protected GenericEditorSettingsProvider( string path, SettingsScope scope ) : base( path, scope ) | |
{} | |
//------------------------------------------------------------------------ | |
protected virtual float VGetRequiredLabelWidth() => 200f; | |
//------------------------------------------------------------------------ | |
protected virtual ObjectInspectorDrawer VGetObjectInspectorDrawer() => new ObjectInspectorDrawer(); | |
private SerializedObject m_GenericEditorSettings; | |
private ObjectInspectorDrawer m_objectInspectorDrawer; | |
} | |
////////////////////////////////////////////////////////////////////////////// | |
/// base class for generically handling a "singleton" settings asset with its | |
/// own editor window | |
/// | |
/// Inherit from GenericEditorSettings to create an asset class which | |
/// contains the properties you want (TSettings) | |
/// | |
/// This generic class uses ObjectInspectorDrawer to display all serialised | |
/// fields of TSettings in the appropriate editor window panel. | |
/// | |
/// NOTES: | |
/// * the settings won't appear until after the settings asset exists EITHER: | |
/// ** after the 1st call to GenericEditorSettings< T >.GetOrCreateSettings() | |
/// ** if you use [CreateAssetMenu( ... )] on the asset type to create one from the editor menu manually | |
/// | |
///<example> | |
/// class ExampleSettingsWindow : GenericEditorSettingsEditorWindow< ExampleSettingsWindow, ExampleSettings > | |
/// { | |
/// [MenuItem( "Example/Show Example Settings Editor" )] | |
/// static void MenuShowWindow() => FindOrCreateWindow(); | |
/// | |
/// protected override float VGetRequiredLabelWidth() => 400f; | |
/// } | |
///</example> | |
////////////////////////////////////////////////////////////////////////////// | |
public class GenericEditorSettingsEditorWindow< TEditorWindow, TSettings > : EditorWindow | |
where TEditorWindow : GenericEditorSettingsEditorWindow< TEditorWindow, TSettings > | |
where TSettings : GenericEditorSettings< TSettings > | |
{ | |
//------------------------------------------------------------------------ | |
protected static void FindOrCreateWindow() | |
{ | |
var thisWindow = EditorWindow.GetWindow< TEditorWindow >( ObjectNames.NicifyVariableName( typeof( TSettings ).Name ) ) as GenericEditorSettingsEditorWindow< TEditorWindow, TSettings >; | |
thisWindow.Show(); | |
} | |
//------------------------------------------------------------------------ | |
public virtual void OnGUI() | |
{ | |
if( m_objectInspectorDrawer is null ) | |
{ | |
m_objectInspectorDrawer = VGetObjectInspectorDrawer(); | |
} | |
using( var scrollScope = new EditorGUILayout.ScrollViewScope( m_v2ScrollPosition, false, true, GUILayout.ExpandWidth( true ) ) ) | |
{ | |
float maxWidthAccountingForTheVerticalScrollbar = position.width - GUI.skin.verticalScrollbar.fixedWidth; | |
using( var vertical = new EditorGUILayout.VerticalScope( GUILayout.MaxWidth( maxWidthAccountingForTheVerticalScrollbar ) ) ) | |
{ | |
using( var checkChanges = new EditorGUI.ChangeCheckScope() ) | |
{ | |
var originalLabelWidth = EditorGUIUtility.labelWidth; | |
EditorGUIUtility.labelWidth = VGetRequiredLabelWidth(); | |
var serialisedObject = GenericEditorSettings< TSettings >.GetSettingsAssetAsSerialisedObject(); | |
m_objectInspectorDrawer.DrawObjectGUI( serialisedObject ); | |
EditorGUIUtility.labelWidth = originalLabelWidth; | |
if( checkChanges.changed ) | |
{ | |
serialisedObject.ApplyModifiedProperties(); | |
} | |
serialisedObject.Dispose(); | |
m_v2ScrollPosition = scrollScope.scrollPosition; | |
} | |
} | |
} | |
} | |
//------------------------------------------------------------------------ | |
protected virtual float VGetRequiredLabelWidth() => 200f; | |
//------------------------------------------------------------------------ | |
protected virtual ObjectInspectorDrawer VGetObjectInspectorDrawer() => new ObjectInspectorDrawer(); | |
[SerializeField] private Vector2 m_v2ScrollPosition = Vector2.zero; | |
private ObjectInspectorDrawer m_objectInspectorDrawer; | |
} | |
/// <summary> | |
/// Hand rolled version of https://docs.unity3d.com/ScriptReference/Editor.DrawDefaultInspector.html but with extensions! | |
/// 1) when tagged with the [DrawObjectInspectorInline] attribute the GUI for referenced objects will be rendered inline after the object reference field | |
/// 2) can override its behaviour to easily customise drawing to extend the default | |
/// | |
/// Also respects the standard Unity property attributes e.g. [Header()], [Space()] etc. | |
/// | |
/// Uses SerialisedObjectReferenceInspectorDrawer to draw the inline GUI for object references, which can also be customised be deriving. | |
/// Use a custom SerialisedObjectReferenceInspectorDrawer derived class by calling the constructor which takes one as a param. | |
/// </summary> | |
public class ObjectInspectorDrawer | |
{ | |
/// <summary> | |
/// add this to a serialized monobehaviour or scriptable object reference to add its GUI inline in the inspector | |
/// </summary> | |
public class ShowInlineGUIAttribute : Attribute | |
{ | |
public bool DrawInBox = true; | |
public bool Indent = true; | |
} | |
public ObjectInspectorDrawer() | |
{ | |
m_objectReferenceGUIDrawer = new SerialisedObjectReferenceInspectorDrawer(); | |
} | |
public ObjectInspectorDrawer( SerialisedObjectReferenceInspectorDrawer objectReferenceDrawer ) | |
{ | |
m_objectReferenceGUIDrawer = objectReferenceDrawer; | |
} | |
public void DrawObjectGUI( SerializedObject serialisedObject ) | |
{ | |
var allSerialisedFields = ReflectionUtils.GetSerialisedFieldsforType( serialisedObject.targetObject.GetType() ); | |
VOnDrawAllProperties( allSerialisedFields, serialisedObject ); | |
if( serialisedObject.hasModifiedProperties ) | |
{ | |
serialisedObject.ApplyModifiedProperties(); | |
} | |
} | |
/// <summary> | |
/// override this in a derived type to easily add additional GUI before / after the default elements | |
/// </summary> | |
/// <param name="allSerialisedFields"></param> | |
/// <param name="serialisedObject"></param> | |
/// <example> | |
/// public class Example : ObjectInspectorDrawer | |
/// { | |
/// protected override void VOnDrawAllProperties( ... ) | |
/// { | |
/// // add additional GUI before default | |
/// base.VOnDrawAllProperties( ... ) | |
/// // add additional GUI after default | |
/// } | |
/// } | |
/// </example> | |
protected virtual void VOnDrawAllProperties( FieldInfo[] allSerialisedFields, SerializedObject serialisedObject ) | |
{ | |
foreach( var finfo in allSerialisedFields ) | |
{ | |
var property = serialisedObject.FindProperty( finfo.Name ); | |
if( property != null ) | |
{ | |
switch( property.propertyType ) | |
{ | |
case SerializedPropertyType.ObjectReference: | |
VOnDrawObjectReferenceProperty( finfo, property ); | |
break; | |
default: | |
VOnDrawProperty( finfo, property ); | |
break; | |
} | |
} | |
} | |
} | |
/// <summary> | |
/// override this in a derived type to change drawing for individual properties | |
/// you can test info.name to do custom drawing for (a) specific propert(y/ies) | |
/// </summary> | |
/// <param name="finfo"></param> | |
/// <param name="property"></param> | |
/// <example> | |
/// public class Example : ObjectInspectorDrawer | |
/// { | |
/// protected override void VOnDrawProperty( ... ) | |
/// { | |
/// // add additional GUI before property | |
/// base.VOnDrawProperty( ... ) | |
/// // add additional GUI after property | |
/// } | |
/// } | |
/// </example> | |
protected virtual void VOnDrawProperty( FieldInfo finfo, SerializedProperty property ) | |
{ | |
EditorGUILayout.PropertyField( property ); | |
} | |
/// <summary> | |
/// override this in a derived type to change drawing for individual object rederence properties | |
/// you can test info.name to do custom drawing for (a) specific propert(y/ies) | |
/// NOTE: if you want to affect the rendering of an object reference's inline GUI | |
/// you will need to create a custom SerialisedObjectReferenceInspectorDrawer | |
/// </summary> | |
/// <param name="finfo"></param> | |
/// <param name="property"></param> | |
/// <example> | |
/// public class Example : ObjectInspectorDrawer | |
/// { | |
/// protected override void VOnDrawObjectReferenceProperty( ... ) | |
/// { | |
/// // add additional GUI before property | |
/// base.VOnDrawObjectReferenceProperty( ... ) | |
/// // add additional GUI after property | |
/// } | |
/// } | |
/// </example> | |
protected virtual void VOnDrawObjectReferenceProperty( FieldInfo finfo, SerializedProperty property ) | |
{ | |
EditorGUILayout.PropertyField( property ); | |
if( finfo.IsDefined( typeof( ShowInlineGUIAttribute ), false ) ) | |
{ | |
var attribute = finfo.GetCustomAttribute< ShowInlineGUIAttribute >(); | |
m_objectReferenceGUIDrawer.DrawInlineGUIForSerialisedObjectReference( property, attribute.DrawInBox, attribute.Indent ); | |
} | |
} | |
SerialisedObjectReferenceInspectorDrawer m_objectReferenceGUIDrawer; | |
} | |
/// <summary> | |
/// Hand rolled version of https://docs.unity3d.com/ScriptReference/Editor.DrawDefaultInspector.html | |
/// but for drawing inline GUI for an Object Reference property (which just show up as | |
/// the reference box in standard editor GUI) | |
/// | |
/// Used by ObjectInspectorDrawer to render inline UI for properties which are object references | |
/// </summary> | |
public class SerialisedObjectReferenceInspectorDrawer | |
{ | |
public void DrawInlineGUIForSerialisedObjectReference( SerializedProperty propertyToDraw, bool inBox = true, bool indented = true ) | |
{ | |
Debug.Assert( propertyToDraw.propertyType == SerializedPropertyType.ObjectReference ); | |
var propertyAsSerialisedObj = new SerializedObject( propertyToDraw.objectReferenceValue ); | |
var allFieldInfo = ReflectionUtils.GetSerialisedFieldsforType( propertyToDraw.objectReferenceValue.GetType() ); | |
var indentAmount = ( indented ? 1f : 0f ) * 20f; | |
var externalScope = ( inBox ? new EditorGUILayout.VerticalScope( EditorStyles.helpBox ) : new EditorGUILayout.VerticalScope() ); | |
using( externalScope ) | |
{ | |
var isToggled = EditorGUILayout.Foldout( IsFoldOutToggledForProperty( propertyToDraw ), ObjectNames.NicifyVariableName( propertyToDraw.name ) ); | |
SetFoldOutToggledForProperty( propertyToDraw, isToggled ); | |
if( isToggled ) | |
{ | |
using( new EditorGUILayout.HorizontalScope() ) | |
{ | |
EditorGUILayout.Space( indentAmount, false ); | |
using( new EditorGUILayout.VerticalScope() ) | |
{ | |
foreach( var finfo in allFieldInfo ) | |
{ | |
var property = propertyAsSerialisedObj.FindProperty( finfo.Name ); | |
if( property != null ) | |
{ | |
VOnDrawPropertyOfObjectReference( property ); | |
} | |
} | |
if( propertyAsSerialisedObj.hasModifiedProperties ) | |
{ | |
propertyAsSerialisedObj.ApplyModifiedProperties(); | |
} | |
propertyAsSerialisedObj.Dispose(); | |
} | |
} | |
} | |
} | |
} | |
protected virtual void VOnDrawPropertyOfObjectReference( SerializedProperty property ) | |
{ | |
EditorGUILayout.PropertyField( property ); | |
} | |
private bool IsFoldOutToggledForProperty( SerializedProperty property ) => ( m_dictFoldoutForPropertyIsToggled.ContainsKey( property.propertyPath ) ? m_dictFoldoutForPropertyIsToggled[ property.propertyPath ] : false ); | |
private bool SetFoldOutToggledForProperty( SerializedProperty property, bool toggled ) => m_dictFoldoutForPropertyIsToggled[ property.propertyPath ] = toggled; | |
private Dictionary< string, bool > m_dictFoldoutForPropertyIsToggled = new Dictionary< string, bool >(); | |
} | |
public static class ReflectionUtils | |
{ | |
public static FieldInfo[] GetAllFieldsIncludingBaseTypes( this Type thisType ) => GetAllFieldsIncludingBaseTypes( thisType, null ); | |
public static FieldInfo[] GetAllFieldsIncludingBaseTypes( this Type thisType, Type baseClassToStopAt ) | |
{ | |
var listSelfAndAllBaseTypes = new List< Type >(); | |
for | |
( | |
var currentType = thisType; | |
( ( currentType != null ) | |
&& ( currentType != baseClassToStopAt ) ); | |
currentType = currentType.BaseType ) | |
{ | |
listSelfAndAllBaseTypes.Add( currentType ); | |
} | |
listSelfAndAllBaseTypes.Reverse(); | |
var allFieldsList = new List< FieldInfo >(); | |
foreach( var type in listSelfAndAllBaseTypes ) | |
{ | |
allFieldsList.AddRange( type.GetFields( BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.DeclaredOnly ) ); | |
} | |
return allFieldsList.ToArray(); | |
} | |
// all fields which are public OR have the [SerializeField] attribute | |
public static FieldInfo[] GetFieldsIncludingBaseSerialisedOnly( this Type thisType ) => GetAllFieldsIncludingBaseTypes( thisType ).Where( field => ( field.IsPublic || field.IsDefined( typeof( SerializeField ), false ) ) ).ToArray(); | |
// this cache will naturally be reset on every domain reload (i.e. script compile) in the editor | |
private static Dictionary< Type, FieldInfo[] > sm_dictTypeToSerialisedFieldsCache = new Dictionary< Type, FieldInfo[] >(); | |
public static FieldInfo[] GetSerialisedFieldsforType( Type queriedType ) | |
{ | |
if( sm_dictTypeToSerialisedFieldsCache.TryGetValue( queriedType, out var serialisedFieldArray ) ) | |
{ | |
return serialisedFieldArray; | |
} | |
serialisedFieldArray = queriedType.GetFieldsIncludingBaseSerialisedOnly(); | |
sm_dictTypeToSerialisedFieldsCache[ queriedType ] = serialisedFieldArray; | |
return serialisedFieldArray; | |
} | |
} |
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
// | |
// GenericUnityEditorSettings by Alex 'darbotron' Darby | |
// | |
// License: https://opensource.org/licenses/unlicense | |
// TL;DR: | |
// 1) you may do what you like with it... | |
// 2) ...except blame me for any consequence of acting on rule 1) | |
// | |
using UnityEngine; | |
using System.Collections.Generic; | |
// | |
// used only to demo the inline object reference GUI in ExampleSettings.cs | |
// | |
[CreateAssetMenu( fileName = "TestAsset.asset", menuName = "Example/TestAsset" )] | |
public class TestAsset : ScriptableObject | |
{ | |
public bool m_someFlag = true; | |
[SerializeField] private bool m_someOtherFlag = true; | |
[SerializeField] private List< int > m_listOfInt; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment