Merge branch '4.1' into 4.2-beta

# Conflicts:
#	spine-ts/package-lock.json
#	spine-ts/package.json
#	spine-ts/spine-canvas/package.json
#	spine-ts/spine-core/package.json
#	spine-ts/spine-phaser/package.json
#	spine-ts/spine-phaser/src/SpinePlugin.ts
#	spine-ts/spine-pixi/package.json
#	spine-ts/spine-player/package.json
#	spine-ts/spine-threejs/package.json
#	spine-ts/spine-webgl/package.json
This commit is contained in:
Mario Zechner 2023-09-04 10:54:21 +02:00
commit 42959e0e72
15 changed files with 1143 additions and 618 deletions

View File

@ -1,79 +1,161 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<title>spine-ts Examples</title>
<meta name="viewport" content="width=device-width, initial-scale=1" />
</head>
<head>
<meta charset='utf-8'>
<meta http-equiv='X-UA-Compatible' content='IE=edge'>
<title>spine-ts Examples</title>
<meta name='viewport' content='width=device-width, initial-scale=1'>
</head>
<body>
<h1>spine-ts Examples</h1>
<ul>
<li>Canvas</li>
<ul>
<li><a href="/spine-canvas/example">Example</a></li>
<li><a href="/spine-canvas/example/mouse-click.html">Mouse click</a></li>
</ul>
<li>Pixi</li>
<ul>
<li><a href="/spine-pixi/example/index.html">Basic example</a></li>
<li><a href="/spine-pixi/example/events-example.html">Events example</a></li>
</ul>
<li>Phaser</li>
<ul>
<li><a href="/spine-phaser/example/basic-example.html">Basic example</a></li>
<li><a href="/spine-phaser/example/events-example.html">Events example</a></li>
<li><a href="/spine-phaser/example/mix-and-match-example.html">Mix and match example</a></li>
<li><a href="/spine-phaser/example/arcade-physics-example.html">Arcade physics example</a></li>
<li><a href="/spine-phaser/example/control-bones-example.html">Control bones example</a></li>
<li><a href="/spine-phaser/example/batching-test.html">Batching test</a></li>
<li><a href="/spine-phaser/example/multi-scene-test.html">Multi-scene test</a></li>
<li><a href="/spine-phaser/example/bounds-test.html">Bounds test</a></li>
<li><a href="/spine-phaser/example/visibility-test.html">Visibility test</a></li>
<li><a href="/spine-phaser/example/blend-test.html">Blend test</a></li>
<li><a href="/spine-phaser/example/camera-pipeline-test.html">Camera pipeline test</a></li>
<li><a href="/spine-phaser/example/extended-class-test.html">Extended class</a></li>
<li><a href="/spine-phaser/example/canvas-test.html">Canvas test</a></li>
<li><a href="/spine-phaser/example/depth-test.html">Depth test</a></li>
<li><a href="/spine-phaser/example/render-to-texture-test.html">Render to texture test</a></li>
</ul>
<li>Player</li>
<ul>
<li><a href="/spine-player/example/example.html">Example</a></li>
<li><a href="/spine-player/example/embedding-binary-example.html">Embedding binary</a></li>
<li><a href="/spine-player/example/embedding-json-example.html">Embedding JSON</a></li>
<li><a href="/spine-player/example/dispose.html">Disposing a player</a></li>
</ul>
<li>WebGL</li>
<ul>
<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 &amp; match</a></li>
<li><a href="/spine-webgl/example/custom-attachment.html">Custom attachment</a></li>
<li><a href="/spine-webgl/example/drag-and-drop.html">Drag &amp; drop</a></li>
<li><a href="/spine-webgl/example/dress-up.html">Dress-up</a></li>
<li><a href="/spine-webgl/example/bone-dragging.html">Bone dragging</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>
<li><a href="/spine-webgl/demos/imagechanges.html">Image changes</a></li>
<li><a href="/spine-webgl/demos/meshes.html">Meshes</a></li>
<li><a href="/spine-webgl/demos/skins.html">Skins</a></li>
<li><a href="/spine-webgl/demos/spritesheets.html">Spritesheets</a></li>
<li><a href="/spine-webgl/demos/stretchyman.html">Stretchyman</a></li>
<li><a href="/spine-webgl/demos/tank.html">Tank</a></li>
<li><a href="/spine-webgl/demos/transforms.html">Transforms</a></li>
<li><a href="/spine-webgl/demos/transitions.html">Transitions</a></li>
<li><a href="/spine-webgl/demos/vine.html">Vine</a></li>
</ul>
<li>THREE.JS</li>
<ul>
<li><a href="/spine-threejs/example/index.html">Example</a></li>
<li><a href="/spine-threejs/example/coordinate-transform.html">Coordinate transform</a></li>
</ul>
</ul>
</body>
</html>
<body>
<h1>spine-ts Examples</h1>
<ul>
<li>Canvas</li>
<ul>
<li><a href="/spine-canvas/example">Example</a></li>
<li>
<a href="/spine-canvas/example/mouse-click.html">Mouse click</a>
</li>
</ul>
<li>Pixi</li>
<ul>
<li><a href="/spine-pixi/example/index.html">Basic example</a></li>
<li>
<a href="/spine-pixi/example/events-example.html">Events example</a>
</li>
</ul>
<li>Phaser</li>
<ul>
<li>
<a href="/spine-phaser/example/basic-example.html">Basic example</a>
</li>
<li>
<a href="/spine-phaser/example/events-example.html">Events example</a>
</li>
<li>
<a href="/spine-phaser/example/mix-and-match-example.html"
>Mix and match example</a
>
</li>
<li>
<a href="/spine-phaser/example/arcade-physics-example.html"
>Arcade physics example</a
>
</li>
<li>
<a href="/spine-phaser/example/control-bones-example.html"
>Control bones example</a
>
</li>
<li>
<a href="/spine-phaser/example/batching-test.html">Batching test</a>
</li>
<li>
<a href="/spine-phaser/example/multi-scene-test.html"
>Multi-scene test</a
>
</li>
<li>
<a href="/spine-phaser/example/bounds-test.html">Bounds test</a>
</li>
<li>
<a href="/spine-phaser/example/visibility-test.html"
>Visibility test</a
>
</li>
<li><a href="/spine-phaser/example/blend-test.html">Blend test</a></li>
<li>
<a href="/spine-phaser/example/camera-pipeline-test.html"
>Camera pipeline test</a
>
</li>
<li>
<a href="/spine-phaser/example/extended-class-test.html"
>Extended class</a
>
</li>
<li>
<a href="/spine-phaser/example/canvas-test.html">Canvas test</a>
</li>
<li><a href="/spine-phaser/example/depth-test.html">Depth test</a></li>
<li>
<a href="/spine-phaser/example/render-to-texture-test.html"
>Render to texture test</a
>
</li>
<li>
<a href="/spine-phaser/example/custom-spine-object-type.html">Custom object factory name</a>
</li>
</ul>
<li>Player</li>
<ul>
<li><a href="/spine-player/example/example.html">Example</a></li>
<li>
<a href="/spine-player/example/embedding-binary-example.html"
>Embedding binary</a
>
</li>
<li>
<a href="/spine-player/example/embedding-json-example.html"
>Embedding JSON</a
>
</li>
<li>
<a href="/spine-player/example/dispose.html">Disposing a player</a>
</li>
</ul>
<li>WebGL</li>
<ul>
<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 &amp; match</a>
</li>
<li>
<a href="/spine-webgl/example/custom-attachment.html"
>Custom attachment</a
>
</li>
<li>
<a href="/spine-webgl/example/drag-and-drop.html">Drag &amp; drop</a>
</li>
<li><a href="/spine-webgl/example/dress-up.html">Dress-up</a></li>
<li>
<a href="/spine-webgl/example/bone-dragging.html">Bone dragging</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>
<li>
<a href="/spine-webgl/demos/imagechanges.html">Image changes</a>
</li>
<li><a href="/spine-webgl/demos/meshes.html">Meshes</a></li>
<li><a href="/spine-webgl/demos/skins.html">Skins</a></li>
<li><a href="/spine-webgl/demos/spritesheets.html">Spritesheets</a></li>
<li><a href="/spine-webgl/demos/stretchyman.html">Stretchyman</a></li>
<li><a href="/spine-webgl/demos/tank.html">Tank</a></li>
<li><a href="/spine-webgl/demos/transforms.html">Transforms</a></li>
<li><a href="/spine-webgl/demos/transitions.html">Transitions</a></li>
<li><a href="/spine-webgl/demos/vine.html">Vine</a></li>
</ul>
<li>THREE.JS</li>
<ul>
<li><a href="/spine-threejs/example/index.html">Example</a></li>
<li>
<a href="/spine-threejs/example/coordinate-transform.html"
>Coordinate transform</a
>
</li>
<li>
<a href="/spine-threejs/example/logarithmic-depth-buffer.html"
>Logarithmic depth buffer</a
>
</li>
</ul>
</ul>
</body>
</html>

View File

