[spine-ts] loading png assets as needed for web
If you create your own loader, it really depends on your implementation.
Here are some possible ideas.
Probably, having a single .atlas is the easiest way. However, in that case, if you don't want to load the textures all together, you have to write your implementation of loadTextureAtlas
that does not load the textures and consequently does not set the textures for the regions.
This implies that you have to load the textures on demand before they are used.
To do that, you can use loadTexture
from the AssetManager
. Then call the setTexture
on the respective atlas page you just loaded. You can write a method that takes as input the atlas page for which you want to load the texture and does the two things mentioned above.
After that, you should be safe to use your animations/skins that use the atlas pages just loaded.
This is an example of what I'm saying.
Add a loadTextureAtlasButNoTextures
method to AssetManagerBase
(you can extend it):
loadTextureAtlasButNoTextures (path: string,
success: (path: string, atlas: TextureAtlas) => void = () => { },
error: (path: string, message: string) => void = () => { },
fileAlias?: { [keyword: string]: string }
) {
path = this.start(path);
this.downloader.downloadText(path, (atlasText: string): void => {
try {
this.success(success, path, new TextureAtlas(atlasText));
} catch (e) {
this.error(error, path, `Couldn't parse texture atlas ${path}: ${(e as any).message}`);
}
}, (status: number, responseText: string): void => {
this.error(error, path, `Couldn't load texture atlas ${path}: status ${status}, ${responseText}`);
});
}
This is the very same code of loadTextureAtlas
without the texture loading.
Then, you can use this for example in a spine-webgl
app.
async loadAssets(canvas) {
// Load the skeleton file.
canvas.assetManager.loadJson("assets/skeleton.json");
// Load the atlas and its pages.
// canvas.assetManager.loadTextureAtlas("assets/skeleton.atlas");
const atlas = await new Promise(resolve => {
canvas.assetManager.loadTextureAtlasButNoTextures("assets/skeleton.atlas", (_, atlas) => resolve(atlas));
});
// assuming we have two pages
const [page1, page2] = atlas.pages;
// load first page immeaditely
canvas.assetManager.loadTexture(`assets/${page1.name}`, (_, texture) => page1.setTexture(texture));
// load second page after two seconds
setTimeout(() => {
canvas.assetManager.loadTexture(`assets/${page2.name}`, (_, texture) => page2.setTexture(texture));
}, 2000);
}
I've used loadTexture
in the loadAssets
, but you can use it wherever you want.
If you want to use multiple .atlas files, the idea should be pretty similar. However, the AtlasAttachmentLoader
is capable of using a single TextureAtlas
. Consequently, you have to write your own.
Hi @Davide ,
Before digging into the custom loading, I want to make sure I'm extending the class right. Is this the right way to extend the AssetManager?
class CustomAssetManager extends spine.AssetManager {
loadTextureAtlasButNoTextures(path, success = () => {}, error = () => {}) {
...
}
}
class App {
constructor() {
this.canvas = null;
this.assetManager = new CustomAssetManager();
this.atlas = null;
this.skeletonData = null;
this.skeleton = null;
this.animationState = null;
this.lastBounds = {};
}
loadAssets(canvas) {
this.assetManager.loadBinary('spine-dynamic/animee.skel');
this.assetManager.loadTextureAtlas(
'spine-dynamic/chibi_04_flipbooktest.atlas',
);
initialize(canvas) {
this.canvas = canvas;
let atlas = this.assetManager.require(
'spine-dynamic/chibi_04_flipbooktest.atlas',
);
...
}
const appInstance = new App();
new spine.SpineCanvas(canvas, {
pathPrefix: '/',
app: appInstance,
});
I'm getting errors because in SpineCanvas's waitForAssets, it calls this.assetManager but that is not using my custom asset manager, so initialize() gets called before loadAssets() can finish. Am I not extending the asset manager properly?
Thanks!
got passed the issue by setting the custom asset manager to the spineCanvas instance, but let me know if there is a better practice!
const appInstance = new App();
const spineCanvas = new spine.SpineCanvas(canvas, {
pathPrefix: '/',
app: appInstance,
});
spineCanvas.assetManager = appInstance.assetManager;
there are some new errors now that I'm looking into now, so I'm not certain if this patch was correct
Oh, sorry, that was my fault. I could have been more precise.
The SpineCanvas
class is just a simple utility to quickly use spine-webgl
. It uses the default AssetManager
of spine-webgl
.
You can create your own custom SpineCanvas
by copying its code, or you can also extract the logic you need from it without creating a dedicated class.
got it working ️
joo Ah ok, no worries. Thanks for your patience and bearing with me while I'm still onboarding onto Spine and asking lots of questions
We're more than happy to assist riggers, animators and developers in using the editor and the runtimes! Feel free anytime to ask anything that is unclear to you
joo got it working ️
I'm glad to hear that!
You are right, the spine-pixi atlasLoader
loads all atlas pages transparently.
This implies you cannot use it to achieve your goal. However, as you can see from the code it just does what our spine-webgl loadTextureAtlas
does.
I didn't try it, but I guess that if you load the atlas as a txt file, instance the TextureAtlas with the atlas txt data, add the TextureAtlas to the asset cache, and eventually initialize your Spine game object, then you can defer the texture pages loading when you want.
Let me know if you need any help with the code
Hi Davide!
It has been a while now and I'm revisiting this again, where I only want to load the textures I need depending on what skins are set, and not the full list of textures.
I ended up using spine-player instead of spine-webgl because spine-player already has a lot of features I need that is built in. However, I'm not sure how to apply a custom asset manager and custom asset loader with spine-player without forking spine-player and adding my custom asset manager/loader. Would love some advice on how to move forward on this.
Thanks!
It's just that the player has a lot of features already implemented that I need, whereas with spine-webgl, I would need to implement a lot of it from scratch. That's why I was hoping I could just use the player and build on top of it. As for the widget, I'm waiting for it to be officially released before using it in production!
Thanks
We were busy with the pixi-v8 runtime release, but now that we've lanuched it, I'll resume on working on the widget.
Regarding the Player customization, if you don't want to fork it, you'll need to monkey patch/extend the AssetManager or the Player overriding the method that loads the texture.
I'll give it a try tomorrow morning and if it works, I'll share with you a piece of code that you can use as a base.