├── LICENSE.md.meta ├── README.md.meta ├── package.json.meta ├── Editor.meta ├── Editor ├── leth.colorlink.Editor.asmdef.meta ├── ColorGroup.cs.meta ├── PaletteEditor.cs.meta ├── PaletteObject.cs.meta ├── ColorPropertyHandler.cs.meta ├── ComponentContextMenu.cs.meta ├── MaterialPropertyWizard.cs.meta ├── leth.colorlink.Editor.asmdef ├── ColorGroup.cs ├── MaterialPropertyWizard.cs ├── ColorPropertyHandler.cs ├── ComponentContextMenu.cs ├── PaletteObject.cs └── PaletteEditor.cs ├── package.json ├── LICENSE.md └── README.md /LICENSE.md.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: cde6d13a6e02d1249a3a19a9b54e7657 3 | TextScriptImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | -------------------------------------------------------------------------------- /README.md.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: a619fd86658f8e04e9b1500a8e18a839 3 | TextScriptImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | -------------------------------------------------------------------------------- /package.json.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 7a4625d0d3be1454c96c4e2fc30ffaee 3 | TextScriptImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | -------------------------------------------------------------------------------- /Editor.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: c2585cc8ad333024590298abd9fefdbe 3 | folderAsset: yes 4 | DefaultImporter: 5 | externalObjects: {} 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /Editor/leth.colorlink.Editor.asmdef.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: b3693f43b8752d244871d4ac59bf9850 3 | AssemblyDefinitionImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | -------------------------------------------------------------------------------- /Editor/ColorGroup.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 0aff98671824e3f44975e804685ff2e8 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Editor/PaletteEditor.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 268bf949d4308f644bfce0328999dab1 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Editor/PaletteObject.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 2037b1cdc6b34764f8b9c60227cb8032 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Editor/ColorPropertyHandler.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 52ec1d8fd871cc749aeb2790812bb72a 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Editor/ComponentContextMenu.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: e1cca5669a4526d4db057ff501d286fb 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Editor/MaterialPropertyWizard.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: c450a8ef8bac33746be962bfba4cb8ff 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "com.leth.colorlink", 3 | "version": "1.0.0", 4 | "displayName": "Colorlink", 5 | "description": "An editor tool that allows to quickly swap the entire color palette of the game. Works with materials, components, prefabs and assets, by directly linking SerializeProperties and material properties to the palette — no extra components needed! The tool is editor only and can't be used in builds.", 6 | "unity": "2021.3" 7 | } -------------------------------------------------------------------------------- /Editor/leth.colorlink.Editor.asmdef: -------------------------------------------------------------------------------- 1 | { 2 | "name": "leth.colorlink.editor", 3 | "rootNamespace": "Colorlink", 4 | "references": [], 5 | "includePlatforms": [ 6 | "Editor" 7 | ], 8 | "excludePlatforms": [], 9 | "allowUnsafeCode": false, 10 | "overrideReferences": false, 11 | "precompiledReferences": [], 12 | "autoReferenced": true, 13 | "defineConstraints": [], 14 | "versionDefines": [], 15 | "noEngineReferences": false 16 | } -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Eugene Radaev 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /Editor/ColorGroup.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using UnityEngine; 3 | 4 | namespace Colorlink 5 | { 6 | [System.Serializable] 7 | public class ColorGroup 8 | { 9 | public string Name; 10 | public Color Color = Color.white; 11 | public List Properties = new List(); 12 | 13 | public void RemoveProperty(ColorProperty property) 14 | { 15 | RemoveProperty(property.GuidString, property.PropertyPath); 16 | } 17 | 18 | public void RemoveProperty(string guidString, string propertyPath) 19 | { 20 | for (int i = 0; i < Properties.Count; i++) 21 | { 22 | if (Properties[i].GuidString != guidString) continue; 23 | if (Properties[i].PropertyPath != propertyPath) continue; 24 | Properties.RemoveAt(i); 25 | break; 26 | } 27 | } 28 | 29 | public void ToggleProperty(ColorProperty property) 30 | { 31 | if (Contains(property.GuidString, property.PropertyPath)) 32 | { 33 | if (property.ObjectType != ColorProperty.Type.Material) 34 | RemoveProperty(property); 35 | return; 36 | } 37 | Properties.Add(property); 38 | } 39 | 40 | public bool Contains(string guid, string propertyPath) 41 | { 42 | foreach (var property in Properties) 43 | { 44 | if (property.GuidString == guid && property.PropertyPath == propertyPath) 45 | return true; 46 | } 47 | return false; 48 | } 49 | } 50 | 51 | [System.Serializable] 52 | public class ColorProperty 53 | { 54 | public string GuidString; 55 | public string PropertyPath; 56 | public string ObjectName; 57 | public Type ObjectType; 58 | 59 | public ColorProperty(string guid, string name, Type path) 60 | { 61 | GuidString = guid; 62 | PropertyPath = name; 63 | ObjectName = ""; 64 | ObjectType = path; 65 | } 66 | 67 | public enum Type 68 | { 69 | GameObject, 70 | Material, 71 | Asset 72 | } 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /Editor/MaterialPropertyWizard.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using UnityEditor; 3 | using UnityEngine; 4 | 5 | namespace Colorlink 6 | { 7 | public class MaterialPropertyWizard : ScriptableWizard 8 | { 9 | private static Material _activeMaterial; 10 | 11 | private string _guidString; 12 | private List _colorProperties = new List(); 13 | private List _selectors = new List(); 14 | private List _groups = new List() { "None" }; 15 | 16 | [MenuItem("CONTEXT/Material/Link Colors", false, 0)] 17 | public static void CreateWizard(MenuCommand command) 18 | { 19 | if (_activeMaterial != null) 20 | { 21 | Debug.LogWarning("Please close an active material property wizard before opening a new one"); 22 | return; 23 | } 24 | 25 | _activeMaterial = (Material)command.context; 26 | 27 | DisplayWizard("Link Colors", "Link"); 28 | } 29 | 30 | private void OnEnable() 31 | { 32 | _guidString = GlobalObjectId.GetGlobalObjectIdSlow(_activeMaterial).ToString(); 33 | 34 | var materialProperties = MaterialEditor.GetMaterialProperties(new Material[] { _activeMaterial }); 35 | foreach (var property in materialProperties) 36 | { 37 | if (property.type == MaterialProperty.PropType.Color) 38 | { 39 | _colorProperties.Add(property.name); 40 | _selectors.Add(0); 41 | } 42 | } 43 | 44 | for (int i = 0; i < PaletteObject.instance.ColorGroups.Count; i++) 45 | { 46 | _groups.Add(PaletteObject.instance.ColorGroups[i].Name); 47 | foreach (var property in PaletteObject.instance.ColorGroups[i].Properties) 48 | { 49 | if (property.GuidString != _guidString) continue; 50 | if (!_colorProperties.Contains(property.PropertyPath)) continue; 51 | _selectors[_colorProperties.IndexOf(property.PropertyPath)] = i + 1; 52 | } 53 | } 54 | } 55 | 56 | protected override bool DrawWizardGUI() 57 | { 58 | for (int i = 0; i < _colorProperties.Count; i++) 59 | { 60 | _selectors[i] = EditorGUILayout.Popup(_colorProperties[i], _selectors[i], _groups.ToArray()); 61 | } 62 | 63 | return true; 64 | } 65 | 66 | private void OnWizardCreate() 67 | { 68 | for (int i = 0; i < _selectors.Count; i++) 69 | { 70 | if (_selectors[i] == 0) 71 | { 72 | foreach (var colorGroup in PaletteObject.instance.ColorGroups) colorGroup.RemoveProperty(_guidString, _colorProperties[i]); 73 | continue; 74 | } 75 | PaletteObject.instance.AddProperty(PaletteObject.instance.ColorGroups[_selectors[i] - 1], new ColorProperty(_guidString, _colorProperties[i], ColorProperty.Type.Material)); 76 | } 77 | PaletteObject.instance.ApplyColors(); 78 | } 79 | 80 | private void OnDestroy() 81 | { 82 | _activeMaterial = null; 83 | } 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /Editor/ColorPropertyHandler.cs: -------------------------------------------------------------------------------- 1 | using UnityEngine; 2 | using UnityEditor; 3 | using UnityEditor.SceneManagement; 4 | 5 | namespace Colorlink 6 | { 7 | public static class ColorPropertyHandler 8 | { 9 | [InitializeOnLoadMethod] 10 | private static void Start() 11 | { 12 | EditorApplication.contextualPropertyMenu += OnPropertyContextMenu; 13 | } 14 | 15 | private static void OnPropertyContextMenu(GenericMenu menu, SerializedProperty property) 16 | { 17 | if (property.type != "Color") return; 18 | 19 | var propertyCopy = property.Copy(); 20 | var guid = GlobalObjectId.GetGlobalObjectIdSlow(propertyCopy.serializedObject.targetObject); 21 | 22 | if (!(propertyCopy.serializedObject.targetObject is Component) && !(propertyCopy.serializedObject.targetObject is ScriptableObject)) return; 23 | 24 | var type = (guid.identifierType == 2 && !PrefabStageUtility.GetCurrentPrefabStage()) ? ColorProperty.Type.GameObject : ColorProperty.Type.Asset; 25 | var guidString = VerifyGUIDForPrefabStage(guid.ToString()); 26 | 27 | foreach (var colorGroup in PaletteObject.instance.ColorGroups) 28 | { 29 | menu.AddItem(new GUIContent($"Link Color/{colorGroup.Name}"), colorGroup.Contains(guidString, propertyCopy.propertyPath), () => 30 | { 31 | PaletteObject.instance.AddProperty(colorGroup, new ColorProperty(guidString, propertyCopy.propertyPath, type)); 32 | PaletteObject.instance.ApplyColors(type == ColorProperty.Type.Asset); 33 | }); 34 | } 35 | } 36 | 37 | [CustomPropertyDrawer(typeof(Color))] 38 | public class ColorPropertyDrawer : PropertyDrawer 39 | { 40 | public override void OnGUI(Rect position, SerializedProperty property, GUIContent label) 41 | { 42 | EditorGUI.BeginProperty(position, label, property); 43 | 44 | var guid = GlobalObjectId.GetGlobalObjectIdSlow(property.serializedObject.targetObject); 45 | var guidString = VerifyGUIDForPrefabStage(guid.ToString()); 46 | foreach (var colorGroup in PaletteObject.instance.ColorGroups) 47 | { 48 | if (colorGroup.Contains(guidString, property.propertyPath)) 49 | { 50 | label.text = "→ " + property.displayName; 51 | break; 52 | } 53 | } 54 | 55 | position = EditorGUI.PrefixLabel(position, GUIUtility.GetControlID(FocusType.Passive), label); 56 | property.colorValue = EditorGUI.ColorField(position, property.colorValue); 57 | 58 | EditorGUI.EndProperty(); 59 | } 60 | } 61 | 62 | public static string VerifyGUIDForPrefabStage(string guid) 63 | { 64 | if (PrefabStageUtility.GetCurrentPrefabStage() == null) return guid; 65 | 66 | var prefabAsset = AssetDatabase.LoadAssetAtPath(PrefabStageUtility.GetCurrentPrefabStage().assetPath); 67 | var prefabGUID = GlobalObjectId.GetGlobalObjectIdSlow(prefabAsset); 68 | return guid.ToString().Replace("-2-", "-1-").Replace("00000000000000000000000000000000", prefabGUID.assetGUID.ToString()); 69 | } 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /Editor/ComponentContextMenu.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using UnityEditor; 4 | using UnityEditor.SceneManagement; 5 | 6 | namespace Colorlink 7 | { 8 | public static class ComponentContextMenu 9 | { 10 | public static Dictionary> Buffer = new Dictionary>(); 11 | private static int _timesPasteApplied = -1; 12 | 13 | [MenuItem("CONTEXT/Component/Copy Color Links", false, 10000)] 14 | private static void CopyColorLinks(MenuCommand command) 15 | { 16 | var guid = GlobalObjectId.GetGlobalObjectIdSlow(command.context); 17 | var guidString = ColorPropertyHandler.VerifyGUIDForPrefabStage(guid.ToString()); 18 | var type = command.context.GetType(); 19 | var serializedObject = new SerializedObject(command.context); 20 | 21 | var properties = new List(); 22 | var serializedProperty = serializedObject.GetIterator(); 23 | while (serializedProperty.NextVisible(true)) 24 | { 25 | properties.Add(serializedProperty.propertyPath); 26 | } 27 | 28 | Buffer[type] = new Dictionary(); 29 | 30 | foreach (var colorGroup in PaletteObject.instance.ColorGroups) 31 | { 32 | foreach (var property in properties) 33 | { 34 | if (!colorGroup.Contains(guidString, property)) continue; 35 | Buffer[type].Add(property, colorGroup); 36 | } 37 | } 38 | } 39 | 40 | [MenuItem("CONTEXT/Component/Paste Color Links", false, 10000)] 41 | private static void PasteColorLinks(MenuCommand command) 42 | { 43 | var type = command.context.GetType(); 44 | if (!Buffer.ContainsKey(type)) return; 45 | 46 | var guid = GlobalObjectId.GetGlobalObjectIdSlow(command.context); 47 | var serializedObject = new SerializedObject(command.context); 48 | var guidString = ColorPropertyHandler.VerifyGUIDForPrefabStage(guid.ToString()); 49 | var isPrefabStage = PrefabStageUtility.GetCurrentPrefabStage(); 50 | 51 | if (isPrefabStage) _timesPasteApplied = _timesPasteApplied == -1 ? 1 : _timesPasteApplied + 1; 52 | 53 | var properties = new List(); 54 | var serializedProperty = serializedObject.GetIterator(); 55 | while (serializedProperty.NextVisible(true)) 56 | { 57 | properties.Add(serializedProperty.Copy()); 58 | PaletteObject.instance.RemoveProperty(guidString, properties[^1].propertyPath); 59 | } 60 | 61 | foreach (var propertyLink in Buffer[type]) 62 | { 63 | foreach (var property in properties) 64 | { 65 | if (property.propertyPath != propertyLink.Key) continue; 66 | var propertyType = (guid.identifierType == 2 && !isPrefabStage) ? ColorProperty.Type.GameObject : ColorProperty.Type.Asset; 67 | PaletteObject.instance.AddProperty(propertyLink.Value, new ColorProperty(guidString, propertyLink.Key, propertyType)); 68 | PaletteObject.instance.ApplyColors(); 69 | } 70 | } 71 | 72 | if (_timesPasteApplied == Selection.gameObjects.Length && isPrefabStage) 73 | { 74 | PaletteObject.instance.ApplyColors(true); 75 | _timesPasteApplied = -1; 76 | } 77 | } 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Colorlink 2 | 3 | An editor tool that allows to quickly change the entire color palette of the game. Works with materials, components, prefabs and assets, by directly linking serialized properties and material properties to the palette — no extra components needed! The tool is **editor only** and can't be used in builds. 4 | 5 | ![image](https://user-images.githubusercontent.com/44412176/236405859-6416a5f9-b133-4374-9ac6-5ae11986c54f.gif) 6 | 7 | ## Compatibility 8 | 9 | Unity 2020.3 or higher. 10 | 11 | ## Installation 12 | 13 | Add the package to your project via the [Package Manager](https://docs.unity3d.com/Manual/upm-ui.html) using the Git URL 14 | `https://github.com/leth4/Colorlink.git`. You can also clone the repository and point the Package Manager to your local copy. 15 | 16 | ## Usage 17 | 18 | ### Creating a palette 19 | 20 | 1. Open the palette editor via Window → Palette. 21 | 2. Click on the `+` button to create palette elements, each with a name and a color assigned. Give every element a unique name. 22 | 23 | ### Linking serialized properties 24 | 25 | 1. Right-click on any color property in the inspector of any component, prefab or asset. 26 | 2. In the context menu, select `Link Color` and then the palette element name. 27 | 3. A little `→` next to the name will show that the property is now linked! Its color will now change every time you change it in the Palette Window. You can unlink it via the same menu. 28 | 29 | ![image](https://user-images.githubusercontent.com/44412176/236387862-a2e81ea4-11e4-4074-bbff-6e4cc952f2ea.png) 30 | ![image](https://user-images.githubusercontent.com/44412176/236388019-7dee1343-33ce-459c-8e12-3a002ee0a5b7.png) 31 | 32 | You can freely move and rename objects with linked properties. The references to the properties are kept the same way that SerializeFields would keep them, and they are not easily lost. 33 | 34 | Note that properties in the custom editors can only be linked if created using SerializedProperty. 35 | 36 | If you want to apply the same links to a component of the same type, right-click on the first component header and select `Copy Color Links`. For the next component, select `Paste Color Links`. 37 | 38 | ### Linking material properties 39 | 40 | 1. Right-click on the material name at the top of the inspector. 41 | 2. In the context menu, select "Link Colors". An additional window will open. 42 | 3. For each property you want to link, select the palette element from the drop-down menu. 43 | 4. Click `Link` at the bottom of the window. The properties are now linked! 44 | 45 | ![image](https://user-images.githubusercontent.com/44412176/236388605-813e4f86-54fa-4416-a420-17c0411e0c70.png) 46 | ![image](https://user-images.githubusercontent.com/44412176/236388615-57969e2c-f603-4644-a60e-1e25b8879fc2.png) 47 | 48 | ### Managing the palette 49 | 50 | To see linked properties, click on the eye icon and unfold the palette elements in question. **You can drag and drop properties** between elements to re-link them! 51 | 52 | When you edit your palette, changes to GameObjects on the current scene and materials apply automatically. Changing assets and other scenes takes time, so you'll need to click the corresponding buttons to apply changes. 53 | 54 | You can move palette elements with arrow buttons on the right. You can also rename them freely — the links won't be lost. 55 | 56 | The palette stores its data in the `Palette.asset` file in the `ProjectSettings` folder, which is subject to version control. 57 | 58 | ![image](https://user-images.githubusercontent.com/44412176/236402915-91264ec8-4278-4a2d-9118-8ca699fceeed.png) 59 | 60 | ### Using palette presets 61 | 62 | 1. Click on the button with a save icon under palette elements. That will create a new palette preset. 63 | 2. You can swap the current palette for a preset by clicking the hand icon next to a preset, or delete it by clicking the cross icon. 64 | 65 | You can also turn a palette image into a preset by dragging it directly on the preset area. Note that only the first 30 colors found will be added to the preset. Make sure that the image has a `Sprite` texture type and `Read/Write` is enabled! 66 | -------------------------------------------------------------------------------- /Editor/PaletteObject.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using UnityEngine; 3 | using UnityEditor; 4 | using UnityEditor.SceneManagement; 5 | 6 | namespace Colorlink 7 | { 8 | [FilePath("/ProjectSettings/Palette.asset", FilePathAttribute.Location.ProjectFolder)] 9 | public class PaletteObject : ScriptableSingleton 10 | { 11 | public List ColorGroups = new List(); 12 | public List Presets = new List(); 13 | 14 | public void SaveChanges() => Save(true); 15 | 16 | public void SavePreset() 17 | { 18 | var colors = new List(); 19 | foreach (var colorGroup in ColorGroups) 20 | { 21 | colors.Add(colorGroup.Color); 22 | } 23 | Presets.Insert(0, new PalettePreset(colors)); 24 | } 25 | 26 | public void AddPreset(List colors) 27 | { 28 | if (Presets == null) Presets = new List(); 29 | Presets.Add(new PalettePreset(colors)); 30 | } 31 | 32 | public void ChangeColorGroupIndex(ColorGroup colorGroup, int change) 33 | { 34 | var index = ColorGroups.IndexOf(colorGroup); 35 | if (index + change < 0 || index + change >= ColorGroups.Count) return; 36 | var temp = ColorGroups[index]; 37 | ColorGroups[index] = ColorGroups[index + change]; 38 | ColorGroups[index + change] = temp; 39 | } 40 | 41 | public void AddColorGroup() 42 | { 43 | ColorGroups.Add(new ColorGroup() { Name = "New Color Group" }); 44 | } 45 | 46 | public void RemoveColorGroup(ColorGroup colorGroup) 47 | { 48 | ColorGroups.Remove(colorGroup); 49 | } 50 | 51 | public void RemoveProperty(string guidString, string propertyPath) 52 | { 53 | foreach (var group in ColorGroups) 54 | { 55 | group.RemoveProperty(guidString, propertyPath); 56 | } 57 | } 58 | 59 | public void AddProperty(ColorGroup colorGroup, ColorProperty property) 60 | { 61 | foreach (var group in ColorGroups) 62 | { 63 | if (group == colorGroup) 64 | group.ToggleProperty(property); 65 | else 66 | group.RemoveProperty(property); 67 | } 68 | } 69 | 70 | public void ApplyColors(bool includeAssets = false) 71 | { 72 | foreach (var colorGroup in ColorGroups) 73 | { 74 | var propertiesToRemove = new List(); 75 | foreach (var property in colorGroup.Properties) 76 | { 77 | if (GlobalObjectId.TryParse(property.GuidString, out GlobalObjectId guidObject)) 78 | { 79 | if (property.ObjectType == ColorProperty.Type.GameObject && EditorSceneManager.GetActiveScene().path != AssetDatabase.GUIDToAssetPath(guidObject.assetGUID)) 80 | { 81 | continue; 82 | } 83 | 84 | var obj = GlobalObjectId.GlobalObjectIdentifierToObjectSlow(guidObject); 85 | if (obj == null) 86 | { 87 | propertiesToRemove.Add(property); 88 | continue; 89 | } 90 | 91 | property.ObjectName = obj.name; 92 | 93 | if (!includeAssets && property.ObjectType == ColorProperty.Type.Asset) continue; 94 | 95 | if (property.ObjectType == ColorProperty.Type.Material) 96 | { 97 | var mat = (Material)obj; 98 | if (!((Material)obj).HasProperty(property.PropertyPath)) 99 | { 100 | propertiesToRemove.Add(property); 101 | } 102 | else 103 | { 104 | ((Material)obj).SetColor(property.PropertyPath, colorGroup.Color); 105 | } 106 | continue; 107 | } 108 | 109 | var serializedObject = new UnityEditor.SerializedObject(guidObject.identifierType == 3 ? obj : (Component)obj); 110 | var serializedProperty = serializedObject.FindProperty(property.PropertyPath); 111 | 112 | if (serializedProperty == null) 113 | { 114 | propertiesToRemove.Add(property); 115 | continue; 116 | } 117 | 118 | serializedProperty.colorValue = colorGroup.Color; 119 | EditorUtility.SetDirty(serializedObject.targetObject); 120 | serializedObject.ApplyModifiedProperties(); 121 | } 122 | else 123 | { 124 | propertiesToRemove.Add(property); 125 | } 126 | } 127 | foreach (var property in propertiesToRemove) 128 | { 129 | colorGroup.RemoveProperty(property); 130 | } 131 | } 132 | 133 | if (includeAssets) 134 | { 135 | EditorSceneManager.SaveScene(EditorSceneManager.GetActiveScene(), EditorSceneManager.GetActiveScene().path); 136 | if (PrefabStageUtility.GetCurrentPrefabStage() != null) 137 | { 138 | var prefabPath = PrefabStageUtility.GetCurrentPrefabStage().assetPath; 139 | StageUtility.GoBackToPreviousStage(); 140 | AssetDatabase.OpenAsset(AssetDatabase.LoadAssetAtPath(prefabPath)); 141 | } 142 | } 143 | } 144 | 145 | public void ApplyColorsOnAllScenes() 146 | { 147 | var initialScenePath = EditorSceneManager.GetActiveScene().path; 148 | EditorSceneManager.SaveScene(EditorSceneManager.GetActiveScene(), initialScenePath); 149 | 150 | foreach (var colorGroup in ColorGroups) 151 | { 152 | var propertiesToRemove = new List(); 153 | foreach (var property in colorGroup.Properties) 154 | { 155 | if (GlobalObjectId.TryParse(property.GuidString, out GlobalObjectId guidObject)) 156 | { 157 | if (property.ObjectType != ColorProperty.Type.GameObject) continue; 158 | 159 | EditorSceneManager.OpenScene(AssetDatabase.GUIDToAssetPath(guidObject.assetGUID)); 160 | 161 | var obj = GlobalObjectId.GlobalObjectIdentifierToObjectSlow(guidObject); 162 | 163 | if (obj == null) 164 | { 165 | propertiesToRemove.Add(property); 166 | continue; 167 | } 168 | 169 | var serializedObject = new UnityEditor.SerializedObject((Component)obj); 170 | var serializedProperty = serializedObject.FindProperty(property.PropertyPath); 171 | 172 | if (serializedProperty == null) 173 | { 174 | propertiesToRemove.Add(property); 175 | continue; 176 | } 177 | 178 | serializedProperty.colorValue = colorGroup.Color; 179 | EditorUtility.SetDirty(serializedObject.targetObject); 180 | serializedObject.ApplyModifiedProperties(); 181 | 182 | EditorSceneManager.SaveScene(EditorSceneManager.GetActiveScene(), AssetDatabase.GUIDToAssetPath(guidObject.assetGUID)); 183 | } 184 | else 185 | { 186 | propertiesToRemove.Add(property); 187 | } 188 | } 189 | 190 | foreach (var property in propertiesToRemove) 191 | { 192 | colorGroup.RemoveProperty(property); 193 | } 194 | } 195 | 196 | EditorSceneManager.OpenScene(initialScenePath); 197 | } 198 | 199 | } 200 | 201 | [System.Serializable] 202 | public struct PalettePreset 203 | { 204 | public List Colors; 205 | 206 | public PalettePreset(List colors) 207 | { 208 | Colors = colors; 209 | } 210 | } 211 | } 212 | -------------------------------------------------------------------------------- /Editor/PaletteEditor.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using UnityEditor; 4 | using UnityEngine; 5 | 6 | namespace Colorlink 7 | { 8 | public class PaletteEditor : EditorWindow 9 | { 10 | private PaletteObject Palette => PaletteObject.instance; 11 | private Dictionary _foldoutStates; 12 | private Vector2 _scrollPosition = Vector2.zero; 13 | private bool _showDetails; 14 | 15 | [MenuItem("Window/Palette", false, 10000)] 16 | public static void ShowWindow() => GetWindow("Palette"); 17 | 18 | 19 | private void OnEnable() 20 | { 21 | FillFoldoutStateDictionary(); 22 | Undo.undoRedoPerformed += ApplyChanges; 23 | } 24 | 25 | private void OnDisable() 26 | { 27 | Undo.undoRedoPerformed -= ApplyChanges; 28 | } 29 | 30 | private void OnGUI() 31 | { 32 | Undo.RecordObject(Palette, "Changed Palette"); 33 | 34 | _scrollPosition = EditorGUILayout.BeginScrollView(_scrollPosition, false, false); 35 | 36 | for (int i = 0; i < Palette.ColorGroups.Count; i++) 37 | { 38 | GUILayout.BeginHorizontal(); 39 | DrawColorGroup(Palette.ColorGroups[i]); 40 | GUILayout.EndHorizontal(); 41 | } 42 | 43 | GUILayout.Space(3); 44 | GUILayout.BeginHorizontal(); 45 | 46 | if (GUILayout.Button(EditorGUIUtility.IconContent("d_Toolbar Plus"))) 47 | { 48 | Palette.AddColorGroup(); 49 | FillFoldoutStateDictionary(); 50 | } 51 | 52 | if (Palette.ColorGroups.Count != 0) 53 | { 54 | if (GUILayout.Button(EditorGUIUtility.IconContent("d_SaveAs"))) Palette.SavePreset(); 55 | if (GUILayout.Button(EditorGUIUtility.IconContent(_showDetails ? "d_SceneViewVisibility" : "ViewToolOrbit On"))) _showDetails = !_showDetails; 56 | } 57 | 58 | GUILayout.EndHorizontal(); 59 | 60 | if (GUILayout.Button("Apply to Assets")) Palette.ApplyColors(true); 61 | if (GUILayout.Button("Apply to all Scenes")) Palette.ApplyColorsOnAllScenes(); 62 | 63 | GUILayout.Space(3); 64 | 65 | using (var vertical = new EditorGUILayout.VerticalScope()) 66 | { 67 | CreateDropArea(vertical.rect, () => 68 | { 69 | foreach (var draggedObject in DragAndDrop.objectReferences) 70 | { 71 | if (!(draggedObject is Texture2D)) continue; 72 | Palette.AddPreset(ColorsFromImage((Texture2D)draggedObject)); 73 | } 74 | }); 75 | 76 | for (int i = 0; i < Palette.Presets.Count; i++) 77 | { 78 | GUILayout.BeginHorizontal(); 79 | DrawPalettePreset(Palette.Presets[i]); 80 | GUILayout.EndHorizontal(); 81 | } 82 | } 83 | 84 | GUILayout.EndScrollView(); 85 | 86 | if (GUI.changed) ApplyChanges(); 87 | } 88 | 89 | private void ApplyChanges() 90 | { 91 | Palette.ApplyColors(); 92 | Palette.SaveChanges(); 93 | } 94 | 95 | private void FillFoldoutStateDictionary() 96 | { 97 | _foldoutStates = new Dictionary(); 98 | foreach (var colorGroup in Palette.ColorGroups) 99 | _foldoutStates.Add(colorGroup, false); 100 | } 101 | 102 | public void DrawColorGroup(ColorGroup colorGroup) 103 | { 104 | if (_showDetails) 105 | GUILayout.BeginVertical(new GUIStyle("Box") { padding = new RectOffset(1, 1, 0, 1) }); 106 | else 107 | GUILayout.BeginVertical(); 108 | 109 | GUILayout.Space(5); 110 | 111 | GUILayout.BeginHorizontal(); 112 | if (_showDetails) _foldoutStates[colorGroup] = EditorGUILayout.Toggle(_foldoutStates[colorGroup], "foldout", GUILayout.Width(15)); 113 | colorGroup.Color = EditorGUILayout.ColorField(colorGroup.Color, GUILayout.MaxWidth(80), GUILayout.Height(20)); 114 | GUILayout.Space(2); 115 | colorGroup.Name = EditorGUILayout.TextField(colorGroup.Name, GUILayout.Height(22)); 116 | 117 | GUILayout.EndHorizontal(); 118 | 119 | if (_showDetails) 120 | { 121 | using (var vertical = new EditorGUILayout.VerticalScope()) 122 | { 123 | CreateDropArea(vertical.rect, () => 124 | { 125 | var data = DragAndDrop.GetGenericData("property"); 126 | if (data == null) return; 127 | if (colorGroup.Contains(((ColorProperty)data).GuidString, ((ColorProperty)data).PropertyPath)) return; 128 | 129 | Palette.AddProperty(colorGroup, (ColorProperty)data); 130 | Palette.ApplyColors(); 131 | }); 132 | 133 | GUILayout.Space(5); 134 | 135 | if (_foldoutStates[colorGroup]) 136 | { 137 | 138 | for (int i = 0; i < colorGroup.Properties.Count; i++) 139 | { 140 | DrawColorProperty(colorGroup.Properties[i], colorGroup); 141 | } 142 | } 143 | } 144 | } 145 | 146 | GUILayout.EndVertical(); 147 | 148 | var buttonStyle = new GUIStyle("Button") { fixedWidth = 25, fixedHeight = 25, margin = new RectOffset(3, 3, 5, 0) }; 149 | 150 | if (GUILayout.Button("↑", buttonStyle)) 151 | { 152 | Palette.ChangeColorGroupIndex(colorGroup, -1); 153 | } 154 | if (GUILayout.Button("↓", buttonStyle)) 155 | { 156 | Palette.ChangeColorGroupIndex(colorGroup, +1); 157 | } 158 | if (GUILayout.Button(EditorGUIUtility.IconContent("d_winbtn_win_close"), buttonStyle)) 159 | { 160 | Palette.RemoveColorGroup(colorGroup); 161 | FillFoldoutStateDictionary(); 162 | } 163 | 164 | } 165 | 166 | private void DrawColorProperty(ColorProperty property, ColorGroup colorGroup) 167 | { 168 | GUILayout.BeginHorizontal(); 169 | 170 | using (var horizontal = new EditorGUILayout.HorizontalScope()) 171 | { 172 | var evt = Event.current; 173 | if (evt.button == 0 && evt.isMouse && horizontal.rect.Contains(evt.mousePosition)) 174 | { 175 | DragAndDrop.SetGenericData("property", property); 176 | DragAndDrop.StartDrag(""); 177 | } 178 | 179 | if (property.ObjectType == ColorProperty.Type.Material) GUILayout.Label(EditorGUIUtility.IconContent("Material On Icon"), GUILayout.Height(20), GUILayout.Width(20)); 180 | if (property.ObjectType == ColorProperty.Type.GameObject) GUILayout.Label(EditorGUIUtility.IconContent("GameObject On Icon"), GUILayout.Height(20), GUILayout.Width(20)); 181 | if (property.ObjectType == ColorProperty.Type.Asset) GUILayout.Label(EditorGUIUtility.IconContent("d_Prefab On Icon"), GUILayout.Height(20), GUILayout.Width(20)); 182 | 183 | GUILayout.BeginHorizontal("HelpBox", GUILayout.MaxWidth(200)); 184 | GUILayout.Label(property.ObjectName); 185 | GUILayout.EndHorizontal(); 186 | GUILayout.BeginHorizontal("HelpBox", GUILayout.MaxWidth(200)); 187 | GUILayout.Label(property.PropertyPath); 188 | GUILayout.EndHorizontal(); 189 | } 190 | GUILayout.FlexibleSpace(); 191 | 192 | if (GUILayout.Button(EditorGUIUtility.IconContent("ViewToolOrbit On"), GUILayout.Width(30), GUILayout.Height(22))) 193 | { 194 | if (GlobalObjectId.TryParse(property.GuidString, out GlobalObjectId guidObject)) 195 | EditorGUIUtility.PingObject(GlobalObjectId.GlobalObjectIdentifierToObjectSlow(guidObject)); 196 | } 197 | 198 | if (GUILayout.Button(EditorGUIUtility.IconContent("d_winbtn_win_close"), GUILayout.Width(30), GUILayout.Height(22))) 199 | { 200 | colorGroup.RemoveProperty(property); 201 | } 202 | 203 | GUILayout.EndHorizontal(); 204 | } 205 | 206 | private void DrawPalettePreset(PalettePreset preset) 207 | { 208 | var colors = preset.Colors; 209 | 210 | GUILayout.BeginVertical("HelpBox"); 211 | 212 | var colorBoxStyle = new GUIStyle("box") 213 | { 214 | padding = new RectOffset(2, 2, 2, 2), 215 | margin = new RectOffset(0, 0, 0, 0), 216 | fixedHeight = 20, 217 | fixedWidth = 20 218 | }; 219 | 220 | var squares = Mathf.Clamp(Mathf.RoundToInt((EditorGUIUtility.currentViewWidth - 100) / 20), 1, 100); 221 | GUILayout.BeginHorizontal(); 222 | for (int i = 0; i < colors.Count; i++) 223 | { 224 | GUILayout.Box(TextureFromColor(colors[i], 20), colorBoxStyle); 225 | if (i % squares == squares - 1) 226 | { 227 | GUILayout.EndHorizontal(); 228 | GUILayout.BeginHorizontal(); 229 | } 230 | } 231 | GUILayout.EndHorizontal(); 232 | 233 | GUILayout.EndVertical(); 234 | 235 | if (GUILayout.Button(EditorGUIUtility.IconContent("d_scenepicking_pickable_hover"), GUILayout.Width(25), GUILayout.Height(25))) 236 | { 237 | for (int i = 0; i < Mathf.Min(Palette.ColorGroups.Count, colors.Count); i++) 238 | { 239 | Palette.ColorGroups[i].Color = colors[i]; 240 | } 241 | } 242 | 243 | if (GUILayout.Button(EditorGUIUtility.IconContent("d_winbtn_win_close"), GUILayout.Width(25), GUILayout.Height(25))) 244 | { 245 | Palette.Presets.Remove(preset); 246 | } 247 | } 248 | 249 | private void CreateDropArea(Rect rect, Action callback) 250 | { 251 | var evt = Event.current; 252 | 253 | if (evt.type != EventType.DragPerform && evt.type != EventType.DragUpdated) return; 254 | if (!rect.Contains(evt.mousePosition)) return; 255 | 256 | DragAndDrop.visualMode = DragAndDropVisualMode.Move; 257 | 258 | if (evt.type != EventType.DragPerform) return; 259 | 260 | DragAndDrop.AcceptDrag(); 261 | 262 | callback.Invoke(); 263 | } 264 | 265 | private static List ColorsFromImage(Texture2D image) 266 | { 267 | var colors = new List(); 268 | var pixels = image.GetPixels(); 269 | 270 | foreach (var pixel in pixels) 271 | { 272 | if (!colors.Contains(pixel)) colors.Add(pixel); 273 | if (colors.Count >= 30) 274 | { 275 | Debug.Log("Selected image has too many unique colors!"); 276 | break; 277 | } 278 | } 279 | 280 | return colors; 281 | } 282 | 283 | private static Texture2D TextureFromColor(Color color, int size) 284 | { 285 | Color[] pix = new Color[size * size]; 286 | for (int i = 0; i < pix.Length; i++) 287 | { 288 | pix[i] = color; 289 | } 290 | Texture2D texture = new Texture2D(size, size); 291 | texture.SetPixels(pix); 292 | texture.Apply(); 293 | return texture; 294 | } 295 | } 296 | } --------------------------------------------------------------------------------