Merge branch '4.1-beta' into 4.2-beta

This commit is contained in:
badlogic 2022-05-02 18:21:14 +02:00
commit f40d65af55
40 changed files with 988 additions and 518 deletions

View File

@ -79,6 +79,8 @@
* Added example component `SkeletonRenderTexture` to render a `SkeletonRenderer` to a `RenderTexture`, mainly for proper transparency. Added an example scene named `RenderTexture FadeOut Transparency` that demonstrates usage for a fadeout transparency effect.
* Added another fadeout example component named `SkeletonRenderTextureFadeout` which takes over transparency fadeout when enabled. You can use this component as-is, attach it in disabled state and enable it to start a fadeout effect.
* Timeline clips now offer an additional `Alpha` parameter for setting a custom constant mix alpha value other than 1.0, just as `TrackEntry.Alpha`. Defaults to 1.0.
* `GetRemappedClone` copying from `Sprite` now provides additional `pmaCloneTextureFormat` and `pmaCloneMipmaps` parameters to explicitly specify the texture format of a newly created PMA texture.
* Spine property Inspector fields (`Animation Name`, `Bone Name`, `Slot` and similar) now display the name in red when the respective animation/bone/etc no longer exists at the skeleton data. This may be helpful when such items have been renamed or deleted.
* **Breaking changes**

View File

@ -16,7 +16,7 @@ fi
echo "Spine exe: $SPINE_EXE"
if [ "$#" -eq 0 ]; then
echo "Enter the Spine editor version to use for the export (eg 3.8.99):"
echo "Enter the Spine editor version to use for the export (eg 4.1.xx):"
read version
else
version=${1%/}
@ -79,6 +79,11 @@ echo "Exporting assets..."
-i ../mix-and-match/images -o ../mix-and-match/export -n mix-and-match -p atlas-0.5.json \
-i ../mix-and-match/images -o ../mix-and-match/export -n mix-and-match-pma -p atlas-0.5-pma.json \
\
-i ../owl/owl-pro.spine -o ../owl/export -e json.json \
-i ../owl/owl-pro.spine -o ../owl/export -e binary.json \
-i ../owl/images -o ../owl/export -n owl -p atlas-0.5.json \
-i ../owl/images -o ../owl/export -n owl-pma -p atlas-0.5-pma.json \
\
-i ../powerup/powerup-ess.spine -o ../powerup/export -e json.json \
-i ../powerup/powerup-ess.spine -o ../powerup/export -e binary.json \
-i ../powerup/powerup-pro.spine -o ../powerup/export -e json.json \
@ -100,6 +105,7 @@ echo "Exporting assets..."
-i ../spineboy/spineboy-ess.spine -o ../spineboy/export -e binary.json \
-i ../spineboy/spineboy-pro.spine -o ../spineboy/export -e json.json \
-i ../spineboy/spineboy-pro.spine -o ../spineboy/export -e binary.json \
-i ../spineboy/spineboy-pro.spine -o ../spineboy/export/spineboy-run.atlas -e png-0.5-frame-by-frame.json \
-i ../spineboy/images -o ../spineboy/export -n spineboy -p atlas-0.5.json \
-i ../spineboy/images -o ../spineboy/export -n spineboy-pma -p atlas-0.5-pma.json \
\
@ -126,14 +132,10 @@ echo "Exporting assets..."
-i ../windmill/images -o ../windmill/export -n windmill -p atlas-0.5.json \
-i ../windmill/images -o ../windmill/export -n windmill-pma -p atlas-0.5-pma.json
# Owl needs separate export, as cleaning would kill keys in idle animation, which
# would lead to incorrect additive animation blending.
# spineboy-old.spine needs separate export, as its images are in an atlas.
"$SPINE_EXE" \
-u $version ${@:2} \
-i ../owl/owl-pro.spine -o ../owl/export -e json.json \
-i ../owl/owl-pro.spine -o ../owl/export -e binary.json \
-i ../owl/images -o ../owl/export -n owl -p atlas-0.5.json \
-i ../owl/images -o ../owl/export -n owl-pma -p atlas-0.5-pma.json \
-i ../../spine-libgdx/spine-libgdx-tests/assets/spineboy-old/spineboy-old.spine -o ../../spine-libgdx/spine-libgdx-tests/assets/spineboy-old -e json.json
# Export Unity Assets
UNITY_BASE_DIR=../spine-unity

View File

@ -0,0 +1,87 @@
{
"class": "export-png",
"exportType": "animation",
"skeletonType": "single",
"skeleton": "spineboy-pro",
"animationType": "single",
"animation": "run",
"skinType": "current",
"skinNone": false,
"skin": null,
"maxBounds": false,
"renderImages": true,
"renderBones": false,
"renderOthers": false,
"scale": 50,
"fitWidth": 0,
"fitHeight": 0,
"enlarge": false,
"background": null,
"fps": 15,
"lastFrame": false,
"cropWidth": 0,
"cropHeight": 0,
"rangeStart": -1,
"rangeEnd": -1,
"packAtlas": {
"stripWhitespaceX": true,
"stripWhitespaceY": true,
"rotation": true,
"alias": true,
"ignoreBlankImages": false,
"alphaThreshold": 3,
"minWidth": 16,
"minHeight": 16,
"maxWidth": 2048,
"maxHeight": 2048,
"pot": false,
"multipleOfFour": false,
"square": false,
"outputFormat": "png",
"jpegQuality": 0.9,
"premultiplyAlpha": true,
"bleed": false,
"scale": [ 1 ],
"scaleSuffix": [ "" ],
"scaleResampling": [ "bicubic" ],
"paddingX": 2,
"paddingY": 2,
"edgePadding": true,
"duplicatePadding": false,
"filterMin": "Linear",
"filterMag": "Linear",
"wrapX": "ClampToEdge",
"wrapY": "ClampToEdge",
"format": "RGBA8888",
"atlasExtension": ".atlas",
"combineSubdirectories": false,
"flattenPaths": false,
"useIndexes": true,
"debug": false,
"fast": false,
"limitMemory": true,
"currentProject": true,
"packing": "rectangles",
"prettyPrint": true,
"legacyOutput": false,
"webp": null,
"bleedIterations": 2,
"ignore": false,
"separator": "_",
"silent": false
},
"compression": 9,
"bruteForce": true,
"quantize": true,
"quality": 100,
"speed": 1,
"dither": 0,
"maxColors": 256,
"pad": false,
"msaa": 4,
"smoothing": 8,
"renderSelection": false,
"cropX": 0,
"cropY": 0,
"open": false
}

Binary file not shown.

View File

