[unity] Added DestroyGeneratedAssets method to RepackAttachmentsOutput struct for ease of use. Updated examples to use new idiom. See #1945.

This commit is contained in:
Harald Csaszar 2026-02-13 15:56:14 +01:00
parent 64b0031db0
commit f1b4d9b770
6 changed files with 120 additions and 47 deletions

View File

@ -370,7 +370,8 @@
- Spine UI Toolkit UPM package now supports PMA atlas textures. At the `SpineVisualElement` expand `Blend Mode Materials` and hit `Detect Materials` to automatically assign the proper PMA or straight alpha material at `Normal Material`. Unity minimum version increased to 6000.3 which added support for UI Toolkit materials.
- Spine UI Toolkit UPM package now supports all Spine blend modes via blend mode materials and multiple materials per skeleton. Enable `Multiple Materials` (enabled by default), expand `Blend Mode Materials` and hit `Detect Materials` to automatically assign the correct PMA or straight alpha blend mode materials.
- Every Spine URP shader now has an `Outline` option to switch to the respective Outline shader variant. Uses multi-pass support of newer URP versions. Requires spine-unity core package version 4.3.44 or newer due to required modifications in custom Sprite Shader GUI.
- Added new variants of `GetRepackedSkin` and `GetRepackedAttachments` supporting blend modes. These new variants take a packing configuration input struct `RepackAttachmentsSettings` which provides optional `additiveMaterialSource`, `multiplyMaterialSource` and `screenMaterialSource` properties, enabling blend mode repacking when any is non-null. Create your `RepackAttachmentsSettings` from default settings via `RepackAttachmentsSettings.Default` and then customize settings as needed.
- Added new variants of `GetRepackedSkin` and `GetRepackedAttachments` supporting blend modes. These new variants take a packing configuration input struct `RepackAttachmentsSettings` which provides optional `additiveMaterialSource`, `multiplyMaterialSource` and `screenMaterialSource` properties, enabling blend mode repacking when any is non-null. Create your `RepackAttachmentsSettings` from default settings via `RepackAttachmentsSettings.Default` and then customize settings as needed. Uses new `RepackAttachmentsOutput` struct providing `DestroyGeneratedAssets` to easily destroy any previously generated assets.
- Updated example scenes to demonstrate new `GetRepackedSkin` variant usage.
- **Deprecated**

View File

