mirror of
https://github.com/EsotericSoftware/spine-runtimes.git
synced 2026-02-04 14:24:53 +08:00
[canvaskit] SkeletonDrawble, simplified examples.
This commit is contained in:
parent
40ce74ff2d
commit
f80ac86bbf
@ -3,7 +3,7 @@ import { fileURLToPath } from 'url';
|
||||
import path from 'path';
|
||||
import CanvasKitInit from "canvaskit-wasm/bin/canvaskit.js";
|
||||
import UPNG from "@pdf-lib/upng"
|
||||
import {loadTextureAtlas, SkeletonRenderer, Skeleton, SkeletonBinary, AnimationState, AnimationStateData, AtlasAttachmentLoader, Physics} from "../dist/index.js"
|
||||
import {loadTextureAtlas, SkeletonRenderer, Skeleton, SkeletonBinary, AnimationState, AnimationStateData, AtlasAttachmentLoader, Physics, loadSkeletonData, SkeletonDrawable} from "../dist/index.js"
|
||||
|
||||
// Get the current directory
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
@ -22,21 +22,21 @@ async function main() {
|
||||
// Load atlas
|
||||
const atlas = await loadTextureAtlas(ck, __dirname + "/assets/spineboy.atlas", async (path) => fs.readFileSync(path));
|
||||
|
||||
// Load skeleton data
|
||||
const binary = new SkeletonBinary(new AtlasAttachmentLoader(atlas));
|
||||
const skeletonData = binary.readSkeletonData(fs.readFileSync(__dirname + "/assets/spineboy-pro.skel"));
|
||||
// Load the skeleton data
|
||||
const skeletonData = await loadSkeletonData(__dirname + "/assets/spineboy-pro.skel", atlas, async (path) => fs.readFileSync(path));
|
||||
|
||||
// Create a skeleton and scale and position it.
|
||||
const skeleton = new Skeleton(skeletonData);
|
||||
skeleton.scaleX = skeleton.scaleY = 0.5;
|
||||
skeleton.x = 300;
|
||||
skeleton.y = 380;
|
||||
// Create a SkeletonDrawable
|
||||
const drawable = new SkeletonDrawable(skeletonData);
|
||||
|
||||
// Create an animation state to apply and mix one or more animations
|
||||
const animationState = new AnimationState(new AnimationStateData(skeletonData));
|
||||
animationState.setAnimation(0, "hoverboard", true);
|
||||
// Scale and position the skeleton
|
||||
drawable.skeleton.x = 300;
|
||||
drawable.skeleton.y = 380;
|
||||
drawable.skeleton.scaleX = drawable.skeleton.scaleY = 0.5;
|
||||
|
||||
// Create a skeleton renderer to render the skeleton with to the canvas
|
||||
// Set the "hoverboard" animation on track one
|
||||
drawable.animationState.setAnimation(0, "hoverboard", true);
|
||||
|
||||
// Create a skeleton renderer to render the skeleton to the canvas with
|
||||
const renderer = new SkeletonRenderer(ck);
|
||||
|
||||
// Render the full animation in 1/30 second steps (30fps) and save it to an APNG
|
||||
@ -50,16 +50,12 @@ async function main() {
|
||||
// Clear the canvas
|
||||
canvas.clear(ck.WHITE);
|
||||
|
||||
// Update and apply the animations to the skeleton
|
||||
animationState.update(deltaTime);
|
||||
animationState.apply(skeleton);
|
||||
|
||||
// Update the skeleton time for physics, and its world transforms
|
||||
skeleton.update(deltaTime);
|
||||
skeleton.updateWorldTransform(Physics.update);
|
||||
// Update the drawable, which will advance the animation(s)
|
||||
// apply them to the skeleton, and update the skeleton's pose.
|
||||
drawable.update(deltaTime);
|
||||
|
||||
// Render the skeleton to the canvas
|
||||
renderer.render(canvas, skeleton)
|
||||
renderer.render(canvas, drawable)
|
||||
|
||||
// Read the pixels of the current frame and store it.
|
||||
canvas.readPixels(0, 0, imageInfo, pixelArray);
|
||||
|
||||
@ -37,19 +37,16 @@
|
||||
const atlas = await spine.loadTextureAtlas(ck, "assets/spineboy.atlas", readFile);
|
||||
|
||||
// Load skeleton data
|
||||
const binary = new spine.SkeletonBinary(new spine.AtlasAttachmentLoader(atlas));
|
||||
const skeletonData = binary.readSkeletonData(await readFile("assets/spineboy-pro.skel"));
|
||||
const skeletonData = await spine.loadSkeletonData("assets/spineboy-pro.skel", atlas, readFile);
|
||||
|
||||
// Create a skeleton and scale and position it.
|
||||
const skeleton = new spine.Skeleton(skeletonData);
|
||||
skeleton.scaleX = skeleton.scaleY = 0.4;
|
||||
skeleton.x = 300;
|
||||
skeleton.y = 380;
|
||||
skeleton.setToSetupPose();
|
||||
// Create a drawable and scale and position the skeleton
|
||||
const drawable = new spine.SkeletonDrawable(skeletonData);
|
||||
drawable.skeleton.scaleX = drawable.skeleton.scaleY = 0.4;
|
||||
drawable.skeleton.x = 300;
|
||||
drawable.skeleton.y = 380;
|
||||
|
||||
// Create an animation state to apply and mix one or more animations
|
||||
const animationState = new spine.AnimationState(new spine.AnimationStateData(skeletonData));
|
||||
animationState.setAnimation(0, "hoverboard", true);
|
||||
// Set the "hoverboard" animation on the first track of the animation state.
|
||||
drawable.animationState.setAnimation(0, "hoverboard", true);
|
||||
|
||||
// Create a skeleton renderer to render the skeleton with to the canvas
|
||||
const renderer = new spine.SkeletonRenderer(ck);
|
||||
@ -64,14 +61,14 @@
|
||||
const deltaTime = (now - lastTime) / 1000;
|
||||
lastTime = now;
|
||||
|
||||
// Update and apply the animations to the skeleton
|
||||
animationState.update(deltaTime);
|
||||
animationState.apply(skeleton);
|
||||
// Update the drawable, which will advance the animation(s)
|
||||
// apply them to the skeleton, and update the skeleton's pose.
|
||||
drawable.update(deltaTime);
|
||||
|
||||
// Update the skeleton time for physics, and its world transforms
|
||||
skeleton.update(deltaTime);
|
||||
skeleton.updateWorldTransform(spine.Physics.update);
|
||||
renderer.render(canvas, skeleton);
|
||||
// Render the skeleton to the canvas
|
||||
renderer.render(canvas, drawable);
|
||||
|
||||
// Request the next frame
|
||||
surface.requestAnimationFrame(drawFrame);
|
||||
}
|
||||
surface.requestAnimationFrame(drawFrame);
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
export * from "@esotericsoftware/spine-core";
|
||||
|
||||
import { BlendMode, ClippingAttachment, Color, MeshAttachment, NumberArrayLike, RegionAttachment, Skeleton, SkeletonClipping, Texture, TextureAtlas, TextureFilter, TextureWrap, Utils } from "@esotericsoftware/spine-core";
|
||||
import { Canvas, CanvasKit, Image, Paint, Shader, BlendMode as CanvasKitBlendMode } from "canvaskit-wasm";
|
||||
import { AnimationState, AnimationStateData, AtlasAttachmentLoader, BlendMode, ClippingAttachment, Color, MeshAttachment, NumberArrayLike, Physics, RegionAttachment, Skeleton, SkeletonBinary, SkeletonClipping, SkeletonData, SkeletonJson, Texture, TextureAtlas, TextureFilter, TextureWrap, Utils } from "@esotericsoftware/spine-core";
|
||||
import { Canvas, Surface, CanvasKit, Image, Paint, Shader, BlendMode as CanvasKitBlendMode } from "canvaskit-wasm";
|
||||
|
||||
Skeleton.yDown = true;
|
||||
|
||||
@ -18,7 +18,17 @@ function toCkBlendMode(ck: CanvasKit, blendMode: BlendMode) {
|
||||
}
|
||||
}
|
||||
|
||||
export class CanvasKitTexture extends Texture {
|
||||
function bufferToUtf8String(buffer: any) {
|
||||
if (typeof Buffer !== 'undefined') {
|
||||
return buffer.toString('utf-8');
|
||||
} else if (typeof TextDecoder !== 'undefined') {
|
||||
return new TextDecoder('utf-8').decode(buffer);
|
||||
} else {
|
||||
throw new Error('Unsupported environment');
|
||||
}
|
||||
}
|
||||
|
||||
class CanvasKitTexture extends Texture {
|
||||
getImage(): CanvasKitImage {
|
||||
return this._image;
|
||||
}
|
||||
@ -60,16 +70,10 @@ export class CanvasKitTexture extends Texture {
|
||||
}
|
||||
}
|
||||
|
||||
function bufferToUtf8String(buffer: any) {
|
||||
if (typeof Buffer !== 'undefined') {
|
||||
return buffer.toString('utf-8');
|
||||
} else if (typeof TextDecoder !== 'undefined') {
|
||||
return new TextDecoder('utf-8').decode(buffer);
|
||||
} else {
|
||||
throw new Error('Unsupported environment');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads a {@link TextureAtlas} and its atlas page images from the given file path using the `readFile(path: string): Promise<Buffer>` function.
|
||||
* Throws an `Error` if the file or one of the atlas page images could not be loaded.
|
||||
*/
|
||||
export async function loadTextureAtlas(ck: CanvasKit, atlasFile: string, readFile: (path: string) => Promise<Buffer>): Promise<TextureAtlas> {
|
||||
const atlas = new TextureAtlas(bufferToUtf8String(await readFile(atlasFile)));
|
||||
const slashIndex = atlasFile.lastIndexOf("/");
|
||||
@ -81,6 +85,51 @@ export async function loadTextureAtlas(ck: CanvasKit, atlasFile: string, readFil
|
||||
return atlas;
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads a {@link SkeletonData} from the given file path (`.json` or `.skel`) using the `readFile(path: string): Promise<Buffer>` function.
|
||||
* Attachments will be looked up in the provided atlas.
|
||||
*/
|
||||
export async function loadSkeletonData(skeletonFile: string, atlas: TextureAtlas, readFile: (path: string) => Promise<Buffer>): Promise<SkeletonData> {
|
||||
const attachmentLoader = new AtlasAttachmentLoader(atlas);
|
||||
const loader = skeletonFile.endsWith(".json") ? new SkeletonJson(attachmentLoader) : new SkeletonBinary(attachmentLoader);
|
||||
const skeletonData = loader.readSkeletonData(await readFile(skeletonFile));
|
||||
return skeletonData;
|
||||
}
|
||||
|
||||
/**
|
||||
* Manages a {@link Skeleton} and its associated {@link AnimationState}. A drawable is constructed from a {@link SkeletonData}, which can
|
||||
* be shared by any number of drawables.
|
||||
*/
|
||||
export class SkeletonDrawable {
|
||||
public readonly skeleton: Skeleton;
|
||||
public readonly animationState: AnimationState;
|
||||
|
||||
/**
|
||||
* Constructs a new drawble from the skeleton data.
|
||||
*/
|
||||
constructor(skeletonData: SkeletonData) {
|
||||
this.skeleton = new Skeleton(skeletonData);
|
||||
this.animationState = new AnimationState(new AnimationStateData(skeletonData));
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the animation state and skeleton time by the delta time. Applies the
|
||||
* animations to the skeleton and calculates the final pose of the skeleton.
|
||||
*
|
||||
* @param deltaTime the time since the last update in seconds
|
||||
* @param physicsUpdate optional {@link Physics} update mode.
|
||||
*/
|
||||
update(deltaTime: number, physicsUpdate: Physics = Physics.update) {
|
||||
this.animationState.update(deltaTime);
|
||||
this.skeleton.update(deltaTime);
|
||||
this.animationState.apply(this.skeleton);
|
||||
this.skeleton.updateWorldTransform(physicsUpdate);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders a {@link Skeleton} or {@link SkeletonDrawable} to a CanvasKit {@link Canvas}.
|
||||
*/
|
||||
export class SkeletonRenderer {
|
||||
private clipper = new SkeletonClipping();
|
||||
private tempColor = new Color();
|
||||
@ -88,9 +137,20 @@ export class SkeletonRenderer {
|
||||
private static QUAD_TRIANGLES = [0, 1, 2, 2, 3, 0];
|
||||
private scratchPositions = Utils.newFloatArray(100);
|
||||
private scratchColors = Utils.newFloatArray(100);
|
||||
|
||||
/**
|
||||
* Creates a new skeleton renderer.
|
||||
* @param ck the {@link CanvasKit} instance returned by `CanvasKitInit()`.
|
||||
*/
|
||||
constructor(private ck: CanvasKit) {}
|
||||
|
||||
render(canvas: Canvas, skeleton: Skeleton) {
|
||||
/**
|
||||
* Renders a skeleton or skeleton drawable in its current pose to the canvas.
|
||||
* @param canvas the canvas to render to.
|
||||
* @param skeleton the skeleton or drawable to render.
|
||||
*/
|
||||
render(canvas: Canvas, skeleton: Skeleton | SkeletonDrawable) {
|
||||
if (skeleton instanceof SkeletonDrawable) skeleton = skeleton.skeleton;
|
||||
let clipper = this.clipper;
|
||||
let drawOrder = skeleton.drawOrder;
|
||||
let skeletonColor = skeleton.color;
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user