@ -0,0 +1,60 @@
spineboy-run.png
size: 1181, 687
filter: Linear, Linear
pma: true
spineboy-pro-run
index: 8
bounds: 2, 371, 303, 314
offsets: 11, 22, 316, 341
origin: 142, 3
spineboy-pro-run
index: 9
bounds: 2, 44, 278, 325
offsets: 30, 2, 316, 341
origin: 142, 3
spineboy-pro-run
index: 0
bounds: 307, 439, 246, 320
offsets: 67, 1, 316, 341
rotate: 90
origin: 142, 3
spineboy-pro-run
index: 3
bounds: 629, 443, 242, 320
offsets: 12, 20, 316, 341
rotate: 90
origin: 142, 3
spineboy-pro-run
index: 7
bounds: 951, 389, 228, 296
offsets: 36, 33, 316, 341
origin: 142, 3
spineboy-pro-run
index: 4
bounds: 307, 195, 242, 320
offsets: 2, 4, 316, 341
rotate: 90
origin: 142, 3
spineboy-pro-run
index: 5
bounds: 629, 200, 241, 316
offsets: 8, 3, 316, 341
rotate: 90
origin: 142, 3
spineboy-pro-run
index: 1
bounds: 282, 2, 191, 318
offsets: 70, 3, 316, 341
rotate: 90
origin: 142, 3
spineboy-pro-run
index: 2
bounds: 947, 81, 226, 306
offsets: 34, 26, 316, 341
origin: 142, 3
spineboy-pro-run
index: 6
bounds: 629, 4, 194, 316
offsets: 68, 4, 316, 341
rotate: 90
origin: 142, 3

Binary file not shown.

After

Width:  |  Height:  |  Size: 336 KiB

View File

@ -0,0 +1,2 @@
eclipse.preferences.version=1
encoding/<project>=Cp1252

View File

