├── Extras~ └── SampleSettings.png ├── .editorconfig ├── Runtime ├── SettingsType.cs ├── Gilzoide.EasyProjectSettings.asmdef.meta ├── SettingsType.cs.meta ├── ProjectSettings.cs.meta ├── ProjectSettingsAttribute.cs.meta ├── ProjectSettingsException.cs.meta ├── ProjectSettingsException.cs ├── Gilzoide.EasyProjectSettings.asmdef ├── ProjectSettingsAttribute.cs └── ProjectSettings.cs ├── UNLICENSE.meta ├── README.md.meta ├── package.json.meta ├── Editor.meta ├── Runtime.meta ├── Editor ├── Gilzoide.EasyProjectSettings.Editor.asmdef.meta ├── SkipScriptEditor.cs.meta ├── ProjectSettingsProvider.cs.meta ├── ErrorProjectSettingsProvider.cs.meta ├── ProjectSettingsProviderGroup.cs.meta ├── Gilzoide.EasyProjectSettings.Editor.asmdef ├── SkipScriptEditor.cs ├── ErrorProjectSettingsProvider.cs ├── ProjectSettingsProvider.cs └── ProjectSettingsProviderGroup.cs ├── package.json ├── .github └── FUNDING.yml ├── UNLICENSE └── README.md /Extras~/SampleSettings.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gilzoide/unity-easy-project-settings/HEAD/Extras~/SampleSettings.png -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | end_of_line = lf 5 | charset = utf-8 6 | indent_style = space 7 | indent_size = 2 8 | 9 | [*.cs] 10 | indent_size = 4 -------------------------------------------------------------------------------- /Runtime/SettingsType.cs: -------------------------------------------------------------------------------- 1 | namespace Gilzoide.EasyProjectSettings 2 | { 3 | public enum SettingsType 4 | { 5 | ProjectSettings, 6 | UserSettings, 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /UNLICENSE.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 73e5f5fa73c63477aa2d8a112383133c 3 | DefaultImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | -------------------------------------------------------------------------------- /README.md.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 293f96833e66e44dd8092714a3945cdd 3 | TextScriptImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | -------------------------------------------------------------------------------- /package.json.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 605a0e2b5164c481e9db306fccf32ba6 3 | PackageManifestImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | -------------------------------------------------------------------------------- /Editor.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 542404fe198524c558bde23f26e37dbe 3 | folderAsset: yes 4 | DefaultImporter: 5 | externalObjects: {} 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /Runtime.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 2194e66f7a0224b399c8d168099dcaf6 3 | folderAsset: yes 4 | DefaultImporter: 5 | externalObjects: {} 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /Runtime/Gilzoide.EasyProjectSettings.asmdef.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 9edf6d04eabfd482385646eaba23fdfc 3 | AssemblyDefinitionImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | -------------------------------------------------------------------------------- /Editor/Gilzoide.EasyProjectSettings.Editor.asmdef.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: c9d5a7836948f4e5ab18cc2530af11a5 3 | AssemblyDefinitionImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | -------------------------------------------------------------------------------- /Runtime/SettingsType.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 078594ed0ad73416ebeaa3d162eda428 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Editor/SkipScriptEditor.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 058253c6ce12340f59b368f22194d6ef 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Runtime/ProjectSettings.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 2249429148697411487196e15a428497 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Editor/ProjectSettingsProvider.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: e743c0c0aaf164de9ab8aa7848fe0c6a 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Editor/ErrorProjectSettingsProvider.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 7cb55f2e59e6d4822957edce1fbbcaab 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Editor/ProjectSettingsProviderGroup.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 60909f43c9bc640bfbfec40eb4ff0b48 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Runtime/ProjectSettingsAttribute.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 26a2c3b694e0642519dfc57ab5d3161b 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Runtime/ProjectSettingsException.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 3ef1d93685ed54b86bb1191b1364198d 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Runtime/ProjectSettingsException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Gilzoide.EasyProjectSettings 4 | { 5 | public class ProjectSettingsException : Exception 6 | { 7 | public ProjectSettingsException(string message) : base(message) 8 | { 9 | } 10 | 11 | public ProjectSettingsException(string message, Exception innerException) : base(message, innerException) 12 | { 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "com.gilzoide.easy-project-settings", 3 | "displayName": "Easy Project Settings", 4 | "version": "1.1.0", 5 | "description": "Easily create custom Project Settings by adding [ProjectSettings(...)] attribute to your ScriptableObject subclass", 6 | "homepage": "https://github.com/gilzoide/unity-easy-project-settings", 7 | "license": "Unlicense", 8 | "author": { 9 | "name": "Gil Barbosa Reis" 10 | }, 11 | "unity": "2018.3" 12 | } 13 | -------------------------------------------------------------------------------- /Runtime/Gilzoide.EasyProjectSettings.asmdef: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Gilzoide.EasyProjectSettings", 3 | "rootNamespace": "Gilzoide.EasyProjectSettings", 4 | "references": [], 5 | "includePlatforms": [], 6 | "excludePlatforms": [], 7 | "allowUnsafeCode": false, 8 | "overrideReferences": false, 9 | "precompiledReferences": [], 10 | "autoReferenced": true, 11 | "defineConstraints": [], 12 | "versionDefines": [], 13 | "noEngineReferences": false 14 | } -------------------------------------------------------------------------------- /Editor/Gilzoide.EasyProjectSettings.Editor.asmdef: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Gilzoide.EasyProjectSettings.Editor", 3 | "rootNamespace": "Gilzoide.EasyProjectSettings.Editor", 4 | "references": [ 5 | "GUID:9edf6d04eabfd482385646eaba23fdfc" 6 | ], 7 | "includePlatforms": [ 8 | "Editor" 9 | ], 10 | "excludePlatforms": [], 11 | "allowUnsafeCode": false, 12 | "overrideReferences": false, 13 | "precompiledReferences": [], 14 | "autoReferenced": false, 15 | "defineConstraints": [], 16 | "versionDefines": [], 17 | "noEngineReferences": false 18 | } -------------------------------------------------------------------------------- /Editor/SkipScriptEditor.cs: -------------------------------------------------------------------------------- 1 | using UnityEditor; 2 | 3 | namespace Gilzoide.EasyProjectSettings.Editor 4 | { 5 | [CanEditMultipleObjects] 6 | public class SkipScriptEditor : UnityEditor.Editor 7 | { 8 | public override void OnInspectorGUI() 9 | { 10 | serializedObject.Update(); 11 | 12 | SerializedProperty iterator = serializedObject.GetIterator().Copy(); 13 | if (iterator.NextVisible(true) && iterator.name != "m_Script") 14 | { 15 | EditorGUILayout.PropertyField(iterator, true); 16 | } 17 | while (iterator.NextVisible(false)) 18 | { 19 | EditorGUILayout.PropertyField(iterator, true); 20 | } 21 | 22 | serializedObject.ApplyModifiedProperties(); 23 | } 24 | } 25 | } -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: [gilzoide] # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] 4 | patreon: # Replace with a single Patreon username 5 | open_collective: # Replace with a single Open Collective username 6 | ko_fi: gilzoide # Replace with a single Ko-fi username 7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | liberapay: gilzoide # Replace with a single Liberapay username 10 | issuehunt: # Replace with a single IssueHunt username 11 | otechie: # Replace with a single Otechie username 12 | lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry 13 | custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] 14 | -------------------------------------------------------------------------------- /UNLICENSE: -------------------------------------------------------------------------------- 1 | This is free and unencumbered software released into the public domain. 2 | 3 | Anyone is free to copy, modify, publish, use, compile, sell, or 4 | distribute this software, either in source code form or as a compiled 5 | binary, for any purpose, commercial or non-commercial, and by any 6 | means. 7 | 8 | In jurisdictions that recognize copyright laws, the author or authors 9 | of this software dedicate any and all copyright interest in the 10 | software to the public domain. We make this dedication for the benefit 11 | of the public at large and to the detriment of our heirs and 12 | successors. We intend this dedication to be an overt act of 13 | relinquishment in perpetuity of all present and future rights to this 14 | software under copyright law. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 20 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 21 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. 23 | 24 | For more information, please refer to 25 | -------------------------------------------------------------------------------- /Editor/ErrorProjectSettingsProvider.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using UnityEditor; 4 | 5 | namespace Gilzoide.EasyProjectSettings.Editor 6 | { 7 | public class ErrorProjectSettingsProvider : SettingsProvider 8 | { 9 | private MonoScript _script; 10 | private Exception _exception; 11 | 12 | public ErrorProjectSettingsProvider(Type type, Exception exception) 13 | : base(ProjectSettingsProviderGroup.GetSettingsPath(type), ProjectSettingsProviderGroup.GetSettingsScope(type)) 14 | { 15 | _exception = exception; 16 | _script = GetScriptForType(type); 17 | } 18 | 19 | public override void OnGUI(string searchContext) 20 | { 21 | EditorGUILayout.HelpBox(_exception.Message, MessageType.Error); 22 | if (_script) 23 | { 24 | using (new EditorGUI.DisabledScope(true)) 25 | { 26 | EditorGUILayout.ObjectField("Script", _script, typeof(MonoScript), false); 27 | } 28 | } 29 | } 30 | 31 | private static MonoScript GetScriptForType(Type type) 32 | { 33 | return AssetDatabase.FindAssets($"t:MonoScript {type.Name}") 34 | .Select(AssetDatabase.GUIDToAssetPath) 35 | .Select(AssetDatabase.LoadAssetAtPath) 36 | .FirstOrDefault(); 37 | } 38 | } 39 | } -------------------------------------------------------------------------------- /Editor/ProjectSettingsProvider.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using UnityEditor; 3 | using UnityEngine; 4 | using UnityEngine.UIElements; 5 | using Object = UnityEngine.Object; 6 | 7 | namespace Gilzoide.EasyProjectSettings.Editor 8 | { 9 | public class ProjectSettingsProvider : SettingsProvider 10 | { 11 | private Type _settingsType; 12 | private ProjectSettingsAttribute _attribute; 13 | private ScriptableObject _object; 14 | private UnityEditor.Editor _objectEditor; 15 | 16 | public ProjectSettingsProvider(Type type) 17 | : base(ProjectSettingsProviderGroup.GetSettingsPath(type), ProjectSettingsProviderGroup.GetSettingsScope(type)) 18 | { 19 | if (!type.IsSubclassOf(typeof(ScriptableObject))) 20 | { 21 | throw new ProjectSettingsException($"Class must derive from ScriptableObject: '{type.Name}'"); 22 | } 23 | 24 | _settingsType = type; 25 | _attribute = ProjectSettings.GetAttribute(type); 26 | if (string.IsNullOrWhiteSpace(_attribute.AssetPath)) 27 | { 28 | throw new ProjectSettingsException($"ProjectSettingsAttribute cannot have an empty AssetPath: '{type.Name}'"); 29 | } 30 | if (!string.IsNullOrWhiteSpace(_attribute.Label)) 31 | { 32 | label = _attribute.Label; 33 | } 34 | } 35 | 36 | public override void OnActivate(string searchContext, VisualElement rootElement) 37 | { 38 | _object = ProjectSettings.LoadOrCreate(_settingsType); 39 | keywords = AssetSettingsProvider.GetSearchKeywordsFromSerializedObject(new SerializedObject(_object)); 40 | 41 | _objectEditor = UnityEditor.Editor.CreateEditor(_object); 42 | if (_objectEditor.GetType().FullName.StartsWith("UnityEditor.")) 43 | { 44 | Object.DestroyImmediate(_objectEditor); 45 | _objectEditor = UnityEditor.Editor.CreateEditor(_object, typeof(SkipScriptEditor)); 46 | } 47 | } 48 | 49 | public override void OnDeactivate() 50 | { 51 | ProjectSettings.Save(_object); 52 | } 53 | 54 | public override void OnGUI(string searchContext) 55 | { 56 | EditorGUILayout.Space(); 57 | _objectEditor.OnInspectorGUI(); 58 | } 59 | 60 | public override void OnTitleBarGUI() 61 | { 62 | if (_attribute.IsRelativeToAssets && _object != null) 63 | { 64 | using (new EditorGUI.DisabledScope(true)) 65 | { 66 | EditorGUILayout.ObjectField(_object, _settingsType, false); 67 | } 68 | } 69 | } 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /Editor/ProjectSettingsProviderGroup.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Reflection; 5 | using UnityEditor; 6 | using UnityEngine; 7 | 8 | namespace Gilzoide.EasyProjectSettings.Editor 9 | { 10 | public static class ProjectSettingsProviderGroup 11 | { 12 | public static string GetSettingsPath(Type type) 13 | { 14 | ProjectSettingsAttribute attribute = ProjectSettings.GetAttribute(type); 15 | if (!string.IsNullOrWhiteSpace(attribute?.SettingsPath)) 16 | { 17 | return attribute.SettingsPath; 18 | } 19 | 20 | switch (attribute.SettingsType) 21 | { 22 | case SettingsType.ProjectSettings: 23 | return "Project/" + type.Name; 24 | 25 | case SettingsType.UserSettings: 26 | return "Preferences/" + type.Name; 27 | 28 | default: 29 | return type.Name; 30 | } 31 | } 32 | 33 | public static SettingsScope GetSettingsScope(Type type) 34 | { 35 | switch (ProjectSettings.GetAttribute(type).SettingsType) 36 | { 37 | case SettingsType.UserSettings: 38 | return SettingsScope.User; 39 | 40 | case SettingsType.ProjectSettings: 41 | default: 42 | return SettingsScope.Project; 43 | } 44 | } 45 | 46 | [SettingsProviderGroup] 47 | private static SettingsProvider[] GetProviders() 48 | { 49 | var providers = new List(); 50 | foreach (Type type in GetTypesWithAttribute()) 51 | { 52 | try 53 | { 54 | providers.Add(new ProjectSettingsProvider(type)); 55 | } 56 | catch (ProjectSettingsException exception) 57 | { 58 | providers.Add(new ErrorProjectSettingsProvider(type, exception)); 59 | } 60 | catch (Exception e) 61 | { 62 | Debug.LogException(e); 63 | } 64 | } 65 | return providers.ToArray(); 66 | } 67 | 68 | private static IList GetTypesWithAttribute() where T : Attribute 69 | { 70 | #if UNITY_2019_2_OR_NEWER 71 | return TypeCache.GetTypesWithAttribute(); 72 | #else 73 | return AppDomain.CurrentDomain.GetAssemblies() 74 | .SelectMany(asm => asm.GetTypes()) 75 | .Where(type => type.GetCustomAttribute() != null) 76 | .ToList(); 77 | #endif 78 | } 79 | } 80 | } -------------------------------------------------------------------------------- /Runtime/ProjectSettingsAttribute.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | 4 | namespace Gilzoide.EasyProjectSettings 5 | { 6 | [AttributeUsage(AttributeTargets.Class)] 7 | public class ProjectSettingsAttribute : Attribute 8 | { 9 | public const string AssetsDirectoryIdentifier = "Assets/"; 10 | public const string ResourcesDirectoryIdentifier = "/Resources/"; 11 | 12 | /// 13 | /// Path where project settings asset will be loaded from/saved to. 14 | /// Since project settings must be ScriptableObjects, the setter forces a ".asset" extension. 15 | /// 16 | /// If the given path is relative to a "Resources" folder, the asset will be loadable by 17 | /// code in built players by using 18 | /// 19 | /// 20 | public string AssetPath 21 | { 22 | get => _assetPath; 23 | set => _assetPath = Path.ChangeExtension(value, "asset"); 24 | } 25 | private string _assetPath; 26 | /// Determines whether settings appear in the Project Settings window or in the Preferences window 27 | public SettingsType SettingsType { get; set; } = SettingsType.ProjectSettings; 28 | /// 29 | /// Path used to place the SettingsProvider in the tree view of the Settings window. 30 | /// The path should be unique among all other settings paths and should use "/" as its separator. 31 | /// 32 | public string SettingsPath { get; set; } 33 | /// Display name of the SettingsProvider as it appears in the Settings window 34 | public string Label { get; set; } 35 | 36 | /// Whether is relative to the "Assets" folder 37 | public bool IsRelativeToAssets => AssetPath.StartsWith(AssetsDirectoryIdentifier); 38 | /// Whether is relative to a "Resources" folder 39 | public bool IsRelativeToResources => AssetPath.IndexOf(ResourcesDirectoryIdentifier) >= 0; 40 | /// 41 | /// If is relative to a "Resources" folder, gets the relative path for loading the 42 | /// asset with Resources.Load. 43 | /// Returns otherwise. 44 | /// 45 | public string ResourcesPath 46 | { 47 | get 48 | { 49 | int resourcesIndex = AssetPath.IndexOf(ResourcesDirectoryIdentifier); 50 | return resourcesIndex >= 0 51 | ? Path.ChangeExtension(AssetPath.Substring(resourcesIndex + ResourcesDirectoryIdentifier.Length), null) 52 | : null; 53 | } 54 | } 55 | 56 | public ProjectSettingsAttribute(string assetPath) 57 | { 58 | AssetPath = assetPath; 59 | } 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Easy Project Settings 2 | [![openupm](https://img.shields.io/npm/v/com.gilzoide.easy-project-settings?label=openupm®istry_uri=https://package.openupm.com)](https://openupm.com/packages/com.gilzoide.easy-project-settings/) 3 | 4 | Easily create custom Project Settings by adding [[ProjectSettings(...)]](Runtime/ProjectSettingsAttribute.cs) attribute to your `ScriptableObject` subclass! 5 | 6 | ```cs 7 | using UnityEngine; 8 | using Gilzoide.EasyProjectSettings; 9 | 10 | [ProjectSettings("Assets/EasyProjectSettingsSamples/SampleSettings", 11 | SettingsPath = "Project/Easy Project Settings Samples/SampleSettings", 12 | Label = "Sample Label")] 13 | public class SampleSettings : ScriptableObject 14 | { 15 | public string SampleMessage; 16 | } 17 | ``` 18 | 19 | ![](Extras~/SampleSettings.png) 20 | 21 | 22 | ## Features 23 | - Simple to use: just add a [[ProjectSettings(...)]](Runtime/ProjectSettingsAttribute.cs) attribute to your `ScriptableObject` subclass and it will automatically appear at the Project Settings window 24 | - Project settings can be easily loaded in script code by calling `ProjectSettings.Load()`, `ProjectSettings.TryLoad(out T)` or `ProjectSettings.LoadAsync()` 25 | + Any settings can be loaded by code in the Unity editor, independent of the asset path 26 | + Settings can be loaded by code in built players if asset paths are relative to a `Resources` folder 27 | - Supports any asset paths, including paths relative to the `ProjectSettings` folder 28 | - Supports your custom editors, no extra code required 29 | - Supports both Project and User [settings scopes](https://docs.unity3d.com/ScriptReference/SettingsScope.html) 30 | 31 | 32 | ## How to install 33 | This package is available on the [openupm registry](https://openupm.com/) 34 | and can be installed using the [openupm-cli](https://github.com/openupm/openupm-cli): 35 | 36 | ``` 37 | openupm add com.gilzoide.easy-project-settings 38 | ``` 39 | 40 | Otherwise, you can install directly using the [Unity Package Manager](https://docs.unity3d.com/Manual/upm-ui-giturl.html) 41 | with the following URL: 42 | 43 | ``` 44 | https://github.com/gilzoide/unity-easy-project-settings.git#1.1.0 45 | ``` 46 | 47 | 48 | ## How to use 49 | When `AssetPath` is relative to the `Assets` folder, it can be referenced anywhere in project, 50 | just like any other `ScriptableObject`, and loaded using `ProjectSettings.Load` in editor code. 51 | 52 | If you don't plan on loading settings at runtime code outside of the Unity editor, this is 53 | the recommended usage. 54 | ```cs 55 | using UnityEngine; 56 | using Gilzoide.EasyProjectSettings; 57 | 58 | [ProjectSettings("Assets/EasyProjectSettingsSamples/SampleSettings", 59 | SettingsPath = "Project/Easy Project Settings Samples/SampleSettings")] 60 | public class SampleSettings : ScriptableObject 61 | { 62 | public string SampleMessage; 63 | } 64 | 65 | public class SampleMonoBehaviour : MonoBehaviour 66 | { 67 | // set me up in the Inspector 68 | [SerializeField] private SampleSettings _sampleSettings; 69 | 70 | void Start() 71 | { 72 | Debug.Log($"Message from settings = '{_sampleSettings.SampleMessage}'"); 73 | } 74 | } 75 | ``` 76 | 77 | 78 | When `AssetPath` is relative to a `Resources` folder inside `Assets` folder, it can be 79 | loaded using `ProjectSettings.Load` both in editor and built players. 80 | 81 | If you plan on loading settings at runtime code outside of the Unity editor, this is the 82 | recommended usage. 83 | ```cs 84 | using UnityEngine; 85 | using Gilzoide.EasyProjectSettings; 86 | 87 | [ProjectSettings("Assets/EasyProjectSettingsSamples/Resources/SampleResourcesSettings", 88 | SettingsPath = "Project/Easy Project Settings Samples/SampleResourcesSettings")] 89 | public class SampleResourcesSettings : ScriptableObject 90 | { 91 | public string SampleResourcesMessage; 92 | } 93 | 94 | public class SampleMonoBehaviour : MonoBehaviour 95 | { 96 | void Start() 97 | { 98 | SampleResourcesSettings settings = ProjectSettings.Load(); 99 | Debug.Log($"Message from settings = '{settings.SampleResourcesMessage}'"); 100 | } 101 | } 102 | ``` 103 | 104 | 105 | When `AssetPath` is not relative to `Assets` folder, the asset is only usable by code 106 | running in editor, loaded using `ProjectSettings.Load`. 107 | 108 | This may be useful for defining settings for editor-only tools. 109 | ```cs 110 | using UnityEditor; 111 | using UnityEditor.Callbacks; 112 | using UnityEngine; 113 | using Gilzoide.EasyProjectSettings; 114 | 115 | [ProjectSettings("ProjectSettings/EasyProjectSettingsSamples/EditorOnlySettings", 116 | SettingsPath = "Project/Easy Project Settings Samples/EditorOnlySettings")] 117 | public class EditorOnlySettings : ScriptableObject 118 | { 119 | public string EditorOnlyMessage; 120 | } 121 | 122 | public static class SampleEditorOnlyPostProcessBuild 123 | { 124 | [PostProcessBuild] 125 | public static void OnPostprocessBuild(BuildTarget target, string pathToBuiltProject) 126 | { 127 | EditorOnlySettings settings = ProjectSettings.Load(); 128 | Debug.Log($"Built project, message from settings = '{settings.EditorOnlyMessage}'"); 129 | } 130 | } 131 | ``` 132 | -------------------------------------------------------------------------------- /Runtime/ProjectSettings.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.Linq; 4 | using System.Reflection; 5 | using System.Threading; 6 | using System.Threading.Tasks; 7 | using UnityEditor; 8 | using UnityEngine; 9 | using Object = UnityEngine.Object; 10 | 11 | namespace Gilzoide.EasyProjectSettings 12 | { 13 | public static class ProjectSettings 14 | { 15 | /// 16 | /// Load the project settings of type . 17 | /// 18 | /// Assets will not be loaded if not present in the path specified by the type's . 19 | /// They will also fail to load on built players if is not relative to a Resources folder. 20 | /// 21 | /// 22 | /// 23 | /// The loaded project settings or if loading failed. 24 | /// 25 | /// subclass. Must have a 26 | /// Thrown when does not have a 27 | public static ScriptableObject Load(Type type) 28 | { 29 | ProjectSettingsAttribute attribute = GetAttribute(type); 30 | #if UNITY_EDITOR 31 | return (ScriptableObject) (attribute.IsRelativeToAssets 32 | ? AssetDatabase.LoadAssetAtPath(attribute.AssetPath, type) 33 | : UnityEditorInternal.InternalEditorUtility.LoadSerializedFileAndForget(attribute.AssetPath).FirstOrDefault()); 34 | #else 35 | return Resources.Load(attribute.ResourcesPath); 36 | #endif 37 | } 38 | /// Typed version of 39 | public static T Load() where T : ScriptableObject 40 | { 41 | return (T) Load(typeof(T)); 42 | } 43 | 44 | /// 45 | /// Async version of 46 | /// 47 | /// subclass. Must have a 48 | /// Thrown when does not have a 49 | public static async Task LoadAsync(Type type, CancellationToken cancellationToken = default) 50 | { 51 | ProjectSettingsAttribute attribute = GetAttribute(type); 52 | #if UNITY_EDITOR 53 | if (!attribute.IsRelativeToAssets) 54 | { 55 | return (ScriptableObject) UnityEditorInternal.InternalEditorUtility.LoadSerializedFileAndForget(attribute.AssetPath).FirstOrDefault(); 56 | } 57 | #endif 58 | var loadOperation = Resources.LoadAsync(attribute.ResourcesPath, type); 59 | while (!loadOperation.isDone) 60 | { 61 | cancellationToken.ThrowIfCancellationRequested(); 62 | await Task.Yield(); 63 | } 64 | return (ScriptableObject) loadOperation.asset; 65 | } 66 | /// Typed version of 67 | public static async Task LoadAsync(CancellationToken cancellationToken = default) where T : ScriptableObject 68 | { 69 | return (T) await LoadAsync(typeof(T), cancellationToken); 70 | } 71 | 72 | /// 73 | /// Try loading the project settings of type into . 74 | /// 75 | /// Assets will not be loaded if not present in the path specified by the type's . 76 | /// They will also fail to load on built players if is not relative to a Resources folder. 77 | /// 78 | /// 79 | /// 80 | /// If the asset was succesfully loaded, will contain the loaded asset and will be returned. 81 | /// On failure, will be and will be returned. 82 | /// 83 | /// subclass. Must have a 84 | /// Thrown when does not have a 85 | public static bool TryLoad(Type type, out ScriptableObject settings) 86 | { 87 | settings = Load(type); 88 | return settings != null; 89 | } 90 | /// Typed version of 91 | public static bool TryLoad(out T settings) where T : ScriptableObject 92 | { 93 | settings = Load(); 94 | return settings != null; 95 | } 96 | 97 | /// 98 | /// Load the project settings of type or create a new instance if asset could not be loaded. 99 | /// 100 | /// When running in the editor, new assets are created in disk if loading failed. 101 | /// 102 | /// 103 | /// 104 | /// The loaded or created project settings. 105 | /// See for details on asset loading. 106 | /// 107 | /// subclass. Must have a 108 | /// Thrown when does not have a 109 | public static ScriptableObject LoadOrCreate(Type type) 110 | { 111 | if (!TryLoad(type, out ScriptableObject settingsObject)) 112 | { 113 | settingsObject = ScriptableObject.CreateInstance(type); 114 | #if UNITY_EDITOR 115 | Save(settingsObject); 116 | #endif 117 | } 118 | return settingsObject; 119 | } 120 | /// Typed version of 121 | public static T LoadOrCreate() where T : ScriptableObject 122 | { 123 | return (T) LoadOrCreate(typeof(T)); 124 | } 125 | 126 | /// 127 | /// Async version of 128 | /// 129 | /// subclass. Must have a 130 | /// Thrown when does not have a 131 | public static async Task LoadOrCreateAsync(Type type, CancellationToken cancellationToken = default) 132 | { 133 | var settingsObject = await LoadAsync(type, cancellationToken); 134 | if (settingsObject == null) 135 | { 136 | settingsObject = ScriptableObject.CreateInstance(type); 137 | #if UNITY_EDITOR 138 | Save(settingsObject); 139 | #endif 140 | } 141 | return settingsObject; 142 | } 143 | /// Typed version of 144 | public static async Task LoadOrCreateAsync(CancellationToken cancellationToken = default) where T : ScriptableObject 145 | { 146 | return (T) await LoadOrCreateAsync(typeof(T), cancellationToken); 147 | } 148 | 149 | /// Get the from 150 | /// Thrown when does not have a 151 | public static ProjectSettingsAttribute GetAttribute(Type type) 152 | { 153 | ProjectSettingsAttribute attribute = type.GetCustomAttribute(); 154 | if (attribute == null) 155 | { 156 | throw new ProjectSettingsException($"Type {type.Name} doesn't have a ProjectSettingsAttribute"); 157 | } 158 | return attribute; 159 | } 160 | 161 | #if UNITY_EDITOR 162 | public static void Save(Object obj) 163 | { 164 | if (obj == null) 165 | { 166 | return; 167 | } 168 | 169 | ProjectSettingsAttribute attribute = GetAttribute(obj.GetType()); 170 | string directoryPath = Path.GetDirectoryName(attribute.AssetPath); 171 | if (!string.IsNullOrWhiteSpace(directoryPath)) 172 | { 173 | Directory.CreateDirectory(directoryPath); 174 | } 175 | 176 | if (attribute.IsRelativeToAssets) 177 | { 178 | if (!AssetDatabase.Contains(obj)) 179 | { 180 | AssetDatabase.CreateAsset(obj, attribute.AssetPath); 181 | } 182 | #if UNITY_2020_3_OR_NEWER 183 | AssetDatabase.SaveAssetIfDirty(obj); 184 | #endif 185 | } 186 | else 187 | { 188 | UnityEditorInternal.InternalEditorUtility.SaveToSerializedFileAndForget(new[] { obj }, attribute.AssetPath, true); 189 | } 190 | } 191 | #endif 192 | } 193 | } 194 | --------------------------------------------------------------------------------