Beginnings of a Spine player that's more advanced than the existing widget.

This commit is contained in:
badlogic 2018-11-01 15:51:03 +01:00
parent 3dd56a5a4f
commit d1622c0dab
6 changed files with 11675 additions and 11186 deletions

View File

@ -1682,6 +1682,40 @@ declare module spine.webgl {
static getSourceGLBlendMode(blendMode: BlendMode, premultipliedAlpha?: boolean): number;
}
}
declare module spine {
interface SpinePlayerConfig {
jsonUrl: string;
atlasUrl: string;
animation: string;
skin: string;
scale: number;
x: number;
y: number;
alpha: boolean;
fitToCanvas: boolean;
backgroundColor: string;
premultipliedAlpha: boolean;
success: (widget: SpineWidget) => void;
error: (widget: SpineWidget, msg: string) => void;
}
class SpinePlayer {
private config;
private sceneRenderer;
private canvas;
private context;
private loadingScreen;
private assetManager;
private loaded;
private skeleton;
private animationState;
constructor(parent: HTMLElement, config: SpinePlayerConfig);
validateConfig(config: SpinePlayerConfig): SpinePlayerConfig;
render(parent: HTMLElement, config: SpinePlayerConfig): void;
drawFrame(): void;
loadSkeleton(): void;
private resize;
}
}
declare module spine {
class SpineWidget {
skeleton: Skeleton;

View File

@ -7174,11 +7174,11 @@ var spine;
var renderer = this.renderer;
var canvas = renderer.canvas;
var gl = renderer.context.gl;
renderer.resize(webgl.ResizeMode.Stretch);
var oldX = renderer.camera.position.x, oldY = renderer.camera.position.y;
renderer.camera.position.set(canvas.width / 2, canvas.height / 2, 0);
renderer.camera.viewportWidth = canvas.width;
renderer.camera.viewportHeight = canvas.height;
renderer.resize(webgl.ResizeMode.Stretch);
if (!complete) {
gl.clearColor(this.backgroundColor.r, this.backgroundColor.g, this.backgroundColor.b, this.backgroundColor.a);
gl.clear(gl.COLOR_BUFFER_BIT);
@ -9399,6 +9399,138 @@ var spine;
})(webgl = spine.webgl || (spine.webgl = {}));
})(spine || (spine = {}));
var spine;
(function (spine) {
var SpinePlayer = (function () {
function SpinePlayer(parent, config) {
this.config = config;
this.validateConfig(config);
this.render(parent, config);
}
SpinePlayer.prototype.validateConfig = function (config) {
if (!config)
throw new Error("Please pass a configuration to new.spine.SpinePlayer().");
if (!config.jsonUrl)
throw new Error("Please specify the URL of the skeleton JSON file.");
if (!config.atlasUrl)
throw new Error("Please specify the URL of the atlas file.");
if (!config.scale)
config.scale = 1;
if (!config.x)
config.x = 0;
if (!config.y)
config.y = 0;
if (!config.alpha)
config.alpha = false;
if (!config.fitToCanvas)
config.fitToCanvas = true;
if (!config.backgroundColor)
config.backgroundColor = "#000000";
if (!config.premultipliedAlpha)
config.premultipliedAlpha = false;
if (!config.success)
config.success = function (widget) { };
if (!config.error)
config.error = function (widget, msg) { };
return config;
};
SpinePlayer.prototype.render = function (parent, config) {
var _this = this;
parent.innerHTML = "\n\t\t\t\t<div class=\"spine-player\">\n\t\t\t\t\t<canvas class=\"spine-player-canvas\"></canvas>\n\t\t\t\t\t<div class=\"spine-player-controls\">\n\t\t\t\t\t\t<div class=\"spine-player-timeline\">\n\t\t\t\t\t\t\t<div class=\"spine-player-timeline-slider\"></div>\n\t\t\t\t\t\t</div>\n\t\t\t\t\t\t<div class=\"spine-player-buttons\">\n\t\t\t\t\t\t\t<button id=\"spine-player-button-play-pause\" class=\"spine-player-button spine-player-button-icon-play\"></button>\n\t\t\t\t\t\t\t<div class=\"spine-player-button-spacer\"></div>\n\t\t\t\t\t\t\t<button id=\"spine-player-button-speed\" class=\"spine-player-button\">\n\t\t\t\t\t\t\t\tSpeed\n\t\t\t\t\t\t\t</button>\n\t\t\t\t\t\t\t<button id=\"spine-player-button-animation\" class=\"spine-player-button\">\n\t\t\t\t\t\t\t\tAnimation\n\t\t\t\t\t\t\t</button>\n\t\t\t\t\t\t\t<button id=\"spine-player-button-skin\" class=\"spine-player-button\">\n\t\t\t\t\t\t\t\tSkin\n\t\t\t\t\t\t\t</button>\n\t\t\t\t\t\t\t<button id=\"spine-player-button-settings\" class=\"spine-player-button\">\n\t\t\t\t\t\t\t\tSettings\n\t\t\t\t\t\t\t</button>\n\t\t\t\t\t\t\t<button id=\"spine-player-button-fullscreen\" class=\"spine-player-button\">\n\t\t\t\t\t\t\t\tFullscreen\n\t\t\t\t\t\t\t</button>\n\t\t\t\t\t\t</div>\n\t\t\t\t\t</div>\n\t\t\t\t</div>\n\t\t\t";
this.canvas = findWithClass(parent, "spine-player-canvas")[0];
var slider = findWithClass(parent, "spine-player-timeline-slider")[0];
var playButton = findWithId(parent, "spine-player-button-play-pause")[0];
var animationButton = findWithId(parent, "spine-player-button-animation")[0];
var skinButton = findWithId(parent, "spine-player-button-skin")[0];
var settingsButton = findWithId(parent, "spine-player-button-settings")[0];
var fullscreenButton = findWithId(parent, "spine-player-button-fullscreen")[0];
var webglConfig = { alpha: config.alpha };
this.context = new spine.webgl.ManagedWebGLRenderingContext(this.canvas, webglConfig);
this.sceneRenderer = new spine.webgl.SceneRenderer(this.canvas, this.context, true);
this.loadingScreen = new spine.webgl.LoadingScreen(this.sceneRenderer);
this.assetManager = new spine.webgl.AssetManager(this.context);
this.assetManager.loadText(config.jsonUrl);
this.assetManager.loadTextureAtlas(config.atlasUrl);
requestAnimationFrame(function () { return _this.drawFrame(); });
};
SpinePlayer.prototype.drawFrame = function () {
var _this = this;
requestAnimationFrame(function () { return _this.drawFrame(); });
var ctx = this.context;
var gl = ctx.gl;
var bg = new spine.Color().setFromString(this.config.backgroundColor);
gl.clearColor(bg.r, bg.g, bg.b, bg.a);
gl.clear(gl.COLOR_BUFFER_BIT);
this.loadingScreen.draw(this.assetManager.isLoadingComplete());
if (this.assetManager.isLoadingComplete() && this.skeleton == null)
this.loadSkeleton();
this.sceneRenderer.resize(spine.webgl.ResizeMode.Expand);
if (this.loaded) {
this.skeleton.x = this.config.x;
this.skeleton.y = this.config.y;
this.skeleton.scaleX = this.skeleton.scaleY = this.config.scale;
this.skeleton.updateWorldTransform();
this.sceneRenderer.camera.position.x = 0;
this.sceneRenderer.camera.position.y = 0;
this.sceneRenderer.begin();
this.sceneRenderer.line(0, 0, 200, 0, spine.Color.RED);
this.sceneRenderer.line(0, 0, 0, 200, spine.Color.GREEN);
this.sceneRenderer.drawSkeleton(this.skeleton, this.config.premultipliedAlpha);
this.sceneRenderer.end();
}
};
SpinePlayer.prototype.loadSkeleton = function () {
if (this.loaded)
return;
var atlas = this.assetManager.get(this.config.atlasUrl);
var jsonText = this.assetManager.get(this.config.jsonUrl);
var json = new spine.SkeletonJson(new spine.AtlasAttachmentLoader(atlas));
var skeletonData = json.readSkeletonData(jsonText);
this.skeleton = new spine.Skeleton(skeletonData);
this.loaded = true;
};
SpinePlayer.prototype.resize = function () {
var canvas = this.canvas;
var w = canvas.clientWidth;
var h = canvas.clientHeight;
var devicePixelRatio = window.devicePixelRatio || 1;
if (canvas.width != Math.floor(w * devicePixelRatio) || canvas.height != Math.floor(h * devicePixelRatio)) {
canvas.width = Math.floor(w * devicePixelRatio);
canvas.height = Math.floor(h * devicePixelRatio);
}
this.context.gl.viewport(0, 0, canvas.width, canvas.height);
this.sceneRenderer.camera.setViewport(canvas.width, canvas.height);
};
return SpinePlayer;
}());
spine.SpinePlayer = SpinePlayer;
function findWithId(dom, id) {
var found = new Array();
var findRecursive = function (dom, id, found) {
for (var i = 0; i < dom.children.length; i++) {
var child = dom.children[i];
if (child.id === id)
found.push(child);
findRecursive(child, id, found);
}
};
findRecursive(dom, id, found);
return found;
}
function findWithClass(dom, className) {
var found = new Array();
var findRecursive = function (dom, className, found) {
for (var i = 0; i < dom.children.length; i++) {
var child = dom.children[i];
if (child.classList.contains(className))
found.push(child);
findRecursive(child, className, found);
}
};
findRecursive(dom, className, found);
return found;
}
})(spine || (spine = {}));
var spine;
(function (spine) {
var SpineWidget = (function () {
function SpineWidget(element, config) {

File diff suppressed because one or more lines are too long

View File

@ -86,11 +86,12 @@ module spine.webgl {
let canvas = renderer.canvas;
let gl = renderer.context.gl;
renderer.resize(ResizeMode.Stretch);
let oldX = renderer.camera.position.x, oldY = renderer.camera.position.y;
renderer.camera.position.set(canvas.width / 2, canvas.height / 2, 0);
renderer.camera.viewportWidth = canvas.width;
renderer.camera.viewportHeight = canvas.height;
renderer.resize(ResizeMode.Stretch);
if (!complete) {
gl.clearColor(this.backgroundColor.r, this.backgroundColor.g, this.backgroundColor.b, this.backgroundColor.a);

View File

@ -0,0 +1,98 @@
<html>
<script src="../../build/spine-widget.js"></script>
<style>
body {
background: gray;
}
.spine-player {
width: 100%;
height: 100%;
background: red;
position: relative;
}
.spine-player canvas {
width: 100%;
height: 100%;
}
.spine-player .spine-player-controls {
display: flex;
flex-direction: column;
position: absolute;
bottom: 0;
left: 0;
width: 100%;
opacity: 1;
}
.spine-player:hover .spine-player-controls {
opacity: 1;
}
.spine-player .spine-player-timeline {
width: 100%;
background: green;
position: relative;
background: rgba(0, 0, 0, 0.7);
cursor: pointer;
}
.spine-player .spine-player-timeline-slider {
width: 50%;
height: 8px;
background: #62B0EE;
cursor: pointer;
}
.spine-player .spine-player-buttons {
display: flex;
flex-direction: row;
width: 100%;
background: rgba(0, 0, 0, 0.6);
}
.spine-player .spine-player-button {
background: none;
outline: 0;
border: none;
height: 32px;
cursor: pointer;
color: white;
}
.spine-player .spine-player-button-spacer {
flex: 1;
}
.spine-player .spine-player-button-icon-play {
width: 32px;
background-image: url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cdefs%3E%3Cstyle%3E.icon-canvas-transparent,.icon-vs-out{fill:%23252526}.icon-canvas-transparent{opacity:0}.icon-vs-action-green{fill:%2389d185}%3C/style%3E%3C/defs%3E%3Ctitle%3Econtinue%3C/title%3E%3Cpath class='icon-canvas-transparent' d='M16 0v16H0V0z' id='canvas'/%3E%3Cpath class='icon-vs-action-green' d='M4 1.5v13L12.667 8 4 1.5z' id='iconBg'/%3E%3C/svg%3E");
fill: white;
}
.spine-player .spine-player-button-icon-pause {
width: 32px;
background-image: url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cdefs%3E%3Cstyle%3E.icon-canvas-transparent,.icon-vs-out{fill:%23252526}.icon-canvas-transparent{opacity:0}.icon-vs-action-blue{fill:%2375beff}%3C/style%3E%3C/defs%3E%3Ctitle%3Epause%3C/title%3E%3Cpath class='icon-canvas-transparent' d='M16 0v16H0V0z' id='canvas'/%3E%3Cpath class='icon-vs-action-blue' d='M4 3h2.5v10H4zm5.5 0v10H12V3z' id='iconBg'/%3E%3C/svg%3E");
}
</style>
<body>
<div id="container" style="width: 100%; height: 100%;"></div>
</body>
<script>
new spine.SpinePlayer(document.getElementById("container"), {
jsonUrl: "assets/spineboy-ess.json",
atlasUrl: "assets/spineboy.atlas",
backgroundColor: "#cccccc",
scale: 0.5,
x: 0,
y: 0
});
</script>
</body>
</html>

View File

@ -0,0 +1,224 @@
/******************************************************************************
* Spine Runtimes Software License v2.5
*
* Copyright (c) 2013-2016, Esoteric Software
* All rights reserved.
*
* You are granted a perpetual, non-exclusive, non-sublicensable, and
* non-transferable license to use, install, execute, and perform the Spine
* Runtimes software and derivative works solely for personal or internal
* use. Without the written permission of Esoteric Software (see Section 2 of
* the Spine Software License Agreement), you may not (a) modify, translate,
* adapt, or develop new applications using the Spine Runtimes or otherwise
* create derivative works or improvements of the Spine Runtimes or (b) remove,
* delete, alter, or obscure any trademarks or any copyright, trademark, patent,
* or other intellectual property or proprietary rights notices on or in the
* Software, including any copy thereof. Redistributions in binary or source
* form must include this license and terms.
*
* THIS SOFTWARE IS PROVIDED BY ESOTERIC SOFTWARE "AS IS" AND ANY EXPRESS OR
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
* EVENT SHALL ESOTERIC SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, BUSINESS INTERRUPTION, OR LOSS OF
* USE, DATA, OR PROFITS) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
* IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*****************************************************************************/
module spine {
export interface SpinePlayerConfig {
jsonUrl: string;
atlasUrl: string;
animation: string;
skin: string;
scale: number;
x: number;
y: number;
alpha: boolean;
fitToCanvas: boolean;
backgroundColor: string;
premultipliedAlpha: boolean;
success: (widget: SpineWidget) => void;
error: (widget: SpineWidget, msg: string) => void;
}
export class SpinePlayer {
private sceneRenderer: spine.webgl.SceneRenderer;
private canvas: HTMLCanvasElement;
private context: spine.webgl.ManagedWebGLRenderingContext;
private loadingScreen: spine.webgl.LoadingScreen;
private assetManager: spine.webgl.AssetManager;
private loaded: boolean;
private skeleton: Skeleton;
private animationState: AnimationState;
constructor(parent: HTMLElement, private config: SpinePlayerConfig) {
this.validateConfig(config);
this.render(parent, config);
}
validateConfig(config: SpinePlayerConfig): SpinePlayerConfig {
if (!config) throw new Error("Please pass a configuration to new.spine.SpinePlayer().");
if (!config.jsonUrl) throw new Error("Please specify the URL of the skeleton JSON file.");
if (!config.atlasUrl) throw new Error("Please specify the URL of the atlas file.");
if (!config.scale) config.scale = 1;
if (!config.x) config.x = 0;
if (!config.y) config.y = 0;
if (!config.alpha) config.alpha = false;
if (!config.fitToCanvas) config.fitToCanvas = true;
if (!config.backgroundColor) config.backgroundColor = "#000000";
if (!config.premultipliedAlpha) config.premultipliedAlpha = false;
if (!config.success) config.success = (widget) => {};
if (!config.error) config.error = (widget, msg) => {};
return config;
}
render(parent: HTMLElement, config: SpinePlayerConfig) {
parent.innerHTML = /*html*/`
<div class="spine-player">
<canvas class="spine-player-canvas"></canvas>
<div class="spine-player-controls">
<div class="spine-player-timeline">
<div class="spine-player-timeline-slider"></div>
</div>
<div class="spine-player-buttons">
<button id="spine-player-button-play-pause" class="spine-player-button spine-player-button-icon-play"></button>
<div class="spine-player-button-spacer"></div>
<button id="spine-player-button-speed" class="spine-player-button">
Speed
</button>
<button id="spine-player-button-animation" class="spine-player-button">
Animation
</button>
<button id="spine-player-button-skin" class="spine-player-button">
Skin
</button>
<button id="spine-player-button-settings" class="spine-player-button">
Settings
</button>
<button id="spine-player-button-fullscreen" class="spine-player-button">
Fullscreen
</button>
</div>
</div>
</div>
`;
this.canvas = findWithClass(parent, "spine-player-canvas")[0] as HTMLCanvasElement;
let slider = findWithClass(parent, "spine-player-timeline-slider")[0];
let playButton = findWithId(parent, "spine-player-button-play-pause")[0];
let animationButton = findWithId(parent, "spine-player-button-animation")[0];
let skinButton = findWithId(parent, "spine-player-button-skin")[0];
let settingsButton = findWithId(parent, "spine-player-button-settings")[0];
let fullscreenButton = findWithId(parent, "spine-player-button-fullscreen")[0];
// Setup the scene renderer and OpenGL context
var webglConfig = { alpha: config.alpha };
this.context = new spine.webgl.ManagedWebGLRenderingContext(this.canvas, webglConfig);
// Setup the scene renderer and loading screen
this.sceneRenderer = new spine.webgl.SceneRenderer(this.canvas, this.context, true);
this.loadingScreen = new spine.webgl.LoadingScreen(this.sceneRenderer);
// Load the assets
this.assetManager = new spine.webgl.AssetManager(this.context);
this.assetManager.loadText(config.jsonUrl);
this.assetManager.loadTextureAtlas(config.atlasUrl);
// Setup rendering loop
requestAnimationFrame(() => this.drawFrame());
}
drawFrame () {
requestAnimationFrame(() => this.drawFrame());
let ctx = this.context;
let gl = ctx.gl;
// Clear the viewport
let bg = new Color().setFromString(this.config.backgroundColor);
gl.clearColor(bg.r, bg.g, bg.b, bg.a);
gl.clear(gl.COLOR_BUFFER_BIT);
// Display loading screen
this.loadingScreen.draw(this.assetManager.isLoadingComplete());
// Have we finished loading the asset? Then set things up
if (this.assetManager.isLoadingComplete() && this.skeleton == null) this.loadSkeleton();
// Resize the canvas
this.sceneRenderer.resize(webgl.ResizeMode.Expand);
// Update and draw the skeleton
if (this.loaded) {
this.skeleton.x = this.config.x;
this.skeleton.y = this.config.y;
this.skeleton.scaleX = this.skeleton.scaleY = this.config.scale;
this.skeleton.updateWorldTransform();
this.sceneRenderer.camera.position.x = 0;
this.sceneRenderer.camera.position.y = 0;
this.sceneRenderer.begin();
this.sceneRenderer.line(0, 0, 200, 0, Color.RED);
this.sceneRenderer.line(0, 0, 0, 200, Color.GREEN);
this.sceneRenderer.drawSkeleton(this.skeleton, this.config.premultipliedAlpha);
this.sceneRenderer.end();
}
}
loadSkeleton () {
if (this.loaded) return;
let atlas = this.assetManager.get(this.config.atlasUrl);
let jsonText = this.assetManager.get(this.config.jsonUrl);
let json = new SkeletonJson(new AtlasAttachmentLoader(atlas));
let skeletonData = json.readSkeletonData(jsonText);
this.skeleton = new Skeleton(skeletonData);
this.loaded = true;
}
private resize () {
let canvas = this.canvas;
let w = canvas.clientWidth;
let h = canvas.clientHeight;
var devicePixelRatio = window.devicePixelRatio || 1;
if (canvas.width != Math.floor(w * devicePixelRatio) || canvas.height != Math.floor(h * devicePixelRatio)) {
canvas.width = Math.floor(w * devicePixelRatio);
canvas.height = Math.floor(h * devicePixelRatio);
}
this.context.gl.viewport(0, 0, canvas.width, canvas.height);
this.sceneRenderer.camera.setViewport(canvas.width, canvas.height);
}
}
function findWithId(dom: HTMLElement, id: string): HTMLElement[] {
let found = new Array<HTMLElement>()
let findRecursive = (dom: HTMLElement, id: string, found: HTMLElement[]) => {
for(var i = 0; i < dom.children.length; i++) {
let child = dom.children[i] as HTMLElement;
if (child.id === id) found.push(child);
findRecursive(child, id, found);
}
};
findRecursive(dom, id, found);
return found;
}
function findWithClass(dom: HTMLElement, className: string): HTMLElement[] {
let found = new Array<HTMLElement>()
let findRecursive = (dom: HTMLElement, className: string, found: HTMLElement[]) => {
for(var i = 0; i < dom.children.length; i++) {
let child = dom.children[i] as HTMLElement;
if (child.classList.contains(className)) found.push(child);
findRecursive(child, className, found);
}
};
findRecursive(dom, className, found);
return found;
}
}