mirror of
https://github.com/EsotericSoftware/spine-runtimes.git
synced 2025-12-20 09:16:01 +08:00
389 lines
12 KiB
HTML
389 lines
12 KiB
HTML
<!-----------------------------------------------------------------------------
|
|
-- Spine Runtimes Software License
|
|
-- Version 2.1
|
|
--
|
|
-- Copyright (c) 2013, Esoteric Software
|
|
-- All rights reserved.
|
|
--
|
|
-- You are granted a perpetual, non-exclusive, non-sublicensable and
|
|
-- non-transferable license to install, execute and perform the Spine Runtimes
|
|
-- Software (the "Software") solely for internal use. Without the written
|
|
-- permission of Esoteric Software (typically granted by licensing Spine), you
|
|
-- may not (a) modify, translate, adapt or otherwise create derivative works,
|
|
-- improvements of the Software or develop new applications using the Software
|
|
-- 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 SOFTARE BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
|
-- SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
|
-- PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
|
|
-- OR BUSINESS INTERRUPTION) 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.
|
|
------------------------------------------------------------------------------>
|
|
|
|
<html>
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<title>spine-threejs</title>
|
|
<script src="../spine-js/spine.js"></script>
|
|
<script src="../three.min.js"></script>
|
|
|
|
<style>body, input { font-family: tahoma; font-size: 11pt }</style>
|
|
</head>
|
|
<body>
|
|
|
|
<script>
|
|
(function () {
|
|
|
|
var loadText = function (url, callback) {
|
|
var req = new XMLHttpRequest();
|
|
req.open("GET", url, true);
|
|
req.responseType = 'text';
|
|
req.addEventListener('error', function (event) {}, false);
|
|
req.addEventListener('abort', function (event) {}, false);
|
|
req.addEventListener('load', function (event) { callback(req.response); }, false);
|
|
req.send();
|
|
return req;
|
|
};
|
|
|
|
var loadImage = function (url, callback) {
|
|
var image = new Image();
|
|
image.addEventListener('error', function (event) {}, false);
|
|
image.addEventListener('abort', function (event) {}, false);
|
|
image.addEventListener('load', function (event) { callback (image); }, false);
|
|
image.src = url;
|
|
return image;
|
|
};
|
|
|
|
SpineAnimation = function (name, path, scale) {
|
|
|
|
THREE.Object3D.call(this);
|
|
|
|
this.name = name;
|
|
|
|
path = path ? (path +
|
|
((path.substr(-1) != '/') ? '/' : '')
|
|
) : '';
|
|
|
|
var self = this;
|
|
|
|
loadText(path + name + '.atlas', function (atlasText) {
|
|
self.atlas = new spine.Atlas(atlasText, {
|
|
load: function (page, image, atlas) {
|
|
loadImage(path + image, function (image) {
|
|
// calculate UVs in atlas regions
|
|
page.width = image.width;
|
|
page.height = image.height;
|
|
|
|
atlas.updateUVs(page);
|
|
|
|
// propagate new UVs to attachments, if they were already created
|
|
if (self.skeleton) {
|
|
var skins = self.skeleton.data.skins;
|
|
for (var s = 0, n = skins.length; s < n; s++) {
|
|
var attachments = skins[s].attachments;
|
|
for (var k in attachments) {
|
|
var attachment = attachments[k];
|
|
if (attachment instanceof spine.RegionAttachment) {
|
|
var region = attachment.rendererObject;
|
|
attachment.setUVs(region.u, region.v, region.u2, region.v2, region.rotate);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// create basic material for the page
|
|
var texture = new THREE.Texture(image);
|
|
texture.needsUpdate = true;
|
|
|
|
page.rendererObject = [
|
|
new THREE.MeshBasicMaterial({
|
|
//color: 0xff00, wireframe: true,
|
|
map : texture, side : THREE.DoubleSide, transparent : true, alphaTest : 0.5
|
|
})
|
|
];
|
|
});
|
|
},
|
|
unload: function (materials) {
|
|
for (var i = 0, n = materials.length; i < n; i++) {
|
|
var material = materials[i];
|
|
if (material.meshes) {
|
|
for (var name in material.meshes) {
|
|
var mesh = material.meshes[name];
|
|
if (mesh.parent) mesh.parent.remove(mesh);
|
|
mesh.geometry.dispose();
|
|
}
|
|
}
|
|
material.map.dispose();
|
|
material.dispose();
|
|
}
|
|
// will be called multiple times
|
|
materials.length = 0;
|
|
}
|
|
});
|
|
|
|
loadText(path + name + '.json', function (skeletonText) {
|
|
var json = new spine.SkeletonJson(new spine.AtlasAttachmentLoader(self.atlas));
|
|
json.scale = scale || 1;
|
|
|
|
var skeletonData = json.readSkeletonData(JSON.parse(skeletonText));
|
|
|
|
self.skeleton = new spine.Skeleton(skeletonData);
|
|
self.stateData = new spine.AnimationStateData(skeletonData);
|
|
self.state = new spine.AnimationState(self.stateData);
|
|
|
|
self.dispatchEvent({
|
|
type : SpineAnimation.SKELETON_DATA_LOADED
|
|
});
|
|
});
|
|
});
|
|
|
|
var matrix = new THREE.Matrix4();
|
|
|
|
// if given, dt must be in ms
|
|
|
|
this.update = function (dt, dz) {
|
|
if (!this.state) return;
|
|
|
|
this.state.update(dt || (1.0 / 60));
|
|
this.state.apply(this.skeleton);
|
|
this.skeleton.updateWorldTransform();
|
|
|
|
this.traverse(function (object) {
|
|
if (object instanceof THREE.Mesh) {
|
|
object.visible = false;
|
|
}
|
|
});
|
|
|
|
var Z = 0;
|
|
var drawOrder = this.skeleton.drawOrder;
|
|
for (var i = 0, n = drawOrder.length; i < n; i++) {
|
|
var slot = drawOrder[i];
|
|
var attachment = slot.attachment;
|
|
if (!(attachment instanceof spine.RegionAttachment)) continue;
|
|
|
|
var materials = attachment.rendererObject.page.rendererObject;
|
|
// texture was not loaded yet
|
|
if (!materials) continue;
|
|
|
|
if (slot.data.additiveBlending && (materials.length == 1)) {
|
|
// create separate material for additive blending
|
|
materials.push(new THREE.MeshBasicMaterial({
|
|
map : materials[0].map,
|
|
side : THREE.DoubleSide,
|
|
transparent : true,
|
|
blending : THREE.AdditiveBlending,
|
|
depthWrite : false
|
|
}));
|
|
}
|
|
|
|
var material = materials[ slot.data.additiveBlending ? 1 : 0 ];
|
|
|
|
material.meshes = material.meshes || {};
|
|
|
|
var mesh = material.meshes[slot.data.name];
|
|
|
|
var geometry;
|
|
|
|
if (mesh) {
|
|
geometry = mesh.geometry;
|
|
|
|
mesh.visible = true;
|
|
} else {
|
|
geometry = new THREE.PlaneGeometry(
|
|
attachment.regionOriginalWidth,
|
|
attachment.regionOriginalHeight
|
|
);
|
|
geometry.dynamic = true;
|
|
|
|
mesh = new THREE.Mesh(geometry, material);
|
|
mesh.matrixAutoUpdate = false;
|
|
|
|
material.meshes[slot.data.name] = mesh;
|
|
this.add(mesh);
|
|
}
|
|
|
|
if (mesh.attachmentTime && (slot.getAttachmentTime () > mesh.attachmentTime)) {
|
|
// do nothing
|
|
} else {
|
|
// update UVs
|
|
geometry.faceVertexUvs[0][0][0].set(attachment.uvs[6], 1- attachment.uvs[7]);
|
|
geometry.faceVertexUvs[0][0][1].set(attachment.uvs[4], 1- attachment.uvs[5]);
|
|
geometry.faceVertexUvs[0][0][2].set(attachment.uvs[0], 1- attachment.uvs[1]);
|
|
geometry.faceVertexUvs[0][1][0].set(attachment.uvs[4], 1- attachment.uvs[5]);
|
|
geometry.faceVertexUvs[0][1][1].set(attachment.uvs[2], 1- attachment.uvs[3]);
|
|
geometry.faceVertexUvs[0][1][2].set(attachment.uvs[0], 1- attachment.uvs[1]);
|
|
geometry.uvsNeedUpdate = true;
|
|
|
|
geometry.vertices[1].set(attachment.offset[0], attachment.offset[1], 0);
|
|
geometry.vertices[3].set(attachment.offset[2], attachment.offset[3], 0);
|
|
geometry.vertices[2].set(attachment.offset[4], attachment.offset[5], 0);
|
|
geometry.vertices[0].set(attachment.offset[6], attachment.offset[7], 0);
|
|
geometry.verticesNeedUpdate = true;
|
|
|
|
mesh.attachmentTime = slot.getAttachmentTime();
|
|
}
|
|
|
|
matrix.makeTranslation(
|
|
this.skeleton.x + slot.bone.worldX,
|
|
this.skeleton.y + slot.bone.worldY,
|
|
(dz || 0.1) * Z++
|
|
);
|
|
|
|
matrix.elements[0] = slot.bone.m00; matrix.elements[4] = slot.bone.m01;
|
|
matrix.elements[1] = slot.bone.m10; matrix.elements[5] = slot.bone.m11;
|
|
|
|
mesh.matrix.copy(matrix);
|
|
|
|
/* TODO slot.r,g,b,a ?? turbulenz example code:
|
|
batch.add(
|
|
attachment.rendererObject.page.rendererObject,
|
|
vertices[0], vertices[1],
|
|
vertices[6], vertices[7],
|
|
vertices[2], vertices[3],
|
|
vertices[4], vertices[5],
|
|
skeleton.r * slot.r,
|
|
skeleton.g * slot.g,
|
|
skeleton.b * slot.b,
|
|
skeleton.a * slot.a,
|
|
attachment.uvs[0], attachment.uvs[1],
|
|
attachment.uvs[4], attachment.uvs[5]
|
|
);
|
|
*/
|
|
}
|
|
|
|
};
|
|
};
|
|
|
|
SpineAnimation.SKELETON_DATA_LOADED = 'skeletonDataLoaded';
|
|
|
|
SpineAnimation.prototype = Object.create(THREE.Object3D.prototype);
|
|
|
|
SpineAnimation.prototype.dispose = function () {
|
|
if (this.parent) this.parent.remove(this); this.atlas.dispose();
|
|
};
|
|
|
|
}) ();
|
|
|
|
var scene, camera, renderer;
|
|
var geometry, material, mesh;
|
|
var anim;
|
|
|
|
function load (name, scale) {
|
|
if (anim) anim.dispose();
|
|
|
|
anim = new SpineAnimation(name, 'data/', scale);
|
|
|
|
anim.addEventListener(SpineAnimation.SKELETON_DATA_LOADED, function () {
|
|
var canvas = renderer.domElement;
|
|
|
|
switch (anim.name) {
|
|
case 'spineboy':
|
|
anim.stateData.setMixByName('walk', 'jump', 0.2);
|
|
anim.stateData.setMixByName('run', 'jump', 0.2);
|
|
anim.stateData.setMixByName('jump', 'run', 0.2);
|
|
anim.state.setAnimationByName(0, 'walk', true);
|
|
canvas.onmousedown = function () {
|
|
anim.state.setAnimationByName(0, 'jump', false);
|
|
anim.state.addAnimationByName(0, 'run', true, 0);
|
|
}
|
|
break;
|
|
case 'hero':
|
|
anim.stateData.defaultMix = 0.2;
|
|
anim.stateData.setMixByName('Walk', 'Attack', 0);
|
|
anim.stateData.setMixByName('Attack', 'Run', 0);
|
|
anim.stateData.setMixByName('Run', 'Attack', 0);
|
|
anim.state.setAnimationByName(0, 'Idle', true);
|
|
canvas.onmousedown = function () {
|
|
var name = anim.state.getCurrent(0).animation.name;
|
|
if (name == 'Idle')
|
|
anim.state.setAnimationByName(0, 'Crouch', true);
|
|
else if (name == 'Crouch')
|
|
anim.state.setAnimationByName(0, 'Walk', true);
|
|
else {
|
|
anim.state.setAnimationByName(0, 'Attack', false);
|
|
anim.state.addAnimationByName(0, 'Run', true, 0);
|
|
}
|
|
}
|
|
break;
|
|
case 'goblins':
|
|
anim.skeleton.setSkinByName('goblingirl'); // TODO - spine.Skeleton.prototype.setSkin doesn't work?
|
|
anim.skeleton.setSlotsToSetupPose();
|
|
anim.state.setAnimationByName(0, 'walk', true);
|
|
canvas.onmousedown = function () {
|
|
anim.skeleton.setSkinByName(anim.skeleton.skin.name == 'goblin' ? 'goblingirl' : 'goblin');
|
|
anim.skeleton.setSlotsToSetupPose();
|
|
}
|
|
break;
|
|
case 'skeleton':
|
|
anim.state.setAnimationByName(0, 'walk test', true);
|
|
canvas.onmousedown = function () {
|
|
var name = anim.state.getCurrent(0).animation.name;
|
|
if (name == 'walk test')
|
|
anim.state.setAnimationByName(0, 'idle test', true);
|
|
else
|
|
anim.state.setAnimationByName(0, 'walk test', true);
|
|
}
|
|
break;
|
|
}
|
|
//anim.skeleton.y = -200;
|
|
});
|
|
|
|
mesh.add(anim);
|
|
}
|
|
|
|
function init() {
|
|
scene = new THREE.Scene();
|
|
|
|
camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 1, 3000);
|
|
camera.position.z = 400;
|
|
|
|
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('spineboy', 0.4);
|
|
|
|
renderer = new THREE.WebGLRenderer();
|
|
renderer.setSize(window.innerWidth, window.innerHeight);
|
|
|
|
document.body.appendChild(renderer.domElement);
|
|
}
|
|
|
|
function animate() {
|
|
requestAnimationFrame(animate);
|
|
|
|
var t = Date.now();
|
|
var a = Math.sin(t * 6e-4);
|
|
var b = Math.cos(t * 3e-4);
|
|
|
|
mesh.rotation.x = a * Math.PI * 0.2;
|
|
mesh.rotation.y = b * Math.PI * 0.4;
|
|
|
|
anim.update();
|
|
|
|
renderer.render(scene, camera);
|
|
}
|
|
|
|
init();
|
|
animate();
|
|
</script>
|
|
|
|
<br>
|
|
<input type="button" value="Spineboy" onclick="load('spineboy', 0.6)">
|
|
<input type="button" value="Hero" onclick="load('hero', 1)">
|
|
<input type="button" value="Goblins" onclick="load('goblins', 1)">
|
|
<input type="button" value="Gypsy" onclick="load('gypsy', 1)">
|
|
Click to change the animation or skin.
|
|
|
|
</body>
|
|
</html> |