How did you try to configure spine-player? You want to hard code a viewport in this case, see:
http://en.esotericsoftware.com/spine-player#Viewports
How to make the size of Root as the size of the canvas
Mario Here is my config, I don't want to hard code the viewport option, I just want the bottom background image(child of the root) can resize to fill the entire canvas, and other nodes won't to exceed the size of the last bottom background image.
instance.current = new SpinePlayer(ref.current, {
jsonUrl: finalJsonUrl,
atlasUrl: finalAtlasUrl,
preserveDrawingBuffer: false,
showControls: false,
showLoading: false,
backgroundColor: '#00000000',
fullScreenBackgroundColor: '#00000000',
viewport: {
padLeft: '0%',
padRight: '0%',
padTop: '0%',
padBottom: '0%',
debugRender: true,
},
success(player) {
player.paused = false
const animation = player.skeleton?.data?.animations?.[0]?.name
console.log(player)
// if (animation) player.setAnimation(animation)
},
})
As Mario mentioned, you need to configure the viewport. See here:
https://esotericsoftware.com/spine-player#Viewports
Set the x
, y
, width
, and height
of your viewport to match your background (or whatever you like). Since it looks like you have the world origin in Spine set to the center of your background, you want to use something like:
x: -bgWidth / 2,
y: 0,
width: bgWidth,
height: bgHeight,
Replace bgWidth
and bgHeight
with numbers that are the width and height of your background.
Nate thanks for replying. I don't want to hard code the bg's width because the code will be used by many spine projects, So how can I get the bg's reference or width, What I can do is let my designer to make the bg as the first child of root in spine editor
You would need a way of knowing which attachment is that background. Then you can get the attachment and inspect it to determine its size and position.
You could use the first slot found on the root bone, but there may be multiple. The order of slots is not controlled on the bone, it is done in the draw order. I think it would be easier to use a name, like bg
. It should still be under the root bone though, so you don't need to compute the world position.
If you use a region attachment, it has x, y, width, and height properties. Note the X and Y are the center of the region attachment.
It looks like you are using a clipping attachment. If you name that bg
and put it in a bg
slot, then you can easily find it. Loop through the vertices and take the min and max values for X and Y out of all the vertices. minX
and minY
are your viewport X and Y. maxX - minX
and maxY - minY
are the viewport width and height.
Make sure the clipping attachment is not weighted to bones, otherwise the vertices are in a more complex format. They shouldn't be animating the clipping attachment vertices for the viewport anyway.
Nate Sounds complex for a newbie to Spine, It seems that I should do the calculation in the frame or update callback?
You need to set the viewport once in the beginning. Start by getting the example to run:
https://github.com/EsotericSoftware/spine-runtimes/tree/4.1/spine-ts/spine-player/example
Eg, install Python 3 if you don't have it, change to the spine-ts/spine-player
folder, then:
python -m http.server
Then open a browser to:
http://localhost:8000/example/example.html
Look at the callbacks available:
https://github.com/EsotericSoftware/spine-runtimes/blob/4.1/spine-ts/spine-player/src/Player.ts#L145-L162
Use the success
callback so your code runs after the skeleton is loaded. See docs here:
https://esotericsoftware.com/spine-player#Advanced-playback
Figure out how to get an attachment by name:
http://esotericsoftware.com/spine-api-reference#Skeleton-getAttachment2
The API is generic and applies to all languages. Check the spine-ts code to be sure of the method you want to call:
https://github.com/EsotericSoftware/spine-runtimes/blob/4.1/spine-ts/spine-core/src/Skeleton.ts#L515
Now you can modify the example:
new spine.SpinePlayer("container", {
skelUrl: "assets/spineboy-pro.skel",
atlasUrl: "assets/spineboy-pma.atlas",
premultipliedAlpha: true,
backgroundColor: "#cccccc",
viewport: {
debugRender: true,
},
showControls: false,
success: function (player) {
var vertices = player.skeleton.getAttachmentByName("head-bb", "head").vertices;
var minX = 99999, minY = 99999, maxX = -99999, maxY = -99999;
for (var i = 0, n = vertices.length; i < n; i += 2) {
var x = vertices[i], y = vertices[i + 1];
minX = Math.min(minX, x);
minY = Math.min(minY, y);
maxX = Math.max(maxX, x);
maxY = Math.max(maxY, y);
}
player.config.viewport.x = minX;
player.config.viewport.y = minY;
player.config.viewport.width = maxX - minX;
player.config.viewport.height = maxX - minX;
// Needed to show skeleton when no animation is specified (bug fixed in 4.2):
player.skeleton.updateWorldTransform();
}
});
For that to work you'll have to move the spineboy head-bb
slot to the root (here spineboy-pro.skel), then you get:
Nate I did find the Background Attachment, But I didn't find the vertices properties, I use code below
success(player) {
player.paused = false
const bg = player.skeleton?.getAttachmentByName('Background', 'Background')
player.config.viewport.x = bg.x
player.config.viewport.y = bg.y
player.config.viewport.width = bg.width
player.config.viewport.height = bg.height
player.skeleton.updateWorldTransform()
const animation = player.skeleton?.data?.animations?.[0]?.name
player.setAnimation(animation)
}
It turns out
You don't get a vertices array, because your attachment is a region, not a mesh. Set viewport.x
to bg.x - bg.width / 2
and viewport.y
to bg.y - bg.height / 2
.
Yep, or use the clipping attachment, not the background.
Nate designer did give me a clipping attachment
using player.skeleton?.getAttachmentByName('Background', 'Background') can get an Attachment, but getAttachmentByName('Background'2, 'Background') can not, it throw an error says: Can't find slot with name Background2
Likely your export doesn't match the project file. Also to get the clipping attachment, use it's name eg 'Background2', 'Background2'
.
Nate It' works well, but it still has some black area in the top and bottom, how can I cut them off
You'll need to adjust the size of your canvas to match the attachment width and height. Note that you'll need to set both canvas.width/.height
and canvas.style.width/height
.
Mario My canvas' size is equal to the screen of the user's phone. Is it possible to adjust the size of viewport to match the canvas's size
Your problem is that your background aspect ratio doesn't match your canvas size. There are only 3 solutions:
- You could stretch your Spine skeleton to fill the canvas.
- You could zoom in so the height fills the canvas, but that will cut off some of the Spine skeleton on the left and right edges.
- You could live with the "black bars".
I suggest option 2. To do it you need to calculate the viewport size and position based on 1) your background size, as we've already computed above, and 2) the aspect ratio of the user's screen. To do that you can look at the functions provided here:
https://github.com/libgdx/libgdx/blob/master/gdx/src/com/badlogic/gdx/utils/Scaling.java
Each function is a different way of fitting a source width/height (your background size) to a target width/height (the user's screen size). The result is the width height you want to use for your viewport, which you should center using this x, y, width, height:
-width / 2, height / 2 - bgHeight / 2, width, height
Which scaling function you choose from my link is up to you. To fill the screen, choose fill
.
I also suggest that you get rid of that clipping attachment. Clipping uses a lot of CPU, based on how many triangles you are clipping. See here:
http://esotericsoftware.com/spine-clipping#Performance
When you are clipping your ENTIRE skeleton, like you are doing, it can use a lot of CPU. That may be acceptable, especially if your app isn't doing much else. However, if it is not needed, then it is wasteful. With option 2, any content off screen can't be seen anyway, so clipping is not needed.