Davide
Sounds nice! But don't I need also the atlas too for a project to be able to generate the images inside spine?
Is there an example or a way on how to retrieve all the images currently displayed in the player, as well as the atlas?
Davide
Sounds nice! But don't I need also the atlas too for a project to be able to generate the images inside spine?
Is there an example or a way on how to retrieve all the images currently displayed in the player, as well as the atlas?
Davide
Oh I thought about that! But then, how do you manage to keep the newly-swapped images?
Thank you for your code and your help! I've managed to generate a ReactJS / Typescript functional variant of this - I'm adding the component's code for the project is too large to get uploaded.
By creating a new ReactJS project and adding this component - as well as the CSS for the Spine Viewer - the results can be checked.
// @ts-ignore
import React, {useEffect, useRef, useState} from 'react';
import {SpinePlayer, Skeleton, AssetManager, TextureAtlasRegion, TextureAtlasPage, TextureAtlas} from "@esotericsoftware/spine-player";
import './SpinePlayer.css'
function SpineViewer ({ jsonUrl, atlasUrl }) {
const playerRef = useRef(null);
const [imageAttachments, setImageAttachments] = useState([]);
const [player, setPlayer] = useState<SpinePlayer>(null);
const [skeleton, setSkeleton] = useState<Skeleton>(null);
useEffect(() => {
if(playerRef.current){
if(player !== null) {
player.dispose();
}
AssetManager.prototype.loadTextureAtlasForSingleImageUrl = function (imgUrl: string) {
const atlasPath = imgUrl;
if (this.assets[atlasPath]) return Promise.resolve(this.assets[atlasPath]);
this.start(atlasPath);
this.start(imgUrl);
let image = new Image();
image.crossOrigin = "anonymous";
image.src = imgUrl;
return new Promise((resolve) => {
image.onload = () => {
const region = new TextureAtlasRegion(new TextureAtlasPage(imgUrl), imgUrl);
region.u = region.v = 0;
region.u2 = region.v2 = 1;
region.page.width = region.width = region.originalWidth = image.width;
region.page.height = region.height = region.originalHeight = image.height;
const atlas = new TextureAtlas("");
atlas.pages.push(region.page);
atlas.regions.push(region);
// set the loaded texture into the page (and internally into all the regions)
region.page.setTexture(this.textureLoader(image));
this.success(() => {}, imgUrl, region.page.texture);
this.success(() => {}, atlasPath, atlas);
resolve(atlas);
};
image.onerror = () => this.error(() => {}, imgUrl, `Couldn't load image: ${imgUrl}`);
});
}
updateSpinePlayer(jsonUrl, atlasUrl, spinePlayerStatesSetup);
return() => {
player?.dispose();
}
}
}, []);
useEffect(() => {
if(skeleton === null) return;
populateAttachments();
}, [skeleton]);
function populateAttachments(){
const currentAttachments = [...imageAttachments];
skeleton.slots.forEach(slot => {
if(slot.attachment === null) return;
const attachment = skeleton.getAttachmentByName(slot.data.name, slot.attachment.name);
currentAttachments.push(attachment);
});
setImageAttachments(currentAttachments);
}
function updateSpinePlayer(skeleton: string, atlas: string, succesCallback?) {
//@ts-ignore
new SpinePlayer(playerRef.current, {
skeleton: skeleton,
atlas: atlas,
success: succesCallback
})
}
function spinePlayerStatesSetup(pl: SpinePlayer){
setPlayer(pl);
setSkeleton(pl.skeleton);
}
async function imageToAtlasRegion(attachment, imageURL) {
const atlas = await player.assetManager?.loadTextureAtlasForSingleImageUrl(imageURL);
updateAttachment(atlas.regions[0], attachment);
}
function updateAttachment(region, attachmentName) {
if(skeleton === null) return;
const slot = skeleton.slots.find(slot => slot.data.name == attachmentName);
const attachment = slot?.attachment;
if (attachment) {
attachment.region = region;
slot.attachment?.updateRegion();
}
else{
alert("NO ATTACHMENT FOUND!");
}
}
function generateInputFields(){
return(
<div style={{width: '100%'}}>
<h2>Attachments</h2>
<div style={{maxHeight: '50vh', overflow:'scroll'}}>
{
imageAttachments.map((item, index) => {
return(
<div>
<h3>{item.name}</h3>
<button id="attachmentInput" onClick={() => imageToAtlasRegion(item.name, '/logo192.png')}>Load Attachment</button>
</div>
);
})
}
</div>
</div>
);
}
return (
<div style={{padding: '3rem'}}>
<div>
<h1>Spine Slot Swap Example</h1>
</div>
<div style={{display: 'flex', flexDirection: 'row', justifyContent: 'center'}}>
<div style={{width: '100%'}}>
<div ref={playerRef} style={{ width: '800px', height: '600px' }}></div>
</div>
{generateInputFields()}
</div>
</div>
);
}
export default SpineViewer;
Now I've got one issue: would it be possible to generate a new spine project that includes the modified attachments? For download purposes! I couldn't find any references to this in the API </3
Davide Thank you so much for your help!! I'll give a try to this and I'll get back with my results! Thank you so much! <3
Davide
Thank you again for your response! I'm getting quite confused on how to work with it, for I'm getting difficulties understanding how Spine works on the back. I'll follow your suggestion I'll wait until the example is created.
Thank you so much for your help!
Davide
Thank you for your kind help!! Now I've finally managed to successfully retrieve the attachments!
Now I get to the complicated part of getting the images attached to those atttachments for them to be displayed in the screen, and change them by the ones the user provide as input. I'm trying to follow the Loading Skelleton Data guide, but I'm getting kind of lost when it comes to the Texture Loader.
Where are the images stored? - Atlas, I'm asuming - and how can I access them, retrieve them and modify them? Because once I have the new user input's images, I think I can add them by sending the new image through the setAttachment method as you suggested. - maybe by modifyng the page or region property of the current attachment? I'm still trying to figure that out, sorry for asking so many questions!
Omg that seems amazing! I'm currently using JetBrains' Webstorm and ReactJS, can I setup an environment using Typescript with these technologies?
For insight, now I'm trying a mix of what you said backed by the Generic Rendering, Changing Attachments Runtime Guides, as well as the Slot and Skin API references, but I'm getting null from it, even though I'm retrieving the slot and slot name correctly:
import React, {useEffect, useRef} from 'react';
import * as spine from '@esotericsoftware/spine-player';
import '../../styles/SpinePlayer.css'
function SpinePlayer ({ jsonUrl, atlasUrl }) {
const playerRef = useRef(null);
useEffect(() => {
if (playerRef.current) {
const player = new spine.SpinePlayer(playerRef.current, {
skeleton: jsonUrl,
atlas: atlasUrl,
success: runSpinePlayer
});
return () => {
player.dispose();
};
}
}, [jsonUrl, atlasUrl]);
function runSpinePlayer(player){
const skel = player.skeleton;
const slots = skel.slots;
slots.forEach(slot => {
if(slot.attachment === null) return;
const attachment = skel.getAttachment(slot, slot.attachment.name);
debugger;
console.log(attachment);
})
}
return (
<div ref={playerRef} style={{ width: '800px', height: '600px' }}></div>
);
}
export default SpinePlayer;
Davide
Thank you for the insight! Is there any documentation on how to set attachments, how to retrieve the information from the attachments and how the data is structured?
Hi Davide! Thank you for response! I'll try to describe what are my intentions with my code:
(context: I'm developing a ReactJS tool that allows an user to quickly swap a bone's slot attached image)
I'm trying to print all the slots that has an image and the image that is attached to the slot. For that, I follow this logic:
I'm not following this structure for anything in specific, it's just one of the multiple things I'm trying to do to achieve my goal. I don't really understand how any of the data that I'm working with works, and I'm finding myself kind of lost in the documentation, not really knowing where to look, so any help is kindly welcomed! <3
Do you have any particular idea on how to achieve the goal I'm seeking?
Mario Hi Mario, thank you for your quick response!
I've been going through the examples and I can't see how to setup the viewer! I'm trying to see the changes on runtime, so the viewer becomes important to me.
I've been trying to implement this JSON and Binary Data load from the Runtimes API, but can't get it to work! Here is the code I'm using:
import React, {useEffect, useRef} from 'react';
import * as spine from '@esotericsoftware/spine-player';
import '../../styles/SpinePlayer.css'
function SpinePlayer ({ jsonUrl, atlasUrl }) {
const playerRef = useRef(null);
let spinePlayer = useRef(null);
let skeleton = useRef();
let atlas = useRef();
useEffect(() => {
fetch('https://esotericsoftware.com/files/examples/4.2/spineboy/export/spineboy-pro.skel')
.then(response => response.blob())
.then(blob => {
atlas.current = blob;
});
fetch('https://esotericsoftware.com/files/examples/4.2/spineboy/export/spineboy-pma.atlas')
.then(response => response.blob())
.then(blob => {
skeleton.current = blob;
})
if (playerRef.current) {
const config = {
skeleton: jsonUrl,
atlas: atlasUrl,
}
spinePlayer.current = new spine.SpinePlayer(playerRef.current, config);
const attachmentLoader = new spine.AtlasAttachmentLoader(atlas.current);
const json = new spine.SkeletonBinary(attachmentLoader);
const data = json.readSkeletonData(skeleton.current); // THIS RETURNS ERROR WHEN READING PROPERTIES OF UNDEFINED
console.log(data);
return () => {
spinePlayer.current.dispose();
};
}
}, [jsonUrl, atlasUrl, spinePlayer]);
return (
<div ref={playerRef} style={{ width: '800px', height: '600px' }}></div>
);
}
export default SpinePlayer;
Is there any way to keep going my way or this could be completely functional using the pixi runtime?
Thanks!!
Hi guys! I'm new to this forum - hi y'all <3 - and I want to try and get some help on something I've been working on!
I'm trying to do a Spine web editor where an user can change the spine's project images to generate new assets from a templated one. I've successfully embedded the spine web player into my ReactJS app, and now I'm trying to get information on how to access the images inside the skeleton's slots, and how to save the end result in a new spine project or data structure! Nevertheless, I can't seem to find anything related to the topic.
Any idea on how I could advance through this?
Thank you so much!!