@ -0,0 +1,49 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<script src="//cdn.jsdelivr.net/npm/phaser@3.55.2/dist/phaser.js"></script>
<script src="../dist/iife/spine-phaser.js"></script>
<title>Spine Phaser Example</title>
</head>
<body>
<h1>Custom object factor name</h1>
</body>
<script>
window.SPINE_GAME_OBJECT_TYPE = "spineOfficial";
class BasicExample extends Phaser.Scene {
preload() {
this.load.spineBinary("spineboy-data", "assets/spineboy-pro.skel");
this.load.spineAtlas("spineboy-atlas", "assets/spineboy-pma.atlas");
}
create() {
const spineboy = this.add.spineOfficial(400, 500, 'spineboy-data', "spineboy-atlas");
spineboy.setInteractive();
spineboy.displayWidth = 200;
spineboy.displayHeight = spineboy.height / spineboy.width * 200;
this.input.enableDebug(spineboy, 0xff00ff);
spineboy.animationState.setAnimation(0, "walk", true);
}
}
new Phaser.Game({
type: Phaser.AUTO,
width: 800,
height: 600,
type: Phaser.WEBGL,
scene: [BasicExample],
plugins: {
scene: [
{ key: "spine.SpinePlugin", plugin: spine.SpinePlugin, mapping: "spine" }
]
}
});
</script>
</html>

View File

