mirror of
https://github.com/EsotericSoftware/spine-runtimes.git
synced 2025-12-20 09:16:01 +08:00
[haxe][flixel] SkeletonSprite extends FlxTypedGroup<FlxObject> rather than FlxObject.
Replace direct bounds calculation with BoundsProvider interface for better performance and correctness. This makes it easier to get the correct bounds. BREAKING CHANGES: - SkeletonSprite extends FlxTypedGroup<FlxObject> rather than FlxObject. This was necessary because the FlxObject bounding/hitbox is always connected to its position and size and cannot be offset. - Removed getAnimationBounds() method. Replace it with the appropriate BoundsProvider implementation based on your use case, or create your own. - Removed setBoundingBox(). Use BoundsProvider features. - hitTest() now uses the assigned BoundsProvider instead of direct calculation. For accurate hit testing, use CurrentPoseBoundsProvider and call calculateBounds() each frame or on click. New features: - Uses BoundsProvider as starling. - SkeletonSprite constructor now accepts a third optional parameter for BoundsProvider. SetupPoseBoundsProvider is used by default. - Added calculateBounds() to recalculate bounds on demand.
This commit is contained in:
parent
196df9c386
commit
5990697d6e
5
spine-haxe/example/assets/quad.atlas
Normal file
5
spine-haxe/example/assets/quad.atlas
Normal file
@ -0,0 +1,5 @@
|
||||
quad.png
|
||||
size:100,100
|
||||
filter:Linear,Linear
|
||||
image
|
||||
bounds:0,0,100,100
|
||||
44
spine-haxe/example/assets/quad.json
Normal file
44
spine-haxe/example/assets/quad.json
Normal file
@ -0,0 +1,44 @@
|
||||
{
|
||||
"bones": [
|
||||
{ "name": "root" },
|
||||
{
|
||||
"name": "pivot",
|
||||
"parent": "root",
|
||||
"scaleX": 1,
|
||||
"scaleY": 1,
|
||||
"rotation": 0,
|
||||
"x": 20,
|
||||
"y": -20
|
||||
},
|
||||
{
|
||||
"name": "pivot2",
|
||||
"parent": "pivot",
|
||||
"scaleX": 1,
|
||||
"scaleY": 1,
|
||||
"rotation": 0,
|
||||
"x": 20,
|
||||
"y": -20
|
||||
},
|
||||
{
|
||||
"name": "replaceMe",
|
||||
"parent": "pivot2",
|
||||
"scaleX": 1,
|
||||
"scaleY": 1,
|
||||
"rotation": 0,
|
||||
"x": 20,
|
||||
"y": -20
|
||||
}
|
||||
],
|
||||
"slots": [
|
||||
{ "name": "replaceMe", "bone": "replaceMe", "attachment": "image" }
|
||||
],
|
||||
"skins": [
|
||||
{
|
||||
"name": "default",
|
||||
"attachments": {
|
||||
"replaceMe": { "image": { "width": 100, "height": 100 } }
|
||||
}
|
||||
}
|
||||
],
|
||||
"animations": { "animation": {} }
|
||||
}
|
||||
BIN
spine-haxe/example/assets/quad.png
Normal file
BIN
spine-haxe/example/assets/quad.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.5 KiB |
@ -57,8 +57,7 @@ class BasicExample extends FlxState {
|
||||
animationStateData.defaultMix = 0.25;
|
||||
|
||||
skeletonSprite = new SkeletonSprite(skeletondata, animationStateData);
|
||||
var animation = skeletonSprite.state.setAnimationByName(0, "walk", true).animation;
|
||||
skeletonSprite.setBoundingBox(animation);
|
||||
// skeletonSprite.state.setAnimationByName(0, "walk", true);
|
||||
skeletonSprite.screenCenter();
|
||||
add(skeletonSprite);
|
||||
|
||||
|
||||
@ -29,6 +29,7 @@
|
||||
|
||||
package flixelExamples;
|
||||
|
||||
import spine.boundsprovider.SkinsAndAnimationBoundsProvider;
|
||||
import flixel.util.FlxColor;
|
||||
import flixel.text.FlxText;
|
||||
import spine.Skin;
|
||||
@ -42,7 +43,7 @@ import spine.SkeletonData;
|
||||
import spine.animation.AnimationStateData;
|
||||
import spine.atlas.TextureAtlas;
|
||||
|
||||
class AnimationBoundExample extends FlxState {
|
||||
class BoundsProviderExample extends FlxState {
|
||||
var loadBinary = true;
|
||||
|
||||
override public function create():Void {
|
||||
@ -60,38 +61,34 @@ class AnimationBoundExample extends FlxState {
|
||||
var animationStateData = new AnimationStateData(data);
|
||||
animationStateData.defaultMix = 0.25;
|
||||
|
||||
var skeletonSpriteClipping = new SkeletonSprite(data, animationStateData);
|
||||
var animationClipping = skeletonSpriteClipping.state.setAnimationByName(0, "portal", true).animation;
|
||||
skeletonSpriteClipping.update(0);
|
||||
skeletonSpriteClipping.setBoundingBox(animationClipping, true);
|
||||
var skeletonSpriteClipping = new SkeletonSprite(data, animationStateData, new SkinsAndAnimationBoundsProvider("portal", null, null, true));
|
||||
skeletonSpriteClipping.state.setAnimationByName(0, "portal", true);
|
||||
skeletonSpriteClipping.screenCenter();
|
||||
skeletonSpriteClipping.x = FlxG.width / 4 - skeletonSpriteClipping.width / 2;
|
||||
skeletonSpriteClipping.x = FlxG.width / 4;
|
||||
add(skeletonSpriteClipping);
|
||||
var textClipping = new FlxText();
|
||||
textClipping.text = "Animation bound with clipping";
|
||||
textClipping.text = "Bounds with clipping";
|
||||
textClipping.size = 12;
|
||||
textClipping.x = skeletonSpriteClipping.x + skeletonSpriteClipping.width / 2 - textClipping.width / 2;
|
||||
textClipping.y = skeletonSpriteClipping.y + skeletonSpriteClipping.height + 20;
|
||||
textClipping.x = skeletonSpriteClipping.boundsX + skeletonSpriteClipping.width / 2 - textClipping.width / 2;
|
||||
textClipping.y = skeletonSpriteClipping.boundsY + skeletonSpriteClipping.height + 20;
|
||||
textClipping.setBorderStyle(FlxTextBorderStyle.OUTLINE, FlxColor.RED, 2);
|
||||
add(textClipping);
|
||||
|
||||
var skeletonSpriteNoClipping = new SkeletonSprite(data, animationStateData);
|
||||
var animationClipping = skeletonSpriteNoClipping.state.setAnimationByName(0, "portal", true).animation;
|
||||
skeletonSpriteNoClipping.update(0);
|
||||
skeletonSpriteNoClipping.setBoundingBox(animationClipping, false);
|
||||
var skeletonSpriteNoClipping = new SkeletonSprite(data, animationStateData, new SkinsAndAnimationBoundsProvider("portal"));
|
||||
skeletonSpriteNoClipping.state.setAnimationByName(0, "portal", true);
|
||||
skeletonSpriteNoClipping.screenCenter();
|
||||
skeletonSpriteNoClipping.x = FlxG.width / 4 * 3 - skeletonSpriteClipping.width / 2 - 50;
|
||||
skeletonSpriteNoClipping.x = FlxG.width / 4 * 3;
|
||||
add(skeletonSpriteNoClipping);
|
||||
var textNoClipping = new FlxText();
|
||||
textNoClipping.text = "Animation bound without clipping";
|
||||
textNoClipping.text = "Bounds without clipping";
|
||||
textNoClipping.size = 12;
|
||||
textNoClipping.x = skeletonSpriteNoClipping.x + skeletonSpriteNoClipping.width / 2 - textNoClipping.width / 2;
|
||||
textNoClipping.y = skeletonSpriteNoClipping.y + skeletonSpriteNoClipping.height + 20;
|
||||
textNoClipping.x = skeletonSpriteNoClipping.boundsX + skeletonSpriteNoClipping.width / 2 - textNoClipping.width / 2;
|
||||
textNoClipping.y = skeletonSpriteNoClipping.boundsY + skeletonSpriteNoClipping.height + 20;
|
||||
textNoClipping.setBorderStyle(FlxTextBorderStyle.OUTLINE, FlxColor.RED, 2);
|
||||
add(textNoClipping);
|
||||
|
||||
var textInstruction = new FlxText();
|
||||
textInstruction.text = "Red rectangle is the animation bound";
|
||||
textInstruction.text = "Red rectangle is the Spine provider bounds";
|
||||
textInstruction.size = 12;
|
||||
textInstruction.screenCenter();
|
||||
textInstruction.y = textNoClipping.y + 40;
|
||||
@ -46,7 +46,7 @@ class CloudPotExample extends FlxState {
|
||||
override public function create():Void {
|
||||
FlxG.cameras.bgColor = 0xffa1b2b0;
|
||||
|
||||
var button = new FlxButton(0, 0, "Next scene", () -> FlxG.switchState(() -> new AnimationBoundExample()));
|
||||
var button = new FlxButton(0, 0, "Next scene", () -> FlxG.switchState(() -> new BoundsProviderExample()));
|
||||
button.setPosition(FlxG.width * .75, FlxG.height / 10);
|
||||
add(button);
|
||||
|
||||
|
||||
@ -65,8 +65,7 @@ class ControlBonesExample extends FlxState {
|
||||
var skeletonSprite = new SkeletonSprite(data, animationStateData);
|
||||
skeletonSprite.scaleX = .5;
|
||||
skeletonSprite.scaleY = .5;
|
||||
var animation = skeletonSprite.state.setAnimationByName(0, "idle", true).animation;
|
||||
skeletonSprite.setBoundingBox(animation);
|
||||
skeletonSprite.state.setAnimationByName(0, "idle", true);
|
||||
skeletonSprite.screenCenter();
|
||||
add(skeletonSprite);
|
||||
|
||||
@ -77,6 +76,8 @@ class ControlBonesExample extends FlxState {
|
||||
"front-leg-ik-target",
|
||||
];
|
||||
|
||||
// we need to update, to ensure scale is applied before getting bone values
|
||||
skeletonSprite.update(0);
|
||||
var radius = 6;
|
||||
for (boneName in controlBoneNames) {
|
||||
var bone = skeletonSprite.skeleton.findBone(boneName);
|
||||
@ -84,7 +85,7 @@ class ControlBonesExample extends FlxState {
|
||||
skeletonSprite.skeletonToHaxeWorldCoordinates(point);
|
||||
var control = new FlxSprite();
|
||||
control.makeGraphic(radius * 2, radius * 2, FlxColor.TRANSPARENT, true);
|
||||
FlxSpriteUtil.drawCircle(control, radius, radius, radius, 0xffff00ff);
|
||||
FlxSpriteUtil.drawCircle(control, -1, -1, -1, 0xffff00ff);
|
||||
control.setPosition(point[0] - radius, point[1] - radius);
|
||||
controlBones.push(bone);
|
||||
controls.push(control);
|
||||
|
||||
@ -68,9 +68,6 @@ class EventsExample extends FlxState {
|
||||
skeletonSprite.state.setAnimationByName(0, "walk", true);
|
||||
|
||||
var trackEntry = skeletonSprite.state.addAnimationByName(0, "run", true, 3);
|
||||
skeletonSprite.setBoundingBox(trackEntry.animation);
|
||||
|
||||
skeletonSprite.setBoundingBox();
|
||||
skeletonSprite.screenCenter();
|
||||
skeletonSprite.skeleton.setupPoseBones();
|
||||
add(skeletonSprite);
|
||||
|
||||
@ -30,6 +30,11 @@
|
||||
package flixelExamples;
|
||||
|
||||
import flixel.ui.FlxButton;
|
||||
import flixel.math.FlxPoint;
|
||||
import flixel.util.FlxColor;
|
||||
import flixel.util.FlxSpriteUtil;
|
||||
import spine.boundsprovider.SetupPoseBoundsProvider;
|
||||
import spine.boundsprovider.SkinsAndAnimationBoundsProvider;
|
||||
import flixel.group.FlxSpriteGroup;
|
||||
import flixel.FlxSprite;
|
||||
import flixel.graphics.FlxGraphic;
|
||||
@ -56,6 +61,11 @@ class FlixelState extends FlxState {
|
||||
var scale = 4;
|
||||
var speed:Float;
|
||||
|
||||
var skeletonOrigin:FlxSprite;
|
||||
var gameObjectOrigin:FlxSprite;
|
||||
var radius = 3;
|
||||
var rootPoint = [.0, .0];
|
||||
|
||||
override public function create():Void {
|
||||
FlxG.cameras.bgColor = 0xffa1b2b0;
|
||||
|
||||
@ -97,12 +107,37 @@ class FlixelState extends FlxState {
|
||||
|
||||
// loading spineboy
|
||||
var atlas = new TextureAtlas(Assets.getText("assets/spineboy.atlas"), new FlixelTextureLoader("assets/spineboy.atlas"));
|
||||
var skeletondata = SkeletonData.from(Assets.getText("assets/spineboy-pro.json"), atlas, 1 / scale);
|
||||
var skeletondata = SkeletonData.from(Assets.getText("assets/spineboy-pro.json"), atlas, 1);
|
||||
|
||||
// var atlas = new TextureAtlas(Assets.getText("assets/quad.atlas"), new FlixelTextureLoader("assets/quad.atlas"));
|
||||
// var skeletondata = SkeletonData.from(Assets.getText("assets/quad.json"), atlas, 2);
|
||||
|
||||
var animationStateData = new AnimationStateData(skeletondata);
|
||||
spineSprite = new SkeletonSprite(skeletondata, animationStateData);
|
||||
// spineSprite = new SkeletonSprite(skeletondata, animationStateData, new SetupPoseBoundsProvider());
|
||||
|
||||
spineSprite = new SkeletonSprite(skeletondata, animationStateData, new SkinsAndAnimationBoundsProvider("walk"));
|
||||
|
||||
spineSprite.scale = new FlxPoint(1 / scale, 1 / scale);
|
||||
|
||||
// positioning spineboy
|
||||
spineSprite.setPosition(.5 * FlxG.width, .5 * FlxG.height);
|
||||
spineSprite.screenCenter();
|
||||
spineSprite.y += spineSprite.height / 2;
|
||||
|
||||
var control3 = new FlxSprite();
|
||||
control3.makeGraphic(radius * 2, radius * 2, FlxColor.TRANSPARENT, true);
|
||||
FlxSpriteUtil.drawCircle(control3, radius, radius, radius, 0xff5500ff);
|
||||
add(control3);
|
||||
control3.setPosition(FlxG.width / 2, FlxG.height / 2);
|
||||
|
||||
skeletonOrigin = new FlxSprite();
|
||||
skeletonOrigin.makeGraphic(radius * 2, radius * 2, FlxColor.TRANSPARENT, true);
|
||||
FlxSpriteUtil.drawCircle(skeletonOrigin, radius, radius, radius, 0xff5500ff);
|
||||
add(skeletonOrigin);
|
||||
|
||||
gameObjectOrigin = new FlxSprite();
|
||||
gameObjectOrigin.makeGraphic(radius * 2, radius * 2, FlxColor.TRANSPARENT, true);
|
||||
FlxSpriteUtil.drawCircle(gameObjectOrigin, radius, radius, radius, 0xffff9100);
|
||||
add(gameObjectOrigin);
|
||||
|
||||
// setting mix times
|
||||
animationStateData.defaultMix = 0.5;
|
||||
@ -119,7 +154,7 @@ class FlixelState extends FlxState {
|
||||
// setting idle animation
|
||||
spineSprite.state.setAnimationByName(0, "idle", true);
|
||||
|
||||
// setting y offset function to move object body while jumping
|
||||
// // setting y offset function to move object body while jumping
|
||||
var hip = spineSprite.skeleton.findBone("hip");
|
||||
var initialY = 0.;
|
||||
var initialOffsetY = 0.;
|
||||
@ -137,13 +172,19 @@ class FlixelState extends FlxState {
|
||||
}
|
||||
});
|
||||
var diff = .0;
|
||||
var tmpPoint = [.0, .0];
|
||||
spineSprite.afterUpdateWorldTransforms = spineSprite -> {
|
||||
if (jumping) {
|
||||
diff -= hip.pose.y;
|
||||
spineSprite.offsetY -= diff;
|
||||
spineSprite.y += diff;
|
||||
tmpPoint[1] = hip.applied.worldY;
|
||||
spineSprite.skeletonToHaxeWorldCoordinates(tmpPoint);
|
||||
diff -= (tmpPoint[1]);
|
||||
spineSprite.offsetY += diff;
|
||||
spineSprite.y -= diff;
|
||||
}
|
||||
diff = hip.pose.y;
|
||||
|
||||
tmpPoint[1] = hip.applied.worldY;
|
||||
spineSprite.skeletonToHaxeWorldCoordinates(tmpPoint);
|
||||
diff = tmpPoint[1];
|
||||
}
|
||||
|
||||
// adding spineboy to the stage
|
||||
@ -152,7 +193,7 @@ class FlixelState extends FlxState {
|
||||
// FlxG.debugger.visible = !FlxG.debugger.visible;
|
||||
// debug ui
|
||||
// FlxG.debugger.visible = true;
|
||||
// FlxG.debugger.drawDebug = true;
|
||||
FlxG.debugger.drawDebug = true;
|
||||
// FlxG.log.redirectTraces = true;
|
||||
|
||||
// FlxG.debugger.track(spineSprite);
|
||||
@ -160,12 +201,14 @@ class FlixelState extends FlxState {
|
||||
// FlxG.watch.add(spineSprite, "offsetY");
|
||||
// FlxG.watch.add(spineSprite, "y");
|
||||
// FlxG.watch.add(this, "jumping");
|
||||
|
||||
super.create();
|
||||
}
|
||||
|
||||
var justSetIdle = true;
|
||||
|
||||
override public function update(elapsed:Float):Void {
|
||||
// spineSprite.calculateBounds();
|
||||
if (FlxG.overlap(spineSprite, group)) {
|
||||
myText.text = "Overlapping";
|
||||
} else {
|
||||
@ -184,6 +227,17 @@ class FlixelState extends FlxState {
|
||||
FlxG.debugger.visible = !FlxG.debugger.visible;
|
||||
}
|
||||
|
||||
if (FlxG.keys.anyPressed([UP, DOWN])) {
|
||||
if (FlxG.keys.anyPressed([UP])) {
|
||||
if (spineSprite.flipY == true)
|
||||
spineSprite.flipY = false;
|
||||
}
|
||||
if (FlxG.keys.anyPressed([DOWN])) {
|
||||
if (spineSprite.flipY == false)
|
||||
spineSprite.flipY = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (FlxG.keys.anyPressed([RIGHT, LEFT])) {
|
||||
justSetIdle = false;
|
||||
var flipped = false;
|
||||
@ -217,6 +271,13 @@ class FlixelState extends FlxState {
|
||||
spineSprite.state.setAnimationByName(0, "idle", true);
|
||||
}
|
||||
|
||||
var rootBone = spineSprite.skeleton.findBone("root");
|
||||
rootPoint[0] = rootBone.applied.worldX;
|
||||
rootPoint[1] = rootBone.applied.worldY;
|
||||
spineSprite.skeletonToHaxeWorldCoordinates(rootPoint);
|
||||
skeletonOrigin.setPosition(rootPoint[0] - radius, rootPoint[1] - radius);
|
||||
gameObjectOrigin.setPosition(spineSprite.x - radius, spineSprite.y - radius);
|
||||
|
||||
super.update(elapsed);
|
||||
}
|
||||
}
|
||||
|
||||
@ -29,6 +29,7 @@
|
||||
|
||||
package flixelExamples;
|
||||
|
||||
import spine.boundsprovider.SkinsAndAnimationBoundsProvider;
|
||||
import spine.Skin;
|
||||
import flixel.ui.FlxButton;
|
||||
import flixel.FlxG;
|
||||
@ -59,7 +60,7 @@ class MixAndMatchExample extends FlxState {
|
||||
var animationStateData = new AnimationStateData(data);
|
||||
animationStateData.defaultMix = 0.25;
|
||||
|
||||
skeletonSprite = new SkeletonSprite(data, animationStateData);
|
||||
skeletonSprite = new SkeletonSprite(data, animationStateData, new SkinsAndAnimationBoundsProvider("dance", ["full-skins/boy"]));
|
||||
var customSkin = new Skin("custom");
|
||||
var skinBase = data.findSkin("skin-base");
|
||||
customSkin.addSkin(skinBase);
|
||||
@ -74,8 +75,7 @@ class MixAndMatchExample extends FlxState {
|
||||
skeletonSprite.skeleton.skin = customSkin;
|
||||
|
||||
skeletonSprite.state.update(0);
|
||||
var animation = skeletonSprite.state.setAnimationByName(0, "dance", true).animation;
|
||||
skeletonSprite.setBoundingBox(animation);
|
||||
skeletonSprite.state.setAnimationByName(0, "dance", true);
|
||||
skeletonSprite.screenCenter();
|
||||
add(skeletonSprite);
|
||||
|
||||
|
||||
@ -59,7 +59,6 @@ class SequenceExample extends FlxState {
|
||||
skeletonSprite = new SkeletonSprite(skeletondata, animationStateData);
|
||||
|
||||
var animation = skeletonSprite.state.setAnimationByName(0, "flying", true).animation;
|
||||
skeletonSprite.setBoundingBox(animation);
|
||||
skeletonSprite.screenCenter();
|
||||
add(skeletonSprite);
|
||||
super.create();
|
||||
|
||||
@ -57,7 +57,6 @@ class TankExample extends FlxState {
|
||||
|
||||
var skeletonSprite = new SkeletonSprite(data, animationStateData);
|
||||
var animation = skeletonSprite.state.setAnimationByName(0, "drive", true).animation;
|
||||
skeletonSprite.setBoundingBox(animation);
|
||||
skeletonSprite.screenCenter();
|
||||
add(skeletonSprite);
|
||||
|
||||
|
||||
@ -57,7 +57,6 @@ class VineExample extends FlxState {
|
||||
|
||||
var skeletonSprite = new SkeletonSprite(data, animationStateData);
|
||||
var animation = skeletonSprite.state.setAnimationByName(0, "grow", true).animation;
|
||||
skeletonSprite.setBoundingBox(animation);
|
||||
skeletonSprite.screenCenter();
|
||||
add(skeletonSprite);
|
||||
|
||||
|
||||
@ -2,9 +2,9 @@
|
||||
<project>
|
||||
|
||||
<meta title="spine-haxe-example" package="spine" version="4.2.0" company="Esoteric Software" />
|
||||
<app main="Main" path="export" file="SpineHaxeExample" />
|
||||
<!-- <app main="Main" path="export" file="SpineHaxeExample" /> -->
|
||||
<!-- <app main="MainStarling" path="export" file="SpineHaxeExample" /> -->
|
||||
<!-- <app main="MainFlixel" path="export" file="SpineHaxeExample" /> -->
|
||||
<app main="MainFlixel" path="export" file="SpineHaxeExample" />
|
||||
<window allow-high-dpi="true" />
|
||||
|
||||
<haxelib name="openfl" />
|
||||
|
||||
@ -45,8 +45,8 @@ class SetupPoseBoundsProvider extends BoundsProvider {
|
||||
}
|
||||
|
||||
public function calculateBounds(gameObject:BoundsGameObject, out:BoundsRectangle):BoundsRectangle {
|
||||
var skeleton = gameObject.skeleton;
|
||||
if (skeleton == null) {
|
||||
var prevSkeleton = gameObject.skeleton;
|
||||
if (prevSkeleton == null) {
|
||||
zeroRectangle(out);
|
||||
return out;
|
||||
}
|
||||
@ -54,7 +54,9 @@ class SetupPoseBoundsProvider extends BoundsProvider {
|
||||
// Make a copy of skeleton as this might be called while
|
||||
// the skeleton in the GameObject has already been heavily modified. We can not
|
||||
// reconstruct that state.
|
||||
var skeleton = new Skeleton(skeleton.data);
|
||||
var skeleton = new Skeleton(prevSkeleton.data);
|
||||
skeleton.scaleX = prevSkeleton.scaleX;
|
||||
skeleton.scaleY = prevSkeleton.scaleY * Bone.yDir;
|
||||
skeleton.setupPose();
|
||||
skeleton.updateWorldTransform(Physics.update);
|
||||
var newBounds = skeleton.getBounds(clipping ? new SkeletonClipping() : null);
|
||||
|
||||
@ -56,9 +56,9 @@ class SkinsAndAnimationBoundsProvider extends BoundsProvider {
|
||||
}
|
||||
|
||||
public function calculateBounds(gameObject:BoundsGameObject, out:BoundsRectangle):BoundsRectangle {
|
||||
var skeleton = gameObject.skeleton;
|
||||
var prevSkeleton = gameObject.skeleton;
|
||||
var state = gameObject.state;
|
||||
if (skeleton == null || state == null) {
|
||||
if (prevSkeleton == null || state == null) {
|
||||
zeroRectangle(out);
|
||||
return out;
|
||||
}
|
||||
@ -67,7 +67,9 @@ class SkinsAndAnimationBoundsProvider extends BoundsProvider {
|
||||
// the skeleton in the GameObject has already been heavily modified. We can not
|
||||
// reconstruct that state.
|
||||
var animationState = new AnimationState(state.data);
|
||||
var skeleton = new Skeleton(skeleton.data);
|
||||
var skeleton = new Skeleton(prevSkeleton.data);
|
||||
skeleton.scaleX = prevSkeleton.scaleX;
|
||||
skeleton.scaleY = prevSkeleton.scaleY * Bone.yDir;
|
||||
var clipper = clipping ? new SkeletonClipping() : null;
|
||||
var data = skeleton.data;
|
||||
if (skins.length > 0) {
|
||||
|
||||
@ -29,42 +29,35 @@
|
||||
|
||||
package spine.flixel;
|
||||
|
||||
import flixel.util.FlxDirectionFlags;
|
||||
import flixel.math.FlxRect;
|
||||
import flixel.FlxCamera;
|
||||
import flixel.FlxBasic;
|
||||
import flixel.util.FlxAxes;
|
||||
import flixel.group.FlxGroup.FlxTypedGroup;
|
||||
import spine.boundsprovider.SetupPoseBoundsProvider;
|
||||
import spine.boundsprovider.BoundsProvider;
|
||||
import openfl.geom.Point;
|
||||
import flixel.math.FlxPoint;
|
||||
import flixel.math.FlxMatrix;
|
||||
import spine.animation.MixDirection;
|
||||
import spine.animation.MixBlend;
|
||||
import spine.animation.Animation;
|
||||
import spine.TextureRegion;
|
||||
import haxe.extern.EitherType;
|
||||
import spine.attachments.Attachment;
|
||||
import flixel.util.typeLimit.OneOfTwo;
|
||||
import flixel.FlxCamera;
|
||||
import flixel.math.FlxRect;
|
||||
import flixel.FlxG;
|
||||
import flixel.FlxObject;
|
||||
import flixel.FlxSprite;
|
||||
import flixel.FlxStrip;
|
||||
import flixel.group.FlxSpriteGroup;
|
||||
import flixel.graphics.FlxGraphic;
|
||||
import flixel.util.FlxColor;
|
||||
import openfl.Vector;
|
||||
import openfl.display.BlendMode;
|
||||
import spine.Bone;
|
||||
import spine.Rectangle;
|
||||
import spine.Skeleton;
|
||||
import spine.SkeletonData;
|
||||
import spine.Slot;
|
||||
import spine.animation.AnimationState;
|
||||
import spine.animation.AnimationStateData;
|
||||
import spine.atlas.TextureAtlasRegion;
|
||||
import spine.attachments.MeshAttachment;
|
||||
import spine.attachments.RegionAttachment;
|
||||
import spine.attachments.ClippingAttachment;
|
||||
import spine.flixel.SkeletonMesh;
|
||||
|
||||
/** A FlxObject that draws a skeleton. The animation state and skeleton must be updated each frame. */
|
||||
class SkeletonSprite extends FlxObject {
|
||||
class SkeletonSprite extends FlxTypedGroup<FlxObject> {
|
||||
public var skeleton(default, null):Skeleton;
|
||||
public var state(default, null):AnimationState;
|
||||
public var stateData(default, null):AnimationStateData;
|
||||
@ -81,6 +74,18 @@ class SkeletonSprite extends FlxObject {
|
||||
public var flipY(default, set):Bool = false;
|
||||
public var antialiasing:Bool = true;
|
||||
|
||||
public var boundsProvider:BoundsProvider;
|
||||
|
||||
public var angle(default, set) = 0.;
|
||||
public var x(default, set) = 0.;
|
||||
public var y(default, set) = 0.;
|
||||
public var width(get, set):Float;
|
||||
public var height(get, set):Float;
|
||||
public var boundsX(get, never):Float;
|
||||
public var boundsY(get, never):Float;
|
||||
|
||||
@:isVar
|
||||
public var scale(never, set):FlxPoint;
|
||||
@:isVar
|
||||
public var scaleX(get, set):Float = 1;
|
||||
@:isVar
|
||||
@ -92,103 +97,50 @@ class SkeletonSprite extends FlxObject {
|
||||
|
||||
private var _tempMatrix = new FlxMatrix();
|
||||
private var _tempPoint = new Point();
|
||||
private var _tempPointFlip = [.0, .0];
|
||||
private var __bounds = new openfl.geom.Rectangle();
|
||||
private var __objectBounds = new FlxObject();
|
||||
|
||||
private static var QUAD_INDICES:Array<Int> = [0, 1, 2, 2, 3, 0];
|
||||
|
||||
/** Creates an uninitialized SkeletonSprite. The renderer, skeleton, and animation state must be set before use. */
|
||||
public function new(skeletonData:SkeletonData, animationStateData:AnimationStateData = null) {
|
||||
super(0, 0);
|
||||
public function new(skeletonData:SkeletonData, animationStateData:AnimationStateData = null, ?boundsProvider:BoundsProvider) {
|
||||
super(1);
|
||||
Bone.yDown = true;
|
||||
skeleton = new Skeleton(skeletonData);
|
||||
skeleton.updateWorldTransform(Physics.update);
|
||||
state = new AnimationState(animationStateData != null ? animationStateData : new AnimationStateData(skeletonData));
|
||||
setBoundingBox();
|
||||
// setBoundingBox();
|
||||
this.boundsProvider = boundsProvider ?? new SetupPoseBoundsProvider();
|
||||
this.calculateBounds();
|
||||
add(__objectBounds);
|
||||
}
|
||||
|
||||
public function setBoundingBox(?animation:Animation, ?clip:Bool = true) {
|
||||
var bounds = animation == null ? skeleton.getBounds() : getAnimationBounds(animation, clip);
|
||||
if (bounds.width > 0 && bounds.height > 0) {
|
||||
width = bounds.width;
|
||||
height = bounds.height;
|
||||
offsetX = -bounds.x;
|
||||
offsetY = -bounds.y;
|
||||
}
|
||||
// TODO: this changes the scale
|
||||
// public function setSize(width:Float, height:Float):Void {
|
||||
// this.width = width;
|
||||
// this.height = height;
|
||||
// }
|
||||
// ============================================================
|
||||
// DEBUG METHODS (if FLX_DEBUG)
|
||||
// ============================================================
|
||||
#if FLX_DEBUG
|
||||
public function drawDebug():Void {
|
||||
__objectBounds.drawDebug();
|
||||
}
|
||||
|
||||
public function getAnimationBounds(animation:Animation, clip:Bool = true):Rectangle {
|
||||
var clipper = clip ? SkeletonSprite.clipper : null;
|
||||
skeleton.setupPose();
|
||||
|
||||
var steps = 100, time = 0.;
|
||||
var stepTime = animation.duration != 0 ? animation.duration / steps : 0;
|
||||
var minX = 100000000.,
|
||||
maxX = -100000000.,
|
||||
minY = 100000000.,
|
||||
maxY = -100000000.;
|
||||
|
||||
for (i in 0...steps) {
|
||||
animation.apply(skeleton, time, time, false, [], 1, MixBlend.setup, MixDirection.mixIn, false);
|
||||
skeleton.updateWorldTransform(Physics.update);
|
||||
var boundsSkel = skeleton.getBounds(clipper);
|
||||
|
||||
if (!Math.isNaN(boundsSkel.x) && !Math.isNaN(boundsSkel.y) && !Math.isNaN(boundsSkel.width) && !Math.isNaN(boundsSkel.height)) {
|
||||
minX = Math.min(boundsSkel.x, minX);
|
||||
minY = Math.min(boundsSkel.y, minY);
|
||||
maxX = Math.max(boundsSkel.x + boundsSkel.width, maxX);
|
||||
maxY = Math.max(boundsSkel.y + boundsSkel.height, maxY);
|
||||
} else
|
||||
throw new SpineException("Animation bounds are invalid: " + animation.name);
|
||||
|
||||
time += stepTime;
|
||||
}
|
||||
|
||||
var bounds = new Rectangle();
|
||||
bounds.x = minX;
|
||||
bounds.y = minY;
|
||||
bounds.width = maxX - minX;
|
||||
bounds.height = maxY - minY;
|
||||
return bounds;
|
||||
public function drawDebugOnCamera(camera:FlxCamera):Void {
|
||||
__objectBounds.drawDebugOnCamera(camera);
|
||||
}
|
||||
#end
|
||||
|
||||
override public function destroy():Void {
|
||||
state.clearListeners();
|
||||
state = null;
|
||||
skeleton = null;
|
||||
|
||||
_tempVertices = null;
|
||||
_quadTriangles = null;
|
||||
_tempMatrix = null;
|
||||
_tempPoint = null;
|
||||
|
||||
if (_meshes != null) {
|
||||
for (mesh in _meshes)
|
||||
mesh.destroy();
|
||||
_meshes = null;
|
||||
}
|
||||
|
||||
super.destroy();
|
||||
}
|
||||
|
||||
override public function update(elapsed:Float):Void {
|
||||
super.update(elapsed);
|
||||
state.update(elapsed);
|
||||
state.apply(skeleton);
|
||||
this.beforeUpdateWorldTransforms(this);
|
||||
skeleton.update(elapsed);
|
||||
skeleton.updateWorldTransform(Physics.update);
|
||||
this.afterUpdateWorldTransforms(this);
|
||||
}
|
||||
|
||||
override public function draw():Void {
|
||||
if (alpha == 0)
|
||||
return;
|
||||
|
||||
renderMeshes();
|
||||
|
||||
#if FLX_DEBUG
|
||||
if (FlxG.debugger.drawDebug)
|
||||
drawDebug();
|
||||
#end
|
||||
// ============================================================
|
||||
// SKELETON SPRITE METHODS
|
||||
// ============================================================
|
||||
public function calculateBounds() {
|
||||
this.boundsProvider.calculateBounds(this, __bounds);
|
||||
__objectBounds.setPosition(x + __bounds.x, y + __bounds.y);
|
||||
__objectBounds.setSize(__bounds.width, __bounds.height);
|
||||
}
|
||||
|
||||
function renderMeshes():Void {
|
||||
@ -320,7 +272,7 @@ class SkeletonSprite extends FlxObject {
|
||||
private function getTransformMatrix():FlxMatrix {
|
||||
_tempMatrix.identity();
|
||||
// scale is connected to the skeleton scale - no need to rescale
|
||||
_tempMatrix.scale(1, 1);
|
||||
// _tempMatrix.scale(1, 1);
|
||||
_tempMatrix.rotate(angle * Math.PI / 180);
|
||||
_tempMatrix.translate(x + offsetX, y + offsetY);
|
||||
return _tempMatrix;
|
||||
@ -375,18 +327,24 @@ class SkeletonSprite extends FlxObject {
|
||||
}
|
||||
|
||||
function set_flipX(value:Bool):Bool {
|
||||
if (value != flipX)
|
||||
if (value != flipX) {
|
||||
skeleton.scaleX = -skeleton.scaleX;
|
||||
this.calculateBounds();
|
||||
}
|
||||
return flipX = value;
|
||||
}
|
||||
|
||||
function set_flipY(value:Bool):Bool {
|
||||
if (value != flipY)
|
||||
skeleton.scaleY = -skeleton.scaleY;
|
||||
if (value != flipY) {
|
||||
skeleton.scaleY = -skeleton.scaleY * Bone.yDir;
|
||||
this.calculateBounds();
|
||||
}
|
||||
return flipY = value;
|
||||
}
|
||||
|
||||
function set_scale(value:FlxPoint):FlxPoint {
|
||||
scaleX = value.x;
|
||||
scaleY = value.y;
|
||||
return value;
|
||||
}
|
||||
|
||||
@ -395,15 +353,199 @@ class SkeletonSprite extends FlxObject {
|
||||
}
|
||||
|
||||
function set_scaleX(value:Float):Float {
|
||||
return skeleton.scaleX = value;
|
||||
skeleton.scaleX = value;
|
||||
this.calculateBounds();
|
||||
return value;
|
||||
}
|
||||
|
||||
function get_scaleY():Float {
|
||||
return skeleton.scaleY;
|
||||
return skeleton.scaleY * Bone.yDir;
|
||||
}
|
||||
|
||||
function set_scaleY(value:Float):Float {
|
||||
return skeleton.scaleY = value;
|
||||
skeleton.scaleY = value;
|
||||
this.calculateBounds();
|
||||
return value;
|
||||
}
|
||||
|
||||
function set_angle(value:Float):Float {
|
||||
__objectBounds.angle = value;
|
||||
return angle = value;
|
||||
}
|
||||
|
||||
function set_x(value:Float):Float {
|
||||
__objectBounds.x = __bounds.x + value;
|
||||
return x = value;
|
||||
}
|
||||
|
||||
function set_y(value:Float):Float {
|
||||
__objectBounds.y = __bounds.y + value;
|
||||
return y = value;
|
||||
}
|
||||
|
||||
function get_height():Float {
|
||||
return __bounds.height;
|
||||
}
|
||||
|
||||
function get_width():Float {
|
||||
return __bounds.width;
|
||||
}
|
||||
|
||||
function set_width(value:Float):Float {
|
||||
var scale = value / __bounds.width;
|
||||
scaleX *= scale;
|
||||
return __bounds.width;
|
||||
}
|
||||
|
||||
function set_height(value:Float):Float {
|
||||
var scale = value / __bounds.height;
|
||||
scaleY *= scale;
|
||||
return __bounds.height;
|
||||
}
|
||||
|
||||
function get_boundsX():Float {
|
||||
return __objectBounds.x;
|
||||
}
|
||||
|
||||
function get_boundsY():Float {
|
||||
return __objectBounds.y;
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// OVERRIDE METHODS FROM FlxBasic
|
||||
// ============================================================
|
||||
|
||||
override public function update(elapsed:Float):Void {
|
||||
super.update(elapsed);
|
||||
state.update(elapsed);
|
||||
state.apply(skeleton);
|
||||
this.beforeUpdateWorldTransforms(this);
|
||||
skeleton.update(elapsed);
|
||||
skeleton.updateWorldTransform(Physics.update);
|
||||
this.afterUpdateWorldTransforms(this);
|
||||
}
|
||||
|
||||
override public function draw():Void {
|
||||
if (alpha == 0)
|
||||
return;
|
||||
|
||||
renderMeshes();
|
||||
|
||||
#if FLX_DEBUG
|
||||
if (FlxG.debugger.drawDebug)
|
||||
__objectBounds.drawDebug();
|
||||
#end
|
||||
}
|
||||
|
||||
override public function destroy():Void {
|
||||
state.clearListeners();
|
||||
state = null;
|
||||
skeleton = null;
|
||||
|
||||
_tempVertices = null;
|
||||
_quadTriangles = null;
|
||||
_tempMatrix = null;
|
||||
_tempPoint = null;
|
||||
|
||||
if (_meshes != null) {
|
||||
for (mesh in _meshes)
|
||||
mesh.destroy();
|
||||
_meshes = null;
|
||||
}
|
||||
|
||||
super.destroy();
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// OVERLAP/COLLISION METHODS
|
||||
// ============================================================
|
||||
|
||||
public function overlaps(objectOrGroup:FlxBasic, inScreenSpace:Bool = false, ?camera:FlxCamera):Bool {
|
||||
return __objectBounds.overlaps(objectOrGroup, inScreenSpace, camera);
|
||||
}
|
||||
|
||||
public function overlapsAt(x:Float, y:Float, objectOrGroup:FlxBasic, inScreenSpace = false, ?camera:FlxCamera):Bool {
|
||||
return __objectBounds.overlapsAt(x, y, objectOrGroup, inScreenSpace, camera);
|
||||
}
|
||||
|
||||
public function overlapsPoint(point:FlxPoint, inScreenSpace = false, ?camera:FlxCamera):Bool {
|
||||
return __objectBounds.overlapsPoint(point, inScreenSpace, camera);
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// BOUNDS/POSITION METHODS
|
||||
// ============================================================
|
||||
|
||||
public function inWorldBounds():Bool {
|
||||
return __objectBounds.inWorldBounds();
|
||||
}
|
||||
|
||||
public function getScreenPosition(?result:FlxPoint, ?camera:FlxCamera):FlxPoint {
|
||||
return __objectBounds.getScreenPosition(result, camera);
|
||||
}
|
||||
|
||||
public function getPosition(?result:FlxPoint):FlxPoint {
|
||||
return __objectBounds.getPosition(result);
|
||||
}
|
||||
|
||||
public function getMidpoint(?point:FlxPoint):FlxPoint {
|
||||
return __objectBounds.getMidpoint(point);
|
||||
}
|
||||
|
||||
public function getHitbox(?rect:FlxRect):FlxRect {
|
||||
return __objectBounds.getHitbox(rect);
|
||||
}
|
||||
|
||||
public function getRotatedBounds(?newRect:FlxRect):FlxRect {
|
||||
return __objectBounds.getRotatedBounds(newRect);
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// STATE METHODS
|
||||
// ============================================================
|
||||
|
||||
public function reset(x:Float, y:Float):Void {
|
||||
__objectBounds.reset(x, y);
|
||||
}
|
||||
|
||||
public function isOnScreen(?camera:FlxCamera):Bool {
|
||||
return __objectBounds.isOnScreen(camera);
|
||||
}
|
||||
|
||||
public function isPixelPerfectRender(?camera:FlxCamera):Bool {
|
||||
return __objectBounds.isPixelPerfectRender(camera);
|
||||
}
|
||||
|
||||
public function isTouching(direction:FlxDirectionFlags):Bool {
|
||||
return __objectBounds.isTouching(direction);
|
||||
}
|
||||
|
||||
public function justTouched(direction:FlxDirectionFlags):Bool {
|
||||
return __objectBounds.justTouched(direction);
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// UTILITY METHODS
|
||||
// ============================================================
|
||||
|
||||
public inline function screenCenter(axes:FlxAxes = XY):SkeletonSprite {
|
||||
if (axes.x)
|
||||
x = (FlxG.width - __bounds.width) / 2 - __bounds.x;
|
||||
|
||||
if (axes.y)
|
||||
y = (FlxG.height - __bounds.height) / 2 - __bounds.y;
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public function setPosition(x = 0.0, y = 0.0):Void {
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
}
|
||||
|
||||
public function setSize(width:Float, height:Float):Void {
|
||||
this.width = width;
|
||||
this.height = height;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -46,9 +46,13 @@
|
||||
|
||||
// Create the spine display object
|
||||
const stretchyman = spine.Spine.from({skeleton: "stretchymanData", atlas: "stretchymanAtlas",
|
||||
scale: 0.75,
|
||||
scale: 1,
|
||||
// scale: 0.75,
|
||||
});
|
||||
|
||||
stretchyman.skeleton.scaleX = .5;
|
||||
stretchyman.skeleton.scaleY = 1;
|
||||
|
||||
// Set the default mix time to use when transitioning
|
||||
// from one animation to the next.
|
||||
stretchyman.state.data.defaultMix = 0.2;
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user