mirror of
https://github.com/EsotericSoftware/spine-runtimes.git
synced 2025-12-21 01:36:02 +08:00
[ts] Added dress-up example
Shows how to render skins to thumbnails which can then be used in an HTML UI.
This commit is contained in:
parent
68413182fb
commit
ad41761293
@ -27,6 +27,7 @@
|
||||
<li><a href="/spine-webgl/example">Example</a></li>
|
||||
<li><a href="/spine-webgl/example/barebones.html">Barebones</a></li>
|
||||
<li><a href="/spine-webgl/example/mix-and-match.html">Mix & match</a></li>
|
||||
<li><a href="/spine-webgl/example/dress-up.html">Dress-up</a></li>
|
||||
<li><a href="/spine-webgl/demos/additiveblending.html">Additive blending</a></li>
|
||||
<li><a href="/spine-webgl/demos/clipping.html">Clipping</a></li>
|
||||
<li><a href="/spine-webgl/demos/hoverboard.html">Hoverboard</a></li>
|
||||
|
||||
@ -11,8 +11,10 @@
|
||||
<canvas id="canvas" style="position: absolute; width: 100%; height: 100%;"></canvas>
|
||||
<script>
|
||||
class App {
|
||||
skeleton;
|
||||
animationState;
|
||||
constructor() {
|
||||
this.skeleton = null;
|
||||
this.animationState = null;
|
||||
}
|
||||
|
||||
loadAssets(canvas) {
|
||||
// Load the skeleton file.
|
||||
|
||||
239
spine-ts/spine-webgl/example/dress-up.html
Normal file
239
spine-ts/spine-webgl/example/dress-up.html
Normal file
@ -0,0 +1,239 @@
|
||||
<html>
|
||||
<script src="../dist/iife/spine-webgl.js"></script>
|
||||
<style>
|
||||
html,
|
||||
body {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
#container {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
height: 100vh;
|
||||
}
|
||||
|
||||
#canvas {
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
#skins {
|
||||
flex-shrink: 0;
|
||||
overflow: scroll;
|
||||
}
|
||||
|
||||
#skins>img {
|
||||
display: block;
|
||||
}
|
||||
</style>
|
||||
|
||||
<body>
|
||||
<div id="container">
|
||||
<div id="skins"></div>
|
||||
<canvas id="canvas"></canvas>
|
||||
</div>
|
||||
<script>
|
||||
// Define the class running in the Spine canvas
|
||||
class App {
|
||||
|
||||
constructor() {
|
||||
this.canvas = null;
|
||||
this.atlas = null;
|
||||
this.skeletonData = null;
|
||||
this.skeleton = null;
|
||||
this.state = null;
|
||||
this.selectedSkins = [];
|
||||
this.skinThumbnails = {};
|
||||
}
|
||||
|
||||
loadAssets(canvas) {
|
||||
canvas.assetManager.AnimationState
|
||||
canvas.assetManager.loadTextureAtlas("mix-and-match-pma.atlas");
|
||||
canvas.assetManager.loadBinary("mix-and-match-pro.skel");
|
||||
}
|
||||
|
||||
initialize(canvas) {
|
||||
this.canvas = canvas;
|
||||
let assetManager = canvas.assetManager;
|
||||
|
||||
// Create the atlas
|
||||
this.atlas = canvas.assetManager.require("mix-and-match-pma.atlas");
|
||||
let atlasLoader = new spine.AtlasAttachmentLoader(this.atlas);
|
||||
|
||||
// Create the skeleton
|
||||
let skeletonBinary = new spine.SkeletonBinary(atlasLoader);
|
||||
this.skeletonData = skeletonBinary.readSkeletonData(assetManager.require("mix-and-match-pro.skel"));
|
||||
this.skeleton = new spine.Skeleton(this.skeletonData);
|
||||
|
||||
// Create the animation state
|
||||
let stateData = new spine.AnimationStateData(this.skeletonData);
|
||||
this.state = new spine.AnimationState(stateData);
|
||||
this.state.setAnimation(0, "dance", true);
|
||||
|
||||
// Create the user interface to selecting skins
|
||||
this.createUI(canvas);
|
||||
|
||||
// Create a default skin.
|
||||
this.addSkin("skin-base");
|
||||
this.addSkin("nose/short");
|
||||
this.addSkin("eyelids/girly");
|
||||
this.addSkin("eyes/violet");
|
||||
this.addSkin("hair/brown");
|
||||
this.addSkin("clothes/hoodie-orange");
|
||||
this.addSkin("legs/pants-jeans");
|
||||
this.addSkin("accessories/bag");
|
||||
this.addSkin("accessories/hat-red-yellow");
|
||||
}
|
||||
|
||||
addSkin(skinName) {
|
||||
if (this.selectedSkins.indexOf(skinName) != -1) return;
|
||||
this.selectedSkins.push(skinName);
|
||||
let thumbnail = this.skinThumbnails[skinName];
|
||||
thumbnail.isSet = true;
|
||||
thumbnail.style.filter = "none";
|
||||
this.updateSkin();
|
||||
}
|
||||
|
||||
removeSkin(skinName) {
|
||||
let index = this.selectedSkins.indexOf(skinName);
|
||||
if (index == -1) return;
|
||||
this.selectedSkins.splice(index, 1);
|
||||
let thumbnail = this.skinThumbnails[skinName];
|
||||
thumbnail.isSet = false;
|
||||
thumbnail.style.filter = "grayscale(1)";
|
||||
this.updateSkin();
|
||||
}
|
||||
|
||||
updateSkin() {
|
||||
// Create a new skin from all the selected skins.
|
||||
let newSkin = new spine.Skin("custom-skin");
|
||||
for (var skinName of this.selectedSkins) {
|
||||
newSkin.addSkin(this.skeletonData.findSkin(skinName));
|
||||
}
|
||||
this.skeleton.setSkin(newSkin);
|
||||
this.skeleton.setToSetupPose();
|
||||
|
||||
// Center and zoom the camera
|
||||
let offset = new spine.Vector2(), size = new spine.Vector2();
|
||||
this.skeleton.getBounds(offset, size);
|
||||
let camera = this.canvas.renderer.camera;
|
||||
camera.position.x = offset.x + size.x / 2;
|
||||
camera.position.y = offset.y + size.y / 2;
|
||||
camera.zoom = size.x > size.y ? size.x / this.canvas.htmlCanvas.width * 1.1 : size.y / this.canvas.htmlCanvas.height * 1.1;
|
||||
camera.update();
|
||||
}
|
||||
|
||||
createUI(canvas) {
|
||||
const THUMBNAIL_SIZE = 300;
|
||||
|
||||
// We'll reuse the webgl context used to render the skeleton for
|
||||
// thumbnail generation. We temporarily resize it to 300x300 pixels
|
||||
// Note: we passed `preserveDrawingBuffer: true` to the SpineCanvas
|
||||
// constructor. Without it, we could not fetch the pixel data from
|
||||
// the canvas after rendering.
|
||||
let oldWidth = canvas.htmlCanvas.width;
|
||||
let oldHeight = canvas.htmlCanvas.height;
|
||||
canvas.htmlCanvas.width = canvas.htmlCanvas.height = THUMBNAIL_SIZE;
|
||||
canvas.gl.viewport(0, 0, THUMBNAIL_SIZE, THUMBNAIL_SIZE);
|
||||
|
||||
// For each skin, generate at thumbnail as follows
|
||||
// 1. Set it on the skeleton
|
||||
// 2. Determine its bounds
|
||||
// 3. Center and scale it to the offscreen canvas and render it
|
||||
// 4. Fetch the rendered image from the canvas and store it.
|
||||
let images = [];
|
||||
for (var skin of this.skeletonData.skins) {
|
||||
// Skip the empty default skin
|
||||
if (skin.name === "default") continue;
|
||||
|
||||
// Set the skin, then update the skeleton
|
||||
// to the setup pose and calculate the world transforms
|
||||
this.skeleton.setSkin(skin);
|
||||
this.skeleton.setToSetupPose();
|
||||
this.skeleton.updateWorldTransform();
|
||||
|
||||
// Calculate the bounding box enclosing the skeleton.
|
||||
let offset = new spine.Vector2(), size = new spine.Vector2();
|
||||
this.skeleton.getBounds(offset, size);
|
||||
|
||||
// Position the renderer camera on the center of the bounds, and
|
||||
// set the zoom so the full skin is visible within the 300x300
|
||||
// rendering area. We leave 10% of empty space around a skin in the
|
||||
// thumbnail, hence the multiplication of 1.1 for the zoom factor.
|
||||
canvas.renderer.camera.position.x = offset.x + size.x / 2;
|
||||
canvas.renderer.camera.position.y = offset.y + size.y / 2;
|
||||
canvas.renderer.camera.zoom = size.x > size.y ? size.x / THUMBNAIL_SIZE * 1.1 : size.y / THUMBNAIL_SIZE * 1.1;
|
||||
canvas.renderer.camera.setViewport(THUMBNAIL_SIZE, THUMBNAIL_SIZE);
|
||||
canvas.renderer.camera.update();
|
||||
|
||||
// Clear the canvas and render the skeleton
|
||||
canvas.clear(0.5, 0.5, 0.5, 1);
|
||||
canvas.renderer.begin();
|
||||
canvas.renderer.drawSkeleton(this.skeleton, true);
|
||||
canvas.renderer.end();
|
||||
|
||||
// Get the image data and convert it to an img element
|
||||
let image = new Image();
|
||||
image.src = canvas.htmlCanvas.toDataURL();
|
||||
image.skinName = skin.name;
|
||||
image.isSet = false;
|
||||
image.style.filter = "grayscale(1)";
|
||||
|
||||
// Set up a click listener that will add/remove the skin
|
||||
image.onclick = () => {
|
||||
if (image.isSet) this.removeSkin(image.skinName);
|
||||
else this.addSkin(image.skinName);
|
||||
}
|
||||
|
||||
// Store the thumbail image in the list of all skin
|
||||
// thumbnails.
|
||||
images.push(image);
|
||||
this.skinThumbnails[image.skinName] = image;
|
||||
}
|
||||
|
||||
// Sort the list of skin thumbnails by name, so items
|
||||
// from the same folder end up next to each other.
|
||||
images.sort((a, b) => {
|
||||
return a.skinName > b.skinName ? 1 : -1;
|
||||
});
|
||||
|
||||
// Add the thumbnails to the skins <div>
|
||||
let skinsDiv = document.getElementById("skins");
|
||||
for (var thumbnail of images) {
|
||||
skinsDiv.appendChild(thumbnail);
|
||||
}
|
||||
|
||||
// Restore the canvas size and camera of the renderer
|
||||
canvas.htmlCanvas.width = oldWidth;
|
||||
canvas.htmlCanvas.height = oldHeight;
|
||||
canvas.renderer.resize(spine.ResizeMode.Expand);
|
||||
canvas.renderer.camera.position.x = 0;
|
||||
canvas.renderer.camera.position.y = 0;
|
||||
canvas.renderer.camera.zoom = 1;
|
||||
}
|
||||
|
||||
update(canvas, delta) {
|
||||
this.state.update(delta);
|
||||
this.state.apply(this.skeleton);
|
||||
this.skeleton.updateWorldTransform();
|
||||
}
|
||||
|
||||
render(canvas) {
|
||||
let renderer = canvas.renderer;
|
||||
renderer.resize(spine.ResizeMode.Expand);
|
||||
canvas.clear(0.2, 0.2, 0.2, 1);
|
||||
renderer.begin();
|
||||
renderer.drawSkeleton(this.skeleton, true);
|
||||
renderer.end();
|
||||
}
|
||||
}
|
||||
|
||||
// Create the Spine canvas which runs the app
|
||||
new spine.SpineCanvas(document.getElementById("canvas"), {
|
||||
pathPrefix: "assets/",
|
||||
app: new App()
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
@ -52,7 +52,9 @@ export interface SpineCanvasConfig {
|
||||
/* The {@link SpineCanvasApp} to be run in the canvas. */
|
||||
app: SpineCanvasApp;
|
||||
/* The path prefix to be used by the {@link AssetManager}. */
|
||||
pathPrefix: string;
|
||||
pathPrefix?: string;
|
||||
/* The WebGL context configuration */
|
||||
webglConfig?: any;
|
||||
}
|
||||
|
||||
/** Manages the life-cycle and WebGL context of a {@link SpineCanvasApp}. The app loads
|
||||
@ -83,9 +85,10 @@ export class SpineCanvas {
|
||||
render: () => { },
|
||||
error: () => { },
|
||||
}
|
||||
if (config.webglConfig === undefined) config.webglConfig = { alpha: true };
|
||||
|
||||
this.htmlCanvas = canvas;
|
||||
this.context = new ManagedWebGLRenderingContext(canvas, { alpha: true });
|
||||
this.context = new ManagedWebGLRenderingContext(canvas, config.webglConfig);
|
||||
this.renderer = new SceneRenderer(canvas, this.context);
|
||||
this.gl = this.context.gl;
|
||||
this.assetManager = new AssetManager(this.context, config.pathPrefix);
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user