@ -29,90 +29,138 @@
import { SPINE_GAME_OBJECT_TYPE } from "./keys";
import { SpinePlugin } from "./SpinePlugin";
import { ComputedSizeMixin, DepthMixin, FlipMixin, ScrollFactorMixin, TransformMixin, VisibleMixin, AlphaMixin, OriginMixin } from "./mixins";
import { AnimationState, AnimationStateData, Bone, MathUtils, Skeleton, Skin, Vector2 } from "@esotericsoftware/spine-core";
import {
ComputedSizeMixin,
DepthMixin,
FlipMixin,
ScrollFactorMixin,
TransformMixin,
VisibleMixin,
AlphaMixin,
OriginMixin,
} from "./mixins";
import {
AnimationState,
AnimationStateData,
Bone,
MathUtils,
Skeleton,
Skin,
Vector2,
} from "@esotericsoftware/spine-core";
class BaseSpineGameObject extends Phaser.GameObjects.GameObject {
constructor (scene: Phaser.Scene, type: string) {
super(scene, type);
}
constructor(scene: Phaser.Scene, type: string) {
super(scene, type);
}
}
/** A bounds provider calculates the bounding box for a skeleton, which is then assigned as the size of the SpineGameObject. */
export interface SpineGameObjectBoundsProvider {
// Returns the bounding box for the skeleton, in skeleton space.
calculateBounds (gameObject: SpineGameObject): { x: number, y: number, width: number, height: number };
// Returns the bounding box for the skeleton, in skeleton space.
calculateBounds(gameObject: SpineGameObject): {
x: number;
y: number;
width: number;
height: number;
};
}
/** A bounds provider that calculates the bounding box from the setup pose. */
export class SetupPoseBoundsProvider implements SpineGameObjectBoundsProvider {
calculateBounds (gameObject: SpineGameObject) {
if (!gameObject.skeleton) return { x: 0, y: 0, width: 0, height: 0 };
// Make a copy of animation state and skeleton as this might be called while
// the skeleton in the GameObject has already been heavily modified. We can not
// reconstruct that state.
const skeleton = new Skeleton(gameObject.skeleton.data);
skeleton.setToSetupPose();
skeleton.updateWorldTransform();
const bounds = skeleton.getBoundsRect();
return bounds.width == Number.NEGATIVE_INFINITY ? { x: 0, y: 0, width: 0, height: 0 } : bounds;
}
calculateBounds(gameObject: SpineGameObject) {
if (!gameObject.skeleton) return { x: 0, y: 0, width: 0, height: 0 };
// Make a copy of animation state and skeleton as this might be called while
// the skeleton in the GameObject has already been heavily modified. We can not
// reconstruct that state.
const skeleton = new Skeleton(gameObject.skeleton.data);
skeleton.setToSetupPose();
skeleton.updateWorldTransform();
const bounds = skeleton.getBoundsRect();
return bounds.width == Number.NEGATIVE_INFINITY
? { x: 0, y: 0, width: 0, height: 0 }
: bounds;
}
}
/** A bounds provider that calculates the bounding box by taking the maximumg bounding box for a combination of skins and specific animation. */
export class SkinsAndAnimationBoundsProvider implements SpineGameObjectBoundsProvider {
/**
* @param animation The animation to use for calculating the bounds. If null, the setup pose is used.
* @param skins The skins to use for calculating the bounds. If empty, the default skin is used.
* @param timeStep The time step to use for calculating the bounds. A smaller time step means more precision, but slower calculation.
*/
constructor (private animation: string | null, private skins: string[] = [], private timeStep: number = 0.05) {
}
export class SkinsAndAnimationBoundsProvider
implements SpineGameObjectBoundsProvider
{
/**
* @param animation The animation to use for calculating the bounds. If null, the setup pose is used.
* @param skins The skins to use for calculating the bounds. If empty, the default skin is used.
* @param timeStep The time step to use for calculating the bounds. A smaller time step means more precision, but slower calculation.
*/
constructor(
private animation: string | null,
private skins: string[] = [],
private timeStep: number = 0.05
) {}
calculateBounds (gameObject: SpineGameObject): { x: number; y: number; width: number; height: number; } {
if (!gameObject.skeleton || !gameObject.animationState) return { x: 0, y: 0, width: 0, height: 0 };
// Make a copy of animation state and skeleton as this might be called while
// the skeleton in the GameObject has already been heavily modified. We can not
// reconstruct that state.
const animationState = new AnimationState(gameObject.animationState.data);
const skeleton = new Skeleton(gameObject.skeleton.data);
const data = skeleton.data;
if (this.skins.length > 0) {
let customSkin = new Skin("custom-skin");
for (const skinName of this.skins) {
const skin = data.findSkin(skinName);
if (skin == null) continue;
customSkin.addSkin(skin);
}
skeleton.setSkin(customSkin);
}
skeleton.setToSetupPose();
calculateBounds(gameObject: SpineGameObject): {
x: number;
y: number;
width: number;
height: number;
} {
if (!gameObject.skeleton || !gameObject.animationState)
return { x: 0, y: 0, width: 0, height: 0 };
// Make a copy of animation state and skeleton as this might be called while
// the skeleton in the GameObject has already been heavily modified. We can not
// reconstruct that state.
const animationState = new AnimationState(gameObject.animationState.data);
const skeleton = new Skeleton(gameObject.skeleton.data);
const data = skeleton.data;
if (this.skins.length > 0) {
let customSkin = new Skin("custom-skin");
for (const skinName of this.skins) {
const skin = data.findSkin(skinName);
if (skin == null) continue;
customSkin.addSkin(skin);
}
skeleton.setSkin(customSkin);
}
skeleton.setToSetupPose();
const animation = this.animation != null ? data.findAnimation(this.animation!) : null;
if (animation == null) {
skeleton.updateWorldTransform();
const bounds = skeleton.getBoundsRect();
return bounds.width == Number.NEGATIVE_INFINITY ? { x: 0, y: 0, width: 0, height: 0 } : bounds;
} else {
let minX = Number.POSITIVE_INFINITY, minY = Number.POSITIVE_INFINITY, maxX = Number.NEGATIVE_INFINITY, maxY = Number.NEGATIVE_INFINITY;
animationState.clearTracks();
animationState.setAnimationWith(0, animation, false);
const steps = Math.max(animation.duration / this.timeStep, 1.0);
for (let i = 0; i < steps; i++) {
animationState.update(i > 0 ? this.timeStep : 0);
animationState.apply(skeleton);
skeleton.updateWorldTransform();
const animation =
this.animation != null ? data.findAnimation(this.animation!) : null;
if (animation == null) {
skeleton.updateWorldTransform();
const bounds = skeleton.getBoundsRect();
return bounds.width == Number.NEGATIVE_INFINITY
? { x: 0, y: 0, width: 0, height: 0 }
: bounds;
} else {
let minX = Number.POSITIVE_INFINITY,
minY = Number.POSITIVE_INFINITY,
maxX = Number.NEGATIVE_INFINITY,
maxY = Number.NEGATIVE_INFINITY;
animationState.clearTracks();
animationState.setAnimationWith(0, animation, false);
const steps = Math.max(animation.duration / this.timeStep, 1.0);
for (let i = 0; i < steps; i++) {
animationState.update(i > 0 ? this.timeStep : 0);
animationState.apply(skeleton);
skeleton.updateWorldTransform();
const bounds = skeleton.getBoundsRect();
minX = Math.min(minX, bounds.x);
minY = Math.min(minY, bounds.y);
maxX = Math.max(maxX, minX + bounds.width);
maxY = Math.max(maxY, minY + bounds.height);
}
const bounds = { x: minX, y: minY, width: maxX - minX, height: maxY - minY };
return bounds.width == Number.NEGATIVE_INFINITY ? { x: 0, y: 0, width: 0, height: 0 } : bounds;
}
}
const bounds = skeleton.getBoundsRect();
minX = Math.min(minX, bounds.x);
minY = Math.min(minY, bounds.y);
maxX = Math.max(maxX, minX + bounds.width);
maxY = Math.max(maxY, minY + bounds.height);
}
const bounds = {
x: minX,
y: minY,
width: maxX - minX,
height: maxY - minY,
};
return bounds.width == Number.NEGATIVE_INFINITY
? { x: 0, y: 0, width: 0, height: 0 }
: bounds;
}
}
}
/**
@ -136,147 +184,215 @@ export class SkinsAndAnimationBoundsProvider implements SpineGameObjectBoundsPro
*
* See {@link skeletonToPhaserWorldCoordinates}, {@link phaserWorldCoordinatesToSkeleton}, and {@link phaserWorldCoordinatesToBoneLocal.}
*/
export class SpineGameObject extends DepthMixin(OriginMixin(ComputedSizeMixin(FlipMixin(ScrollFactorMixin(TransformMixin(VisibleMixin(AlphaMixin(BaseSpineGameObject)))))))) {
blendMode = -1;
skeleton: Skeleton;
animationStateData: AnimationStateData;
animationState: AnimationState;
beforeUpdateWorldTransforms: (object: SpineGameObject) => void = () => { };
afterUpdateWorldTransforms: (object: SpineGameObject) => void = () => { };
private premultipliedAlpha = false;
export class SpineGameObject extends DepthMixin(
OriginMixin(
ComputedSizeMixin(
FlipMixin(
ScrollFactorMixin(
TransformMixin(VisibleMixin(AlphaMixin(BaseSpineGameObject)))
)
)
)
)
) {
blendMode = -1;
skeleton: Skeleton;
animationStateData: AnimationStateData;
animationState: AnimationState;
beforeUpdateWorldTransforms: (object: SpineGameObject) => void = () => {};
afterUpdateWorldTransforms: (object: SpineGameObject) => void = () => {};
private premultipliedAlpha = false;
constructor (scene: Phaser.Scene, private plugin: SpinePlugin, x: number, y: number, dataKey: string, atlasKey: string, public boundsProvider: SpineGameObjectBoundsProvider = new SetupPoseBoundsProvider()) {
super(scene, SPINE_GAME_OBJECT_TYPE);
this.setPosition(x, y);
constructor(
scene: Phaser.Scene,
private plugin: SpinePlugin,
x: number,
y: number,
dataKey: string,
atlasKey: string,
public boundsProvider: SpineGameObjectBoundsProvider = new SetupPoseBoundsProvider()
) {
super(scene, (window as any).SPINE_GAME_OBJECT_TYPE ? (window as any).SPINE_GAME_OBJECT_TYPE : SPINE_GAME_OBJECT_TYPE);
this.setPosition(x, y);
this.premultipliedAlpha = this.plugin.isAtlasPremultiplied(atlasKey);
this.skeleton = this.plugin.createSkeleton(dataKey, atlasKey);
this.animationStateData = new AnimationStateData(this.skeleton.data);
this.animationState = new AnimationState(this.animationStateData);
this.skeleton.updateWorldTransform();
this.updateSize();
}
this.premultipliedAlpha = this.plugin.isAtlasPremultiplied(atlasKey);
this.skeleton = this.plugin.createSkeleton(dataKey, atlasKey);
this.animationStateData = new AnimationStateData(this.skeleton.data);
this.animationState = new AnimationState(this.animationStateData);
this.skeleton.updateWorldTransform();
this.updateSize();
}
updateSize () {
if (!this.skeleton) return;
let bounds = this.boundsProvider.calculateBounds(this);
// For some reason the TS compiler and the ComputedSize mixin don't work well together and we have
// to cast to any.
let self = this as any;
self.width = bounds.width;
self.height = bounds.height;
this.displayOriginX = -bounds.x;
this.displayOriginY = -bounds.y;
}
updateSize() {
if (!this.skeleton) return;
let bounds = this.boundsProvider.calculateBounds(this);
// For some reason the TS compiler and the ComputedSize mixin don't work well together and we have
// to cast to any.
let self = this as any;
self.width = bounds.width;
self.height = bounds.height;
this.displayOriginX = -bounds.x;
this.displayOriginY = -bounds.y;
}
/** Converts a point from the skeleton coordinate system to the Phaser world coordinate system. */
skeletonToPhaserWorldCoordinates (point: { x: number, y: number }) {
let transform = this.getWorldTransformMatrix();
let a = transform.a, b = transform.b, c = transform.c, d = transform.d, tx = transform.tx, ty = transform.ty;
let x = point.x
let y = point.y
point.x = x * a + y * c + tx;
point.y = x * b + y * d + ty;
}
/** Converts a point from the skeleton coordinate system to the Phaser world coordinate system. */
skeletonToPhaserWorldCoordinates(point: { x: number; y: number }) {
let transform = this.getWorldTransformMatrix();
let a = transform.a,
b = transform.b,
c = transform.c,
d = transform.d,
tx = transform.tx,
ty = transform.ty;
let x = point.x;
let y = point.y;
point.x = x * a + y * c + tx;
point.y = x * b + y * d + ty;
}
/** Converts a point from the Phaser world coordinate system to the skeleton coordinate system. */
phaserWorldCoordinatesToSkeleton (point: { x: number, y: number }) {
let transform = this.getWorldTransformMatrix();
transform = transform.invert();
let a = transform.a, b = transform.b, c = transform.c, d = transform.d, tx = transform.tx, ty = transform.ty;
let x = point.x
let y = point.y
point.x = x * a + y * c + tx;
point.y = x * b + y * d + ty;
}
/** Converts a point from the Phaser world coordinate system to the skeleton coordinate system. */
phaserWorldCoordinatesToSkeleton(point: { x: number; y: number }) {
let transform = this.getWorldTransformMatrix();
transform = transform.invert();
let a = transform.a,
b = transform.b,
c = transform.c,
d = transform.d,
tx = transform.tx,
ty = transform.ty;
let x = point.x;
let y = point.y;
point.x = x * a + y * c + tx;
point.y = x * b + y * d + ty;
}
/** Converts a point from the Phaser world coordinate system to the bone's local coordinate system. */
phaserWorldCoordinatesToBone (point: { x: number, y: number }, bone: Bone) {
this.phaserWorldCoordinatesToSkeleton(point);
if (bone.parent) {
bone.parent.worldToLocal(point as Vector2);
} else {
bone.worldToLocal(point as Vector2);
}
}
/** Converts a point from the Phaser world coordinate system to the bone's local coordinate system. */
phaserWorldCoordinatesToBone(point: { x: number; y: number }, bone: Bone) {
this.phaserWorldCoordinatesToSkeleton(point);
if (bone.parent) {
bone.parent.worldToLocal(point as Vector2);
} else {
bone.worldToLocal(point as Vector2);
}
}
/**
* Updates the {@link AnimationState}, applies it to the {@link Skeleton}, then updates the world transforms of all bones.
* @param delta The time delta in milliseconds
*/
updatePose (delta: number) {
this.animationState.update(delta / 1000);
this.animationState.apply(this.skeleton);
this.beforeUpdateWorldTransforms(this);
this.skeleton.updateWorldTransform();
this.afterUpdateWorldTransforms(this);
}
/**
* Updates the {@link AnimationState}, applies it to the {@link Skeleton}, then updates the world transforms of all bones.
* @param delta The time delta in milliseconds
*/
updatePose(delta: number) {
this.animationState.update(delta / 1000);
this.animationState.apply(this.skeleton);
this.beforeUpdateWorldTransforms(this);
this.skeleton.updateWorldTransform();
this.afterUpdateWorldTransforms(this);
}
preUpdate (time: number, delta: number) {
if (!this.skeleton || !this.animationState) return;
this.updatePose(delta);
}
preUpdate(time: number, delta: number) {
if (!this.skeleton || !this.animationState) return;
this.updatePose(delta);
}
preDestroy () {
// FIXME tear down any event emitters
}
preDestroy() {
// FIXME tear down any event emitters
}
willRender (camera: Phaser.Cameras.Scene2D.Camera) {
if (!this.visible) return false;
willRender(camera: Phaser.Cameras.Scene2D.Camera) {
if (!this.visible) return false;
var GameObjectRenderMask = 0xf;
var result = (!this.skeleton || !(GameObjectRenderMask !== this.renderFlags || (this.cameraFilter !== 0 && (this.cameraFilter & camera.id))));
var GameObjectRenderMask = 0xf;
var result = !this.skeleton || !(GameObjectRenderMask !== this.renderFlags || (this.cameraFilter !== 0 && this.cameraFilter & camera.id));
return result;
}
if (!result && this.parentContainer && this.plugin.webGLRenderer) {
var sceneRenderer = this.plugin.webGLRenderer;
renderWebGL (renderer: Phaser.Renderer.WebGL.WebGLRenderer, src: SpineGameObject, camera: Phaser.Cameras.Scene2D.Camera, parentMatrix: Phaser.GameObjects.Components.TransformMatrix) {
if (!this.skeleton || !this.animationState || !this.plugin.webGLRenderer) return;
if (this.plugin.gl && this.plugin.phaserRenderer instanceof Phaser.Renderer.WebGL.WebGLRenderer && sceneRenderer.batcher.isDrawing) {
sceneRenderer.end();
this.plugin.phaserRenderer.pipelines.rebind();
}
}
let sceneRenderer = this.plugin.webGLRenderer;
if (renderer.newType) {
renderer.pipelines.clear();
sceneRenderer.begin();
}
return result;
}
camera.addToRenderList(src);
let transform = Phaser.GameObjects.GetCalcMatrix(src, camera, parentMatrix).calc;
let a = transform.a, b = transform.b, c = transform.c, d = transform.d, tx = transform.tx, ty = transform.ty;
sceneRenderer.drawSkeleton(this.skeleton, this.premultipliedAlpha, -1, -1, (vertices, numVertices, stride) => {
for (let i = 0; i < numVertices; i += stride) {
let vx = vertices[i];
let vy = vertices[i + 1];
vertices[i] = vx * a + vy * c + tx;
vertices[i + 1] = vx * b + vy * d + ty;
}
});
renderWebGL(
renderer: Phaser.Renderer.WebGL.WebGLRenderer,
src: SpineGameObject,
camera: Phaser.Cameras.Scene2D.Camera,
parentMatrix: Phaser.GameObjects.Components.TransformMatrix
) {
if (!this.skeleton || !this.animationState || !this.plugin.webGLRenderer)
return;
if (!renderer.nextTypeMatch) {
sceneRenderer.end();
renderer.pipelines.rebind();
}
}
let sceneRenderer = this.plugin.webGLRenderer;
if (renderer.newType) {
renderer.pipelines.clear();
sceneRenderer.begin();
}
renderCanvas (renderer: Phaser.Renderer.Canvas.CanvasRenderer, src: SpineGameObject, camera: Phaser.Cameras.Scene2D.Camera, parentMatrix: Phaser.GameObjects.Components.TransformMatrix) {
if (!this.skeleton || !this.animationState || !this.plugin.canvasRenderer) return;
camera.addToRenderList(src);
let transform = Phaser.GameObjects.GetCalcMatrix(
src,
camera,
parentMatrix
).calc;
let a = transform.a,
b = transform.b,
c = transform.c,
d = transform.d,
tx = transform.tx,
ty = transform.ty;
sceneRenderer.drawSkeleton(
this.skeleton,
this.premultipliedAlpha,
-1,
-1,
(vertices, numVertices, stride) => {
for (let i = 0; i < numVertices; i += stride) {
let vx = vertices[i];
let vy = vertices[i + 1];
vertices[i] = vx * a + vy * c + tx;
vertices[i + 1] = vx * b + vy * d + ty;
}
}
);
let context = renderer.currentContext;
let skeletonRenderer = this.plugin.canvasRenderer;
(skeletonRenderer as any).ctx = context;
if (!renderer.nextTypeMatch) {
sceneRenderer.end();
renderer.pipelines.rebind();
}
}
camera.addToRenderList(src);
let transform = Phaser.GameObjects.GetCalcMatrix(src, camera, parentMatrix).calc;
let skeleton = this.skeleton;
skeleton.x = transform.tx;
skeleton.y = transform.ty;
skeleton.scaleX = transform.scaleX;
skeleton.scaleY = transform.scaleY;
let root = skeleton.getRootBone()!;
root.rotation = -MathUtils.radiansToDegrees * transform.rotationNormalized;
this.skeleton.updateWorldTransform();
renderCanvas(
renderer: Phaser.Renderer.Canvas.CanvasRenderer,
src: SpineGameObject,
camera: Phaser.Cameras.Scene2D.Camera,
parentMatrix: Phaser.GameObjects.Components.TransformMatrix
) {
if (!this.skeleton || !this.animationState || !this.plugin.canvasRenderer)
return;
context.save();
skeletonRenderer.draw(skeleton);
context.restore();
}
let context = renderer.currentContext;
let skeletonRenderer = this.plugin.canvasRenderer;
(skeletonRenderer as any).ctx = context;
camera.addToRenderList(src);
let transform = Phaser.GameObjects.GetCalcMatrix(
src,
camera,
parentMatrix
).calc;
let skeleton = this.skeleton;
skeleton.x = transform.tx;
skeleton.y = transform.ty;
skeleton.scaleX = transform.scaleX;
skeleton.scaleY = transform.scaleY;
let root = skeleton.getRootBone()!;
root.rotation = -MathUtils.radiansToDegrees * transform.rotationNormalized;
this.skeleton.updateWorldTransform();
context.save();
skeletonRenderer.draw(skeleton);
context.restore();
}
}

View File

@ -28,7 +28,7 @@
*****************************************************************************/
import Phaser from "phaser";
import { SPINE_ATLAS_CACHE_KEY, SPINE_CONTAINER_TYPE, SPINE_GAME_OBJECT_TYPE, SPINE_SKELETON_DATA_FILE_TYPE, SPINE_ATLAS_FILE_TYPE, SPINE_SKELETON_FILE_CACHE_KEY as SPINE_SKELETON_DATA_CACHE_KEY } from "./keys";
import { SPINE_ATLAS_CACHE_KEY, SPINE_GAME_OBJECT_TYPE, SPINE_SKELETON_DATA_FILE_TYPE, SPINE_ATLAS_FILE_TYPE, SPINE_SKELETON_FILE_CACHE_KEY as SPINE_SKELETON_DATA_CACHE_KEY } from "./keys";
import { AtlasAttachmentLoader, GLTexture, SceneRenderer, Skeleton, SkeletonBinary, SkeletonData, SkeletonJson, TextureAtlas } from "@esotericsoftware/spine-webgl"
import { SpineGameObject, SpineGameObjectBoundsProvider } from "./SpineGameObject";
import { CanvasTexture, SkeletonRenderer } from "@esotericsoftware/spine-canvas";
@ -74,9 +74,10 @@ export interface SpineGameObjectConfig extends Phaser.Types.GameObjects.GameObje
export class SpinePlugin extends Phaser.Plugins.ScenePlugin {
game: Phaser.Game;
private isWebGL: boolean;
private gl: WebGLRenderingContext | null;
gl: WebGLRenderingContext | null;
webGLRenderer: SceneRenderer | null;
canvasRenderer: SkeletonRenderer | null;
phaserRenderer: Phaser.Renderer.Canvas.CanvasRenderer | Phaser.Renderer.WebGL.WebGLRenderer;
private skeletonDataCache: Phaser.Cache.BaseCache;
private atlasCache: Phaser.Cache.BaseCache;
@ -85,6 +86,7 @@ export class SpinePlugin extends Phaser.Plugins.ScenePlugin {
this.game = pluginManager.game;
this.isWebGL = this.game.config.renderType === 2;
this.gl = this.isWebGL ? (this.game.renderer as Phaser.Renderer.WebGL.WebGLRenderer).gl : null;
this.phaserRenderer = this.game.renderer;
this.webGLRenderer = null;
this.canvasRenderer = null;
this.skeletonDataCache = this.game.cache.addCustom(SPINE_SKELETON_DATA_CACHE_KEY);
@ -136,7 +138,7 @@ export class SpinePlugin extends Phaser.Plugins.ScenePlugin {
}
return Phaser.GameObjects.BuildGameObject(this.scene, gameObject, config);
}
pluginManager.registerGameObject(SPINE_GAME_OBJECT_TYPE, addSpineGameObject, makeSpineGameObject);
pluginManager.registerGameObject((window as any).SPINE_GAME_OBJECT_TYPE ? (window as any).SPINE_GAME_OBJECT_TYPE : SPINE_GAME_OBJECT_TYPE, addSpineGameObject, makeSpineGameObject);
}
boot () {
@ -186,8 +188,7 @@ export class SpinePlugin extends Phaser.Plugins.ScenePlugin {
}
gameDestroy () {
this.pluginManager.removeGameObject(SPINE_GAME_OBJECT_TYPE, true, true);
this.pluginManager.removeGameObject(SPINE_CONTAINER_TYPE, true, true);
this.pluginManager.removeGameObject((window as any).SPINE_GAME_OBJECT_TYPE ? (window as any).SPINE_GAME_OBJECT_TYPE : SPINE_GAME_OBJECT_TYPE, true, true);
if (this.webGLRenderer) this.webGLRenderer.dispose();
}
@ -264,15 +265,13 @@ interface SpineSkeletonDataFileConfig {
class SpineSkeletonDataFile extends Phaser.Loader.MultiFile {
constructor (loader: Phaser.Loader.LoaderPlugin, key: string | SpineSkeletonDataFileConfig, url?: string, public fileType?: SpineSkeletonDataFileType, xhrSettings?: Phaser.Types.Loader.XHRSettingsObject) {
if (typeof key !== "string") {
const config = key;
key = config.key;
url = config.url;
const config = key;
key = config.key;
url = config.url;
fileType = config.type === "spineJson" ? SpineSkeletonDataFileType.json : SpineSkeletonDataFileType.binary;
xhrSettings = config.xhrSettings;
}
xhrSettings = config.xhrSettings;
}
let file = null;
let isJson = fileType == SpineSkeletonDataFileType.json;
if (isJson) {
@ -311,14 +310,13 @@ interface SpineAtlasFileConfig {
class SpineAtlasFile extends Phaser.Loader.MultiFile {
constructor (loader: Phaser.Loader.LoaderPlugin, key: string | SpineAtlasFileConfig, url?: string, public premultipliedAlpha: boolean = true, xhrSettings?: Phaser.Types.Loader.XHRSettingsObject) {
if (typeof key !== "string") {
const config = key;
key = config.key;
url = config.url;
const config = key;
key = config.key;
url = config.url;
premultipliedAlpha = config.premultipliedAlpha ?? true;
xhrSettings = config.xhrSettings;
}
xhrSettings = config.xhrSettings;
}
super(loader, SPINE_ATLAS_FILE_TYPE, key, [
new Phaser.Loader.FileTypes.TextFile(loader, {

View File

@ -33,4 +33,3 @@ export const SPINE_LOADER_TYPE = "spine";
export const SPINE_SKELETON_DATA_FILE_TYPE = "spineSkeletonData";
export const SPINE_ATLAS_FILE_TYPE = "spineAtlasData";
export const SPINE_GAME_OBJECT_TYPE = "spine";
export const SPINE_CONTAINER_TYPE = "spineContainer";

View File

@ -1,141 +1,150 @@
<html>
<head>
<meta charset="UTF-8" />
<title>spine-threejs</title>
<script src="https://unpkg.com/three@0.141.0/build/three.js"></script>
<script src="../dist/iife/spine-threejs.js"></script>
<script src="./OrbitalControls.js"></script>
</head>
<style>
* {
margin: 0;
padding: 0;
}
<head>
<meta charset="UTF-8">
<title>spine-threejs</title>
<script src="https://unpkg.com/three@0.141.0/build/three.js"></script>
<script src="../dist/iife/spine-threejs.js"></script>
<script src="./OrbitalControls.js"></script>
</head>
<style>
* {
margin: 0;
padding: 0;
}
body,
html {
height: 100%;
}
body,
html {
height: 100%
}
canvas {
position: absolute;
width: 100%;
height: 100%;
}
</style>
canvas {
position: absolute;
width: 100%;
height: 100%;
}
</style>
<body>
<script>
(function () {
let scene, camera, renderer;
let geometry, material, mesh, skeletonMesh;
let assetManager;
let canvas;
let controls;
let lastFrameTime = Date.now() / 1000;
<body>
<script>
(function () {
let scene, camera, renderer;
let geometry, material, mesh, skeletonMesh;
let assetManager;
let canvas;
let controls;
let lastFrameTime = Date.now() / 1000;
let baseUrl = "assets/";
let skeletonFile = "raptor-pro.json";
let atlasFile = skeletonFile
.replace("-pro", "")
.replace("-ess", "")
.replace(".json", ".atlas");
let animation = "walk";
let baseUrl = "assets/";
let skeletonFile = "raptor-pro.json";
let atlasFile = skeletonFile.replace("-pro", "").replace("-ess", "").replace(".json", ".atlas");
let animation = "walk";
function init() {
// create the THREE.JS camera, scene and renderer (WebGL)
let width = window.innerWidth,
height = window.innerHeight;
camera = new THREE.PerspectiveCamera(75, width / height, 1, 3000);
camera.position.y = 100;
camera.position.z = 400;
scene = new THREE.Scene();
renderer = new THREE.WebGLRenderer();
renderer.setSize(width, height);
document.body.appendChild(renderer.domElement);
canvas = renderer.domElement;
controls = new OrbitControls(camera, renderer.domElement);
function init() {
// create the THREE.JS camera, scene and renderer (WebGL)
let width = window.innerWidth, height = window.innerHeight;
camera = new THREE.PerspectiveCamera(75, width / height, 1, 3000);
camera.position.y = 100;
camera.position.z = 400;
scene = new THREE.Scene();
renderer = new THREE.WebGLRenderer();
renderer.setSize(width, height);
document.body.appendChild(renderer.domElement);
canvas = renderer.domElement;
controls = new OrbitControls(camera, renderer.domElement);
// load the assets required to display the Raptor model
assetManager = new spine.AssetManager(baseUrl);
assetManager.loadText(skeletonFile);
assetManager.loadTextureAtlas(atlasFile);
// load the assets required to display the Raptor model
assetManager = new spine.AssetManager(baseUrl);
assetManager.loadText(skeletonFile);
assetManager.loadTextureAtlas(atlasFile);
requestAnimationFrame(load);
}
requestAnimationFrame(load);
}
function load(name, scale) {
if (assetManager.isLoadingComplete()) {
// Add a box to the scene to which we attach the skeleton mesh
geometry = new THREE.BoxGeometry(200, 200, 200);
material = new THREE.MeshBasicMaterial({
color: 0xff0000,
wireframe: true,
});
mesh = new THREE.Mesh(geometry, material);
scene.add(mesh);
function load(name, scale) {
if (assetManager.isLoadingComplete()) {
// Add a box to the scene to which we attach the skeleton mesh
geometry = new THREE.BoxGeometry(200, 200, 200);
material = new THREE.MeshBasicMaterial({ color: 0xff0000, wireframe: true });
mesh = new THREE.Mesh(geometry, material);
scene.add(mesh);
// Load the texture atlas using name.atlas and name.png from the AssetManager.
// The function passed to TextureAtlas is used to resolve relative paths.
atlas = assetManager.require(atlasFile);
// Load the texture atlas using name.atlas and name.png from the AssetManager.
// The function passed to TextureAtlas is used to resolve relative paths.
atlas = assetManager.require(atlasFile);
// Create a AtlasAttachmentLoader that resolves region, mesh, boundingbox and path attachments
atlasLoader = new spine.AtlasAttachmentLoader(atlas);
// Create a AtlasAttachmentLoader that resolves region, mesh, boundingbox and path attachments
atlasLoader = new spine.AtlasAttachmentLoader(atlas);
// Create a SkeletonJson instance for parsing the .json file.
let skeletonJson = new spine.SkeletonJson(atlasLoader);
// Create a SkeletonJson instance for parsing the .json file.
let skeletonJson = new spine.SkeletonJson(atlasLoader);
// Set the scale to apply during parsing, parse the file, and create a new skeleton.
skeletonJson.scale = 0.4;
let skeletonData = skeletonJson.readSkeletonData(
assetManager.require(skeletonFile)
);
// Set the scale to apply during parsing, parse the file, and create a new skeleton.
skeletonJson.scale = 0.4;
let skeletonData = skeletonJson.readSkeletonData(assetManager.require(skeletonFile));
// Create a SkeletonMesh from the data and attach it to the scene
skeletonMesh = new spine.SkeletonMesh(
skeletonData,
(parameters) => {
parameters.depthTest = true;
parameters.depthWrite = true;
parameters.alphaTest = 0.001;
}
);
skeletonMesh.state.setAnimation(0, animation, true);
mesh.add(skeletonMesh);
// Create a SkeletonMesh from the data and attach it to the scene
skeletonMesh = new spine.SkeletonMesh(skeletonData, (parameters) => {
parameters.depthTest = true;
parameters.depthWrite = true;
parameters.alphaTest = 0.001;
});
skeletonMesh.state.setAnimation(0, animation, true);
mesh.add(skeletonMesh);
requestAnimationFrame(render);
} else requestAnimationFrame(load);
}
let lastTime = Date.now();
function render() {
// calculate delta time for animation purposes
let now = Date.now() / 1000;
let delta = now - lastFrameTime;
lastFrameTime = now;
requestAnimationFrame(render);
} else requestAnimationFrame(load);
}
// resize canvas to use full page, adjust camera/renderer
resize();
let lastTime = Date.now();
function render() {
// calculate delta time for animation purposes
let now = Date.now() / 1000;
let delta = now - lastFrameTime;
lastFrameTime = now;
// Update orbital controls
controls.update();
// resize canvas to use full page, adjust camera/renderer
resize();
// update the animation
skeletonMesh.update(delta);
// Update orbital controls
controls.update();
// render the scene
renderer.render(scene, camera);
// update the animation
skeletonMesh.update(delta);
requestAnimationFrame(render);
}
// render the scene
renderer.render(scene, camera);
function resize() {
let w = window.innerWidth;
let h = window.innerHeight;
if (canvas.width != w || canvas.height != h) {
canvas.width = w;
canvas.height = h;
}
requestAnimationFrame(render);
}
camera.aspect = w / h;
camera.updateProjectionMatrix();
function resize() {
let w = window.innerWidth;
let h = window.innerHeight;
if (canvas.width != w || canvas.height != h) {
canvas.width = w;
canvas.height = h;
}
renderer.setSize(w, h);
}
camera.aspect = w / h;
camera.updateProjectionMatrix();
renderer.setSize(w, h);
}
init();
}());
</script>
</body>
</html>
init();
})();
</script>
</body>
</html>

View File

@ -0,0 +1,186 @@
<html>
<head>
<meta charset="UTF-8" />
<title>spine-threejs</title>
<script src="https://unpkg.com/three@0.141.0/build/three.js"></script>
<script src="../dist/iife/spine-threejs.js"></script>
<script src="./OrbitalControls.js"></script>
</head>
<style>
* {
margin: 0;
padding: 0;
}
body,
html {
height: 100%;
}
canvas {
position: absolute;
width: 100%;
height: 100%;
}
</style>
<body>
<script>
(function () {
let scene, camera, renderer;
let geometry, material, mesh, skeletonMesh;
let assetManager;
let canvas;
let controls;
let lastFrameTime = Date.now() / 1000;
let baseUrl = "assets/";
let skeletonFile = "raptor-pro.json";
let atlasFile = skeletonFile
.replace("-pro", "")
.replace("-ess", "")
.replace(".json", ".atlas");
let animation = "walk";
function init() {
// create the THREE.JS camera, scene and renderer (WebGL)
let width = window.innerWidth,
height = window.innerHeight;
camera = new THREE.PerspectiveCamera(75, width / height, 1, 3000);
camera.position.y = 100;
camera.position.z = 400;
scene = new THREE.Scene();
renderer = new THREE.WebGLRenderer({ logarithmicDepthBuffer: true });
renderer.setSize(width, height);
document.body.appendChild(renderer.domElement);
canvas = renderer.domElement;
controls = new OrbitControls(camera, renderer.domElement);
// load the assets required to display the Raptor model
assetManager = new spine.AssetManager(baseUrl);
assetManager.loadText(skeletonFile);
assetManager.loadTextureAtlas(atlasFile);
requestAnimationFrame(load);
}
function load(name, scale) {
if (assetManager.isLoadingComplete()) {
// Add a box to the scene to which we attach the skeleton mesh
geometry = new THREE.BoxGeometry(200, 200, 200);
material = new THREE.MeshBasicMaterial({
color: 0xff0000,
wireframe: true,
});
mesh = new THREE.Mesh(geometry, material);
scene.add(mesh);
// Load the texture atlas using name.atlas and name.png from the AssetManager.
// The function passed to TextureAtlas is used to resolve relative paths.
atlas = assetManager.require(atlasFile);
// Create a AtlasAttachmentLoader that resolves region, mesh, boundingbox and path attachments
atlasLoader = new spine.AtlasAttachmentLoader(atlas);
// Create a SkeletonJson instance for parsing the .json file.
let skeletonJson = new spine.SkeletonJson(atlasLoader);
// Set the scale to apply during parsing, parse the file, and create a new skeleton.
skeletonJson.scale = 0.4;
let skeletonData = skeletonJson.readSkeletonData(
assetManager.require(skeletonFile)
);
// Create a SkeletonMesh from the data and attach it to the scene
// Provide a custom vertex/fragment shader pair that supports
// the logarithmic depth buffer.
// See https://discourse.threejs.org/t/shadermaterial-render-order-with-logarithmicdepthbuffer-is-wrong/49221/3
skeletonMesh = new spine.SkeletonMesh(
skeletonData,
(materialParameters) => {
materialParameters.vertexShader = `
attribute vec4 color;
varying vec2 vUv;
varying vec4 vColor;
#include <common>
#include <logdepthbuf_pars_vertex>
void main() {
vUv = uv;
vColor = color;
gl_Position = projectionMatrix*modelViewMatrix*vec4(position,1.0);
#include <logdepthbuf_vertex>
}
`;
materialParameters.fragmentShader = `
uniform sampler2D map;
#ifdef USE_SPINE_ALPHATEST
uniform float alphaTest;
#endif
varying vec2 vUv;
varying vec4 vColor;
#include <common>
#include <logdepthbuf_pars_fragment>
void main(void) {
#include <logdepthbuf_fragment>
gl_FragColor = texture2D(map, vUv)*vColor;
#ifdef USE_SPINE_ALPHATEST
if (gl_FragColor.a < alphaTest) discard;
#endif
}
`;
}
);
skeletonMesh.state.setAnimation(0, animation, true);
mesh.add(skeletonMesh);
requestAnimationFrame(render);
} else requestAnimationFrame(load);
}
let lastTime = Date.now();
function render() {
// calculate delta time for animation purposes
let now = Date.now() / 1000;
let delta = now - lastFrameTime;
lastFrameTime = now;
// resize canvas to use full page, adjust camera/renderer
resize();
// Update orbital controls
controls.update();
// update the animation
skeletonMesh.update(delta);
// render the scene
renderer.render(scene, camera);
requestAnimationFrame(render);
}
function resize() {
let w = window.innerWidth;
let h = window.innerHeight;
if (canvas.width != w || canvas.height != h) {
canvas.width = w;
canvas.height = h;
}
camera.aspect = w / h;
camera.updateProjectionMatrix();
renderer.setSize(w, h);
}
init();
})();
</script>
</body>
</html>

View File

@ -27,16 +27,33 @@
* SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*****************************************************************************/
import { AnimationState, AnimationStateData, BlendMode, ClippingAttachment, Color, MeshAttachment, NumberArrayLike, RegionAttachment, Skeleton, SkeletonClipping, SkeletonData, TextureAtlasRegion, Utils, Vector2 } from "@esotericsoftware/spine-core";
import {
AnimationState,
AnimationStateData,
BlendMode,
ClippingAttachment,
Color,
MeshAttachment,
NumberArrayLike,
RegionAttachment,
Skeleton,
SkeletonClipping,
SkeletonData,
TextureAtlasRegion,
Utils,
Vector2,
} from "@esotericsoftware/spine-core";
import { MeshBatcher } from "./MeshBatcher";
import * as THREE from "three";
import { ThreeJsTexture } from "./ThreeJsTexture";
export type SkeletonMeshMaterialParametersCustomizer = (materialParameters: THREE.ShaderMaterialParameters) => void;
export type SkeletonMeshMaterialParametersCustomizer = (
materialParameters: THREE.ShaderMaterialParameters
) => void;
export class SkeletonMeshMaterial extends THREE.ShaderMaterial {
constructor (customizer: SkeletonMeshMaterialParametersCustomizer) {
let vertexShader = `
constructor(customizer: SkeletonMeshMaterialParametersCustomizer) {
let vertexShader = `
attribute vec4 color;
varying vec2 vUv;
varying vec4 vColor;
@ -46,7 +63,7 @@ export class SkeletonMeshMaterial extends THREE.ShaderMaterial {
gl_Position = projectionMatrix*modelViewMatrix*vec4(position,1.0);
}
`;
let fragmentShader = `
let fragmentShader = `
uniform sampler2D map;
#ifdef USE_SPINE_ALPHATEST
uniform float alphaTest;
@ -61,212 +78,253 @@ export class SkeletonMeshMaterial extends THREE.ShaderMaterial {
}
`;
let parameters: THREE.ShaderMaterialParameters = {
uniforms: {
map: { value: null },
},
vertexShader: vertexShader,
fragmentShader: fragmentShader,
side: THREE.DoubleSide,
transparent: true,
depthWrite: false,
alphaTest: 0.0
};
customizer(parameters);
if (parameters.alphaTest && parameters.alphaTest > 0) {
parameters.defines = { "USE_SPINE_ALPHATEST": 1 };
if (!parameters.uniforms) parameters.uniforms = {};
parameters.uniforms["alphaTest"] = { value: parameters.alphaTest };
}
super(parameters);
};
let parameters: THREE.ShaderMaterialParameters = {
uniforms: {
map: { value: null },
},
vertexShader: vertexShader,
fragmentShader: fragmentShader,
side: THREE.DoubleSide,
transparent: true,
depthWrite: true,
alphaTest: 0.0,
};
customizer(parameters);
if (parameters.alphaTest && parameters.alphaTest > 0) {
parameters.defines = { USE_SPINE_ALPHATEST: 1 };
if (!parameters.uniforms) parameters.uniforms = {};
parameters.uniforms["alphaTest"] = { value: parameters.alphaTest };
}
super(parameters);
}
}
export class SkeletonMesh extends THREE.Object3D {
tempPos: Vector2 = new Vector2();
tempUv: Vector2 = new Vector2();
tempLight = new Color();
tempDark = new Color();
skeleton: Skeleton;
state: AnimationState;
zOffset: number = 0.1;
tempPos: Vector2 = new Vector2();
tempUv: Vector2 = new Vector2();
tempLight = new Color();
tempDark = new Color();
skeleton: Skeleton;
state: AnimationState;
zOffset: number = 0.1;
private batches = new Array<MeshBatcher>();
private nextBatchIndex = 0;
private clipper: SkeletonClipping = new SkeletonClipping();
private batches = new Array<MeshBatcher>();
private nextBatchIndex = 0;
private clipper: SkeletonClipping = new SkeletonClipping();
static QUAD_TRIANGLES = [0, 1, 2, 2, 3, 0];
static VERTEX_SIZE = 2 + 2 + 4;
static QUAD_TRIANGLES = [0, 1, 2, 2, 3, 0];
static VERTEX_SIZE = 2 + 2 + 4;
private vertices = Utils.newFloatArray(1024);
private tempColor = new Color();
private vertices = Utils.newFloatArray(1024);
private tempColor = new Color();
constructor (skeletonData: SkeletonData, private materialCustomerizer: SkeletonMeshMaterialParametersCustomizer = (material) => { }) {
super();
constructor(
skeletonData: SkeletonData,
private materialCustomerizer: SkeletonMeshMaterialParametersCustomizer = (
material
) => {}
) {
super();
this.skeleton = new Skeleton(skeletonData);
let animData = new AnimationStateData(skeletonData);
this.state = new AnimationState(animData);
}
this.skeleton = new Skeleton(skeletonData);
let animData = new AnimationStateData(skeletonData);
this.state = new AnimationState(animData);
}
update (deltaTime: number) {
let state = this.state;
let skeleton = this.skeleton;
update(deltaTime: number) {
let state = this.state;
let skeleton = this.skeleton;
state.update(deltaTime);
state.apply(skeleton);
skeleton.updateWorldTransform();
state.update(deltaTime);
state.apply(skeleton);
skeleton.updateWorldTransform();
this.updateGeometry();
}
this.updateGeometry();
}
dispose () {
for (var i = 0; i < this.batches.length; i++) {
this.batches[i].dispose();
}
}
dispose() {
for (var i = 0; i < this.batches.length; i++) {
this.batches[i].dispose();
}
}
private clearBatches () {
for (var i = 0; i < this.batches.length; i++) {
this.batches[i].clear();
this.batches[i].visible = false;
}
this.nextBatchIndex = 0;
}
private clearBatches() {
for (var i = 0; i < this.batches.length; i++) {
this.batches[i].clear();
this.batches[i].visible = false;
}
this.nextBatchIndex = 0;
}
private nextBatch () {
if (this.batches.length == this.nextBatchIndex) {
let batch = new MeshBatcher(10920, this.materialCustomerizer);
this.add(batch);
this.batches.push(batch);
}
let batch = this.batches[this.nextBatchIndex++];
batch.visible = true;
return batch;
}
private nextBatch() {
if (this.batches.length == this.nextBatchIndex) {
let batch = new MeshBatcher(10920, this.materialCustomerizer);
this.add(batch);
this.batches.push(batch);
}
let batch = this.batches[this.nextBatchIndex++];
batch.visible = true;
return batch;
}
private updateGeometry () {
this.clearBatches();
private updateGeometry() {
this.clearBatches();
let tempPos = this.tempPos;
let tempUv = this.tempUv;
let tempLight = this.tempLight;
let tempDark = this.tempDark;
let clipper = this.clipper;
let tempPos = this.tempPos;
let tempUv = this.tempUv;
let tempLight = this.tempLight;
let tempDark = this.tempDark;
let clipper = this.clipper;
let vertices: NumberArrayLike = this.vertices;
let triangles: Array<number> | null = null;
let uvs: NumberArrayLike | null = null;
let drawOrder = this.skeleton.drawOrder;
let batch = this.nextBatch();
batch.begin();
let z = 0;
let zOffset = this.zOffset;
for (let i = 0, n = drawOrder.length; i < n; i++) {
let vertexSize = clipper.isClipping() ? 2 : SkeletonMesh.VERTEX_SIZE;
let slot = drawOrder[i];
if (!slot.bone.active) {
clipper.clipEndWithSlot(slot);
continue;
}
let attachment = slot.getAttachment();
let attachmentColor: Color | null;
let texture: ThreeJsTexture | null;
let numFloats = 0;
if (attachment instanceof RegionAttachment) {
let region = <RegionAttachment>attachment;
attachmentColor = region.color;
vertices = this.vertices;
numFloats = vertexSize * 4;
region.computeWorldVertices(slot, vertices, 0, vertexSize);
triangles = SkeletonMesh.QUAD_TRIANGLES;
uvs = region.uvs;
texture = <ThreeJsTexture>region.region!.texture;
} else if (attachment instanceof MeshAttachment) {
let mesh = <MeshAttachment>attachment;
attachmentColor = mesh.color;
vertices = this.vertices;
numFloats = (mesh.worldVerticesLength >> 1) * vertexSize;
if (numFloats > vertices.length) {
vertices = this.vertices = Utils.newFloatArray(numFloats);
}
mesh.computeWorldVertices(slot, 0, mesh.worldVerticesLength, vertices, 0, vertexSize);
triangles = mesh.triangles;
uvs = mesh.uvs;
texture = <ThreeJsTexture>mesh.region!.texture;
} else if (attachment instanceof ClippingAttachment) {
let clip = <ClippingAttachment>(attachment);
clipper.clipStart(slot, clip);
continue;
} else {
clipper.clipEndWithSlot(slot);
continue;
}
let vertices: NumberArrayLike = this.vertices;
let triangles: Array<number> | null = null;
let uvs: NumberArrayLike | null = null;
let drawOrder = this.skeleton.drawOrder;
let batch = this.nextBatch();
batch.begin();
let z = 0;
let zOffset = this.zOffset;
for (let i = 0, n = drawOrder.length; i < n; i++) {
let vertexSize = clipper.isClipping() ? 2 : SkeletonMesh.VERTEX_SIZE;
let slot = drawOrder[i];
if (!slot.bone.active) {
clipper.clipEndWithSlot(slot);
continue;
}
let attachment = slot.getAttachment();
let attachmentColor: Color | null;
let texture: ThreeJsTexture | null;
let numFloats = 0;
if (attachment instanceof RegionAttachment) {
let region = <RegionAttachment>attachment;
attachmentColor = region.color;
vertices = this.vertices;
numFloats = vertexSize * 4;
region.computeWorldVertices(slot, vertices, 0, vertexSize);
triangles = SkeletonMesh.QUAD_TRIANGLES;
uvs = region.uvs;
texture = <ThreeJsTexture>region.region!.texture;
} else if (attachment instanceof MeshAttachment) {
let mesh = <MeshAttachment>attachment;
attachmentColor = mesh.color;
vertices = this.vertices;
numFloats = (mesh.worldVerticesLength >> 1) * vertexSize;
if (numFloats > vertices.length) {
vertices = this.vertices = Utils.newFloatArray(numFloats);
}
mesh.computeWorldVertices(
slot,
0,
mesh.worldVerticesLength,
vertices,
0,
vertexSize
);
triangles = mesh.triangles;
uvs = mesh.uvs;
texture = <ThreeJsTexture>mesh.region!.texture;
} else if (attachment instanceof ClippingAttachment) {
let clip = <ClippingAttachment>attachment;
clipper.clipStart(slot, clip);
continue;
} else {
clipper.clipEndWithSlot(slot);
continue;
}
if (texture != null) {
let skeleton = slot.bone.skeleton;
let skeletonColor = skeleton.color;
let slotColor = slot.color;
let alpha = skeletonColor.a * slotColor.a * attachmentColor.a;
let color = this.tempColor;
color.set(skeletonColor.r * slotColor.r * attachmentColor.r,
skeletonColor.g * slotColor.g * attachmentColor.g,
skeletonColor.b * slotColor.b * attachmentColor.b,
alpha);
if (texture != null) {
let skeleton = slot.bone.skeleton;
let skeletonColor = skeleton.color;
let slotColor = slot.color;
let alpha = skeletonColor.a * slotColor.a * attachmentColor.a;
let color = this.tempColor;
color.set(
skeletonColor.r * slotColor.r * attachmentColor.r,
skeletonColor.g * slotColor.g * attachmentColor.g,
skeletonColor.b * slotColor.b * attachmentColor.b,
alpha
);
let finalVertices: NumberArrayLike;
let finalVerticesLength: number;
let finalIndices: NumberArrayLike;
let finalIndicesLength: number;
let finalVertices: NumberArrayLike;
let finalVerticesLength: number;
let finalIndices: NumberArrayLike;
let finalIndicesLength: number;
if (clipper.isClipping()) {
clipper.clipTriangles(vertices, numFloats, triangles, triangles.length, uvs, color, tempLight, false);
let clippedVertices = clipper.clippedVertices;
let clippedTriangles = clipper.clippedTriangles;
finalVertices = clippedVertices;
finalVerticesLength = clippedVertices.length;
finalIndices = clippedTriangles;
finalIndicesLength = clippedTriangles.length;
} else {
let verts = vertices;
for (let v = 2, u = 0, n = numFloats; v < n; v += vertexSize, u += 2) {
verts[v] = color.r;
verts[v + 1] = color.g;
verts[v + 2] = color.b;
verts[v + 3] = color.a;
verts[v + 4] = uvs[u];
verts[v + 5] = uvs[u + 1];
}
finalVertices = vertices;
finalVerticesLength = numFloats;
finalIndices = triangles;
finalIndicesLength = triangles.length;
}
if (clipper.isClipping()) {
clipper.clipTriangles(
vertices,
numFloats,
triangles,
triangles.length,
uvs,
color,
tempLight,
false
);
let clippedVertices = clipper.clippedVertices;
let clippedTriangles = clipper.clippedTriangles;
finalVertices = clippedVertices;
finalVerticesLength = clippedVertices.length;
finalIndices = clippedTriangles;
finalIndicesLength = clippedTriangles.length;
} else {
let verts = vertices;
for (
let v = 2, u = 0, n = numFloats;
v < n;
v += vertexSize, u += 2
) {
verts[v] = color.r;
verts[v + 1] = color.g;
verts[v + 2] = color.b;
verts[v + 3] = color.a;
verts[v + 4] = uvs[u];
verts[v + 5] = uvs[u + 1];
}
finalVertices = vertices;
finalVerticesLength = numFloats;
finalIndices = triangles;
finalIndicesLength = triangles.length;
}
if (finalVerticesLength == 0 || finalIndicesLength == 0) {
clipper.clipEndWithSlot(slot);
continue;
}
if (finalVerticesLength == 0 || finalIndicesLength == 0) {
clipper.clipEndWithSlot(slot);
continue;
}
// Start new batch if this one can't hold vertices/indices
if (!batch.canBatch(finalVerticesLength / SkeletonMesh.VERTEX_SIZE, finalIndicesLength)) {
batch.end();
batch = this.nextBatch();
batch.begin();
}
// Start new batch if this one can't hold vertices/indices
if (
!batch.canBatch(
finalVerticesLength / SkeletonMesh.VERTEX_SIZE,
finalIndicesLength
)
) {
batch.end();
batch = this.nextBatch();
batch.begin();
}
const slotBlendMode = slot.data.blendMode;
const slotTexture = texture.texture;
const materialGroup = batch.findMaterialGroup(slotTexture, slotBlendMode);
const slotBlendMode = slot.data.blendMode;
const slotTexture = texture.texture;
const materialGroup = batch.findMaterialGroup(
slotTexture,
slotBlendMode
);
batch.addMaterialGroup(finalIndicesLength, materialGroup);
batch.batch(finalVertices, finalVerticesLength, finalIndices, finalIndicesLength, z);
z += zOffset;
}
batch.addMaterialGroup(finalIndicesLength, materialGroup);
batch.batch(
finalVertices,
finalVerticesLength,
finalIndices,
finalIndicesLength,
z
);
z += zOffset;
}
clipper.clipEndWithSlot(slot);
}
clipper.clipEnd();
batch.end();
}
clipper.clipEndWithSlot(slot);
}
clipper.clipEnd();
batch.end();
}
}

View File

@ -88,7 +88,7 @@ export class LoadingScreen implements Disposable {
renderer.resize(ResizeMode.Expand);
renderer.camera.position.set(canvas.width / 2, canvas.height / 2, 0);
renderer.batcher.setBlendMode(gl.ONE, gl.ONE, gl.ONE_MINUS_SRC_ALPHA);
renderer.batcher.setBlendMode(gl.ONE, gl.ONE, gl.ONE_MINUS_SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);
if (complete) {
this.fadeOut += this.timeKeeper.delta * (this.timeKeeper.totalTime < 1 ? 2 : 1);

View File

@ -39,7 +39,7 @@ export class PolygonBatcher implements Disposable {
private context: ManagedWebGLRenderingContext;
private drawCalls = 0;
private static globalDrawCalls = 0;
private isDrawing = false;
isDrawing = false;
private mesh: Mesh;
private shader: Shader | null = null;
private lastTexture: GLTexture | null = null;
@ -47,7 +47,8 @@ export class PolygonBatcher implements Disposable {
private indicesLength = 0;
private srcColorBlend: number;
private srcAlphaBlend: number;
private dstBlend: number;
private dstColorBlend: number;
private dstAlphaBlend: number;
private cullWasEnabled = false;
constructor (context: ManagedWebGLRenderingContext | WebGLRenderingContext, twoColorTint: boolean = true, maxVertices: number = 10920) {
@ -60,7 +61,8 @@ export class PolygonBatcher implements Disposable {
let gl = this.context.gl;
this.srcColorBlend = gl.SRC_ALPHA;
this.srcAlphaBlend = gl.ONE;
this.dstBlend = gl.ONE_MINUS_SRC_ALPHA;
this.dstColorBlend = gl.ONE_MINUS_SRC_ALPHA;
this.dstAlphaBlend = gl.ONE_MINUS_SRC_ALPHA;
}
begin (shader: Shader) {
@ -72,7 +74,7 @@ export class PolygonBatcher implements Disposable {
let gl = this.context.gl;
gl.enable(gl.BLEND);
gl.blendFuncSeparate(this.srcColorBlend, this.dstBlend, this.srcAlphaBlend, this.dstBlend);
gl.blendFuncSeparate(this.srcColorBlend, this.dstColorBlend, this.srcAlphaBlend, this.dstAlphaBlend);
if (PolygonBatcher.disableCulling) {
this.cullWasEnabled = gl.isEnabled(gl.CULL_FACE);
@ -80,15 +82,16 @@ export class PolygonBatcher implements Disposable {
}
}
setBlendMode (srcColorBlend: number, srcAlphaBlend: number, dstBlend: number) {
if (this.srcColorBlend == srcColorBlend && this.srcAlphaBlend == srcAlphaBlend && this.dstBlend == dstBlend) return;
setBlendMode (srcColorBlend: number, srcAlphaBlend: number, dstColorBlend: number, dstAlphaBlend: number) {
if (this.srcColorBlend == srcColorBlend && this.srcAlphaBlend == srcAlphaBlend && this.dstColorBlend == dstColorBlend && this.dstAlphaBlend == dstAlphaBlend) return;
this.srcColorBlend = srcColorBlend;
this.srcAlphaBlend = srcAlphaBlend;
this.dstBlend = dstBlend;
this.dstColorBlend = dstColorBlend;
this.dstAlphaBlend = dstAlphaBlend;
if (this.isDrawing) {
this.flush();
let gl = this.context.gl;
gl.blendFuncSeparate(srcColorBlend, dstBlend, srcAlphaBlend, dstBlend);
gl.blendFuncSeparate(srcColorBlend, dstColorBlend, srcAlphaBlend, dstAlphaBlend);
}
}

View File

@ -43,7 +43,8 @@ export class ShapeRenderer implements Disposable {
private tmp = new Vector2();
private srcColorBlend: number;
private srcAlphaBlend: number;
private dstBlend: number;
private dstColorBlend: number;
private dstAlphaBlend: number;
constructor (context: ManagedWebGLRenderingContext | WebGLRenderingContext, maxVertices: number = 10920) {
if (maxVertices > 10920) throw new Error("Can't have more than 10920 triangles per batch: " + maxVertices);
@ -52,7 +53,8 @@ export class ShapeRenderer implements Disposable {
let gl = this.context.gl;
this.srcColorBlend = gl.SRC_ALPHA;
this.srcAlphaBlend = gl.ONE;
this.dstBlend = gl.ONE_MINUS_SRC_ALPHA;
this.dstColorBlend = gl.ONE_MINUS_SRC_ALPHA;
this.dstAlphaBlend = gl.ONE_MINUS_SRC_ALPHA;
}
begin (shader: Shader) {
@ -63,17 +65,18 @@ export class ShapeRenderer implements Disposable {
let gl = this.context.gl;
gl.enable(gl.BLEND);
gl.blendFuncSeparate(this.srcColorBlend, this.dstBlend, this.srcAlphaBlend, this.dstBlend);
gl.blendFuncSeparate(this.srcColorBlend, this.dstColorBlend, this.srcAlphaBlend, this.dstAlphaBlend);
}
setBlendMode (srcColorBlend: number, srcAlphaBlend: number, dstBlend: number) {
setBlendMode (srcColorBlend: number, srcAlphaBlend: number, dstColorBlend: number, dstAlphaBlend: number) {
this.srcColorBlend = srcColorBlend;
this.srcAlphaBlend = srcAlphaBlend;
this.dstBlend = dstBlend;
this.dstColorBlend = dstColorBlend;
this.dstAlphaBlend = dstAlphaBlend;
if (this.isDrawing) {
this.flush();
let gl = this.context.gl;
gl.blendFuncSeparate(srcColorBlend, dstBlend, srcAlphaBlend, dstBlend);
gl.blendFuncSeparate(srcColorBlend, dstColorBlend, srcAlphaBlend, dstAlphaBlend);
}
}

View File

@ -67,7 +67,7 @@ export class SkeletonDebugRenderer implements Disposable {
let skeletonY = skeleton.y;
let gl = this.context.gl;
let srcFunc = this.premultipliedAlpha ? gl.ONE : gl.SRC_ALPHA;
shapes.setBlendMode(srcFunc, gl.ONE, gl.ONE_MINUS_SRC_ALPHA);
shapes.setBlendMode(srcFunc, gl.ONE, gl.ONE_MINUS_SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);
let bones = skeleton.bones;
if (this.drawBones) {

View File

@ -168,8 +168,9 @@ export class SkeletonRenderer {
blendMode = slotBlendMode;
batcher.setBlendMode(
WebGLBlendModeConverter.getSourceColorGLBlendMode(blendMode, premultipliedAlpha),
WebGLBlendModeConverter.getSourceAlphaGLBlendMode(blendMode),
WebGLBlendModeConverter.getDestGLBlendMode(blendMode));
WebGLBlendModeConverter.getSourceAlphaGLBlendMode(blendMode, premultipliedAlpha),
WebGLBlendModeConverter.getDestColorGLBlendMode(blendMode),
WebGLBlendModeConverter.getDestAlphaGLBlendMode(blendMode, premultipliedAlpha) );
}
if (clipper.isClipping()) {

View File

@ -71,6 +71,7 @@ const ONE_MINUS_DST_ALPHA = 0x0305;
const DST_COLOR = 0x0306;
export class WebGLBlendModeConverter {
static getDestGLBlendMode (blendMode: BlendMode) {
switch (blendMode) {
case BlendMode.Normal: return ONE_MINUS_SRC_ALPHA;
@ -81,22 +82,42 @@ export class WebGLBlendModeConverter {
}
}
static getDestColorGLBlendMode (blendMode: BlendMode) {
switch (blendMode) {
case BlendMode.Normal: return ONE_MINUS_SRC_ALPHA;
case BlendMode.Additive: return ONE;
case BlendMode.Multiply: return ONE_MINUS_SRC_ALPHA;
case BlendMode.Screen: return ONE_MINUS_SRC_COLOR;
default: throw new Error("Unknown blend mode: " + blendMode);
}
}
static getDestAlphaGLBlendMode (blendMode: BlendMode, premultipliedAlpha: boolean = false) {
switch (blendMode) {
case BlendMode.Normal: return ONE_MINUS_SRC_ALPHA;
case BlendMode.Additive: return premultipliedAlpha ? ONE_MINUS_SRC_ALPHA : ONE;
case BlendMode.Multiply: return ONE_MINUS_SRC_ALPHA;
case BlendMode.Screen: return ONE_MINUS_SRC_ALPHA;
default: throw new Error("Unknown blend mode: " + blendMode);
}
}
static getSourceColorGLBlendMode (blendMode: BlendMode, premultipliedAlpha: boolean = false) {
switch (blendMode) {
case BlendMode.Normal: return premultipliedAlpha ? ONE : SRC_ALPHA;
case BlendMode.Additive: return premultipliedAlpha ? ONE : SRC_ALPHA;
case BlendMode.Multiply: return DST_COLOR;
case BlendMode.Screen: return ONE;
case BlendMode.Screen: return premultipliedAlpha ? ONE : SRC_ALPHA;
default: throw new Error("Unknown blend mode: " + blendMode);
}
}
static getSourceAlphaGLBlendMode (blendMode: BlendMode) {
static getSourceAlphaGLBlendMode (blendMode: BlendMode, premultipliedAlpha: boolean = false) {
switch (blendMode) {
case BlendMode.Normal: return ONE;
case BlendMode.Additive: return ONE;
case BlendMode.Multiply: return ONE_MINUS_SRC_ALPHA;
case BlendMode.Screen: return ONE_MINUS_SRC_COLOR;
case BlendMode.Normal: return premultipliedAlpha ? SRC_ALPHA : ONE;
case BlendMode.Additive: return premultipliedAlpha ? SRC_ALPHA : ONE;
case BlendMode.Multiply: return ONE;
case BlendMode.Screen: return ONE;
default: throw new Error("Unknown blend mode: " + blendMode);
}
}

View File

@ -14,15 +14,15 @@ ASpineboyCppPawn::ASpineboyCppPawn() {
// Called when the game starts or when spawned
void ASpineboyCppPawn::BeginPlay() {
Super::BeginPlay();
USpineSkeletonAnimationComponent *animation = FindComponentByClass<USpineSkeletonAnimationComponent>();
animation->SetAnimation(0, FString("walk"), true);
USpineSkeletonAnimationComponent *animationComponent = FindComponentByClass<USpineSkeletonAnimationComponent>();
animationComponent->SetAnimation(0, FString("walk"), true);
}
// Called every frame
void ASpineboyCppPawn::Tick(float DeltaTime) {
Super::Tick(DeltaTime);
USpineSkeletonAnimationComponent *animation = FindComponentByClass<USpineSkeletonAnimationComponent>();
spine::AnimationState *state = animation->GetAnimationState();
USpineSkeletonAnimationComponent *animationComponent = FindComponentByClass<USpineSkeletonAnimationComponent>();
spine::AnimationState *state = animationComponent->GetAnimationState();
spine::TrackEntry *entry = state->getCurrent(0);
if (entry) {
GEngine->AddOnScreenDebugMessage(-1, 0.5f, FColor::Yellow, FString(entry->getAnimation()->getName().buffer()));