/****************************************************************************** * Spine Runtimes License Agreement * Last updated July 28, 2023. Replaces all prior versions. * * Copyright (c) 2013-2023, 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 Spine.Unity.AttachmentTools; using System.Collections.Generic; using UnityEngine; namespace Spine.Unity.Examples { public class MixAndMatchSkinsExample : MonoBehaviour { // character skins [SpineSkin] public string baseSkin = "skin-base"; [SpineSkin] public string eyelidsSkin = "eyelids/girly"; // here we use arrays of strings to be able to cycle between them easily. [SpineSkin] public string[] hairSkins = { "hair/brown", "hair/blue", "hair/pink", "hair/short-red", "hair/long-blue-with-scarf" }; public int activeHairIndex = 0; [SpineSkin] public string[] eyesSkins = { "eyes/violet", "eyes/green", "eyes/yellow" }; public int activeEyesIndex = 0; [SpineSkin] public string[] noseSkins = { "nose/short", "nose/long" }; public int activeNoseIndex = 0; // equipment skins public enum ItemType { Cloth, Pants, Bag, Hat } [SpineSkin] public string clothesSkin = "clothes/hoodie-orange"; [SpineSkin] public string pantsSkin = "legs/pants-jeans"; [SpineSkin] public string bagSkin = ""; [SpineSkin] public string hatSkin = "accessories/hat-red-yellow"; SkeletonAnimation skeletonAnimation; // This "naked body" skin will likely change only once upon character creation, // so we store this combined set of non-equipment Skins for later re-use. Skin characterSkin; // for repacking the skin to a new atlas texture public Material runtimeMaterial; public Texture2D runtimeAtlas; void Awake () { skeletonAnimation = this.GetComponent(); } void Start () { UpdateCharacterSkin(); UpdateCombinedSkin(); } public void NextHairSkin () { activeHairIndex = (activeHairIndex + 1) % hairSkins.Length; UpdateCharacterSkin(); UpdateCombinedSkin(); } public void PrevHairSkin () { activeHairIndex = (activeHairIndex + hairSkins.Length - 1) % hairSkins.Length; UpdateCharacterSkin(); UpdateCombinedSkin(); } public void NextEyesSkin () { activeEyesIndex = (activeEyesIndex + 1) % eyesSkins.Length; UpdateCharacterSkin(); UpdateCombinedSkin(); } public void PrevEyesSkin () { activeEyesIndex = (activeEyesIndex + eyesSkins.Length - 1) % eyesSkins.Length; UpdateCharacterSkin(); UpdateCombinedSkin(); } public void NextNoseSkin () { activeNoseIndex = (activeNoseIndex + 1) % noseSkins.Length; UpdateCharacterSkin(); UpdateCombinedSkin(); } public void PrevNoseSkin () { activeNoseIndex = (activeNoseIndex + noseSkins.Length - 1) % noseSkins.Length; UpdateCharacterSkin(); UpdateCombinedSkin(); } public void Equip (string itemSkin, ItemType itemType) { switch (itemType) { case ItemType.Cloth: clothesSkin = itemSkin; break; case ItemType.Pants: pantsSkin = itemSkin; break; case ItemType.Bag: bagSkin = itemSkin; break; case ItemType.Hat: hatSkin = itemSkin; break; default: break; } UpdateCombinedSkin(); } public void OptimizeSkin () { // Create a repacked skin. Skin previousSkin = skeletonAnimation.Skeleton.Skin; // Note: materials and textures returned by GetRepackedSkin() behave like 'new Texture2D()' and need to be destroyed if (runtimeMaterial) Destroy(runtimeMaterial); if (runtimeAtlas) Destroy(runtimeAtlas); Skin repackedSkin = previousSkin.GetRepackedSkin("Repacked skin", skeletonAnimation.SkeletonDataAsset.atlasAssets[0].PrimaryMaterial, out runtimeMaterial, out runtimeAtlas); previousSkin.Clear(); // Use the repacked skin. skeletonAnimation.Skeleton.Skin = repackedSkin; skeletonAnimation.Skeleton.SetSlotsToSetupPose(); skeletonAnimation.AnimationState.Apply(skeletonAnimation.Skeleton); // `GetRepackedSkin()` and each call to `GetRemappedClone()` with parameter `premultiplyAlpha` set to `true` // cache necessarily created Texture copies which can be cleared by calling AtlasUtilities.ClearCache(). // You can optionally clear the textures cache after multiple repack operations. // Just be aware that while this cleanup frees up memory, it is also a costly operation // and will likely cause a spike in the framerate. AtlasUtilities.ClearCache(); Resources.UnloadUnusedAssets(); } void UpdateCharacterSkin () { Skeleton skeleton = skeletonAnimation.Skeleton; SkeletonData skeletonData = skeleton.Data; characterSkin = new Skin("character-base"); // Note that the result Skin returned by calls to skeletonData.FindSkin() // could be cached once in Start() instead of searching for the same skin // every time. For demonstration purposes we keep it simple here. characterSkin.AddSkin(skeletonData.FindSkin(baseSkin)); characterSkin.AddSkin(skeletonData.FindSkin(noseSkins[activeNoseIndex])); characterSkin.AddSkin(skeletonData.FindSkin(eyelidsSkin)); characterSkin.AddSkin(skeletonData.FindSkin(eyesSkins[activeEyesIndex])); characterSkin.AddSkin(skeletonData.FindSkin(hairSkins[activeHairIndex])); } void AddEquipmentSkinsTo (Skin combinedSkin) { Skeleton skeleton = skeletonAnimation.Skeleton; SkeletonData skeletonData = skeleton.Data; combinedSkin.AddSkin(skeletonData.FindSkin(clothesSkin)); combinedSkin.AddSkin(skeletonData.FindSkin(pantsSkin)); if (!string.IsNullOrEmpty(bagSkin)) combinedSkin.AddSkin(skeletonData.FindSkin(bagSkin)); if (!string.IsNullOrEmpty(hatSkin)) combinedSkin.AddSkin(skeletonData.FindSkin(hatSkin)); } void UpdateCombinedSkin () { Skeleton skeleton = skeletonAnimation.Skeleton; Skin resultCombinedSkin = new Skin("character-combined"); resultCombinedSkin.AddSkin(characterSkin); AddEquipmentSkinsTo(resultCombinedSkin); skeleton.SetSkin(resultCombinedSkin); skeleton.SetSlotsToSetupPose(); } } }