├── 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 | [](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 | 
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 |
--------------------------------------------------------------------------------