rcopat

Hi all,

I've created something similar to what is seen here: http://pt.esotericsoftware.com/spine-skins-view#Skin-list

I am pinning two skins in the skin view and it works perfectly for images and animations. One of my skins is the Character and the other is an attached weapon.

However, i'm unable to reproduce the same thing in Unity runtime. I've tried using the code below in a way to have the same results as I have in the Spine editor.
skeletonAnimation.skeleton.SetSkin("Char/1");
skeletonAnimation.skeleton.SetSkin("Swords/sword");
skeletonAnimation.skeleton.SetSlotsToSetupPose();
skeletonAnimation.AnimationState.Apply(skeletonAnimation.Skeleton);
Any ideas on how to replicate the same behaviour seen in the Skin Views (by pinning skins) on Unity runtime?

Regards,

Rafael
rcopat
  • Postovi: 3

Misaki

Hi, Rafael

To combine multiple skins at runtime, you have to create a new skin and add the skins that you want to combine, then set the new skin using setSkin.
See this blog post for more information:
Blog: Spine 3.8 released: Improved skin API

You can examine the example scenes Spine Examples/Other Examples/Mix and Match and Spine Examples/Other Examples/Mix and Match Equip and the used MixAndMatch.cs example script for further insights.

I hope this will help.
Avatar
Misaki

Misaki
  • Postovi: 768

rcopat

Misaki, thank you very much! I've tried what you sent me, and it worked perfectly. Create a new skin and adding skins, than setting to what I want.

Thank you once again!
rcopat
  • Postovi: 3

Misaki

I'm glad to hear that, thank you for getting back to us! :D
Avatar
Misaki

Misaki
  • Postovi: 768

Harald

@rcopat Please especially have a look at the Spine Examples/Other Examples/Mix and Match Skins example, which uses the new and improved 3.8 Skin API. The scripts used therein show the spine-unity equivalent of the Improved Skin API page's code. You can also have a look at the spine-unity documentation page section Combining Skins which shows additional helpful information.

The example scenes Mix and Match and Mix and Match Equip show a slightly different approach which was necessary before Spine 3.8, which we would not recommend as the primary solution.
Avatar
Harald

Harri
  • Postovi: 4101

rcopat

@Harald

The code below is what you've mentioned in your last post? I'll follow it. Posting here for future fast ref.
/******************************************************************************
* 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 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.
var 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 () {
var skeleton = skeletonAnimation.Skeleton;
var 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) {
var skeleton = skeletonAnimation.Skeleton;
var 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 () {
var skeleton = skeletonAnimation.Skeleton;
var resultCombinedSkin = new Skin("character-combined");

resultCombinedSkin.AddSkin(characterSkin);
AddEquipmentSkinsTo(resultCombinedSkin);

skeleton.SetSkin(resultCombinedSkin);
skeleton.SetSlotsToSetupPose();
}
}
}
rcopat
  • Postovi: 3

Harald

Yes, that's exactly what I meant. Thanks for posting the code, that's a good idea in case others search the forum and end up here.
Avatar
Harald

Harri
  • Postovi: 4101


Natrag na Runtimes