edesmet

Hello,
we're trying to implement a skin system with texture loading on demand (using adressable textures later).

We have alredy read those both threads around that topic :

- http://fr.esotericsoftware.com/forum/Mix-and-match-outfits-Addressables-loading-issue-16427?p=71952&hilit=skin+texture+on+demand#p71952
- http://fr.esotericsoftware.com/forum/Memory-management-of-character-with-many-outfits-15867

we have created a script allowing to instantiate the information necessary to the runtime (SpineAtlasAsset, SkeletonDataAsset & SkeletonAnimation) with a temporary empty material during the creation of the SpineAtlasAsset. When we want to display a particular skin, we change the corresponding texture and we update the material.

There is still a problem: An error appears at runtime when calling the method SpineAtlasAsset.CreateRuntimeInstance(...) and more precisely when calling GetAtlas() then new Atlas(...); with the MaterialsTextureLoader.

At the beginning we don't have the textures of the skins yet. Our materials are initialized with a dummy texture of 2x2 pixels with the right page name. Otherwise the MaterialsTextureLoader throws some errors as the given material has a null texture or a wrong texture name.

Would it be possible to pass in argument a custom MaterialsTextureLoader to the GetAtlas method, in order to avoid these errors ? The creation of the dummy textures looks like a workaround : it would be nice to have materials without the final textures in advance, and without throwing errors at that time.

Here is the prototype script that allows us to do this for now:
public TextAsset skeletonJson;
public TextAsset atlasText;
public Texture2D[] textures;

public Material materialPropertySource;

SpineAtlasAsset runtimeAtlasAsset;
SkeletonDataAsset runtimeSkeletonDataAsset;
SkeletonAnimation runtimeSkeletonAnimation;

void CreateRuntimeAssetsAndGameObject () {
runtimeAtlasAsset = CreateRuntimeSpineAtlasAsset(atlasText, materialPropertySource, true);
runtimeSkeletonDataAsset = SkeletonDataAsset.CreateRuntimeInstance(skeletonJson, runtimeAtlasAsset, true);
}

IEnumerator Start () {
CreateRuntimeAssetsAndGameObject();

runtimeSkeletonDataAsset.GetSkeletonData(true); // preload.
yield return new WaitForSeconds(0.5f);

// Create SkeletonAnimation
runtimeSkeletonAnimation = SkeletonAnimation.NewSkeletonAnimationGameObject(runtimeSkeletonDataAsset);

// Extra Stuff
runtimeSkeletonAnimation.Skeleton.SetSkin("common");
runtimeSkeletonAnimation.AnimationState.SetAnimation(0, "idle", true);

yield return new WaitForSeconds(2.0f);
Debug.Log("load first texture on demand (skin common at index 0)");
runtimeAtlasAsset.materials[0].mainTexture = textures[0];
}

private static SpineAtlasAsset CreateRuntimeSpineAtlasAsset (TextAsset atlasText, Material materialPropertySource, bool initialize) {
// Get atlas page names
string atlasString = atlasText.text;
atlasString = atlasString.Replace("\r", "");
string[] atlasLines = atlasString.Split('\n');
var pages = new List<string>();
for (int i = 0; i < atlasLines.Length - 1; i++) {
string line = atlasLines[i].Trim();
if (line.EndsWith(".png"))
pages.Add(line.Replace(".png", ""));
}
Debug.Log($"Pages identified from atlas: {string.Join(", ", pages)}");

var materials = new Material[pages.Count];
for (int i = 0, n = pages.Count; i < n; i++) {
// Create dummy texture with right page name to avoid error with current MaterialsTextureLoader
Texture2D dummy = new Texture2D(2, 2);
dummy.name = pages[i];

Material mat = new Material(materialPropertySource);
mat.mainTexture = dummy;
materials[i] = mat;
}

return SpineAtlasAsset.CreateRuntimeInstance(atlasText, materials, initialize);
}
edesmet
  • Postovi: 11

Mario

I'm not a Unity wizard, but this makes sense to me. Harri is going to decide if and what the best approach is to integrate this with Spine Unity. He's currently on vacation and will reply next week.
Avatar
Mario

Mario
  • Postovi: 2968

Harald

Thanks for your input and sorry for the late reply. We will adjust the interface accordingly to allow for better delayed-loading in spine-unity on branch 4.1-beta soon. Unfortunately we can't adjust it on the 4.0 or 3.8 branches, since the clean solution would require to add additional parameters to methods such as AtlasAssetBase.GetAtlas() or TextureLoader.Load() which will then break code of potentially existing subclasses that override these same methods.

If you need to remain on version 3.8 or 4.0, the best way would be to adjust the code according to your needs, either commenting out obsolete error log messages or creating textures when the reference is set to null.
Avatar
Harald

Harri
  • Postovi: 3914

edesmet

Great, thanks for your answer.
It is not a problem for us to move to the next version.
Can we have an idea of how long it will take?
edesmet
  • Postovi: 11

Harald

It should not take too long, initial 4.1 porting should be complete soon, thereafter these changes will be integrated.

---

We are sorry for the late reply. A new commit has just been pushed to the 4.1-beta branch which extends the SpineAtlasAsset.CreateRuntimeInstance parameters accordingly to allow for a custom TextureLoader:
changelog.md je napisao/la:SpineAtlasAsset.CreateRuntimeInstance methods now provide an optional newCustomTextureLoader parameter (defaults to null) which can be set to e.g. (a) => new YourCustomTextureLoader(a) to use your own TextureLoader subclass instead of MaterialsTextureLoader.
So you should now be able to call it like this:
runtimeAtlasAsset = SpineAtlasAsset.CreateRuntimeInstance(atlasText, textures,
materialPropertySource, true, (a) => new YourCustomTextureLoader(a));
(This solution did not even require breaking the API signature of AtlasAssetBase.GetAtlas(), since it is implemented via an additional member variable in SpineAtlasAsset.)

A new 4.1-beta unitypackage is available for download.
Spine Unity Download
Please let us know if this resolves your problem, and thanks again for reporting!
Avatar
Harald

Harri
  • Postovi: 3914


Natrag na Unity