@ -1,5 +1,5 @@
{
com.badlogic.gdx.graphics.g2d.BitmapFont: { default-font: { file: com/badlogic/gdx/utils/arial-15.fnt } },
com.badlogic.gdx.graphics.g2d.BitmapFont: { default-font: { file: com/badlogic/gdx/utils/lsans-15.fnt } },
com.badlogic.gdx.graphics.Color: {
green: { a: 1, b: 0, g: 1, r: 0 },
white: { a: 1, b: 1, g: 1, r: 1 },

View File

@ -0,0 +1,60 @@
spineboy-run.png
size: 1181, 687
filter: Linear, Linear
pma: true
spineboy-pro-run
index: 8
bounds: 2, 371, 303, 314
offsets: 11, 22, 316, 341
origin: 142, 3
spineboy-pro-run
index: 9
bounds: 2, 44, 278, 325
offsets: 30, 2, 316, 341
origin: 142, 3
spineboy-pro-run
index: 0
bounds: 307, 439, 246, 320
offsets: 67, 1, 316, 341
rotate: 90
origin: 142, 3
spineboy-pro-run
index: 3
bounds: 629, 443, 242, 320
offsets: 12, 20, 316, 341
rotate: 90
origin: 142, 3
spineboy-pro-run
index: 7
bounds: 951, 389, 228, 296
offsets: 36, 33, 316, 341
origin: 142, 3
spineboy-pro-run
index: 4
bounds: 307, 195, 242, 320
offsets: 2, 4, 316, 341
rotate: 90
origin: 142, 3
spineboy-pro-run
index: 5
bounds: 629, 200, 241, 316
offsets: 8, 3, 316, 341
rotate: 90
origin: 142, 3
spineboy-pro-run
index: 1
bounds: 282, 2, 191, 318
offsets: 70, 3, 316, 341
rotate: 90
origin: 142, 3
spineboy-pro-run
index: 2
bounds: 947, 81, 226, 306
offsets: 34, 26, 316, 341
origin: 142, 3
spineboy-pro-run
index: 6
bounds: 629, 4, 194, 316
offsets: 68, 4, 316, 341
rotate: 90
origin: 142, 3

Binary file not shown.

After

Width:  |  Height:  |  Size: 336 KiB

View File

@ -50,6 +50,7 @@ import com.esotericsoftware.spine.attachments.PointAttachment;
import com.esotericsoftware.spine.attachments.RegionAttachment;
import com.esotericsoftware.spine.attachments.Sequence;
/** Unit tests to ensure {@link AnimationState} is working as expected. */
public class AnimationStateTests {
final SkeletonJson json = new SkeletonJson(new AttachmentLoader() {
public RegionAttachment newRegionAttachment (Skin skin, String name, String path, @Null Sequence sequence) {

View File

@ -35,7 +35,7 @@ import com.esotericsoftware.spine.Animation.AttachmentTimeline;
import com.esotericsoftware.spine.Animation.Timeline;
import com.esotericsoftware.spine.attachments.Attachment;
/** Unit tests for {@link AttachmentTimeline}. */
/** Unit tests to ensure {@link AttachmentTimeline} is working as expected. */
public class AttachmentTimelineTests {
private final SkeletonData skeletonData;
private final Skeleton skeleton;

View File

@ -43,9 +43,10 @@ import com.esotericsoftware.spine.attachments.PointAttachment;
import com.esotericsoftware.spine.attachments.RegionAttachment;
import com.esotericsoftware.spine.attachments.Sequence;
/** Demonstrates loading skeleton data without an atlas and plotting bone transform for each animation. */
public class BonePlotting {
static public void main (String[] args) throws Exception {
// This example shows how to load skeleton data and plot a bone transform for each animation.
// Create a skeleton loader that doesn't use an atlas and doesn't create any attachments.
SkeletonJson json = new SkeletonJson(new AttachmentLoader() {
public RegionAttachment newRegionAttachment (Skin skin, String name, String path, @Null Sequence sequence) {
return null;
@ -71,17 +72,22 @@ public class BonePlotting {
return null;
}
});
SkeletonData skeletonData = json.readSkeletonData(new FileHandle("assets/spineboy/spineboy-ess.json"));
Skeleton skeleton = new Skeleton(skeletonData);
Bone bone = skeleton.findBone("gun-tip");
// Pose the skeleton at regular intervals throughout each animation.
float fps = 1 / 15f;
for (Animation animation : skeletonData.getAnimations()) {
float time = 0;
while (time < animation.getDuration()) {
animation.apply(skeleton, time, time, false, null, 1, MixBlend.first, MixDirection.in);
skeleton.updateWorldTransform();
System.out
.println(animation.getName() + "," + bone.getWorldX() + "," + bone.getWorldY() + "," + bone.getWorldRotationX());
System.out.println(animation.getName() + "," //
+ bone.getWorldX() + "," + bone.getWorldY() + "," + bone.getWorldRotationX());
time += fps;
}
}

View File

@ -58,6 +58,7 @@ import com.esotericsoftware.spine.attachments.AtlasAttachmentLoader;
import com.esotericsoftware.spine.attachments.RegionAttachment;
import com.esotericsoftware.spine.attachments.Sequence;
/** Demonstrates positioning physics bodies for a skeleton. */
public class Box2DExample extends ApplicationAdapter {
SpriteBatch batch;
ShapeRenderer renderer;
@ -84,8 +85,8 @@ public class Box2DExample extends ApplicationAdapter {
atlas = new TextureAtlas(Gdx.files.internal("spineboy/spineboy-pma.atlas"));
// This loader creates Box2dAttachments instead of RegionAttachments for an easy way to keep
// track of the Box2D body for each attachment.
// This loader creates Box2dAttachments instead of RegionAttachments for an easy way to keep track of the Box2D body for
// each attachment.
AtlasAttachmentLoader atlasLoader = new AtlasAttachmentLoader(atlas) {
public RegionAttachment newRegionAttachment (Skin skin, String name, String path, @Null Sequence sequence) {
Box2dAttachment attachment = new Box2dAttachment(name);
@ -154,7 +155,7 @@ public class Box2DExample extends ApplicationAdapter {
batch.end();
// Position each attachment body.
// Position the physics body for each attachment.
for (Slot slot : skeleton.getSlots()) {
if (!(slot.getAttachment() instanceof Box2dAttachment)) continue;
Box2dAttachment attachment = (Box2dAttachment)slot.getAttachment();
@ -182,25 +183,19 @@ public class Box2DExample extends ApplicationAdapter {
PolygonShape shape = new PolygonShape();
shape.set(vertices);
// next we create a static ground platform. This platform
// is not moveable and will not react to any influences from
// outside. It will however influence other bodies. First we
// create a PolygonShape that holds the form of the platform.
// it will be 100 meters wide and 2 meters high, centered
// around the origin
// Next we create a static ground platform. This platform is not moveable and will not react to any outside influences. It
// will however influence other bodies. First we create a PolygonShape that holds the form of the platform. It will be
// 100 meters wide and 2 meters high, centered around the origin.
PolygonShape groundPoly = new PolygonShape();
groundPoly.setAsBox(50, 1);
// next we create the body for the ground platform. It's
// simply a static body.
// Next we create the body for the ground platform. It's simply a static body.
BodyDef groundBodyDef = new BodyDef();
groundBodyDef.type = BodyType.StaticBody;
groundBody = world.createBody(groundBodyDef);
// finally we add a fixture to the body using the polygon
// defined above. Note that we have to dispose PolygonShapes
// and CircleShapes once they are no longer used. This is the
// only time you have to care explicitely for memomry managment.
// Finally we add a fixture to the body using the polygon defined above. Note that we have to dispose PolygonShapes and
// CircleShapes once they are no longer used. This is the only time you have to care explicitely for memomry managment.
FixtureDef fixtureDef = new FixtureDef();
fixtureDef.shape = groundPoly;
fixtureDef.filter.groupIndex = 0;
@ -210,12 +205,10 @@ public class Box2DExample extends ApplicationAdapter {
PolygonShape boxPoly = new PolygonShape();
boxPoly.setAsBox(1, 1);
// Next we create the 50 box bodies using the PolygonShape we just
// defined. This process is similar to the one we used for the ground
// body. Note that we reuse the polygon for each body fixture.
// Next we create the 50 box bodies using the PolygonShape we just defined. This process is similar to the one we used for
// the ground body. Note that we reuse the polygon for each body fixture.
for (int i = 0; i < 45; i++) {
// Create the BodyDef, set a random position above the
// ground and create a new body
// Create the BodyDef, set a random position above the ground and create a new body.
BodyDef boxBodyDef = new BodyDef();
boxBodyDef.type = BodyType.DynamicBody;
boxBodyDef.position.x = -24 + (float)(Math.random() * 48);
@ -225,7 +218,7 @@ public class Box2DExample extends ApplicationAdapter {
boxBody.createFixture(boxPoly, 1);
}
// we are done, all that's left is disposing the boxPoly
// We are done, all that's left is disposing the boxPoly.
boxPoly.dispose();
}

View File

@ -38,7 +38,7 @@ import com.esotericsoftware.spine.Animation.EventTimeline;
import com.esotericsoftware.spine.Animation.MixBlend;
import com.esotericsoftware.spine.Animation.MixDirection;
/** Unit tests for {@link EventTimeline}. */
/** Unit tests to ensure {@link EventTimeline} is working as expected. */
public class EventTimelineTests {
private final SkeletonData skeletonData;
private final Skeleton skeleton;

View File

@ -44,6 +44,7 @@ import com.badlogic.gdx.utils.ScreenUtils;
import com.esotericsoftware.spine.utils.TwoColorPolygonBatch;
/** Demonstrates rendering an animation to a frame buffer (FBO) and then rendering the FBO to the screen. */
public class FboTest extends ApplicationAdapter {
OrthographicCamera camera;
TwoColorPolygonBatch batch;

View File

@ -46,32 +46,22 @@ public class FrameByFrameTest extends ApplicationAdapter {
TextureAtlas atlas;
float time;
Animation<AtlasSprite> walkAnimation, deathAnimation, current;
Animation<AtlasSprite> walkAnimation, current;
public void create () {
camera = new OrthographicCamera();
batch = new PolygonSpriteBatch();
atlas = new TextureAtlas("spineboy/frame-by-frame.atlas");
atlas = new TextureAtlas("spineboy/spineboy-run.atlas");
walkAnimation = new Animation(1 / 20f, atlas.createSprites("spineboy-pro-walk"));
walkAnimation = new Animation(1 / 15f, atlas.createSprites("spineboy-pro-run"));
walkAnimation.setPlayMode(PlayMode.LOOP);
deathAnimation = new Animation(1 / 20f, atlas.createSprites("spineboy-pro-death"));
current = walkAnimation;
}
public void render () {
time += Gdx.graphics.getDeltaTime();
if (current == deathAnimation && current.isAnimationFinished(time)) {
current = walkAnimation;
time = 0;
}
if (Gdx.input.justTouched()) {
current = deathAnimation;
time = 0;
}
AtlasSprite frame = current.getKeyFrame(time);
float x = Math.round(Gdx.graphics.getWidth() / 2), y = 25;

View File

@ -37,7 +37,8 @@ import com.badlogic.gdx.graphics.g2d.PolygonSpriteBatch;
import com.badlogic.gdx.graphics.g2d.TextureAtlas;
import com.badlogic.gdx.utils.ScreenUtils;
public class SkinBonesMixAndMatchTest extends ApplicationAdapter {
/** Demonstrates creating and configuring a new skin at runtime. */
public class MixAndMatchTest extends ApplicationAdapter {
OrthographicCamera camera;
PolygonSpriteBatch batch;
SkeletonRenderer renderer;
@ -67,13 +68,11 @@ public class SkinBonesMixAndMatchTest extends ApplicationAdapter {
AnimationStateData stateData = new AnimationStateData(skeletonData); // Defines mixing (crossfading) between animations.
state = new AnimationState(stateData); // Holds the animation state for a skeleton (current animation, time, etc).
// Queue animations on track 0.
// Set animations on track 0.
state.setAnimation(0, "dance", true);
// Create a new skin, by mixing and matching other skins
// that fit together. Items making up the girl are individual
// skins. Using the skin API, a new skin is created which is
// a combination of all these individual item skins.
// Create a new skin, by mixing and matching other skins that fit together. Items making up the girl are individual skins.
// Using the skin API, a new skin is created which is a combination of all these individual item skins.
Skin mixAndMatchSkin = new Skin("custom-girl");
mixAndMatchSkin.addSkin(skeletonData.findSkin("skin-base"));
mixAndMatchSkin.addSkin(skeletonData.findSkin("nose/short"));
@ -113,6 +112,6 @@ public class SkinBonesMixAndMatchTest extends ApplicationAdapter {
}
public static void main (String[] args) throws Exception {
new Lwjgl3Application(new SkinBonesMixAndMatchTest());
new Lwjgl3Application(new MixAndMatchTest());
}
}

View File

@ -59,6 +59,9 @@ import com.badlogic.gdx.utils.ScreenUtils;
import com.esotericsoftware.spine.Animation.MixBlend;
import com.esotericsoftware.spine.Animation.MixDirection;
/** Demonstrates simplistic usage of lighting with normal maps.
* <p>
* Note the normals are not rotated when bones are rotated, making lighting incorrect. */
public class NormalMapTest extends ApplicationAdapter {
String skeletonPath, animationName;
SpriteBatch batch;

View File

@ -0,0 +1,138 @@
/******************************************************************************
* Spine Runtimes License Agreement
* Last updated September 24, 2021. Replaces all prior versions.
*
* Copyright (c) 2013-2021, Esoteric Software LLC
*
* Integration of the Spine Runtimes into software or otherwise creating
* derivative works of the Spine Runtimes is permitted under the terms and
* conditions of Section 2 of the Spine Editor License Agreement:
* http://esotericsoftware.com/spine-editor-license
*
* Otherwise, it is permitted to integrate the Spine Runtimes into software
* or otherwise create derivative works of the Spine Runtimes (collectively,
* "Products"), provided that each user of the Products must obtain their own
* Spine Editor license and redistribution of the Products in any form must
* include this license and copyright notice.
*
* THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "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 LLC 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
* THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*****************************************************************************/
package com.esotericsoftware.spine;
import com.badlogic.gdx.ApplicationAdapter;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.backends.lwjgl3.Lwjgl3Application;
import com.badlogic.gdx.backends.lwjgl3.Lwjgl3ApplicationConfiguration;
import com.badlogic.gdx.files.FileHandle;
import com.badlogic.gdx.graphics.Color;
import com.badlogic.gdx.graphics.GL20;
import com.badlogic.gdx.graphics.OrthographicCamera;
import com.badlogic.gdx.graphics.Pixmap;
import com.badlogic.gdx.graphics.Pixmap.Format;
import com.badlogic.gdx.graphics.PixmapIO;
import com.badlogic.gdx.graphics.g2d.BitmapFont;
import com.badlogic.gdx.graphics.g2d.TextureAtlas;
import com.badlogic.gdx.graphics.g2d.TextureRegion;
import com.badlogic.gdx.graphics.glutils.FrameBuffer;
import com.badlogic.gdx.utils.ScreenUtils;
import com.esotericsoftware.spine.Animation.MixBlend;
import com.esotericsoftware.spine.Animation.MixDirection;
import com.esotericsoftware.spine.utils.TwoColorPolygonBatch;
/** Demonstrates rendering an animation to a frame buffer (FBO) and then writing each frame as a PNG. */
public class PngExportTest extends ApplicationAdapter {
OrthographicCamera camera;
TwoColorPolygonBatch batch;
SkeletonRenderer renderer;
BitmapFont font;
TextureAtlas atlas;
Skeleton skeleton;
FrameBuffer fbo;
TextureRegion fboRegion;
boolean drawFbo = true;
public void create () {
camera = new OrthographicCamera();
batch = new TwoColorPolygonBatch();
renderer = new SkeletonRenderer();
renderer.setPremultipliedAlpha(true);
font = new BitmapFont();
font.setColor(Color.BLACK);
// Load the atlas and skeleton.
atlas = new TextureAtlas(Gdx.files.internal("spineboy/spineboy-pma.atlas"));
SkeletonJson json = new SkeletonJson(atlas);
json.setScale(0.66f);
SkeletonData skeletonData = json.readSkeletonData(Gdx.files.internal("spineboy/spineboy-ess.json"));
// Create a skeleton instance, set the position of its root bone, and update its world transform.
skeleton = new Skeleton(skeletonData);
skeleton.setPosition(250, 20);
skeleton.updateWorldTransform();
// Create an FBO and a texture region.
fbo = new FrameBuffer(Pixmap.Format.RGBA8888, 512, 512, false);
fboRegion = new TextureRegion(fbo.getColorBufferTexture());
// Create a pixmap of the same size.
Pixmap pixmap = new Pixmap(fbo.getWidth(), fbo.getHeight(), Format.RGBA8888);
// Configure the camera and batch for rendering to the FBO's size.
camera.setToOrtho(true, fbo.getWidth(), fbo.getHeight());
camera.update();
batch.getProjectionMatrix().set(camera.combined);
// Start rendering to the FBO.
fbo.begin();
// Pose the skeleton at regular intervals throughout the animation.
Animation animation = skeletonData.findAnimation("run");
float fps = 1 / 15f, time = 0;
int frame = 1;
while (time < animation.getDuration()) {
animation.apply(skeleton, time, time, false, null, 1, MixBlend.first, MixDirection.in);
skeleton.updateWorldTransform();
// Render the skeleton to the FBO.
ScreenUtils.clear(0, 0, 0, 0);
batch.begin();
renderer.draw(batch, skeleton);
batch.end();
// Copy the FBO to the pixmap and write it to a PNG file.
String name = animation.getName() + "_" + frame + ".png";
System.out.println(name);
Gdx.gl.glPixelStorei(GL20.GL_PACK_ALIGNMENT, 1); // Have glReadPixels use byte alignment for each pixel row.
Gdx.gl.glReadPixels(0, 0, fbo.getWidth(), fbo.getHeight(), GL20.GL_RGBA, GL20.GL_UNSIGNED_BYTE, pixmap.getPixels());
PixmapIO.writePNG(new FileHandle(name), pixmap);
frame++;
time += fps;
}
pixmap.dispose();
fbo.end();
// Terminate without showing a window.
System.exit(0);
}
static public void main (String[] args) throws Exception {
Lwjgl3ApplicationConfiguration config = new Lwjgl3ApplicationConfiguration();
config.setInitialVisible(false);
new Lwjgl3Application(new PngExportTest(), config);
}
}

View File

@ -35,8 +35,11 @@ import com.badlogic.gdx.backends.lwjgl3.Lwjgl3Application;
import com.badlogic.gdx.graphics.GL20;
import com.badlogic.gdx.graphics.OrthographicCamera;
import com.badlogic.gdx.graphics.g2d.TextureAtlas;
import com.esotericsoftware.spine.utils.TwoColorPolygonBatch;
/** Demonstrates loading, animating, and rendering a skeleton.
* @see SkeletonAssetManagerTest */
public class SimpleTest1 extends ApplicationAdapter {
OrthographicCamera camera;
TwoColorPolygonBatch batch;

View File

@ -38,11 +38,13 @@ import com.badlogic.gdx.graphics.GL20;
import com.badlogic.gdx.graphics.OrthographicCamera;
import com.badlogic.gdx.graphics.g2d.TextureAtlas;
import com.badlogic.gdx.math.Vector3;
import com.esotericsoftware.spine.AnimationState.AnimationStateListener;
import com.esotericsoftware.spine.AnimationState.TrackEntry;
import com.esotericsoftware.spine.attachments.BoundingBoxAttachment;
import com.esotericsoftware.spine.utils.TwoColorPolygonBatch;
/** Demonstrates loading, animating, and rendering a skeleton with hit detectiong using a bounding box attachment. */
public class SimpleTest2 extends ApplicationAdapter {
OrthographicCamera camera;
TwoColorPolygonBatch batch;

View File

@ -37,6 +37,7 @@ import com.badlogic.gdx.graphics.g2d.PolygonSpriteBatch;
import com.badlogic.gdx.graphics.g2d.TextureAtlas;
import com.badlogic.gdx.utils.ScreenUtils;
/** Demonstrates applying multiple animations at once using {@link AnimationState} tracks. */
public class SimpleTest3 extends ApplicationAdapter {
OrthographicCamera camera;
PolygonSpriteBatch batch;
@ -61,7 +62,7 @@ public class SimpleTest3 extends ApplicationAdapter {
SkeletonJson loader = new SkeletonJson(atlas); // This loads skeleton JSON data, which is stateless.
// SkeletonLoader loader = new SkeletonBinary(atlas); // Or use SkeletonBinary to load binary data.
loader.setScale(0.1f); // Load the skeleton at 50% the size it was in Spine.
loader.setScale(0.5f); // Load the skeleton at 50% the size it was in Spine.
SkeletonData skeletonData = loader.readSkeletonData(Gdx.files.internal("raptor/raptor-pro.json"));
skeleton = new Skeleton(skeletonData); // Skeleton holds skeleton state (bone positions, slot attachments, etc).

View File

@ -1,116 +0,0 @@
/******************************************************************************
* Spine Runtimes License Agreement
* Last updated September 24, 2021. Replaces all prior versions.
*
* Copyright (c) 2013-2021, Esoteric Software LLC
*
* Integration of the Spine Runtimes into software or otherwise creating
* derivative works of the Spine Runtimes is permitted under the terms and
* conditions of Section 2 of the Spine Editor License Agreement:
* http://esotericsoftware.com/spine-editor-license
*
* Otherwise, it is permitted to integrate the Spine Runtimes into software
* or otherwise create derivative works of the Spine Runtimes (collectively,
* "Products"), provided that each user of the Products must obtain their own
* Spine Editor license and redistribution of the Products in any form must
* include this license and copyright notice.
*
* THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "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 LLC 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
* THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*****************************************************************************/
package com.esotericsoftware.spine;
import com.badlogic.gdx.ApplicationAdapter;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.backends.lwjgl3.Lwjgl3Application;
import com.badlogic.gdx.graphics.OrthographicCamera;
import com.badlogic.gdx.graphics.g2d.TextureAtlas;
import com.badlogic.gdx.utils.ScreenUtils;
import com.esotericsoftware.spine.utils.TwoColorPolygonBatch;
public class SimpleTest4 extends ApplicationAdapter {
OrthographicCamera camera;
TwoColorPolygonBatch batch;
SkeletonRenderer renderer;
SkeletonRendererDebug debugRenderer;
TextureAtlas atlas;
Skeleton skeleton;
AnimationState state;
public void create () {
camera = new OrthographicCamera();
batch = new TwoColorPolygonBatch();
renderer = new SkeletonRenderer();
renderer.setPremultipliedAlpha(true); // PMA results in correct blending without outlines.
debugRenderer = new SkeletonRendererDebug();
debugRenderer.setBoundingBoxes(false);
debugRenderer.setRegionAttachments(false);
atlas = new TextureAtlas(Gdx.files.internal("goblins/goblins-pma.atlas"));
SkeletonJson loader = new SkeletonJson(atlas); // This loads skeleton JSON data, which is stateless.
// SkeletonLoader loader = new SkeletonBinary(atlas); // Or use SkeletonBinary to load binary data.
loader.setScale(1.3f); // Load the skeleton at 130% the size it was in Spine.
SkeletonData skeletonData = loader.readSkeletonData(Gdx.files.internal("goblins/goblins-pro.json"));
skeleton = new Skeleton(skeletonData); // Skeleton holds skeleton state (bone positions, slot attachments, etc).
skeleton.setPosition(250, 20);
AnimationStateData stateData = new AnimationStateData(skeletonData); // Defines mixing (crossfading) between animations.
state = new AnimationState(stateData); // Holds the animation state for a skeleton (current animation, time, etc).
state.setTimeScale(0.5f); // Slow all animations down to 50% speed.
// Queue animations on track 0.
state.setAnimation(0, "walk", true);
// Create an empty skin and copy the goblingirl skin into it.
Skin skin = new Skin("test");
skin.copySkin(skeletonData.findSkin("goblingirl"));
skeleton.setSkin(skin);
skeleton.setSlotsToSetupPose();
}
public void render () {
state.update(Gdx.graphics.getDeltaTime()); // Update the animation time.
ScreenUtils.clear(0, 0, 0, 0);
state.apply(skeleton); // Poses skeleton using current animations. This sets the bones' local SRT.
skeleton.updateWorldTransform(); // Uses the bones' local SRT to compute their world SRT.
// Configure the camera, SpriteBatch, and SkeletonRendererDebug.
camera.update();
batch.getProjectionMatrix().set(camera.combined);
debugRenderer.getShapeRenderer().setProjectionMatrix(camera.combined);
batch.begin();
renderer.draw(batch, skeleton); // Draw the skeleton images.
batch.end();
debugRenderer.draw(skeleton); // Draw debug lines.
}
public void resize (int width, int height) {
camera.setToOrtho(false); // Update camera with new size.
}
public void dispose () {
atlas.dispose();
}
public static void main (String[] args) throws Exception {
new Lwjgl3Application(new SimpleTest4());
}
}

View File

@ -41,7 +41,7 @@ import com.badlogic.gdx.utils.ScreenUtils;
import com.esotericsoftware.spine.utils.SkeletonDataLoader;
import com.esotericsoftware.spine.utils.SkeletonDataLoader.SkeletonDataParameter;
/** Like {@link SimpleTest1}, but using {@link AssetManager} to load the atlas and skeleton data. */
/** Demonstrates loading an atlas and skeleton using {@link AssetManager}. */
public class SkeletonAssetManagerTest extends ApplicationAdapter {
OrthographicCamera camera;
PolygonSpriteBatch batch;
@ -85,8 +85,8 @@ public class SkeletonAssetManagerTest extends ApplicationAdapter {
skeleton = new Skeleton(skeletonData); // Skeleton holds skeleton state (bone positions, slot attachments, etc).
skeleton.setPosition(250, 20);
AnimationStateData stateData = new AnimationStateData(skeletonData); // Defines mixing (crossfading) between
// animations.
// Define the default mixing (crossfading) between animations.
AnimationStateData stateData = new AnimationStateData(skeletonData);
stateData.setMix("run", "jump", 0.2f);
stateData.setMix("jump", "run", 0.2f);

View File

@ -39,6 +39,7 @@ import com.badlogic.gdx.utils.ScreenUtils;
import com.esotericsoftware.spine.attachments.SkeletonAttachment;
/** Demonstrates using {@link SkeletonAttachment} to use an entire skeleton as an attachment. */
public class SkeletonAttachmentTest extends ApplicationAdapter {
OrthographicCamera camera;
PolygonSpriteBatch batch;

View File

@ -42,6 +42,7 @@ import com.badlogic.gdx.utils.ScreenUtils;
import com.esotericsoftware.spine.vertexeffects.SwirlEffect;
/** Boilerplate for basic skeleton rendering, used for various testing. */
public class TestHarness extends ApplicationAdapter {
// static String JSON = "coin/coin-pro.json";
// static String ATLAS = "coin/coin-pma.atlas";

View File

@ -40,7 +40,10 @@ import com.badlogic.gdx.utils.ScreenUtils;
import com.esotericsoftware.spine.Animation.MixBlend;
import com.esotericsoftware.spine.Animation.MixDirection;
public class MixTest extends ApplicationAdapter {
/** Demonstrates using the timeline API. See {@link SimpleTest1} for a higher level API using {@link AnimationState}.
* <p>
* See: http://esotericsoftware.com/spine-applying-animations */
public class TimelineApiTest extends ApplicationAdapter {
SpriteBatch batch;
float time;
Array<Event> events = new Array();
@ -139,6 +142,6 @@ public class MixTest extends ApplicationAdapter {
}
public static void main (String[] args) throws Exception {
new Lwjgl3Application(new MixTest());
new Lwjgl3Application(new TimelineApiTest());
}
}

View File

@ -39,8 +39,10 @@ import com.badlogic.gdx.graphics.g2d.TextureAtlas;
import com.badlogic.gdx.math.Interpolation;
import com.badlogic.gdx.utils.ScreenUtils;
import com.esotericsoftware.spine.SkeletonRenderer.VertexEffect;
import com.esotericsoftware.spine.vertexeffects.SwirlEffect;
/** Demonstrates applying a {@link VertexEffect}. */
public class VertexEffectTest extends ApplicationAdapter {
OrthographicCamera camera;
PolygonSpriteBatch batch;

View File

@ -0,0 +1,2 @@
eclipse.preferences.version=1
encoding/<project>=Cp1252

View File

@ -415,6 +415,18 @@ public class Skeleton {
public void updateWorldTransform (Bone parent) {
if (parent == null) throw new IllegalArgumentException("parent cannot be null.");
Object[] bones = this.bones.items;
for (int i = 1, n = this.bones.size; i < n; i++) { // Skip root bone.
Bone bone = (Bone)bones[i];
bone.ax = bone.x;
bone.ay = bone.y;
bone.arotation = bone.rotation;
bone.ascaleX = bone.scaleX;
bone.ascaleY = bone.scaleY;
bone.ashearX = bone.shearX;
bone.ashearY = bone.shearY;
}
// Apply the parent bone transform to the root bone. The root bone always inherits scale, rotation and reflection.
Bone rootBone = getRootBone();
float pa = parent.a, pb = parent.b, pc = parent.c, pd = parent.d;

View File

@ -0,0 +1,2 @@
eclipse.preferences.version=1
encoding/<project>=Cp1252

View File

@ -159,7 +159,15 @@ void USpineSkeletonRendererComponent::Flush(int &Idx, TArray<FVector> &Vertices,
if (Vertices.Num() == 0) return;
SetMaterial(Idx, Material);
CreateMeshSection(Idx, Vertices, Indices, Normals, Uvs, Colors, TArray<FProcMeshTangent>(), bCreateCollision);
bool bShouldCreateCollision = false;
if (bCreateCollision) {
UWorld* world = GetWorld();
if (world && world->IsGameWorld()) {
bShouldCreateCollision = true;
}
}
CreateMeshSection(Idx, Vertices, Indices, Normals, Uvs, Colors, TArray<FProcMeshTangent>(), bShouldCreateCollision);
Vertices.SetNum(0);
Indices.SetNum(0);

View File

@ -455,7 +455,7 @@ namespace Spine.Unity.Editor {
}
if (cc.changed) {
targetSkeletonDataAsset.FillStateData();
targetSkeletonDataAsset.FillStateData(quiet: true);
EditorUtility.SetDirty(targetSkeletonDataAsset);
serializedObject.ApplyModifiedProperties();
}

View File

@ -61,11 +61,34 @@ namespace Spine.Unity.Editor {
return noneLabel;
}
static GUIStyle errorPopupStyle;
GUIStyle ErrorPopupStyle {
get {
if (errorPopupStyle == null) errorPopupStyle = new GUIStyle(EditorStyles.popup);
errorPopupStyle.normal.textColor = Color.red;
errorPopupStyle.hover.textColor = Color.red;
errorPopupStyle.focused.textColor = Color.red;
errorPopupStyle.active.textColor = Color.red;
return errorPopupStyle;
}
}
protected T TargetAttribute { get { return (T)attribute; } }
protected SerializedProperty SerializedProperty { get; private set; }
protected abstract Texture2D Icon { get; }
protected bool IsValueValid (SerializedProperty property) {
if (skeletonDataAsset != null) {
SkeletonData skeletonData = skeletonDataAsset.GetSkeletonData(true);
if (skeletonData != null && !string.IsNullOrEmpty(property.stringValue))
return IsValueValid(skeletonData, property);
}
return true;
}
protected virtual bool IsValueValid (SkeletonData skeletonData, SerializedProperty property) { return true; }
public override void OnGUI (Rect position, SerializedProperty property, GUIContent label) {
SerializedProperty = property;
@ -123,8 +146,10 @@ namespace Spine.Unity.Editor {
position = EditorGUI.PrefixLabel(position, label);
Texture2D image = Icon;
GUIStyle usedStyle = IsValueValid(property) ? EditorStyles.popup : ErrorPopupStyle;
string propertyStringValue = (property.hasMultipleDifferentValues) ? SpineInspectorUtility.EmDash : property.stringValue;
if (GUI.Button(position, string.IsNullOrEmpty(propertyStringValue) ? NoneLabel(image) : SpineInspectorUtility.TempContent(propertyStringValue, image), EditorStyles.popup))
if (GUI.Button(position, string.IsNullOrEmpty(propertyStringValue) ? NoneLabel(image) :
SpineInspectorUtility.TempContent(propertyStringValue, image), usedStyle))
Selector(property);
}
@ -174,6 +199,10 @@ namespace Spine.Unity.Editor {
protected override Texture2D Icon { get { return SpineEditorUtilities.Icons.slot; } }
protected override bool IsValueValid (SkeletonData skeletonData, SerializedProperty property) {
return skeletonData.FindSlot(property.stringValue) != null;
}
protected override void PopulateMenu (GenericMenu menu, SerializedProperty property, SpineSlot targetAttribute, SkeletonData data) {
if (TargetAttribute.includeNone)
menu.AddItem(new GUIContent(NoneString), !property.hasMultipleDifferentValues && string.IsNullOrEmpty(property.stringValue), HandleSelect, new SpineDrawerValuePair(string.Empty, property));
@ -223,6 +252,10 @@ namespace Spine.Unity.Editor {
internal override string NoneString { get { return TargetAttribute.defaultAsEmptyString ? DefaultSkinName : NoneStringConstant; } }
protected override bool IsValueValid (SkeletonData skeletonData, SerializedProperty property) {
return skeletonData.FindSkin(property.stringValue) != null;
}
public static void GetSkinMenuItems (SkeletonData data, List<string> outputNames, List<GUIContent> outputMenuItems, bool includeNone = true) {
if (data == null) return;
if (outputNames == null) return;
@ -269,6 +302,10 @@ namespace Spine.Unity.Editor {
protected override Texture2D Icon { get { return SpineEditorUtilities.Icons.animation; } }
protected override bool IsValueValid (SkeletonData skeletonData, SerializedProperty property) {
return skeletonData.FindAnimation(property.stringValue) != null;
}
public static void GetAnimationMenuItems (SkeletonData data, List<string> outputNames, List<GUIContent> outputMenuItems, bool includeNone = true) {
if (data == null) return;
if (outputNames == null) return;
@ -311,6 +348,10 @@ namespace Spine.Unity.Editor {
protected override Texture2D Icon { get { return SpineEditorUtilities.Icons.userEvent; } }
protected override bool IsValueValid (SkeletonData skeletonData, SerializedProperty property) {
return skeletonData.FindEvent(property.stringValue) != null;
}
public static void GetEventMenuItems (SkeletonData data, List<string> eventNames, List<GUIContent> menuItems, bool includeNone = true) {
if (data == null) return;
@ -356,6 +397,10 @@ namespace Spine.Unity.Editor {
protected override Texture2D Icon { get { return SpineEditorUtilities.Icons.constraintIK; } }
protected override bool IsValueValid (SkeletonData skeletonData, SerializedProperty property) {
return skeletonData.FindIkConstraint(property.stringValue) != null;
}
protected override void PopulateMenu (GenericMenu menu, SerializedProperty property, SpineIkConstraint targetAttribute, SkeletonData data) {
var constraints = skeletonDataAsset.GetSkeletonData(false).IkConstraints;
@ -376,6 +421,10 @@ namespace Spine.Unity.Editor {
protected override Texture2D Icon { get { return SpineEditorUtilities.Icons.constraintTransform; } }
protected override bool IsValueValid (SkeletonData skeletonData, SerializedProperty property) {
return skeletonData.FindTransformConstraint(property.stringValue) != null;
}
protected override void PopulateMenu (GenericMenu menu, SerializedProperty property, SpineTransformConstraint targetAttribute, SkeletonData data) {
var constraints = skeletonDataAsset.GetSkeletonData(false).TransformConstraints;
@ -395,6 +444,10 @@ namespace Spine.Unity.Editor {
protected override Texture2D Icon { get { return SpineEditorUtilities.Icons.constraintPath; } }
protected override bool IsValueValid (SkeletonData skeletonData, SerializedProperty property) {
return skeletonData.FindPathConstraint(property.stringValue) != null;
}
protected override void PopulateMenu (GenericMenu menu, SerializedProperty property, SpinePathConstraint targetAttribute, SkeletonData data) {
var constraints = skeletonDataAsset.GetSkeletonData(false).PathConstraints;
@ -516,6 +569,10 @@ namespace Spine.Unity.Editor {
protected override Texture2D Icon { get { return SpineEditorUtilities.Icons.bone; } }
protected override bool IsValueValid (SkeletonData skeletonData, SerializedProperty property) {
return skeletonData.FindBone(property.stringValue) != null;
}
protected override void PopulateMenu (GenericMenu menu, SerializedProperty property, SpineBone targetAttribute, SkeletonData data) {
menu.AddDisabledItem(new GUIContent(skeletonDataAsset.name));
menu.AddSeparator("");

View File

@ -349,6 +349,8 @@ namespace Spine.Unity.Editor {
}
}
AddDependentAtlasIfImageChanged(atlasPaths, imagePaths);
// Import atlases first.
var newAtlases = new List<AtlasAssetBase>();
foreach (string ap in atlasPaths) {
@ -453,6 +455,18 @@ namespace Spine.Unity.Editor {
}
}
static void AddDependentAtlasIfImageChanged (List<string> atlasPaths, List<string> imagePaths) {
foreach (var imagePath in imagePaths) {
string atlasPath = imagePath.Replace(".png", ".atlas.txt");
if (!System.IO.File.Exists(atlasPath))
continue;
if (!atlasPaths.Contains(atlasPath)) {
atlasPaths.Add(atlasPath);
}
}
}
static void AddDependentSkeletonIfAtlasChanged (List<PathAndProblemInfo> skeletonPaths, List<string> atlasPaths) {
foreach (var atlasPath in atlasPaths) {
string skeletonPathJson = atlasPath.Replace(".atlas.txt", ".json");

View File

@ -67,6 +67,9 @@ namespace Spine.Unity.Editor {
/// </summary>
public static class SkeletonBaker {
const string SpineEventStringId = "SpineEvent";
const float EventTimeEqualityEpsilon = 0.01f;
#region SkeletonMecanim's Mecanim Clips
#if SPINE_SKELETONMECANIM
public static void UpdateMecanimClips (SkeletonDataAsset skeletonDataAsset) {
@ -145,11 +148,14 @@ namespace Spine.Unity.Editor {
settings.stopTime = animations.Duration;
SetAnimationSettings(clip, settings);
AnimationUtility.SetAnimationEvents(clip, new AnimationEvent[0]);
var previousAnimationEvents = AnimationUtility.GetAnimationEvents(clip);
var animationEvents = new List<AnimationEvent>();
foreach (Timeline t in animations.Timelines) {
if (t is EventTimeline)
ParseEventTimeline((EventTimeline)t, clip, SendMessageOptions.DontRequireReceiver);
ParseEventTimeline(ref animationEvents, (EventTimeline)t, SendMessageOptions.DontRequireReceiver);
}
AddPreviousUserEvents(ref animationEvents, previousAnimationEvents);
AnimationUtility.SetAnimationEvents(clip, animationEvents.ToArray());
EditorUtility.SetDirty(clip);
unityAnimationClipTable.Remove(animationName);
@ -1495,10 +1501,17 @@ namespace Spine.Unity.Editor {
}
static void ParseEventTimeline (EventTimeline timeline, AnimationClip clip, SendMessageOptions eventOptions) {
var animationEvents = new List<AnimationEvent>();
ParseEventTimeline(ref animationEvents, timeline, eventOptions);
AnimationUtility.SetAnimationEvents(clip, animationEvents.ToArray());
}
static void ParseEventTimeline (ref List<AnimationEvent> animationEvents,
EventTimeline timeline, SendMessageOptions eventOptions) {
float[] frames = timeline.Frames;
var events = timeline.Events;
var animEvents = new List<AnimationEvent>();
for (int i = 0, n = frames.Length; i < n; i++) {
var spineEvent = events[i];
string eventName = spineEvent.Data.Name;
@ -1509,7 +1522,8 @@ namespace Spine.Unity.Editor {
var unityAnimationEvent = new AnimationEvent {
time = frames[i],
functionName = eventName,
messageOptions = eventOptions
messageOptions = eventOptions,
stringParameter = SpineEventStringId
};
if (!string.IsNullOrEmpty(spineEvent.String)) {
@ -1520,10 +1534,23 @@ namespace Spine.Unity.Editor {
unityAnimationEvent.floatParameter = spineEvent.Float;
} // else, paramless function/Action.
animEvents.Add(unityAnimationEvent);
animationEvents.Add(unityAnimationEvent);
}
}
AnimationUtility.SetAnimationEvents(clip, animEvents.ToArray());
static void AddPreviousUserEvents (ref List<AnimationEvent> allEvents, AnimationEvent[] previousEvents) {
foreach (AnimationEvent previousEvent in previousEvents) {
if (previousEvent.stringParameter == SpineEventStringId)
continue;
var identicalEvent = allEvents.Find(newEvent => {
return newEvent.functionName == previousEvent.functionName &&
Mathf.Abs(newEvent.time - previousEvent.time) < EventTimeEqualityEpsilon;
});
if (identicalEvent != null)
continue;
allEvents.Add(previousEvent);
}
}
static void ParseAttachmentTimeline (Skeleton skeleton, AttachmentTimeline timeline, Dictionary<int, List<string>> slotLookup, AnimationClip clip) {

View File

@ -217,14 +217,30 @@ namespace Spine.Unity {
FillStateData();
}
public void FillStateData () {
public void FillStateData (bool quiet = false) {
if (stateData != null) {
stateData.DefaultMix = defaultMix;
for (int i = 0, n = fromAnimation.Length; i < n; i++) {
if (fromAnimation[i].Length == 0 || toAnimation[i].Length == 0)
string fromAnimationName = fromAnimation[i];
string toAnimationName = toAnimation[i];
if (fromAnimationName.Length == 0 || toAnimationName.Length == 0)
continue;
stateData.SetMix(fromAnimation[i], toAnimation[i], duration[i]);
#if UNITY_EDITOR
if (skeletonData.FindAnimation(fromAnimationName) == null) {
if (!quiet) Debug.LogError(
string.Format("Custom Mix Durations: Animation '{0}' not found, was it renamed?",
fromAnimationName), this);
continue;
}
if (skeletonData.FindAnimation(toAnimationName) == null) {
if (!quiet) Debug.LogError(
string.Format("Custom Mix Durations: Animation '{0}' not found, was it renamed?",
toAnimationName), this);
continue;
}
#endif
stateData.SetMix(fromAnimationName, toAnimationName, duration[i]);
}
}
}

View File

@ -52,14 +52,23 @@ namespace Spine.Unity.AttachmentTools {
/// <param name="useOriginalRegionScale">If <c>true</c> and the original Attachment is a RegionAttachment, then
/// the original region's scale value is used instead of the Sprite's pixels per unit property. Since uniform scale is used,
/// x scale of the original attachment (width scale) is used, scale in y direction (height scale) is ignored.</param>
/// <param name="pmaCloneTextureFormat">If <c>premultiplyAlpha</c> is <c>true></c>, the TextureFormat of the
/// newly created PMA attachment Texture.</param>
/// <param name="pmaCloneMipmaps">If <c>premultiplyAlpha</c> is <ctrue></c>, whether the newly created
/// PMA attachment Texture has mipmaps enabled.</param>
/// <remarks>When parameter <c>premultiplyAlpha</c> is set to <c>true</c>, a premultiply alpha clone of the
/// original texture will be created. Additionally, this PMA Texture clone is cached for later re-use,
/// which might steadily increase the Texture memory footprint when used excessively.
/// See <see cref="AtlasUtilities.ClearCache()"/> on how to clear these cached textures.</remarks>
public static Attachment GetRemappedClone (this Attachment o, Sprite sprite, Material sourceMaterial,
bool premultiplyAlpha = true, bool cloneMeshAsLinked = true, bool useOriginalRegionSize = false,
bool pivotShiftsMeshUVCoords = true, bool useOriginalRegionScale = false) {
var atlasRegion = premultiplyAlpha ? sprite.ToAtlasRegionPMAClone(sourceMaterial) : sprite.ToAtlasRegion(new Material(sourceMaterial) { mainTexture = sprite.texture });
bool pivotShiftsMeshUVCoords = true, bool useOriginalRegionScale = false,
TextureFormat pmaCloneTextureFormat = AtlasUtilities.SpineTextureFormat,
bool pmaCloneMipmaps = AtlasUtilities.UseMipMaps) {
var atlasRegion = premultiplyAlpha ?
sprite.ToAtlasRegionPMAClone(sourceMaterial, pmaCloneTextureFormat, pmaCloneMipmaps) :
sprite.ToAtlasRegion(new Material(sourceMaterial) { mainTexture = sprite.texture });
if (!pivotShiftsMeshUVCoords && o is MeshAttachment) {
// prevent non-central sprite pivot setting offsetX/Y and shifting uv coords out of mesh bounds
atlasRegion.offsetX = 0;