@ -31,12 +31,10 @@
#define CONFIGURABLE_ENTER_PLAY_MODE
#endif
using System;
using System.Collections.Generic;
using UnityEngine;
namespace Spine.Unity.AttachmentTools {
public static class AtlasUtilities {
@ -266,6 +264,27 @@ namespace Spine.Unity.AttachmentTools {
/// as main texture.
/// Materials and textures returned behave like <c>new Texture2D()</c> and need to be destroyed.</summary>
public Material outputScreenMaterial;
/// <summary>
/// Destroys any assigned previously generated assets. If you decide to store
/// <see cref="RepackAttachmentsOutput"/> in a MonoBehaviour for re-use, call this method before each
/// <see cref="GetRepackedSkin"/> or <see cref="GetRepackedAttachments"/> call, and also once in OnDestroy.
/// </summary>
public void DestroyGeneratedAssets () {
if (outputMaterial) { UnityEngine.Object.Destroy(outputMaterial); outputMaterial = null; }
if (outputTexture) { UnityEngine.Object.Destroy(outputTexture); outputTexture = null; }
if (additionalOutputTextures != null) {
for (int i = 0; i < additionalOutputTextures.Length; ++i) {
if (additionalOutputTextures[i]) {
UnityEngine.Object.Destroy(additionalOutputTextures[i]);
additionalOutputTextures[i] = null;
}
}
}
if (outputAdditiveMaterial) { UnityEngine.Object.Destroy(outputAdditiveMaterial); outputAdditiveMaterial = null; }
if (outputMultiplyMaterial) { UnityEngine.Object.Destroy(outputMultiplyMaterial); outputMultiplyMaterial = null; }
if (outputScreenMaterial) { UnityEngine.Object.Destroy(outputScreenMaterial); outputScreenMaterial = null; }
}
}
/// <summary>
@ -351,25 +370,63 @@ namespace Spine.Unity.AttachmentTools {
public bool[] additionalTextureIsLinear;
/// <summary>Default settings providing reasonable parameters, modify according to your needs.</summary>
public static RepackAttachmentsSettings Default = new RepackAttachmentsSettings {
materialPropertySource = null,
additiveMaterialSource = null,
multiplyMaterialSource = null,
screenMaterialSource = null,
public static RepackAttachmentsSettings Default = new RepackAttachmentsSettings(true);
newAssetName = "Repacked Attachments",
/// <summary>Hidden pseudo-default ctor, use <see cref="Default"/> instead.</summary>
private RepackAttachmentsSettings (bool _) {
newAssetName = DefaultTextureName;
maxAtlasSize = 1024,
padding = 2,
textureFormat = SpineTextureFormat,
mipmaps = UseMipMaps,
clearCache = false,
maxAtlasSize = 1024;
padding = 2;
textureFormat = SpineTextureFormat;
mipmaps = UseMipMaps;
clearCache = false;
useOriginalNonrenderables = true;
useOriginalNonrenderables = true,
additionalTexturePropertyIDsToCopy = null,
additionalTextureFormats = null,
additionalTextureIsLinear = null
};
shader = null;
materialPropertySource = null;
additiveMaterialSource = null;
multiplyMaterialSource = null;
screenMaterialSource = null;
additionalTexturePropertyIDsToCopy = null;
additionalTextureFormats = null;
additionalTextureIsLinear = null;
}
/// <summary>
/// Default settings providing reasonable parameters, with source materials assigned according to the
/// provided <paramref name="skeletonDataAsset"/>. Modify according to your needs.
/// </summary>
/// <param name="skeletonDataAsset">Reference <see cref="SkeletonDataAsset"/> used to provide source
/// materials for all blend modes.</param>
public RepackAttachmentsSettings (SkeletonDataAsset skeletonDataAsset)
: this(true) {
UseSourceMaterialsFrom(skeletonDataAsset);
}
/// <summary>
/// Assigns source materials from the provided <paramref name="skeletonDataAsset"/> for all blend modes
/// including normal blend mode.
/// </summary>
public void UseSourceMaterialsFrom (SkeletonDataAsset skeletonDataAsset) {
materialPropertySource = skeletonDataAsset.atlasAssets[0].PrimaryMaterial;
UseBlendModeMaterialsFrom(skeletonDataAsset);
}
/// <summary>
/// Assigns source materials from the provided <paramref name="skeletonDataAsset"/> for
/// additive, multiply and screen blend modes.
/// </summary>
public void UseBlendModeMaterialsFrom (SkeletonDataAsset skeletonDataAsset) {
BlendModeMaterials materials = skeletonDataAsset.blendModeMaterials;
if (materials.additiveMaterials.Count > 0)
additiveMaterialSource = materials.additiveMaterials[0].material;
if (materials.multiplyMaterials.Count > 0)
multiplyMaterialSource = materials.multiplyMaterials[0].material;
if (materials.screenMaterials.Count > 0)
screenMaterialSource = materials.screenMaterials[0].material;
}
}
private struct BlendModeAtlasPages {

View File

@ -43,8 +43,7 @@ namespace Spine.Unity.Examples {
Spine.Skin equipsSkin;
Spine.Skin collectedSkin;
public Material runtimeMaterial;
public Texture2D runtimeAtlas;
AtlasUtilities.RepackAttachmentsOutput repackingOutput;
void Start () {
equipsSkin = new Skin("Equips");
@ -58,6 +57,11 @@ namespace Spine.Unity.Examples {
RefreshSkeletonAttachments();
}
void OnDestroy () {
// Note: materials and textures returned by GetRepackedSkin() behave like 'new Texture2D()' and need to be destroyed
repackingOutput.DestroyGeneratedAssets();
}
public void Equip (int slotIndex, string attachmentName, Attachment attachment) {
equipsSkin.SetAttachment(slotIndex, attachmentName, attachment);
skeletonAnimation.Skeleton.SetSkin(equipsSkin);
@ -72,13 +76,13 @@ namespace Spine.Unity.Examples {
collectedSkin.AddSkin(equipsSkin);
// 2. Create a repacked 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 = collectedSkin.GetRepackedSkin("Repacked skin", skeletonAnimation.SkeletonDataAsset.atlasAssets[0].PrimaryMaterial,
out runtimeMaterial, out runtimeAtlas, maxAtlasSize: 1024, clearCache: false);
// Note: materials and textures returned by previous GetRepackedSkin() calls behave like 'new Texture2D()'
// and need to be destroyed.
repackingOutput.DestroyGeneratedAssets();
AtlasUtilities.RepackAttachmentsSettings settings = AtlasUtilities.RepackAttachmentsSettings.Default;
settings.UseSourceMaterialsFrom(skeletonAnimation.SkeletonDataAsset);
settings.maxAtlasSize = 1024;
Skin repackedSkin = collectedSkin.GetRepackedSkin("repacked skin", settings, ref repackingOutput);
collectedSkin.Clear();
// You can optionally clear the textures cache after each ore multiple repack operations are done.

View File

@ -65,9 +65,8 @@ namespace Spine.Unity.Examples {
Skin characterSkin;
// for repacking the skin to a new atlas texture
public Material runtimeMaterial;
public Texture2D runtimeAtlas;
AtlasUtilities.RepackAttachmentsOutput repackingOutput;
void Awake () {
skeletonAnimation = this.GetComponent<SkeletonAnimation>();
}
@ -77,6 +76,11 @@ namespace Spine.Unity.Examples {
UpdateCombinedSkin();
}
void OnDestroy () {
// Note: materials and textures returned by GetRepackedSkin() behave like 'new Texture2D()' and need to be destroyed
repackingOutput.DestroyGeneratedAssets();
}
public void NextHairSkin () {
activeHairIndex = (activeHairIndex + 1) % hairSkins.Length;
UpdateCharacterSkin();
@ -136,12 +140,14 @@ namespace Spine.Unity.Examples {
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);
// Note: materials and textures returned by previous GetRepackedSkin() calls behave like 'new Texture2D()'
// and need to be destroyed.
repackingOutput.DestroyGeneratedAssets();
AtlasUtilities.RepackAttachmentsSettings settings = AtlasUtilities.RepackAttachmentsSettings.Default;
settings.UseSourceMaterialsFrom(skeletonAnimation.SkeletonDataAsset);
settings.maxAtlasSize = 1024;
Skin repackedSkin = previousSkin.GetRepackedSkin("repacked skin", settings, ref repackingOutput);
previousSkin.Clear();
// Use the repacked skin.

View File

@ -55,9 +55,7 @@ namespace Spine.Unity.Examples {
public bool repack = true;
public BoundingBoxFollower bbFollower;
[Header("Do not assign")]
public Texture2D runtimeAtlas;
public Material runtimeMaterial;
AtlasUtilities.RepackAttachmentsOutput repackingOutput;
#endregion
Skin customSkin;
@ -75,6 +73,11 @@ namespace Spine.Unity.Examples {
Apply();
}
void OnDestroy () {
// Note: materials and textures returned by GetRepackedSkin() behave like 'new Texture2D()' and need to be destroyed
repackingOutput.DestroyGeneratedAssets();
}
void Apply () {
SkeletonAnimation skeletonAnimation = GetComponent<SkeletonAnimation>();
Skeleton skeleton = skeletonAnimation.Skeleton;
@ -124,12 +127,14 @@ namespace Spine.Unity.Examples {
repackedSkin.AddSkin(skeleton.Data.DefaultSkin); // Include the "default" skin. (everything outside of skin placeholders)
repackedSkin.AddSkin(customSkin); // Include your new custom 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);
repackedSkin = repackedSkin.GetRepackedSkin("repacked skin", sourceMaterial, out runtimeMaterial, out runtimeAtlas); // Pack all the items in the skin.
// Note: materials and textures returned by previous GetRepackedSkin() calls behave like 'new Texture2D()'
// and need to be destroyed.
repackingOutput.DestroyGeneratedAssets();
AtlasUtilities.RepackAttachmentsSettings settings = AtlasUtilities.RepackAttachmentsSettings.Default;
settings.UseSourceMaterialsFrom(skeletonAnimation.SkeletonDataAsset);
settings.maxAtlasSize = 1024;
repackedSkin = repackedSkin.GetRepackedSkin("repacked skin", settings, ref repackingOutput); // Pack all the items in the skin.
skeleton.SetSkin(repackedSkin); // Assign the repacked skin to your Skeleton.
if (bbFollower != null) bbFollower.Initialize(true);
} else {

View File

@ -2,7 +2,7 @@
"name": "com.esotericsoftware.spine.spine-unity",
"displayName": "spine-unity Runtime",
"description": "This plugin provides the spine-unity runtime core and examples. Spine Examples can be installed via the Samples tab.",
"version": "4.3.45",
"version": "4.3.46",
"unity": "2018.3",
"author": {
"name": "Esoteric Software",