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,64 +1,138 @@
<!DOCTYPE html>
<html>
<head>
<meta charset='utf-8'>
<meta http-equiv='X-UA-Compatible' content='IE=edge'>
<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>
<meta name="viewport" content="width=device-width, initial-scale=1" />
</head>
<body>
<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>
<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>
<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/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/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/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>
<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/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/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/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>
@ -71,9 +145,17 @@
<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/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>
</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,11 +29,28 @@
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) {
constructor(scene: Phaser.Scene, type: string) {
super(scene, type);
}
}
@ -41,12 +58,17 @@ class BaseSpineGameObject extends Phaser.GameObjects.GameObject {
/** 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 };
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) {
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
@ -55,22 +77,35 @@ export class SetupPoseBoundsProvider implements SpineGameObjectBoundsProvider {
skeleton.setToSetupPose();
skeleton.updateWorldTransform();
const bounds = skeleton.getBoundsRect();
return bounds.width == Number.NEGATIVE_INFINITY ? { x: 0, y: 0, width: 0, height: 0 } : bounds;
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 {
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) {
}
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 };
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.
@ -88,13 +123,19 @@ export class SkinsAndAnimationBoundsProvider implements SpineGameObjectBoundsPro
}
skeleton.setToSetupPose();
const animation = this.animation != null ? data.findAnimation(this.animation!) : null;
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;
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;
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);
@ -109,8 +150,15 @@ export class SkinsAndAnimationBoundsProvider implements SpineGameObjectBoundsPro
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 = {
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,17 +184,35 @@ 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)))))))) {
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 = () => { };
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);
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);
@ -157,7 +223,7 @@ export class SpineGameObject extends DepthMixin(OriginMixin(ComputedSizeMixin(Fl
this.updateSize();
}
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
@ -170,28 +236,38 @@ export class SpineGameObject extends DepthMixin(OriginMixin(ComputedSizeMixin(Fl
}
/** Converts a point from the skeleton coordinate system to the Phaser world coordinate system. */
skeletonToPhaserWorldCoordinates (point: { x: number, y: number }) {
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
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 }) {
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
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) {
phaserWorldCoordinatesToBone(point: { x: number; y: number }, bone: Bone) {
this.phaserWorldCoordinatesToSkeleton(point);
if (bone.parent) {
bone.parent.worldToLocal(point as Vector2);
@ -204,7 +280,7 @@ export class SpineGameObject extends DepthMixin(OriginMixin(ComputedSizeMixin(Fl
* 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) {
updatePose(delta: number) {
this.animationState.update(delta / 1000);
this.animationState.apply(this.skeleton);
this.beforeUpdateWorldTransforms(this);
@ -212,26 +288,41 @@ export class SpineGameObject extends DepthMixin(OriginMixin(ComputedSizeMixin(Fl
this.afterUpdateWorldTransforms(this);
}
preUpdate (time: number, delta: number) {
preUpdate(time: number, delta: number) {
if (!this.skeleton || !this.animationState) return;
this.updatePose(delta);
}
preDestroy () {
preDestroy() {
// FIXME tear down any event emitters
}
willRender (camera: Phaser.Cameras.Scene2D.Camera) {
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 result = !this.skeleton || !(GameObjectRenderMask !== this.renderFlags || (this.cameraFilter !== 0 && this.cameraFilter & camera.id));
if (!result && this.parentContainer && this.plugin.webGLRenderer) {
var sceneRenderer = this.plugin.webGLRenderer;
if (this.plugin.gl && this.plugin.phaserRenderer instanceof Phaser.Renderer.WebGL.WebGLRenderer && sceneRenderer.batcher.isDrawing) {
sceneRenderer.end();
this.plugin.phaserRenderer.pipelines.rebind();
}
}
return result;
}
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;
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;
let sceneRenderer = this.plugin.webGLRenderer;
if (renderer.newType) {
@ -240,16 +331,31 @@ export class SpineGameObject extends DepthMixin(OriginMixin(ComputedSizeMixin(Fl
}
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) => {
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;
}
});
}
);
if (!renderer.nextTypeMatch) {
sceneRenderer.end();
@ -257,15 +363,25 @@ export class SpineGameObject extends DepthMixin(OriginMixin(ComputedSizeMixin(Fl
}
}
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;
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;
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 transform = Phaser.GameObjects.GetCalcMatrix(
src,
camera,
parentMatrix
).calc;
let skeleton = this.skeleton;
skeleton.x = transform.tx;
skeleton.y = transform.ty;

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,7 +265,6 @@ 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;
@ -272,7 +272,6 @@ class SpineSkeletonDataFile extends Phaser.Loader.MultiFile {
fileType = config.type === "spineJson" ? SpineSkeletonDataFileType.json : SpineSkeletonDataFileType.binary;
xhrSettings = config.xhrSettings;
}
let file = null;
let isJson = fileType == SpineSkeletonDataFileType.json;
if (isJson) {
@ -311,7 +310,6 @@ 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;

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,13 +1,12 @@
<html>
<head>
<meta charset="UTF-8">
<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>
</head>
<style>
* {
margin: 0;
padding: 0;
@ -15,7 +14,7 @@
body,
html {
height: 100%
height: 100%;
}
canvas {
@ -23,9 +22,9 @@
width: 100%;
height: 100%;
}
</style>
</style>
<body>
<body>
<script>
(function () {
let scene, camera, renderer;
@ -37,12 +36,16 @@
let baseUrl = "assets/";
let skeletonFile = "raptor-pro.json";
let atlasFile = skeletonFile.replace("-pro", "").replace("-ess", "").replace(".json", ".atlas");
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;
let width = window.innerWidth,
height = window.innerHeight;
camera = new THREE.PerspectiveCamera(75, width / height, 1, 3000);
camera.position.y = 100;
camera.position.z = 400;
@ -65,7 +68,10 @@
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 });
material = new THREE.MeshBasicMaterial({
color: 0xff0000,
wireframe: true,
});
mesh = new THREE.Mesh(geometry, material);
scene.add(mesh);
@ -81,18 +87,22 @@
// 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));
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) => {
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);
}
@ -134,8 +144,7 @@
}
init();
}());
})();
</script>
</body>
</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,15 +27,32 @@
* 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) {
constructor(customizer: SkeletonMeshMaterialParametersCustomizer) {
let vertexShader = `
attribute vec4 color;
varying vec2 vUv;
@ -69,17 +86,17 @@ export class SkeletonMeshMaterial extends THREE.ShaderMaterial {
fragmentShader: fragmentShader,
side: THREE.DoubleSide,
transparent: true,
depthWrite: false,
alphaTest: 0.0
depthWrite: true,
alphaTest: 0.0,
};
customizer(parameters);
if (parameters.alphaTest && parameters.alphaTest > 0) {
parameters.defines = { "USE_SPINE_ALPHATEST": 1 };
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 {
@ -101,7 +118,12 @@ export class SkeletonMesh extends THREE.Object3D {
private vertices = Utils.newFloatArray(1024);
private tempColor = new Color();
constructor (skeletonData: SkeletonData, private materialCustomerizer: SkeletonMeshMaterialParametersCustomizer = (material) => { }) {
constructor(
skeletonData: SkeletonData,
private materialCustomerizer: SkeletonMeshMaterialParametersCustomizer = (
material
) => {}
) {
super();
this.skeleton = new Skeleton(skeletonData);
@ -109,7 +131,7 @@ export class SkeletonMesh extends THREE.Object3D {
this.state = new AnimationState(animData);
}
update (deltaTime: number) {
update(deltaTime: number) {
let state = this.state;
let skeleton = this.skeleton;
@ -120,13 +142,13 @@ export class SkeletonMesh extends THREE.Object3D {
this.updateGeometry();
}
dispose () {
dispose() {
for (var i = 0; i < this.batches.length; i++) {
this.batches[i].dispose();
}
}
private clearBatches () {
private clearBatches() {
for (var i = 0; i < this.batches.length; i++) {
this.batches[i].clear();
this.batches[i].visible = false;
@ -134,7 +156,7 @@ export class SkeletonMesh extends THREE.Object3D {
this.nextBatchIndex = 0;
}
private nextBatch () {
private nextBatch() {
if (this.batches.length == this.nextBatchIndex) {
let batch = new MeshBatcher(10920, this.materialCustomerizer);
this.add(batch);
@ -145,7 +167,7 @@ export class SkeletonMesh extends THREE.Object3D {
return batch;
}
private updateGeometry () {
private updateGeometry() {
this.clearBatches();
let tempPos = this.tempPos;
@ -190,12 +212,19 @@ export class SkeletonMesh extends THREE.Object3D {
if (numFloats > vertices.length) {
vertices = this.vertices = Utils.newFloatArray(numFloats);
}
mesh.computeWorldVertices(slot, 0, mesh.worldVerticesLength, vertices, 0, vertexSize);
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);
let clip = <ClippingAttachment>attachment;
clipper.clipStart(slot, clip);
continue;
} else {
@ -209,10 +238,12 @@ export class SkeletonMesh extends THREE.Object3D {
let slotColor = slot.color;
let alpha = skeletonColor.a * slotColor.a * attachmentColor.a;
let color = this.tempColor;
color.set(skeletonColor.r * slotColor.r * attachmentColor.r,
color.set(
skeletonColor.r * slotColor.r * attachmentColor.r,
skeletonColor.g * slotColor.g * attachmentColor.g,
skeletonColor.b * slotColor.b * attachmentColor.b,
alpha);
alpha
);
let finalVertices: NumberArrayLike;
let finalVerticesLength: number;
@ -220,7 +251,16 @@ export class SkeletonMesh extends THREE.Object3D {
let finalIndicesLength: number;
if (clipper.isClipping()) {
clipper.clipTriangles(vertices, numFloats, triangles, triangles.length, uvs, color, tempLight, false);
clipper.clipTriangles(
vertices,
numFloats,
triangles,
triangles.length,
uvs,
color,
tempLight,
false
);
let clippedVertices = clipper.clippedVertices;
let clippedTriangles = clipper.clippedTriangles;
finalVertices = clippedVertices;
@ -229,7 +269,11 @@ export class SkeletonMesh extends THREE.Object3D {
finalIndicesLength = clippedTriangles.length;
} else {
let verts = vertices;
for (let v = 2, u = 0, n = numFloats; v < n; v += vertexSize, u += 2) {
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;
@ -249,7 +293,12 @@ export class SkeletonMesh extends THREE.Object3D {
}
// Start new batch if this one can't hold vertices/indices
if (!batch.canBatch(finalVerticesLength / SkeletonMesh.VERTEX_SIZE, finalIndicesLength)) {
if (
!batch.canBatch(
finalVerticesLength / SkeletonMesh.VERTEX_SIZE,
finalIndicesLength
)
) {
batch.end();
batch = this.nextBatch();
batch.begin();
@ -257,10 +306,19 @@ export class SkeletonMesh extends THREE.Object3D {
const slotBlendMode = slot.data.blendMode;
const slotTexture = texture.texture;
const materialGroup = batch.findMaterialGroup(slotTexture, slotBlendMode);
const materialGroup = batch.findMaterialGroup(
slotTexture,
slotBlendMode
);
batch.addMaterialGroup(finalIndicesLength, materialGroup);
batch.batch(finalVertices, finalVerticesLength, finalIndices, finalIndicesLength, z);
batch.batch(
finalVertices,
finalVerticesLength,
finalIndices,
finalIndicesLength,
z
);
z += zOffset;
}

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()));