196 lines
7.3 KiB
C#
196 lines
7.3 KiB
C#
|
/******************************************************************************
|
||
|
* 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<SkeletonAnimation>();
|
||
|
}
|
||
|
|
||
|
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();
|
||
|
}
|
||
|
}
|
||
|
}
|