From 752e72eb8f9e896260892301849abb462e5830f2 Mon Sep 17 00:00:00 2001 From: Harald Csaszar Date: Fri, 22 Jan 2021 11:03:24 +0100 Subject: [PATCH] [unity] Added native support for slot blend modes `Additive`, `Multiply` and `Screen` with automatic assignment at newly imported skeleton assets. Added upgrade functionality. Closes #1822, closes #1559. --- CHANGELOG.md | 1 + .../Asset Types/SkeletonDataAssetInspector.cs | 29 +- .../Editor/Utility/AssetUtility.cs | 6 +- .../Utility/BlendModeMaterialsUtility.cs | 282 ++++++++++++++++++ .../Utility/BlendModeMaterialsUtility.cs.meta | 11 + .../spine-unity/Editor/Utility/Preferences.cs | 65 ++++ .../Editor/Windows/SpinePreferences.cs | 63 ++++ .../Asset Types/BlendModeMaterials.cs | 144 +++++++++ .../Asset Types/BlendModeMaterials.cs.meta | 11 + .../Asset Types/SkeletonDataAsset.cs | 11 +- 10 files changed, 619 insertions(+), 4 deletions(-) create mode 100644 spine-unity/Assets/Spine/Editor/spine-unity/Editor/Utility/BlendModeMaterialsUtility.cs create mode 100644 spine-unity/Assets/Spine/Editor/spine-unity/Editor/Utility/BlendModeMaterialsUtility.cs.meta create mode 100644 spine-unity/Assets/Spine/Runtime/spine-unity/Asset Types/BlendModeMaterials.cs create mode 100644 spine-unity/Assets/Spine/Runtime/spine-unity/Asset Types/BlendModeMaterials.cs.meta diff --git a/CHANGELOG.md b/CHANGELOG.md index 53db38f50..9e914577e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -250,6 +250,7 @@ * `SkeletonRenderer` components now provide an additional update mode `Only Event Timelines` at the `Update When Invisible` property. This mode saves additional timeline updates compared to update mode `Everything Except Mesh`. * Now all URP (Universal Render Pipeline) and LWRP (Lightweight Render Pipeline) shaders support SRP (Scriptable Render Pipeline) batching. See [Unity SRPBatcher documentation pages](https://docs.unity3d.com/Manual/SRPBatcher.html) for additional information. * Sprite shaders now provide four `Diffuse Ramp` modes as an Inspector Material parameter: `Hard`, `Soft`, `Old Hard` and `Old Soft`. In spine-unity 3.8 it defaults to `Old Hard` to keep the behaviour of existing projects unchanged. Note that `Old Hard` and `Old Soft` ramp versions were using only the right half of the ramp texture, and additionally multiplying the light intensity by 2, both leading to brighter lighting than without a ramp texture active. The new ramp modes `Hard` and `Soft` use the full ramp texture and do not modify light intensity, being consistent with lighting without a ramp texture active. + * Added **native support for slot blend modes** `Additive`, `Multiply` and `Screen` with automatic assignment at newly imported skeleton assets. `BlendModeMaterialAssets` are now obsolete and replaced by the native properties at `SkeletonDataAsset`. The `SkeletonDataAsset` Inspector provides a new `Blend Modes - Upgrade` button to upgrade an obsolete `BlendModeMaterialAsset` to the native blend modes properties. This upgrade will be performed automatically on imported and re-imported assets in Unity 2020.1 and newer to prevent reported `BlendModeMaterialAsset` issues in these Unity versions. spine-unity 4.0 and newer will automatically perform this upgrade regardless of the Unity version. * **Changes of default values** * `SkeletonMecanim`'s `Layer Mix Mode` now defaults to `MixMode.MixNext` instead of `MixMode.MixAlways`. diff --git a/spine-unity/Assets/Spine/Editor/spine-unity/Editor/Asset Types/SkeletonDataAssetInspector.cs b/spine-unity/Assets/Spine/Editor/spine-unity/Editor/Asset Types/SkeletonDataAssetInspector.cs index 0579ab53b..9aafdeae4 100644 --- a/spine-unity/Assets/Spine/Editor/spine-unity/Editor/Asset Types/SkeletonDataAssetInspector.cs +++ b/spine-unity/Assets/Spine/Editor/spine-unity/Editor/Asset Types/SkeletonDataAssetInspector.cs @@ -56,6 +56,7 @@ namespace Spine.Unity.Editor { SerializedProperty atlasAssets, skeletonJSON, scale, fromAnimation, toAnimation, duration, defaultMix; SerializedProperty skeletonDataModifiers; + SerializedProperty blendModeMaterials; #if SPINE_TK2D SerializedProperty spriteCollection; #endif @@ -114,6 +115,7 @@ namespace Spine.Unity.Editor { defaultMix = serializedObject.FindProperty("defaultMix"); skeletonDataModifiers = serializedObject.FindProperty("skeletonDataModifiers"); + blendModeMaterials = serializedObject.FindProperty("blendModeMaterials"); #if SPINE_SKELETON_MECANIM controller = serializedObject.FindProperty("controller"); @@ -125,7 +127,7 @@ namespace Spine.Unity.Editor { #else // Analysis disable once ConvertIfToOrExpression if (newAtlasAssets) atlasAssets.isExpanded = true; -#endif + #endif // This handles the case where the managed editor assembly is unloaded before recompilation when code changes. AppDomain.CurrentDomain.DomainUnload -= OnDomainUnload; @@ -286,6 +288,8 @@ namespace Spine.Unity.Editor { EditorGUILayout.DelayedFloatField(scale); //EditorGUILayout.PropertyField(scale); EditorGUILayout.Space(); EditorGUILayout.PropertyField(skeletonDataModifiers, true); + + DrawBlendModeMaterialProperties(); } // Texture source field. @@ -311,6 +315,27 @@ namespace Spine.Unity.Editor { } + void DrawBlendModeMaterialProperties () { + if (skeletonDataModifiers.arraySize > 0) { + EditorGUILayout.BeginHorizontal(GUILayout.Height(EditorGUIUtility.singleLineHeight + 5)); + EditorGUILayout.PrefixLabel("Blend Modes"); + if (GUILayout.Button(new GUIContent("Upgrade", "Upgrade BlendModeMaterialAsset to built-in BlendModeMaterials."), EditorStyles.miniButton, GUILayout.Width(65f))) { + foreach (SkeletonDataAsset skeletonData in targets) { + BlendModeMaterialsUtility.UpgradeBlendModeMaterials(skeletonData); + } + } + EditorGUILayout.EndHorizontal(); + } + EditorGUI.BeginChangeCheck(); + EditorGUILayout.PropertyField(blendModeMaterials, true); + if (EditorGUI.EndChangeCheck()) { + serializedObject.ApplyModifiedProperties(); + foreach (SkeletonDataAsset skeletonData in targets) { + BlendModeMaterialsUtility.UpdateBlendModeMaterials(skeletonData); + } + } + } + void DrawSkeletonDataFields () { using (new EditorGUILayout.HorizontalScope()) { EditorGUILayout.LabelField("SkeletonData", EditorStyles.boldLabel); @@ -331,6 +356,8 @@ namespace Spine.Unity.Editor { EditorGUILayout.DelayedFloatField(scale); //EditorGUILayout.PropertyField(scale); EditorGUILayout.Space(); EditorGUILayout.PropertyField(skeletonDataModifiers, true); + + DrawBlendModeMaterialProperties(); } void DrawAtlasAssetsFields () { diff --git a/spine-unity/Assets/Spine/Editor/spine-unity/Editor/Utility/AssetUtility.cs b/spine-unity/Assets/Spine/Editor/spine-unity/Editor/Utility/AssetUtility.cs index d0a973e72..4abc23d31 100644 --- a/spine-unity/Assets/Spine/Editor/spine-unity/Editor/Utility/AssetUtility.cs +++ b/spine-unity/Assets/Spine/Editor/spine-unity/Editor/Utility/AssetUtility.cs @@ -427,6 +427,8 @@ namespace Spine.Unity.Editor { } SkeletonData skeletonData = skeletonDataAsset.GetSkeletonData(true); + BlendModeMaterialsUtility.UpdateBlendModeMaterials(skeletonDataAsset, ref skeletonData); + string currentHash = skeletonData != null ? skeletonData.Hash : null; #if SPINE_SKELETONMECANIM @@ -887,6 +889,7 @@ namespace Spine.Unity.Editor { skeletonDataAsset.skeletonJSON = spineJson; skeletonDataAsset.defaultMix = SpineEditorUtilities.Preferences.defaultMix; skeletonDataAsset.scale = SpineEditorUtilities.Preferences.defaultScale; + skeletonDataAsset.blendModeMaterials.applyAdditiveMaterial = !SpineEditorUtilities.Preferences.UsesPMAWorkflow; } AssetDatabase.CreateAsset(skeletonDataAsset, filePath); @@ -894,7 +897,8 @@ namespace Spine.Unity.Editor { } else { skeletonDataAsset.atlasAssets = atlasAssets; skeletonDataAsset.Clear(); - skeletonDataAsset.GetSkeletonData(true); + var skeletonData = skeletonDataAsset.GetSkeletonData(true); + BlendModeMaterialsUtility.UpdateBlendModeMaterials(skeletonDataAsset, ref skeletonData); } return skeletonDataAsset; diff --git a/spine-unity/Assets/Spine/Editor/spine-unity/Editor/Utility/BlendModeMaterialsUtility.cs b/spine-unity/Assets/Spine/Editor/spine-unity/Editor/Utility/BlendModeMaterialsUtility.cs new file mode 100644 index 000000000..91baf3c7e --- /dev/null +++ b/spine-unity/Assets/Spine/Editor/spine-unity/Editor/Utility/BlendModeMaterialsUtility.cs @@ -0,0 +1,282 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated January 1, 2020. Replaces all prior versions. + * + * Copyright (c) 2013-2020, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#if UNITY_2020_1_OR_NEWER +#define UPGRADE_ALL_BLEND_MODE_MATERIALS +#endif + +using UnityEngine; +using UnityEditor; +using System.Collections.Generic; +using System.IO; +using System; + +namespace Spine.Unity.Editor { + + public class BlendModeMaterialsUtility { + + public const string MATERIAL_SUFFIX_MULTIPLY = "-Multiply"; + public const string MATERIAL_SUFFIX_SCREEN = "-Screen"; + public const string MATERIAL_SUFFIX_ADDITIVE = "-Additive"; + +#if UPGRADE_ALL_BLEND_MODE_MATERIALS + public const bool ShallUpgradeBlendModeMaterials = true; +#else + public const bool ShallUpgradeBlendModeMaterials = false; +#endif + + protected class TemplateMaterials { + public Material multiplyTemplate; + public Material screenTemplate; + public Material additiveTemplate; + }; + + public static void UpgradeBlendModeMaterials (SkeletonDataAsset skeletonDataAsset) { + var skeletonData = skeletonDataAsset.GetSkeletonData(true); + if (skeletonData == null) + return; + UpdateBlendModeMaterials(skeletonDataAsset, ref skeletonData, true); + } + + public static void UpdateBlendModeMaterials (SkeletonDataAsset skeletonDataAsset) { + var skeletonData = skeletonDataAsset.GetSkeletonData(true); + if (skeletonData == null) + return; + UpdateBlendModeMaterials(skeletonDataAsset, ref skeletonData, false); + } + + public static void UpdateBlendModeMaterials (SkeletonDataAsset skeletonDataAsset, ref SkeletonData skeletonData, + bool upgradeFromModifierAssets = ShallUpgradeBlendModeMaterials) { + + TemplateMaterials templateMaterials = new TemplateMaterials(); + bool anyMaterialsChanged = ClearUndesiredMaterialEntries(skeletonDataAsset); + + var blendModesModifierAsset = FindBlendModeMaterialsModifierAsset(skeletonDataAsset); + if (blendModesModifierAsset) { + if (upgradeFromModifierAssets) { + TransferSettingsFromModifierAsset(blendModesModifierAsset, + skeletonDataAsset, templateMaterials); + UpdateBlendmodeMaterialsRequiredState(skeletonDataAsset, skeletonData); + } + else + return; + } + else { + if (!UpdateBlendmodeMaterialsRequiredState(skeletonDataAsset, skeletonData)) + return; + AssignPreferencesTemplateMaterials(templateMaterials); + } + bool success = CreateAndAssignMaterials(skeletonDataAsset, templateMaterials, ref anyMaterialsChanged); + if (success) { + if (blendModesModifierAsset != null) { + RemoveObsoleteModifierAsset(blendModesModifierAsset, skeletonDataAsset); + } + } + + skeletonDataAsset.Clear(); + skeletonData = skeletonDataAsset.GetSkeletonData(true); + if (anyMaterialsChanged) + ReloadSceneSkeletons(skeletonDataAsset); + AssetDatabase.SaveAssets(); + } + + protected static bool ClearUndesiredMaterialEntries (SkeletonDataAsset skeletonDataAsset) { + Predicate ifMaterialMissing = r => r.material == null; + + bool anyMaterialsChanged = false; + if (!skeletonDataAsset.blendModeMaterials.applyAdditiveMaterial) { + anyMaterialsChanged |= skeletonDataAsset.blendModeMaterials.additiveMaterials.Count > 0; + skeletonDataAsset.blendModeMaterials.additiveMaterials.Clear(); + } + else + anyMaterialsChanged |= skeletonDataAsset.blendModeMaterials.additiveMaterials.RemoveAll(ifMaterialMissing) != 0; + anyMaterialsChanged |= skeletonDataAsset.blendModeMaterials.multiplyMaterials.RemoveAll(ifMaterialMissing) != 0; + anyMaterialsChanged |= skeletonDataAsset.blendModeMaterials.screenMaterials.RemoveAll(ifMaterialMissing) != 0; + return anyMaterialsChanged; + } + + protected static BlendModeMaterialsAsset FindBlendModeMaterialsModifierAsset (SkeletonDataAsset skeletonDataAsset) { + foreach (var modifierAsset in skeletonDataAsset.skeletonDataModifiers) { + if (modifierAsset is BlendModeMaterialsAsset) + return (BlendModeMaterialsAsset)modifierAsset; + } + return null; + } + + protected static bool UpdateBlendmodeMaterialsRequiredState (SkeletonDataAsset skeletonDataAsset, SkeletonData skeletonData) { + return skeletonDataAsset.blendModeMaterials.UpdateBlendmodeMaterialsRequiredState(skeletonData); + } + + protected static void TransferSettingsFromModifierAsset (BlendModeMaterialsAsset modifierAsset, + SkeletonDataAsset skeletonDataAsset, TemplateMaterials templateMaterials) { + + skeletonDataAsset.blendModeMaterials.TransferSettingsFrom(modifierAsset); + + templateMaterials.multiplyTemplate = modifierAsset.multiplyMaterialTemplate; + templateMaterials.screenTemplate = modifierAsset.screenMaterialTemplate; + templateMaterials.additiveTemplate = modifierAsset.additiveMaterialTemplate; + } + + protected static void RemoveObsoleteModifierAsset (BlendModeMaterialsAsset modifierAsset, + SkeletonDataAsset skeletonDataAsset) { + + skeletonDataAsset.skeletonDataModifiers.Remove(modifierAsset); + Debug.Log(string.Format("BlendModeMaterialsAsset upgraded to built-in BlendModeMaterials at SkeletonDataAsset '{0}'.", + skeletonDataAsset.name), skeletonDataAsset); + EditorUtility.SetDirty(skeletonDataAsset); + } + + protected static void AssignPreferencesTemplateMaterials (TemplateMaterials templateMaterials) { + + templateMaterials.multiplyTemplate = SpineEditorUtilities.Preferences.BlendModeMaterialMultiply; + templateMaterials.screenTemplate = SpineEditorUtilities.Preferences.BlendModeMaterialScreen; + templateMaterials.additiveTemplate = SpineEditorUtilities.Preferences.BlendModeMaterialAdditive; + } + + protected static bool CreateAndAssignMaterials (SkeletonDataAsset skeletonDataAsset, + TemplateMaterials templateMaterials, ref bool anyReplacementMaterialsChanged) { + + bool anyCreationFailed = false; + var blendModeMaterials = skeletonDataAsset.blendModeMaterials; + bool applyAdditiveMaterial = blendModeMaterials.applyAdditiveMaterial; + + var skinEntries = new List(); + + skeletonDataAsset.Clear(); + skeletonDataAsset.isUpgradingBlendModeMaterials = true; + SkeletonData skeletonData = skeletonDataAsset.GetSkeletonData(true); + + var slotsItems = skeletonData.Slots.Items; + for (int slotIndex = 0, slotCount = skeletonData.Slots.Count; slotIndex < slotCount; slotIndex++) { + var slot = slotsItems[slotIndex]; + if (slot.BlendMode == BlendMode.Normal) continue; + if (!applyAdditiveMaterial && slot.BlendMode == BlendMode.Additive) continue; + + List replacementMaterials = null; + Material materialTemplate = null; + string materialSuffix = null; + switch (slot.BlendMode) { + case BlendMode.Multiply: + replacementMaterials = blendModeMaterials.multiplyMaterials; + materialTemplate = templateMaterials.multiplyTemplate; + materialSuffix = MATERIAL_SUFFIX_MULTIPLY; + break; + case BlendMode.Screen: + replacementMaterials = blendModeMaterials.screenMaterials; + materialTemplate = templateMaterials.screenTemplate; + materialSuffix = MATERIAL_SUFFIX_SCREEN; + break; + case BlendMode.Additive: + replacementMaterials = blendModeMaterials.additiveMaterials; + materialTemplate = templateMaterials.additiveTemplate; + materialSuffix = MATERIAL_SUFFIX_ADDITIVE; + break; + } + + skinEntries.Clear(); + foreach (var skin in skeletonData.Skins) + skin.GetAttachments(slotIndex, skinEntries); + + foreach (var entry in skinEntries) { + var renderableAttachment = entry.Attachment as IHasRendererObject; + if (renderableAttachment != null) { + var originalRegion = (AtlasRegion)renderableAttachment.RendererObject; + bool replacementExists = replacementMaterials.Exists( + replacement => replacement.pageName == originalRegion.page.name); + if (!replacementExists) { + bool createdNewMaterial; + var replacement = CreateOrLoadReplacementMaterial(originalRegion, materialTemplate, materialSuffix, out createdNewMaterial); + if (replacement != null) { + replacementMaterials.Add(replacement); + anyReplacementMaterialsChanged = true; + if (createdNewMaterial) { + Debug.Log(string.Format("Created blend mode Material '{0}' for SkeletonDataAsset '{1}'.", + replacement.material.name, skeletonDataAsset), replacement.material); + } + } + else { + Debug.LogError(string.Format("Failed creating blend mode Material for SkeletonDataAsset '{0}',"+ + " atlas page '{1}', template '{2}'.", + skeletonDataAsset.name, originalRegion.page.name, materialTemplate.name), + skeletonDataAsset); + anyCreationFailed = true; + } + } + } + } + } + + skeletonDataAsset.isUpgradingBlendModeMaterials = false; + EditorUtility.SetDirty(skeletonDataAsset); + return !anyCreationFailed; + } + + protected static string GetBlendModeMaterialPath(AtlasPage originalPage, string materialSuffix) { + var originalMaterial = originalPage.rendererObject as Material; + var originalPath = AssetDatabase.GetAssetPath(originalMaterial); + return originalPath.Replace(".mat", materialSuffix + ".mat"); + } + + protected static BlendModeMaterials.ReplacementMaterial CreateOrLoadReplacementMaterial ( + AtlasRegion originalRegion, Material materialTemplate, string materialSuffix, out bool createdNewMaterial) { + + createdNewMaterial = false; + var newReplacement = new BlendModeMaterials.ReplacementMaterial(); + var originalPage = originalRegion.page; + var originalMaterial = originalPage.rendererObject as Material; + var blendMaterialPath = GetBlendModeMaterialPath(originalPage, materialSuffix); + + newReplacement.pageName = originalPage.name; + if (File.Exists(blendMaterialPath)) { + newReplacement.material = AssetDatabase.LoadAssetAtPath(blendMaterialPath); + } + else { + var blendModeMaterial = new Material(materialTemplate) { + name = originalMaterial.name + " " + materialTemplate.name, + mainTexture = originalMaterial.mainTexture + }; + newReplacement.material = blendModeMaterial; + + AssetDatabase.CreateAsset(blendModeMaterial, blendMaterialPath); + EditorUtility.SetDirty(blendModeMaterial); + createdNewMaterial = true; + } + + if (newReplacement.material) + return newReplacement; + else + return null; + } + + protected static void ReloadSceneSkeletons (SkeletonDataAsset skeletonDataAsset) { + if (SpineEditorUtilities.Preferences.autoReloadSceneSkeletons) + SpineEditorUtilities.DataReloadHandler.ReloadSceneSkeletonComponents(skeletonDataAsset); + } + } +} diff --git a/spine-unity/Assets/Spine/Editor/spine-unity/Editor/Utility/BlendModeMaterialsUtility.cs.meta b/spine-unity/Assets/Spine/Editor/spine-unity/Editor/Utility/BlendModeMaterialsUtility.cs.meta new file mode 100644 index 000000000..5d96bd875 --- /dev/null +++ b/spine-unity/Assets/Spine/Editor/spine-unity/Editor/Utility/BlendModeMaterialsUtility.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 8094f8aedb33b7744b109c2c1294d37a +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/spine-unity/Assets/Spine/Editor/spine-unity/Editor/Utility/Preferences.cs b/spine-unity/Assets/Spine/Editor/spine-unity/Editor/Utility/Preferences.cs index bbff54627..6d13c6fc9 100644 --- a/spine-unity/Assets/Spine/Editor/spine-unity/Editor/Utility/Preferences.cs +++ b/spine-unity/Assets/Spine/Editor/spine-unity/Editor/Utility/Preferences.cs @@ -124,6 +124,32 @@ namespace Spine.Unity.Editor { const string TEXTURE_SETTINGS_REFERENCE_KEY = "SPINE_TEXTURE_SETTINGS_REFERENCE"; public static string textureSettingsReference = SpinePreferences.DEFAULT_TEXTURE_SETTINGS_REFERENCE; + public static bool UsesPMAWorkflow { + get { + return SpinePreferences.IsPMAWorkflow(textureSettingsReference); + } + } + + const string BLEND_MODE_MATERIAL_MULTIPLY_KEY = "SPINE_BLENDMODE_MATERIAL_MULTIPLY"; + const string BLEND_MODE_MATERIAL_SCREEN_KEY = "SPINE_BLENDMODE_MATERIAL_SCREEN"; + const string BLEND_MODE_MATERIAL_ADDITIVE_KEY = "SPINE_BLENDMODE_MATERIAL_ADDITIVE"; + public static string blendModeMaterialMultiply = ""; + public static string blendModeMaterialScreen = ""; + public static string blendModeMaterialAdditive = ""; + public const string DEFAULT_BLEND_MODE_MULTIPLY_MATERIAL = SpinePreferences.DEFAULT_BLEND_MODE_MULTIPLY_MATERIAL; + public const string DEFAULT_BLEND_MODE_SCREEN_MATERIAL = SpinePreferences.DEFAULT_BLEND_MODE_SCREEN_MATERIAL; + public const string DEFAULT_BLEND_MODE_ADDITIVE_MATERIAL = SpinePreferences.DEFAULT_BLEND_MODE_ADDITIVE_MATERIAL; + + public static Material BlendModeMaterialMultiply { + get { return AssetDatabase.LoadAssetAtPath(blendModeMaterialMultiply); } + } + public static Material BlendModeMaterialScreen { + get { return AssetDatabase.LoadAssetAtPath(blendModeMaterialScreen); } + } + public static Material BlendModeMaterialAdditive { + get { return AssetDatabase.LoadAssetAtPath(blendModeMaterialAdditive); } + } + const string ATLASTXT_WARNING_KEY = "SPINE_ATLASTXT_WARNING"; public static bool atlasTxtImportWarning = SpinePreferences.DEFAULT_ATLASTXT_WARNING; @@ -161,6 +187,9 @@ namespace Spine.Unity.Editor { showHierarchyIcons = EditorPrefs.GetBool(SHOW_HIERARCHY_ICONS_KEY, SpinePreferences.DEFAULT_SHOW_HIERARCHY_ICONS); setTextureImporterSettings = EditorPrefs.GetBool(SET_TEXTUREIMPORTER_SETTINGS_KEY, SpinePreferences.DEFAULT_SET_TEXTUREIMPORTER_SETTINGS); textureSettingsReference = EditorPrefs.GetString(TEXTURE_SETTINGS_REFERENCE_KEY, SpinePreferences.DEFAULT_TEXTURE_SETTINGS_REFERENCE); + blendModeMaterialMultiply = EditorPrefs.GetString(BLEND_MODE_MATERIAL_MULTIPLY_KEY, ""); + blendModeMaterialScreen = EditorPrefs.GetString(BLEND_MODE_MATERIAL_SCREEN_KEY, ""); + blendModeMaterialAdditive = EditorPrefs.GetString(BLEND_MODE_MATERIAL_ADDITIVE_KEY, ""); autoReloadSceneSkeletons = EditorPrefs.GetBool(AUTO_RELOAD_SCENESKELETONS_KEY, SpinePreferences.DEFAULT_AUTO_RELOAD_SCENESKELETONS); mecanimEventIncludeFolderName = EditorPrefs.GetBool(MECANIM_EVENT_INCLUDE_FOLDERNAME_KEY, SpinePreferences.DEFAULT_MECANIM_EVENT_INCLUDE_FOLDERNAME); atlasTxtImportWarning = EditorPrefs.GetBool(ATLASTXT_WARNING_KEY, SpinePreferences.DEFAULT_ATLASTXT_WARNING); @@ -246,6 +275,28 @@ namespace Spine.Unity.Editor { EditorPrefs.SetString(TEXTURE_SETTINGS_REFERENCE_KEY, textureSettingsReference); } } + + SpineEditorUtilities.MaterialPrefsField(ref blendModeMaterialMultiply, BLEND_MODE_MATERIAL_MULTIPLY_KEY, new GUIContent("Multiply Material", "Multiply blend mode Material template.")); + if (string.IsNullOrEmpty(blendModeMaterialMultiply)) { + var blendModeMaterialMultiplyGUIDS = AssetDatabase.FindAssets(DEFAULT_BLEND_MODE_MULTIPLY_MATERIAL); + if (blendModeMaterialMultiplyGUIDS.Length > 0) { + blendModeMaterialMultiply = AssetDatabase.GUIDToAssetPath(blendModeMaterialMultiplyGUIDS[0]); + } + } + SpineEditorUtilities.MaterialPrefsField(ref blendModeMaterialScreen, BLEND_MODE_MATERIAL_SCREEN_KEY, new GUIContent("Screen Material", "Screen blend mode Material template.")); + if (string.IsNullOrEmpty(blendModeMaterialScreen)) { + var blendModeMaterialScreenGUIDS = AssetDatabase.FindAssets(DEFAULT_BLEND_MODE_SCREEN_MATERIAL); + if (blendModeMaterialScreenGUIDS.Length > 0) { + blendModeMaterialScreen = AssetDatabase.GUIDToAssetPath(blendModeMaterialScreenGUIDS[0]); + } + } + SpineEditorUtilities.MaterialPrefsField(ref blendModeMaterialAdditive, BLEND_MODE_MATERIAL_ADDITIVE_KEY, new GUIContent("Additive Material", "Additive blend mode Material template.")); + if (string.IsNullOrEmpty(blendModeMaterialAdditive)) { + var blendModeMaterialAdditiveGUIDS = AssetDatabase.FindAssets(DEFAULT_BLEND_MODE_ADDITIVE_MATERIAL); + if (blendModeMaterialAdditiveGUIDS.Length > 0) { + blendModeMaterialAdditive = AssetDatabase.GUIDToAssetPath(blendModeMaterialAdditiveGUIDS[0]); + } + } } EditorGUILayout.Space(); @@ -344,6 +395,16 @@ namespace Spine.Unity.Editor { } } + static void MaterialPrefsField (ref string currentValue, string editorPrefsKey, GUIContent label) { + EditorGUI.BeginChangeCheck(); + EditorGUIUtility.wideMode = true; + var material = (EditorGUILayout.ObjectField(label, AssetDatabase.LoadAssetAtPath(currentValue), typeof(Object), false) as Material); + currentValue = material != null ? AssetDatabase.GetAssetPath(material) : ""; + if (EditorGUI.EndChangeCheck()) { + EditorPrefs.SetString(editorPrefsKey, currentValue); + } + } + public static void FloatPropertyField (SerializedProperty property, GUIContent label, float min = float.NegativeInfinity, float max = float.PositiveInfinity) { EditorGUI.BeginChangeCheck(); property.floatValue = EditorGUILayout.DelayedFloatField(label, property.floatValue); @@ -357,6 +418,10 @@ namespace Spine.Unity.Editor { property.stringValue = shader != null ? shader.name : fallbackShaderName; } + public static void MaterialPropertyField (SerializedProperty property, GUIContent label) { + var material = (EditorGUILayout.ObjectField(label, AssetDatabase.LoadAssetAtPath(property.stringValue), typeof(Material), false) as Material); + property.stringValue = material ? AssetDatabase.GetAssetPath(material) : ""; + } #if NEW_PREFERENCES_SETTINGS_PROVIDER public static void PresetAssetPropertyField (SerializedProperty property, GUIContent label) { diff --git a/spine-unity/Assets/Spine/Editor/spine-unity/Editor/Windows/SpinePreferences.cs b/spine-unity/Assets/Spine/Editor/spine-unity/Editor/Windows/SpinePreferences.cs index eaed36cdd..98ebe9f7b 100644 --- a/spine-unity/Assets/Spine/Editor/spine-unity/Editor/Windows/SpinePreferences.cs +++ b/spine-unity/Assets/Spine/Editor/spine-unity/Editor/Windows/SpinePreferences.cs @@ -78,6 +78,65 @@ namespace Spine.Unity.Editor { internal const string DEFAULT_TEXTURE_SETTINGS_REFERENCE = ""; public string textureSettingsReference = DEFAULT_TEXTURE_SETTINGS_REFERENCE; + public bool UsesPMAWorkflow { + get { + return IsPMAWorkflow(textureSettingsReference); + } + } + public static bool IsPMAWorkflow(string textureSettingsReference) { + if (textureSettingsReference == null) + return true; + string settingsReference = textureSettingsReference.ToLower(); + if (settingsReference.Contains("straight") || !settingsReference.Contains("pma")) + return false; + return true; + } + + public const string DEFAULT_BLEND_MODE_MULTIPLY_MATERIAL = "SkeletonPMAMultiply"; + public const string DEFAULT_BLEND_MODE_SCREEN_MATERIAL = "SkeletonPMAScreen"; + public const string DEFAULT_BLEND_MODE_ADDITIVE_MATERIAL = "SkeletonPMAAdditive"; + + public Material blendModeMaterialMultiply = null; + public Material blendModeMaterialScreen = null; + public Material blendModeMaterialAdditive = null; + + public string FindPathOfAsset (string assetName) { + string typeSearchString = assetName; + string[] guids = AssetDatabase.FindAssets(typeSearchString); + if (guids.Length > 0) { + return AssetDatabase.GUIDToAssetPath(guids[0]); + } + return null; + } + + public Material BlendModeMaterialMultiply { + get { + if (blendModeMaterialMultiply == null) { + string path = FindPathOfAsset(DEFAULT_BLEND_MODE_MULTIPLY_MATERIAL); + blendModeMaterialMultiply = AssetDatabase.LoadAssetAtPath(path); + } + return blendModeMaterialMultiply; + } + } + public Material BlendModeMaterialScreen { + get { + if (blendModeMaterialScreen == null) { + string path = FindPathOfAsset(DEFAULT_BLEND_MODE_SCREEN_MATERIAL); + blendModeMaterialScreen = AssetDatabase.LoadAssetAtPath(path); + } + return blendModeMaterialScreen; + } + } + public Material BlendModeMaterialAdditive { + get { + if (blendModeMaterialAdditive == null) { + string path = FindPathOfAsset(DEFAULT_BLEND_MODE_ADDITIVE_MATERIAL); + blendModeMaterialAdditive = AssetDatabase.LoadAssetAtPath(path); + } + return blendModeMaterialAdditive; + } + } + internal const bool DEFAULT_ATLASTXT_WARNING = true; public bool atlasTxtImportWarning = DEFAULT_ATLASTXT_WARNING; @@ -180,6 +239,10 @@ namespace Spine.Unity.Editor { textureSettingsRef.stringValue = AssetDatabase.GUIDToAssetPath(pmaTextureSettingsReferenceGUIDS[0]); } } + + EditorGUILayout.PropertyField(settings.FindProperty("blendModeMaterialMultiply"), new GUIContent("Multiply Material", "Multiply blend mode Material template.")); + EditorGUILayout.PropertyField(settings.FindProperty("blendModeMaterialScreen"), new GUIContent("Screen Material", "Screen blend mode Material template.")); + EditorGUILayout.PropertyField(settings.FindProperty("blendModeMaterialAdditive"), new GUIContent("Additive Material", "Additive blend mode Material template.")); } EditorGUILayout.Space(); diff --git a/spine-unity/Assets/Spine/Runtime/spine-unity/Asset Types/BlendModeMaterials.cs b/spine-unity/Assets/Spine/Runtime/spine-unity/Asset Types/BlendModeMaterials.cs new file mode 100644 index 000000000..4ddcf1b5c --- /dev/null +++ b/spine-unity/Assets/Spine/Runtime/spine-unity/Asset Types/BlendModeMaterials.cs @@ -0,0 +1,144 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated January 1, 2020. Replaces all prior versions. + * + * Copyright (c) 2013-2020, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +using System; +using System.Collections.Generic; +using System.IO; +using UnityEngine; +using Spine; + +namespace Spine.Unity { + [System.Serializable] + public class BlendModeMaterials { + + [System.Serializable] + public class ReplacementMaterial { + public string pageName; + public Material material; + } + + [SerializeField, HideInInspector] protected bool requiresBlendModeMaterials = false; + public bool applyAdditiveMaterial = false; + + public List additiveMaterials = new List(); + public List multiplyMaterials = new List(); + public List screenMaterials = new List(); + + public bool RequiresBlendModeMaterials { get { return requiresBlendModeMaterials; } set { requiresBlendModeMaterials = value; } } + + #if UNITY_EDITOR + public void TransferSettingsFrom (BlendModeMaterialsAsset modifierAsset) { + applyAdditiveMaterial = modifierAsset.applyAdditiveMaterial; + } + + public bool UpdateBlendmodeMaterialsRequiredState (SkeletonData skeletonData) { + requiresBlendModeMaterials = false; + + if (skeletonData == null) throw new ArgumentNullException("skeletonData"); + + var skinEntries = new List(); + var slotsItems = skeletonData.Slots.Items; + for (int slotIndex = 0, slotCount = skeletonData.Slots.Count; slotIndex < slotCount; slotIndex++) { + var slot = slotsItems[slotIndex]; + if (slot.blendMode == BlendMode.Normal) continue; + if (!applyAdditiveMaterial && slot.blendMode == BlendMode.Additive) continue; + + skinEntries.Clear(); + foreach (var skin in skeletonData.Skins) + skin.GetAttachments(slotIndex, skinEntries); + + foreach (var entry in skinEntries) { + if (entry.Attachment is IHasRendererObject) { + requiresBlendModeMaterials = true; + return true; + } + } + } + return false; + } + #endif + public void ApplyMaterials (SkeletonData skeletonData) { + if (skeletonData == null) throw new ArgumentNullException("skeletonData"); + if (!requiresBlendModeMaterials) + return; + + var skinEntries = new List(); + var slotsItems = skeletonData.Slots.Items; + for (int slotIndex = 0, slotCount = skeletonData.Slots.Count; slotIndex < slotCount; slotIndex++) { + var slot = slotsItems[slotIndex]; + if (slot.blendMode == BlendMode.Normal) continue; + if (!applyAdditiveMaterial && slot.blendMode == BlendMode.Additive) continue; + + List replacementMaterials = null; + switch (slot.blendMode) { + case BlendMode.Multiply: + replacementMaterials = multiplyMaterials; + break; + case BlendMode.Screen: + replacementMaterials = screenMaterials; + break; + case BlendMode.Additive: + replacementMaterials = additiveMaterials; + break; + } + if (replacementMaterials == null) + continue; + + skinEntries.Clear(); + foreach (var skin in skeletonData.Skins) + skin.GetAttachments(slotIndex, skinEntries); + + foreach (var entry in skinEntries) { + var renderableAttachment = entry.Attachment as IHasRendererObject; + if (renderableAttachment != null) { + renderableAttachment.RendererObject = CloneAtlasRegionWithMaterial( + (AtlasRegion)renderableAttachment.RendererObject, replacementMaterials); + } + } + } + } + + protected AtlasRegion CloneAtlasRegionWithMaterial (AtlasRegion originalRegion, List replacementMaterials) { + var newRegion = originalRegion.Clone(); + Material material = null; + foreach (var replacement in replacementMaterials) { + if (replacement.pageName == originalRegion.page.name) { + material = replacement.material; + break; + } + } + + AtlasPage originalPage = originalRegion.page; + var newPage = originalPage.Clone(); + newPage.rendererObject = material; + newRegion.page = newPage; + return newRegion; + } + } +} diff --git a/spine-unity/Assets/Spine/Runtime/spine-unity/Asset Types/BlendModeMaterials.cs.meta b/spine-unity/Assets/Spine/Runtime/spine-unity/Asset Types/BlendModeMaterials.cs.meta new file mode 100644 index 000000000..8c1222e4a --- /dev/null +++ b/spine-unity/Assets/Spine/Runtime/spine-unity/Asset Types/BlendModeMaterials.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: d03ca55657e89b949a4c07bc9207beac +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/spine-unity/Assets/Spine/Runtime/spine-unity/Asset Types/SkeletonDataAsset.cs b/spine-unity/Assets/Spine/Runtime/spine-unity/Asset Types/SkeletonDataAsset.cs index f642b10a6..769cc319f 100644 --- a/spine-unity/Assets/Spine/Runtime/spine-unity/Asset Types/SkeletonDataAsset.cs +++ b/spine-unity/Assets/Spine/Runtime/spine-unity/Asset Types/SkeletonDataAsset.cs @@ -49,6 +49,9 @@ namespace Spine.Unity { #endif public TextAsset skeletonJSON; + public bool isUpgradingBlendModeMaterials = false; + public BlendModeMaterials blendModeMaterials = new BlendModeMaterials(); + [Tooltip("Use SkeletonDataModifierAssets to apply changes to the SkeletonData after being loaded, such as apply blend mode Materials to Attachments under slots with special blend modes.")] public List skeletonDataModifiers = new List(); @@ -187,10 +190,14 @@ namespace Spine.Unity { return null; if (skeletonDataModifiers != null) { - foreach (var m in skeletonDataModifiers) { - if (m != null) m.Apply(loadedSkeletonData); + foreach (var modifier in skeletonDataModifiers) { + if (modifier != null && !(isUpgradingBlendModeMaterials && modifier is BlendModeMaterialsAsset)) { + modifier.Apply(loadedSkeletonData); + } } } + if (!isUpgradingBlendModeMaterials) + blendModeMaterials.ApplyMaterials(loadedSkeletonData); this.InitializeWithData(loadedSkeletonData);