[haxe] Support for HaxeFlixel (#2764)

* [haxe] Flixel support

* [haxe][flixel] WIP fix alpha and color tinting not working on meshes.

* [haxe][flixel] Added most of examples - Color/alpha is broken on flixel for meshes.

* [haxe][flixel] Flixel color bug example

* [haxe][flixel] Add same example of starling to Flixel.

* [haxe][flixel] Fix rotation for clipped attachments.

* [haxe][flixel] Minor modifications to avoid warning on flixel 6.0.0.

* [haxe] Updated readme.

* [haxe][flixel] Remove unused assets.

* [haxe] Removed useless flipX/flipY on core Skeleton.hx.
This commit is contained in:
Davide 2025-02-24 10:44:05 +01:00 committed by GitHub
parent 01a847b7f7
commit 7f2e4c1df7
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
36 changed files with 1719 additions and 34 deletions

View File

@ -23,12 +23,17 @@ spine-haxe works with data exported from Spine 4.2.xx.
spine-haxe supports all Spine features except premultiplied alpha atlases and two color tinting. spine-haxe supports all Spine features except premultiplied alpha atlases and two color tinting.
## Setup ## Setup
The core module of spine-haxe has zero dependencies. The rendering implementation through Starling has two dependencies: openfl and starling. The spine-haxe runtime is composed of a core module, that is a Haxe implementation of the renderer-agnostic Spine Runtimes core APIs, and the following specific renderer implementations:
- [Starling](https://lib.haxe.org/p/starling/)
- [HaxeFlixel](https://lib.haxe.org/p/flixel/) (minimum supported version 5.9.0)
The core module of spine-haxe has zero dependencies. The rendering implementation depends on: openfl, starling, and flixel.
To use spine-haxe you have first to install all the necessary dependencies: To use spine-haxe you have first to install all the necessary dependencies:
``` ```
haxelib install openfl haxelib install openfl
haxelib install starling haxelib install starling
haxelib install flixel
``` ```
Once you have installed the dependencies, you can [download the latest version of spine-haxe](https://esotericsoftware.com/files/spine-haxe/4.2/spine-haxe-latest.zip) and install it: Once you have installed the dependencies, you can [download the latest version of spine-haxe](https://esotericsoftware.com/files/spine-haxe/4.2/spine-haxe-latest.zip) and install it:
@ -60,6 +65,7 @@ To setup the development environment install the following:
haxelib install openfl haxelib install openfl
haxelib run openfl setup haxelib run openfl setup
haxelib install starling haxelib install starling
haxelib install flixel
``` ```
3. Clone the `spine-runtimes` repository, and use `haxelib` to setup a dev library: 3. Clone the `spine-runtimes` repository, and use `haxelib` to setup a dev library:
``` ```

View File

@ -27,24 +27,145 @@
* SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*****************************************************************************/ *****************************************************************************/
import Scene.SceneManager; package;
import openfl.display.Sprite;
import openfl.geom.Rectangle; import flixelExamples.FlixelState;
import starlingExamples.BasicExample;
import starlingExamples.Scene.SceneManager;
import starling.core.Starling; import starling.core.Starling;
import flixel.FlxG;
import flixel.FlxGame;
import openfl.display.Sprite;
import openfl.text.TextField;
import openfl.text.TextFormat;
import openfl.events.MouseEvent;
import openfl.geom.Rectangle;
import starling.events.Event; import starling.events.Event;
class Main extends Sprite { class Main extends Sprite {
private var background:Sprite;
private var flixelButton:Sprite;
private var starlingButton:Sprite;
private var uiContainer:Sprite;
private static inline var ratio = 4;
private static inline var STAGE_WIDTH:Int = 100 * ratio;
private static inline var STAGE_HEIGHT:Int = 200 * ratio;
private static inline var BUTTON_WIDTH:Int = 80 * ratio;
private static inline var BUTTON_HEIGHT:Int = 40 * ratio;
private static inline var BUTTON_SPACING:Int = 20 * ratio;
public function new() {
super();
addEventListener(Event.ADDED_TO_STAGE, onAddedToStage);
}
private function onAddedToStage(e:Event):Void {
removeEventListener(Event.ADDED_TO_STAGE, onAddedToStage);
createUI();
centerUI();
stage.addEventListener(Event.RESIZE, onResize);
}
private function createUI():Void {
uiContainer = new Sprite();
addChild(uiContainer);
background = new Sprite();
background.graphics.beginFill(0xA2A2A2);
background.graphics.drawRect(0, 0, STAGE_WIDTH, STAGE_HEIGHT);
background.graphics.endFill();
uiContainer.addChild(background);
flixelButton = createButton("Flixel", 0xFF0000);
uiContainer.addChild(flixelButton);
starlingButton = createButton("Starling", 0x00FF00);
uiContainer.addChild(starlingButton);
positionButtons();
flixelButton.addEventListener(MouseEvent.CLICK, onFlixelClick);
starlingButton.addEventListener(MouseEvent.CLICK, onStarlingClick);
}
private function createButton(label:String, color:Int):Sprite {
var button = new Sprite();
var g = button.graphics;
g.beginFill(color);
g.drawRoundRect(0, 0, BUTTON_WIDTH, BUTTON_HEIGHT, 10, 10);
g.endFill();
// Add button text
var tf = new TextField();
var format = new TextFormat("_sans", 14 * ratio, 0x000000, true, null, null, null, null, "center");
tf.defaultTextFormat = format;
tf.text = label;
tf.width = BUTTON_WIDTH;
tf.height = BUTTON_HEIGHT;
tf.mouseEnabled = false;
tf.selectable = false;
tf.y = (BUTTON_HEIGHT - tf.textHeight) / 2;
button.addChild(tf);
return button;
}
private function positionButtons():Void {
var totalHeight = (BUTTON_HEIGHT * 2) + BUTTON_SPACING;
var startY = (STAGE_HEIGHT - totalHeight) / 2;
flixelButton.x = (STAGE_WIDTH - BUTTON_WIDTH) / 2;
flixelButton.y = startY + BUTTON_HEIGHT + BUTTON_SPACING;
starlingButton.x = (STAGE_WIDTH - BUTTON_WIDTH) / 2;
starlingButton.y = startY;
}
private function centerUI():Void {
uiContainer.x = (stage.stageWidth - STAGE_WIDTH) / 2;
uiContainer.y = (stage.stageHeight - STAGE_HEIGHT) / 2;
}
private function onResize(e:Event):Void {
centerUI();
}
private function onFlixelClick(e:MouseEvent):Void {
trace("Launching Flixel game");
destroyUI();
addChild(new FlxGame(640, 480, FlixelState));
FlxG.autoPause = false;
}
private function destroyUI():Void {
flixelButton.removeEventListener(MouseEvent.CLICK, onFlixelClick);
starlingButton.removeEventListener(MouseEvent.CLICK, onStarlingClick);
stage.removeEventListener(Event.RESIZE, onResize);
removeChild(uiContainer);
background = null;
flixelButton = null;
starlingButton = null;
uiContainer = null;
}
private var starlingSingleton:Starling; private var starlingSingleton:Starling;
private function onStarlingClick(e:MouseEvent):Void {
public function new() { trace("Launching Starling game");
super();
starlingSingleton = new Starling(starling.display.Sprite, stage, new Rectangle(0, 0, 800, 600)); starlingSingleton = new Starling(starling.display.Sprite, stage, new Rectangle(0, 0, 800, 600));
starlingSingleton.supportHighResolutions = true; starlingSingleton.supportHighResolutions = true;
starlingSingleton.addEventListener(Event.ROOT_CREATED, onStarlingRootCreated); starlingSingleton.addEventListener(Event.ROOT_CREATED, onStarlingRootCreated);
} }
private function onStarlingRootCreated(event:Event):Void { private function onStarlingRootCreated(event:Event):Void {
destroyUI();
starlingSingleton.removeEventListener(Event.ROOT_CREATED, onStarlingRootCreated); starlingSingleton.removeEventListener(Event.ROOT_CREATED, onStarlingRootCreated);
starlingSingleton.start(); starlingSingleton.start();
Starling.current.stage.color = 0x000000; Starling.current.stage.color = 0x000000;

View File

@ -0,0 +1,45 @@
/******************************************************************************
* Spine Runtimes License Agreement
* Last updated July 28, 2023. Replaces all prior versions.
*
* Copyright (c) 2013-2023, 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;
import flixelExamples.FlixelState;
import flixel.FlxG;
import flixel.FlxGame;
import openfl.display.Sprite;
class MainFlixel extends Sprite
{
public function new()
{
super();
addChild(new FlxGame(640, 480, FlixelState));
FlxG.autoPause = false;
}
}

View File

@ -0,0 +1,57 @@
/******************************************************************************
* Spine Runtimes License Agreement
* Last updated July 28, 2023. Replaces all prior versions.
*
* Copyright (c) 2013-2023, 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;
import starlingExamples.BasicExample;
import starlingExamples.Scene.SceneManager;
import openfl.display.Sprite;
import openfl.geom.Rectangle;
import starling.core.Starling;
import starling.events.Event;
class MainStarling extends Sprite {
private var starlingSingleton:Starling;
public function new() {
super();
starlingSingleton = new Starling(starling.display.Sprite, stage, new Rectangle(0, 0, 800, 600));
starlingSingleton.supportHighResolutions = true;
starlingSingleton.addEventListener(Event.ROOT_CREATED, onStarlingRootCreated);
}
private function onStarlingRootCreated(event:Event):Void {
starlingSingleton.removeEventListener(Event.ROOT_CREATED, onStarlingRootCreated);
starlingSingleton.start();
Starling.current.stage.color = 0x000000;
SceneManager.getInstance().switchScene(new BasicExample());
}
}

View File

@ -0,0 +1,77 @@
package flixelExamples;
import flixel.util.FlxColor;
import flixel.text.FlxText;
import spine.Skin;
import flixel.ui.FlxButton;
import flixel.FlxG;
import spine.flixel.SkeletonSprite;
import spine.flixel.FlixelTextureLoader;
import flixel.FlxState;
import openfl.utils.Assets;
import spine.SkeletonData;
import spine.animation.AnimationStateData;
import spine.atlas.TextureAtlas;
class AnimationBoundExample extends FlxState {
var loadBinary = true;
override public function create():Void {
FlxG.cameras.bgColor = 0xffa1b2b0;
var button = new FlxButton(0, 0, "Next scene", () -> {
FlxG.debugger.drawDebug = false;
FlxG.switchState(() -> new ControlBonesExample());
});
button.setPosition(FlxG.width * .75, FlxG.height / 10);
add(button);
var atlas = new TextureAtlas(Assets.getText("assets/spineboy.atlas"), new FlixelTextureLoader("assets/spineboy.atlas"));
var data = SkeletonData.from(loadBinary ? Assets.getBytes("assets/spineboy-pro.skel") : Assets.getText("assets/spineboy-pro.json"), atlas, .2);
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);
skeletonSpriteClipping.screenCenter();
skeletonSpriteClipping.x = FlxG.width / 4 - skeletonSpriteClipping.width / 2;
add(skeletonSpriteClipping);
var textClipping = new FlxText();
textClipping.text = "Animation bound with clipping";
textClipping.size = 12;
textClipping.x = skeletonSpriteClipping.x + skeletonSpriteClipping.width / 2 - textClipping.width / 2;
textClipping.y = skeletonSpriteClipping.y + 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);
skeletonSpriteNoClipping.screenCenter();
skeletonSpriteNoClipping.x = FlxG.width / 4 * 3 - skeletonSpriteClipping.width / 2 - 50;
add(skeletonSpriteNoClipping);
var textNoClipping = new FlxText();
textNoClipping.text = "Animation bound without clipping";
textNoClipping.size = 12;
textNoClipping.x = skeletonSpriteNoClipping.x + skeletonSpriteNoClipping.width / 2 - textNoClipping.width / 2;
textNoClipping.y = skeletonSpriteNoClipping.y + 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.size = 12;
textInstruction.screenCenter();
textInstruction.y = textNoClipping.y + 40;
textInstruction.setBorderStyle(FlxTextBorderStyle.OUTLINE, FlxColor.RED, 2);
add(textInstruction);
FlxG.debugger.drawDebug = true;
super.create();
}
}

View File

@ -0,0 +1,87 @@
/******************************************************************************
* Spine Runtimes License Agreement
* Last updated July 28, 2023. Replaces all prior versions.
*
* Copyright (c) 2013-2023, 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 flixelExamples;
import flixel.ui.FlxButton;
import flixel.FlxG;
import spine.flixel.SkeletonSprite;
import spine.flixel.FlixelTextureLoader;
import flixel.FlxState;
import openfl.utils.Assets;
import spine.SkeletonData;
import spine.animation.AnimationStateData;
import spine.atlas.TextureAtlas;
class BasicExample extends FlxState {
var loadBinary = true;
var skeletonSprite:SkeletonSprite;
override public function create():Void {
FlxG.cameras.bgColor = 0xffa1b2b0;
var button = new FlxButton(0, 0, "Next scene", () -> FlxG.switchState(() -> new SequenceExample()));
button.setPosition(FlxG.width * .75, FlxG.height / 10);
add(button);
var atlas = new TextureAtlas(Assets.getText("assets/raptor.atlas"), new FlixelTextureLoader("assets/raptor-pro.atlas"));
var skeletondata = SkeletonData.from(loadBinary ? Assets.getBytes("assets/raptor-pro.skel") : Assets.getText("assets/raptor-pro.json"), atlas, .25);
var animationStateData = new AnimationStateData(skeletondata);
animationStateData.defaultMix = 0.25;
skeletonSprite = new SkeletonSprite(skeletondata, animationStateData);
var animation = skeletonSprite.state.setAnimationByName(0, "walk", true).animation;
skeletonSprite.setBoundingBox(animation);
skeletonSprite.screenCenter();
add(skeletonSprite);
super.create();
trace("loaded");
}
override public function update(elapsed:Float):Void
{
if (FlxG.keys.anyPressed([RIGHT])) {
skeletonSprite.x += 15;
}
if (FlxG.keys.anyPressed([LEFT])) {
skeletonSprite.x -= 15;
}
if (FlxG.keys.anyPressed([DOWN])) {
skeletonSprite.y += 15;
}
if (FlxG.keys.anyPressed([UP])) {
skeletonSprite.y -= 15;
}
super.update(elapsed);
}
}

View File

@ -0,0 +1,75 @@
package flixelExamples;
import flixel.text.FlxText;
import flixel.math.FlxPoint;
import spine.Skin;
import flixel.ui.FlxButton;
import flixel.FlxG;
import spine.flixel.SkeletonSprite;
import spine.flixel.FlixelTextureLoader;
import flixel.FlxState;
import openfl.utils.Assets;
import spine.SkeletonData;
import spine.animation.AnimationStateData;
import spine.atlas.TextureAtlas;
class CelestialCircusExample extends FlxState {
var loadBinary = true;
var skeletonSprite:SkeletonSprite;
override public function create():Void {
FlxG.cameras.bgColor = 0xffa1b2b0;
var button = new FlxButton(0, 0, "Next scene", () -> FlxG.switchState(() -> new SnowglobeExample()));
button.setPosition(FlxG.width * .75, FlxG.height / 10);
add(button);
var atlas = new TextureAtlas(Assets.getText("assets/celestial-circus.atlas"), new FlixelTextureLoader("assets/celestial-circus.atlas"));
var data = SkeletonData.from(loadBinary ? Assets.getBytes("assets/celestial-circus-pro.skel") : Assets.getText("assets/celestial-circus-pro.json"), atlas, .15);
var animationStateData = new AnimationStateData(data);
animationStateData.defaultMix = 0.25;
skeletonSprite = new SkeletonSprite(data, animationStateData);
skeletonSprite.screenCenter();
skeletonSprite.state.setAnimationByName(0, "eyeblink-long", true);
add(skeletonSprite);
add(new FlxText(50, 50, 200, "Drag Celeste to move her around", 16));
super.create();
}
var mousePosition = FlxPoint.get();
var dragging:Bool = false;
var lastX:Float = 0;
var lastY:Float = 0;
override public function update(elapsed:Float):Void
{
super.update(elapsed);
mousePosition = FlxG.mouse.getPosition();
if (FlxG.mouse.justPressed && skeletonSprite.overlapsPoint(mousePosition))
{
dragging = true;
lastX = mousePosition.x;
lastY = mousePosition.y;
}
if (FlxG.mouse.justReleased) dragging = false;
if (dragging)
{
skeletonSprite.x += mousePosition.x - lastX;
skeletonSprite.y += mousePosition.y - lastY;
skeletonSprite.skeleton.physicsTranslate(
mousePosition.x - lastX,
mousePosition.y - lastY,
);
lastX = mousePosition.x;
lastY = mousePosition.y;
}
}
}

View File

@ -0,0 +1,37 @@
package flixelExamples;
import spine.Skin;
import flixel.ui.FlxButton;
import flixel.FlxG;
import spine.flixel.SkeletonSprite;
import spine.flixel.FlixelTextureLoader;
import flixel.FlxState;
import openfl.utils.Assets;
import spine.SkeletonData;
import spine.animation.AnimationStateData;
import spine.atlas.TextureAtlas;
class CloudPotExample extends FlxState {
var loadBinary = true;
override public function create():Void {
FlxG.cameras.bgColor = 0xffa1b2b0;
var button = new FlxButton(0, 0, "Next scene", () -> FlxG.switchState(() -> new AnimationBoundExample()));
button.setPosition(FlxG.width * .75, FlxG.height / 10);
add(button);
var atlas = new TextureAtlas(Assets.getText("assets/cloud-pot.atlas"), new FlixelTextureLoader("assets/cloud-pot.atlas"));
var data = SkeletonData.from(loadBinary ? Assets.getBytes("assets/cloud-pot.skel") : Assets.getText("assets/cloud-pot.json"), atlas, .25);
var animationStateData = new AnimationStateData(data);
animationStateData.defaultMix = 0.25;
var skeletonSprite = new SkeletonSprite(data, animationStateData);
skeletonSprite.screenCenter();
skeletonSprite.state.setAnimationByName(0, "playing-in-the-rain", true);
add(skeletonSprite);
super.create();
}
}

View File

@ -0,0 +1,108 @@
package flixelExamples;
import flixel.util.FlxSave;
import flixel.math.FlxPoint;
import flixel.util.FlxColor;
import flixel.util.FlxSpriteUtil;
import flixel.FlxSprite;
import flixel.ui.FlxButton;
import flixel.FlxG;
import spine.flixel.SkeletonSprite;
import spine.flixel.FlixelTextureLoader;
import flixel.FlxState;
import openfl.utils.Assets;
import spine.SkeletonData;
import spine.animation.AnimationStateData;
import spine.atlas.TextureAtlas;
class ControlBonesExample extends FlxState {
var loadBinary = true;
private var controlBones = [];
private var controls:Array<FlxSprite> = [];
override public function create():Void {
FlxG.cameras.bgColor = 0xffa1b2b0;
var button = new FlxButton(0, 0, "Next scene", () -> FlxG.switchState(() -> new EventsExample()));
button.setPosition(FlxG.width * .75, FlxG.height / 10);
add(button);
var atlas = new TextureAtlas(Assets.getText("assets/stretchyman.atlas"), new FlixelTextureLoader("assets/stretchyman.atlas"));
var data = SkeletonData.from(loadBinary ? Assets.getBytes("assets/stretchyman-pro.skel") : Assets.getText("assets/stretchyman-pro.json"), atlas);
var animationStateData = new AnimationStateData(data);
animationStateData.defaultMix = 0.25;
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.screenCenter();
add(skeletonSprite);
var controlBoneNames = [
"back-arm-ik-target",
"back-leg-ik-target",
"front-arm-ik-target",
"front-leg-ik-target",
];
var radius = 6;
for (boneName in controlBoneNames) {
var bone = skeletonSprite.skeleton.findBone(boneName);
var point = [bone.worldX, bone.worldY];
skeletonSprite.skeletonToHaxeWorldCoordinates(point);
var control = new FlxSprite();
control.makeGraphic(radius * 2, radius * 2, FlxColor.TRANSPARENT, true);
FlxSpriteUtil.drawCircle(control, radius, radius, radius, 0xffff00ff);
control.setPosition(point[0] - radius, point[1] - radius);
controlBones.push(bone);
controls.push(control);
add(control);
}
var point = [.0, .0];
skeletonSprite.beforeUpdateWorldTransforms = function (go) {
for (i in 0...controls.length) {
var bone = controlBones[i];
var control = controls[i];
point[0] = control.x + radius;
point[1] = control.y + radius;
go.haxeWorldCoordinatesToBone(point, bone);
bone.x = point[0];
bone.y = point[1];
}
};
super.create();
}
var mousePosition = FlxPoint.get();
var offsetX:Float = 0;
var offsetY:Float = 0;
var sprite:FlxSprite;
override public function update(elapsed:Float):Void
{
super.update(elapsed);
mousePosition = FlxG.mouse.getPosition();
for (control in controls) {
if (FlxG.mouse.justPressed && control.overlapsPoint(mousePosition))
{
sprite = control;
offsetX = mousePosition.x - sprite.x;
offsetY = mousePosition.y - sprite.y;
}
}
if (FlxG.mouse.justReleased) sprite = null;
if (sprite != null)
{
sprite.x = mousePosition.x - offsetX;
sprite.y = mousePosition.y - offsetY;
}
}
}

View File

@ -0,0 +1,77 @@
package flixelExamples;
import flixel.text.FlxText;
import flixel.ui.FlxButton;
import flixel.FlxG;
import flixel.group.FlxSpriteGroup;
import spine.flixel.SkeletonSprite;
import spine.flixel.FlixelTextureLoader;
import flixel.FlxState;
import openfl.utils.Assets;
import spine.SkeletonData;
import spine.animation.AnimationStateData;
import spine.atlas.TextureAtlas;
class EventsExample extends FlxState {
var loadBinary = true;
override public function create():Void {
FlxG.cameras.bgColor = 0xffa1b2b0;
var button = new FlxButton(0, 0, "Next scene", () -> FlxG.switchState(() -> new FlixelState()));
button.setPosition(FlxG.width * .75, FlxG.height / 10);
add(button);
var atlas = new TextureAtlas(Assets.getText("assets/spineboy.atlas"), new FlixelTextureLoader("assets/spineboy.atlas"));
var data = SkeletonData.from(loadBinary ? Assets.getBytes("assets/spineboy-pro.skel") : Assets.getText("assets/spineboy-pro.json"), atlas, .25);
var animationStateData = new AnimationStateData(data);
animationStateData.defaultMix = 0.25;
var skeletonSprite = new SkeletonSprite(data, animationStateData);
// add callback to the AnimationState
skeletonSprite.state.onStart.add(entry -> log('Started animation ${entry.animation.name}'));
skeletonSprite.state.onInterrupt.add(entry -> log('Interrupted animation ${entry.animation.name}'));
skeletonSprite.state.onEnd.add(entry -> log('Ended animation ${entry.animation.name}'));
skeletonSprite.state.onDispose.add(entry -> log('Disposed animation ${entry.animation.name}'));
skeletonSprite.state.onComplete.add(entry -> log('Completed animation ${entry.animation.name}'));
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.setBonesToSetupPose();
add(skeletonSprite);
trackEntry.onEvent.add(
(entry, event) -> log('Custom event for ${entry.animation.name}: ${event.data.name}'));
add(textContainer);
super.create();
}
private var textContainer = new FlxSpriteGroup();
private var logs = new Array<FlxText>();
private var logsNumber = 0;
private var yOffset = 12;
private function log(text:String) {
var length = logs.length;
var newLog = new FlxText(250, 30, text);
newLog.x = 50;
newLog.y = 20 + yOffset * logsNumber++;
newLog.color = 0xffffffff;
textContainer.add(newLog);
if (logs.length < 35) {
logs.push(newLog);
} else {
logs.shift().destroy();
logs.push(newLog);
textContainer.y -= yOffset;
}
}
}

View File

@ -0,0 +1,195 @@
package flixelExamples;
import flixel.ui.FlxButton;
import flixel.group.FlxSpriteGroup;
import flixel.FlxSprite;
import flixel.graphics.FlxGraphic;
import spine.animation.AnimationStateData;
import openfl.Assets;
import spine.atlas.TextureAtlas;
import spine.SkeletonData;
import spine.flixel.SkeletonSprite;
import spine.flixel.FlixelTextureLoader;
import flixel.FlxG;
import flixel.FlxState;
import flixel.text.FlxText;
class FlixelState extends FlxState
{
var spineSprite:SkeletonSprite;
var sprite:FlxSprite;
var sprite2:FlxSprite;
var myText:FlxText;
var group:FlxSpriteGroup;
var justSetWalking = false;
var jumping = false;
var scale = 4;
var speed:Float;
override public function create():Void
{
FlxG.cameras.bgColor = 0xffa1b2b0;
// setting speed of spineboy (450 is the speed to not let him slide)
speed = 450 / scale;
// creating a group
group = new FlxSpriteGroup();
group.setPosition(50, 50);
add(group);
// creating the sprite to check overlapping
sprite = new FlxSprite();
sprite.loadGraphic(FlxGraphic.fromRectangle(150, 100, 0xff8d008d));
group.add(sprite);
// creating the text to display overlapping state
myText = new FlxText(0, 25, 150, "", 16);
myText.alignment = CENTER;
group.add(myText);
var button = new FlxButton(0, 0, "Next scene", () -> FlxG.switchState(() -> new BasicExample()));
button.setPosition(FlxG.width * .75, FlxG.height / 10);
add(button);
// creating a sprite for the floor
var floor = new FlxSprite();
floor.loadGraphic(FlxGraphic.fromRectangle(FlxG.width, FlxG.height - 100, 0xff822f02));
floor.y = FlxG.height - 100;
add(floor);
// instructions
var groupInstructions = new FlxSpriteGroup();
groupInstructions.setPosition(50, 405);
groupInstructions.add(new FlxText(0, 0, 200, "Left/Right - Move", 16));
groupInstructions.add(new FlxText(0, 25, 150, "Space - Jump", 16));
groupInstructions.add(new FlxText(200, 25, 400, "Click the button for the next example", 16));
add(groupInstructions);
// 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 animationStateData = new AnimationStateData(skeletondata);
spineSprite = new SkeletonSprite(skeletondata, animationStateData);
// positioning spineboy
spineSprite.setPosition(.5 * FlxG.width, .5 * FlxG.height);
// setting mix times
animationStateData.defaultMix = 0.5;
animationStateData.setMixByName("idle", "walk", 0.1);
animationStateData.setMixByName("walk", "idle", 0.1);
animationStateData.setMixByName("idle", "idle-turn", 0.05);
animationStateData.setMixByName("idle-turn", "idle", 0.05);
animationStateData.setMixByName("idle-turn", "walk", 0.3);
animationStateData.setMixByName("idle", "jump", 0);
animationStateData.setMixByName("jump", "idle", 0.05);
animationStateData.setMixByName("jump", "walk", 0.05);
animationStateData.setMixByName("walk", "jump", 0.05);
// setting idle animation
spineSprite.state.setAnimationByName(0, "idle", true);
// setting y offset function to move object body while jumping
var hip = spineSprite.skeleton.findBone("hip");
var initialY = 0.;
var initialOffsetY = 0.;
spineSprite.state.onStart.add(entry -> {
if (entry.animation.name == "jump") {
initialY = spineSprite.y;
initialOffsetY = spineSprite.offsetY;
}
});
spineSprite.state.onComplete.add(entry -> {
if (entry.animation.name == "jump") {
jumping = false;
spineSprite.y = initialY;
spineSprite.offsetY = initialOffsetY;
}
});
var diff = .0;
spineSprite.afterUpdateWorldTransforms = spineSprite -> {
if (jumping) {
diff -= hip.y;
spineSprite.offsetY -= diff;
spineSprite.y += diff;
}
diff = hip.y;
}
// adding spineboy to the stage
add(spineSprite);
// FlxG.debugger.visible = !FlxG.debugger.visible;
// debug ui
// FlxG.debugger.visible = true;
// FlxG.debugger.drawDebug = true;
// FlxG.log.redirectTraces = true;
// FlxG.debugger.track(spineSprite);
// FlxG.watch.add(spineSprite, "width");
// 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
{
if (FlxG.overlap(spineSprite, group)) {
myText.text = "Overlapping";
} else {
myText.text = "Non overlapping";
}
if (!jumping && FlxG.keys.anyJustPressed([SPACE])) {
spineSprite.state.setAnimationByName(0, "jump", false);
jumping = true;
justSetIdle = false;
justSetWalking = false;
}
if (FlxG.keys.anyJustPressed([J])) {
// spineSprite.antialiasing = !spineSprite.antialiasing;
FlxG.debugger.visible = !FlxG.debugger.visible;
}
if (FlxG.keys.anyPressed([RIGHT, LEFT])) {
justSetIdle = false;
var flipped = false;
var deltaX;
if (FlxG.keys.anyPressed([RIGHT])) {
if (spineSprite.flipX == true) flipped = true;
spineSprite.flipX = false;
}
if (FlxG.keys.anyPressed([LEFT])) {
if (spineSprite.flipX == false) flipped = true;
spineSprite.flipX = true;
}
deltaX = (spineSprite.flipX == false ? 1 : -1) * speed * elapsed;
spineSprite.x += deltaX;
if (!jumping && !justSetWalking) {
justSetWalking = true;
if (flipped) {
spineSprite.state.setAnimationByName(0, "idle-turn", false);
spineSprite.state.addAnimationByName(0, "walk", true, 0);
} else {
spineSprite.state.setAnimationByName(0, "walk", true);
}
}
} else if (!jumping && !justSetIdle) {
justSetWalking = false;
justSetIdle = true;
spineSprite.state.setAnimationByName(0, "idle", true);
}
super.update(elapsed);
}
}

View File

@ -0,0 +1,55 @@
package flixelExamples;
import spine.Skin;
import flixel.ui.FlxButton;
import flixel.FlxG;
import spine.flixel.SkeletonSprite;
import spine.flixel.FlixelTextureLoader;
import flixel.FlxState;
import openfl.utils.Assets;
import spine.SkeletonData;
import spine.animation.AnimationStateData;
import spine.atlas.TextureAtlas;
class MixAndMatchExample extends FlxState {
var loadBinary = false;
// var loadBinary = true;
var skeletonSprite:SkeletonSprite;
override public function create():Void {
FlxG.cameras.bgColor = 0xffa1b2b0;
var button = new FlxButton(0, 0, "Next scene", () -> FlxG.switchState(() -> new TankExample()));
button.setPosition(FlxG.width * .75, FlxG.height / 10);
add(button);
var atlas = new TextureAtlas(Assets.getText("assets/mix-and-match.atlas"), new FlixelTextureLoader("assets/mix-and-match.atlas"));
var data = SkeletonData.from(loadBinary ? Assets.getBytes("assets/mix-and-match-pro.skel") : Assets.getText("assets/mix-and-match-pro.json"), atlas, .5);
var animationStateData = new AnimationStateData(data);
animationStateData.defaultMix = 0.25;
skeletonSprite = new SkeletonSprite(data, animationStateData);
var customSkin = new Skin("custom");
var skinBase = data.findSkin("skin-base");
customSkin.addSkin(skinBase);
customSkin.addSkin(data.findSkin("nose/short"));
customSkin.addSkin(data.findSkin("eyelids/girly"));
customSkin.addSkin(data.findSkin("eyes/violet"));
customSkin.addSkin(data.findSkin("hair/brown"));
customSkin.addSkin(data.findSkin("clothes/hoodie-orange"));
customSkin.addSkin(data.findSkin("legs/pants-jeans"));
customSkin.addSkin(data.findSkin("accessories/bag"));
customSkin.addSkin(data.findSkin("accessories/hat-red-yellow"));
skeletonSprite.skeleton.skin = customSkin;
skeletonSprite.state.update(0);
var animation = skeletonSprite.state.setAnimationByName(0, "dance", true).animation;
skeletonSprite.setBoundingBox(animation);
skeletonSprite.screenCenter();
add(skeletonSprite);
super.create();
}
}

View File

@ -0,0 +1,38 @@
package flixelExamples;
import spine.Skin;
import flixel.ui.FlxButton;
import flixel.FlxG;
import spine.flixel.SkeletonSprite;
import spine.flixel.FlixelTextureLoader;
import flixel.FlxState;
import openfl.utils.Assets;
import spine.SkeletonData;
import spine.animation.AnimationStateData;
import spine.atlas.TextureAtlas;
class SackExample extends FlxState {
var loadBinary = false;
override public function create():Void {
FlxG.cameras.bgColor = 0xffa1b2b0;
var button = new FlxButton(0, 0, "Next scene", () -> FlxG.switchState(() -> new CelestialCircusExample()));
button.setPosition(FlxG.width * .75, FlxG.height / 10);
add(button);
var atlas = new TextureAtlas(Assets.getText("assets/sack.atlas"), new FlixelTextureLoader("assets/sack.atlas"));
var data = SkeletonData.from(loadBinary ? Assets.getBytes("assets/sack-pro.skel") : Assets.getText("assets/sack-pro.json"), atlas, .25);
var animationStateData = new AnimationStateData(data);
animationStateData.defaultMix = 0.25;
var skeletonSprite = new SkeletonSprite(data, animationStateData);
skeletonSprite.screenCenter();
skeletonSprite.x -= 100;
skeletonSprite.state.setAnimationByName(0, "cape-follow-example", true);
add(skeletonSprite);
super.create();
}
}

View File

@ -0,0 +1,39 @@
package flixelExamples;
import flixel.ui.FlxButton;
import flixel.FlxG;
import spine.flixel.SkeletonSprite;
import spine.flixel.FlixelTextureLoader;
import flixel.FlxState;
import openfl.utils.Assets;
import spine.SkeletonData;
import spine.animation.AnimationStateData;
import spine.atlas.TextureAtlas;
class SequenceExample extends FlxState {
var loadBinary = true;
var skeletonSprite:SkeletonSprite;
override public function create():Void {
FlxG.cameras.bgColor = 0xffa1b2b0;
var button = new FlxButton(0, 0, "Next scene", () -> FlxG.switchState(() -> new MixAndMatchExample()));
button.setPosition(FlxG.width * .75, FlxG.height / 10);
add(button);
var atlas = new TextureAtlas(Assets.getText("assets/dragon.atlas"), new FlixelTextureLoader("assets/dragon.atlas"));
var skeletondata = SkeletonData.from(loadBinary ? Assets.getBytes("assets/dragon-ess.skel") : Assets.getText("assets/dragon-.json"), atlas, .5);
var animationStateData = new AnimationStateData(skeletondata);
animationStateData.defaultMix = 0.25;
skeletonSprite = new SkeletonSprite(skeletondata, animationStateData);
var animation = skeletonSprite.state.setAnimationByName(0, "flying", true).animation;
skeletonSprite.setBoundingBox(animation);
skeletonSprite.screenCenter();
add(skeletonSprite);
super.create();
}
}

View File

@ -0,0 +1,37 @@
package flixelExamples;
import spine.Skin;
import flixel.ui.FlxButton;
import flixel.FlxG;
import spine.flixel.SkeletonSprite;
import spine.flixel.FlixelTextureLoader;
import flixel.FlxState;
import openfl.utils.Assets;
import spine.SkeletonData;
import spine.animation.AnimationStateData;
import spine.atlas.TextureAtlas;
class SnowglobeExample extends FlxState {
var loadBinary = false;
override public function create():Void {
FlxG.cameras.bgColor = 0xffa1b2b0;
var button = new FlxButton(0, 0, "Next scene", () -> FlxG.switchState(() -> new CloudPotExample()));
button.setPosition(FlxG.width * .75, FlxG.height / 10);
add(button);
var atlas = new TextureAtlas(Assets.getText("assets/snowglobe.atlas"), new FlixelTextureLoader("assets/snowglobe.atlas"));
var data = SkeletonData.from(loadBinary ? Assets.getBytes("assets/snowglobe-pro.skel") : Assets.getText("assets/snowglobe-pro.json"), atlas, .125);
var animationStateData = new AnimationStateData(data);
animationStateData.defaultMix = 0.25;
var skeletonSprite = new SkeletonSprite(data, animationStateData);
skeletonSprite.screenCenter();
skeletonSprite.state.setAnimationByName(0, "shake", true);
add(skeletonSprite);
super.create();
}
}

View File

@ -0,0 +1,38 @@
package flixelExamples;
import spine.Skin;
import flixel.ui.FlxButton;
import flixel.FlxG;
import spine.flixel.SkeletonSprite;
import spine.flixel.FlixelTextureLoader;
import flixel.FlxState;
import openfl.utils.Assets;
import spine.SkeletonData;
import spine.animation.AnimationStateData;
import spine.atlas.TextureAtlas;
class TankExample extends FlxState {
var loadBinary = true;
override public function create():Void {
FlxG.cameras.bgColor = 0xffa1b2b0;
var button = new FlxButton(0, 0, "Next scene", () -> FlxG.switchState(() -> new VineExample()));
button.setPosition(FlxG.width * .75, FlxG.height / 10);
add(button);
var atlas = new TextureAtlas(Assets.getText("assets/tank.atlas"), new FlixelTextureLoader("assets/tank.atlas"));
var data = SkeletonData.from(loadBinary ? Assets.getBytes("assets/tank-pro.skel") : Assets.getText("assets/tank-pro.json"), atlas, .125);
var animationStateData = new AnimationStateData(data);
animationStateData.defaultMix = 0.25;
var skeletonSprite = new SkeletonSprite(data, animationStateData);
var animation = skeletonSprite.state.setAnimationByName(0, "drive", true).animation;
skeletonSprite.setBoundingBox(animation);
skeletonSprite.screenCenter();
add(skeletonSprite);
super.create();
}
}

View File

@ -0,0 +1,38 @@
package flixelExamples;
import spine.Skin;
import flixel.ui.FlxButton;
import flixel.FlxG;
import spine.flixel.SkeletonSprite;
import spine.flixel.FlixelTextureLoader;
import flixel.FlxState;
import openfl.utils.Assets;
import spine.SkeletonData;
import spine.animation.AnimationStateData;
import spine.atlas.TextureAtlas;
class VineExample extends FlxState {
var loadBinary = true;
override public function create():Void {
FlxG.cameras.bgColor = 0xffa1b2b0;
var button = new FlxButton(0, 0, "Next scene", () -> FlxG.switchState(() -> new SackExample()));
button.setPosition(FlxG.width * .75, FlxG.height / 10);
add(button);
var atlas = new TextureAtlas(Assets.getText("assets/vine.atlas"), new FlixelTextureLoader("assets/vine.atlas"));
var data = SkeletonData.from(loadBinary ? Assets.getBytes("assets/vine-pro.skel") : Assets.getText("assets/vine-pro.json"), atlas, .4);
var animationStateData = new AnimationStateData(data);
animationStateData.defaultMix = 0.25;
var skeletonSprite = new SkeletonSprite(data, animationStateData);
var animation = skeletonSprite.state.setAnimationByName(0, "grow", true).animation;
skeletonSprite.setBoundingBox(animation);
skeletonSprite.screenCenter();
add(skeletonSprite);
super.create();
}
}

View File

@ -27,7 +27,9 @@
* SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*****************************************************************************/ *****************************************************************************/
import Scene.SceneManager; package starlingExamples;
import starlingExamples.Scene.SceneManager;
import openfl.utils.Assets; import openfl.utils.Assets;
import spine.SkeletonData; import spine.SkeletonData;
import spine.Physics; import spine.Physics;
@ -56,17 +58,17 @@ class AnimationBoundExample extends Scene {
skeletonSpriteClipping = new SkeletonSprite(skeletondata, animationStateDataClipping); skeletonSpriteClipping = new SkeletonSprite(skeletondata, animationStateDataClipping);
skeletonSpriteClipping.skeleton.updateWorldTransform(Physics.update); skeletonSpriteClipping.skeleton.updateWorldTransform(Physics.update);
skeletonSpriteClipping.scale = scale; skeletonSpriteClipping.scale = scale;
skeletonSpriteClipping.x = Starling.current.stage.stageWidth / 3 * 2; skeletonSpriteClipping.x = Starling.current.stage.stageWidth / 3 * 2;
skeletonSpriteClipping.y = Starling.current.stage.stageHeight / 2; skeletonSpriteClipping.y = Starling.current.stage.stageHeight / 2;
var animationClipping = skeletonSpriteClipping.state.setAnimationByName(0, "portal", true).animation; var animationClipping = skeletonSpriteClipping.state.setAnimationByName(0, "portal", true).animation;
var animationBoundClipping = skeletonSpriteClipping.getAnimationBounds(animationClipping, true); var animationBoundClipping = skeletonSpriteClipping.getAnimationBounds(animationClipping, true);
var quad:Quad = new Quad(animationBoundClipping.width * scale, animationBoundClipping.height * scale, 0xc70000); var quad:Quad = new Quad(animationBoundClipping.width * scale, animationBoundClipping.height * scale, 0xc70000);
quad.x = skeletonSpriteClipping.x + animationBoundClipping.x * scale; quad.x = skeletonSpriteClipping.x + animationBoundClipping.x * scale;
quad.y = skeletonSpriteClipping.y + animationBoundClipping.y * scale; quad.y = skeletonSpriteClipping.y + animationBoundClipping.y * scale;
var animationStateDataNoClipping = new AnimationStateData(skeletondata); var animationStateDataNoClipping = new AnimationStateData(skeletondata);
animationStateDataNoClipping.defaultMix = 0.25; animationStateDataNoClipping.defaultMix = 0.25;
skeletonSpriteNoClipping = new SkeletonSprite(skeletondata, animationStateDataNoClipping); skeletonSpriteNoClipping = new SkeletonSprite(skeletondata, animationStateDataNoClipping);
@ -83,7 +85,7 @@ class AnimationBoundExample extends Scene {
addChild(quad); addChild(quad);
addChild(quadNoClipping); addChild(quadNoClipping);
addChild(skeletonSpriteClipping); addChild(skeletonSpriteClipping);
addChild(skeletonSpriteNoClipping); addChild(skeletonSpriteNoClipping);
addText("Animation bound without clipping", 75, 350); addText("Animation bound without clipping", 75, 350);
addText("Animation bound with clipping", 370, 350); addText("Animation bound with clipping", 370, 350);

View File

@ -27,7 +27,9 @@
* SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*****************************************************************************/ *****************************************************************************/
import Scene.SceneManager; package starlingExamples;
import starlingExamples.Scene.SceneManager;
import openfl.utils.Assets; import openfl.utils.Assets;
import spine.SkeletonData; import spine.SkeletonData;
import spine.animation.AnimationStateData; import spine.animation.AnimationStateData;

View File

@ -27,8 +27,10 @@
* SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*****************************************************************************/ *****************************************************************************/
package starlingExamples;
import spine.BlendMode; import spine.BlendMode;
import Scene.SceneManager; import starlingExamples.Scene.SceneManager;
import openfl.utils.Assets; import openfl.utils.Assets;
import spine.SkeletonData; import spine.SkeletonData;
import spine.Physics; import spine.Physics;

View File

@ -27,8 +27,10 @@
* SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*****************************************************************************/ *****************************************************************************/
package starlingExamples;
import spine.BlendMode; import spine.BlendMode;
import Scene.SceneManager; import starlingExamples.Scene.SceneManager;
import openfl.utils.Assets; import openfl.utils.Assets;
import spine.SkeletonData; import spine.SkeletonData;
import spine.Physics; import spine.Physics;
@ -56,11 +58,11 @@ class CloudPotExample extends Scene {
skeletonSprite.skeleton.updateWorldTransform(Physics.update); skeletonSprite.skeleton.updateWorldTransform(Physics.update);
var bounds = skeletonSprite.skeleton.getBounds(); var bounds = skeletonSprite.skeleton.getBounds();
skeletonSprite.scale = 0.2; skeletonSprite.scale = 0.2;
skeletonSprite.x = Starling.current.stage.stageWidth / 2; skeletonSprite.x = Starling.current.stage.stageWidth / 2;
skeletonSprite.y = Starling.current.stage.stageHeight / 2; skeletonSprite.y = Starling.current.stage.stageHeight / 2;
skeletonSprite.state.setAnimationByName(0, "playing-in-the-rain", true); skeletonSprite.state.setAnimationByName(0, "playing-in-the-rain", true);
addChild(skeletonSprite); addChild(skeletonSprite);

View File

@ -27,8 +27,10 @@
* SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*****************************************************************************/ *****************************************************************************/
package starlingExamples;
import openfl.geom.Point; import openfl.geom.Point;
import Scene.SceneManager; import starlingExamples.Scene.SceneManager;
import openfl.utils.Assets; import openfl.utils.Assets;
import spine.SkeletonData; import spine.SkeletonData;
import spine.animation.AnimationStateData; import spine.animation.AnimationStateData;
@ -42,7 +44,7 @@ import starling.display.Canvas;
class ControlBonesExample extends Scene { class ControlBonesExample extends Scene {
var loadBinary = true; var loadBinary = true;
var skeletonSprite:SkeletonSprite; var skeletonSprite:SkeletonSprite;
private var movement = new openfl.geom.Point(); private var movement = new openfl.geom.Point();
private var controlBones = []; private var controlBones = [];
@ -133,7 +135,7 @@ class ControlBonesExample extends Scene {
skeletonSprite.skeleton.y += movement.y / skeletonSprite.scale; skeletonSprite.skeleton.y += movement.y / skeletonSprite.scale;
} }
} }
if (touchBackground) { if (touchBackground) {
var sceneTouch = e.getTouch(this); var sceneTouch = e.getTouch(this);
if (sceneTouch != null && sceneTouch.phase == TouchPhase.ENDED) { if (sceneTouch != null && sceneTouch.phase == TouchPhase.ENDED) {

View File

@ -27,8 +27,10 @@
* SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*****************************************************************************/ *****************************************************************************/
package starlingExamples;
import spine.animation.TrackEntry; import spine.animation.TrackEntry;
import Scene.SceneManager; import starlingExamples.Scene.SceneManager;
import openfl.utils.Assets; import openfl.utils.Assets;
import spine.SkeletonData; import spine.SkeletonData;
import spine.animation.AnimationStateData; import spine.animation.AnimationStateData;

View File

@ -27,8 +27,10 @@
* SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*****************************************************************************/ *****************************************************************************/
package starlingExamples;
import spine.Skin; import spine.Skin;
import Scene.SceneManager; import starlingExamples.Scene.SceneManager;
import openfl.utils.Assets; import openfl.utils.Assets;
import spine.SkeletonData; import spine.SkeletonData;
import spine.animation.AnimationStateData; import spine.animation.AnimationStateData;

View File

@ -27,7 +27,9 @@
* SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*****************************************************************************/ *****************************************************************************/
import Scene.SceneManager; package starlingExamples;
import starlingExamples.Scene.SceneManager;
import openfl.utils.Assets; import openfl.utils.Assets;
import spine.SkeletonData; import spine.SkeletonData;
import spine.Physics; import spine.Physics;
@ -53,11 +55,11 @@ class SackExample extends Scene {
var skeletonSprite = new SkeletonSprite(skeletondata, animationStateData); var skeletonSprite = new SkeletonSprite(skeletondata, animationStateData);
skeletonSprite.skeleton.updateWorldTransform(Physics.update); skeletonSprite.skeleton.updateWorldTransform(Physics.update);
skeletonSprite.scale = 0.2; skeletonSprite.scale = 0.2;
skeletonSprite.x = Starling.current.stage.stageWidth / 2; skeletonSprite.x = Starling.current.stage.stageWidth / 2;
skeletonSprite.y = Starling.current.stage.stageHeight/ 2; skeletonSprite.y = Starling.current.stage.stageHeight/ 2;
skeletonSprite.state.setAnimationByName(0, "cape-follow-example", true); skeletonSprite.state.setAnimationByName(0, "cape-follow-example", true);
addChild(skeletonSprite); addChild(skeletonSprite);

View File

@ -27,6 +27,8 @@
* SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*****************************************************************************/ *****************************************************************************/
package starlingExamples;
import starling.display.Quad; import starling.display.Quad;
import starling.text.TextField; import starling.text.TextField;
import starling.core.Starling; import starling.core.Starling;

View File

@ -27,7 +27,9 @@
* SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*****************************************************************************/ *****************************************************************************/
import Scene.SceneManager; package starlingExamples;
import starlingExamples.Scene.SceneManager;
import openfl.utils.Assets; import openfl.utils.Assets;
import spine.SkeletonData; import spine.SkeletonData;
import spine.animation.AnimationStateData; import spine.animation.AnimationStateData;

View File

@ -27,7 +27,9 @@
* SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*****************************************************************************/ *****************************************************************************/
import Scene.SceneManager; package starlingExamples;
import starlingExamples.Scene.SceneManager;
import openfl.utils.Assets; import openfl.utils.Assets;
import spine.SkeletonData; import spine.SkeletonData;
import spine.Physics; import spine.Physics;
@ -55,11 +57,11 @@ class SnowglobeExample extends Scene {
skeletonSprite.skeleton.updateWorldTransform(Physics.update); skeletonSprite.skeleton.updateWorldTransform(Physics.update);
var bounds = skeletonSprite.skeleton.getBounds(); var bounds = skeletonSprite.skeleton.getBounds();
skeletonSprite.scale = 0.15; skeletonSprite.scale = 0.15;
skeletonSprite.x = Starling.current.stage.stageWidth / 2; skeletonSprite.x = Starling.current.stage.stageWidth / 2;
skeletonSprite.y = Starling.current.stage.stageHeight/ 1.5; skeletonSprite.y = Starling.current.stage.stageHeight/ 1.5;
skeletonSprite.state.setAnimationByName(0, "shake", true); skeletonSprite.state.setAnimationByName(0, "shake", true);
addChild(skeletonSprite); addChild(skeletonSprite);

View File

@ -27,7 +27,9 @@
* SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*****************************************************************************/ *****************************************************************************/
import Scene.SceneManager; package starlingExamples;
import starlingExamples.Scene.SceneManager;
import openfl.utils.Assets; import openfl.utils.Assets;
import spine.SkeletonData; import spine.SkeletonData;
import spine.animation.AnimationStateData; import spine.animation.AnimationStateData;

View File

@ -27,7 +27,9 @@
* SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*****************************************************************************/ *****************************************************************************/
import Scene.SceneManager; package starlingExamples;
import starlingExamples.Scene.SceneManager;
import openfl.utils.Assets; import openfl.utils.Assets;
import spine.SkeletonData; import spine.SkeletonData;
import spine.animation.AnimationStateData; import spine.animation.AnimationStateData;

View File

@ -27,7 +27,9 @@
* SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*****************************************************************************/ *****************************************************************************/
import Scene.SceneManager; package starlingExamples;
import starlingExamples.Scene.SceneManager;
import openfl.utils.Assets; import openfl.utils.Assets;
import spine.SkeletonData; import spine.SkeletonData;
import spine.Physics; import spine.Physics;

View File

@ -3,10 +3,13 @@
<meta title="spine-haxe-example" package="spine" version="4.2.0" company="Esoteric Software" /> <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" /> -->
<window allow-high-dpi="true" /> <window allow-high-dpi="true" />
<haxelib name="openfl" /> <haxelib name="openfl" />
<haxelib name="starling" /> <haxelib name="starling" />
<haxelib name="flixel" />
<haxelib name="spine-haxe" /> <haxelib name="spine-haxe" />
<source path="example/src" /> <source path="example/src" />

View File

@ -0,0 +1,72 @@
/******************************************************************************
* Spine Runtimes License Agreement
* Last updated July 28, 2023. Replaces all prior versions.
*
* Copyright (c) 2013-2023, 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 spine.flixel;
import flixel.graphics.FlxGraphic;
import flixel.FlxG;
import spine.atlas.TextureAtlasPage;
import spine.atlas.TextureAtlasRegion;
import spine.atlas.TextureLoader;
import spine.flixel.SpineTexture;
class FlixelTextureLoader implements TextureLoader
{
private var basePath:String;
public function new(prefix:String) {
basePath = "";
var slashIndex = prefix.lastIndexOf("/");
if (slashIndex != -1) {
basePath = prefix.substring(0, slashIndex);
}
}
public function loadPage(page:TextureAtlasPage, path:String):Void
{
var bitmapData = openfl.utils.Assets.getBitmapData(basePath + "/" + path);
if (bitmapData == null) {
throw new SpineException("Could not load atlas page texture " + basePath + "/" + path);
}
var texture:FlxGraphic = SpineTexture.from(bitmapData);
// TODO: reset this value to true when destroy skeleton
// this is needed for sequence, otherwise the previous texture would be detroyed
texture.destroyOnNoUse = false;
page.texture = texture;
}
public function loadRegion(region:TextureAtlasRegion):Void {
region.texture = region.page.texture;
}
public function unloadPage(page:TextureAtlasPage):Void
{
FlxG.bitmap.remove(cast page.texture);
}
}

View File

@ -0,0 +1,40 @@
/******************************************************************************
* Spine Runtimes License Agreement
* Last updated July 28, 2023. Replaces all prior versions.
*
* Copyright (c) 2013-2023, 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 spine.flixel;
import flixel.FlxStrip;
// this class is just to make the implementation coherent with the starling implementation
class SkeletonMesh extends FlxStrip {
public function new(/*texture:FlxGraphicAsset*/) {
super();
// graphic = texture;
}
}

View File

@ -0,0 +1,382 @@
package spine.flixel;
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.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;
class SkeletonSprite extends FlxObject
{
public var skeleton(default, null):Skeleton;
public var state(default, null):AnimationState;
public var stateData(default, null):AnimationStateData;
public var beforeUpdateWorldTransforms: SkeletonSprite -> Void = function(_) {};
public var afterUpdateWorldTransforms: SkeletonSprite -> Void = function(_) {};
public static var clipper(default, never):SkeletonClipping = new SkeletonClipping();
public var offsetX = .0;
public var offsetY = .0;
public var alpha = 1.; // TODO: clamp
public var color:FlxColor = 0xffffff;
public var flipX(default, set):Bool = false;
public var flipY(default, set):Bool = false;
public var antialiasing:Bool = true;
@:isVar
public var scaleX(get, set):Float = 1;
@:isVar
public var scaleY(get, set):Float = 1;
var _tempVertices:Array<Float> = new Array<Float>();
var _quadTriangles:Array<Int>;
var _meshes(default, null):Array<SkeletonMesh> = new Array<SkeletonMesh>();
private var _tempMatrix = new FlxMatrix();
private var _tempPoint = new Point();
private static var QUAD_INDICES:Array<Int> = [0, 1, 2, 2, 3, 0];
public function new(skeletonData:SkeletonData, animationStateData:AnimationStateData = null)
{
super(0, 0);
Bone.yDown = true;
skeleton = new Skeleton(skeletonData);
skeleton.updateWorldTransform(Physics.update);
state = new AnimationState(animationStateData != null ? animationStateData : new AnimationStateData(skeletonData));
setBoundingBox();
}
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;
}
}
public function getAnimationBounds(animation:Animation, clip:Bool = true): lime.math.Rectangle {
var clipper = clip ? SkeletonSprite.clipper : null;
skeleton.setToSetupPose();
var steps = 100, time = 0.;
var stepTime = animation.duration != 0 ? animation.duration / steps : 0;
var minX = 100000000., maxX = -100000000., minY = 100000000., maxY = -100000000.;
var bounds = new lime.math.Rectangle();
for (i in 0...steps) {
animation.apply(skeleton, time , time, false, [], 1, MixBlend.setup, MixDirection.mixIn);
skeleton.updateWorldTransform(Physics.update);
bounds = skeleton.getBounds(clipper);
if (!Math.isNaN(bounds.x) && !Math.isNaN(bounds.y) && !Math.isNaN(bounds.width) && !Math.isNaN(bounds.height)) {
minX = Math.min(bounds.x, minX);
minY = Math.min(bounds.y, minY);
maxX = Math.max(bounds.right, maxX);
maxY = Math.max(bounds.bottom, maxY);
} else
trace("ERROR");
time += stepTime;
}
bounds.x = minX;
bounds.y = minY;
bounds.width = maxX - minX;
bounds.height = maxY - minY;
return bounds;
}
override public function destroy():Void
{
skeleton = null;
state = null;
stateData = 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
}
function renderMeshes():Void {
var clipper:SkeletonClipping = SkeletonSprite.clipper;
var drawOrder:Array<Slot> = skeleton.drawOrder;
var attachmentColor:spine.Color;
var mesh:SkeletonMesh = null;
var numVertices:Int;
var numFloats:Int;
var triangles:Array<Int> = null;
var uvs:Array<Float>;
var twoColorTint:Bool = false;
var vertexSize:Int = twoColorTint ? 12 : 8;
_tempMatrix = getTransformMatrix();
for (slot in drawOrder) {
var clippedVertexSize:Int = clipper.isClipping() ? 2 : vertexSize;
if (!slot.bone.active) {
clipper.clipEndWithSlot(slot);
continue;
}
var worldVertices:Array<Float> = _tempVertices;
if (Std.isOfType(slot.attachment, RegionAttachment)) {
var region:RegionAttachment = cast(slot.attachment, RegionAttachment);
numVertices = 4;
numFloats = clippedVertexSize << 2;
if (numFloats > worldVertices.length) {
worldVertices.resize(numFloats);
}
region.computeWorldVertices(slot, worldVertices, 0, clippedVertexSize);
mesh = getFlixelMeshFromRendererAttachment(region);
mesh.graphic = region.region.texture;
triangles = QUAD_INDICES;
uvs = region.uvs;
attachmentColor = region.color;
} else if (Std.isOfType(slot.attachment, MeshAttachment)) {
var meshAttachment:MeshAttachment = cast(slot.attachment, MeshAttachment);
numVertices = meshAttachment.worldVerticesLength >> 1;
numFloats = numVertices * clippedVertexSize; // 8 for now because I'm excluding clipping
if (numFloats > worldVertices.length) {
worldVertices.resize(numFloats);
}
meshAttachment.computeWorldVertices(slot, 0, meshAttachment.worldVerticesLength, worldVertices, 0, clippedVertexSize);
mesh = getFlixelMeshFromRendererAttachment(meshAttachment);
mesh.graphic = meshAttachment.region.texture;
triangles = meshAttachment.triangles;
uvs = meshAttachment.uvs;
attachmentColor = meshAttachment.color;
} else if (Std.isOfType(slot.attachment, ClippingAttachment)) {
var clip:ClippingAttachment = cast(slot.attachment, ClippingAttachment);
clipper.clipStart(slot, clip);
continue;
} else {
clipper.clipEndWithSlot(slot);
continue;
}
if (mesh != null) {
// cannot use directly mesh.color.setRGBFloat otherwise the setter won't be called and transfor color not set
mesh.color = FlxColor.fromRGBFloat(
skeleton.color.r * slot.color.r * attachmentColor.r * color.redFloat,
skeleton.color.g * slot.color.g * attachmentColor.g * color.greenFloat,
skeleton.color.b * slot.color.b * attachmentColor.b * color.blueFloat,
1
);
mesh.alpha = skeleton.color.a * slot.color.a * attachmentColor.a * alpha;
if (clipper.isClipping()) {
clipper.clipTriangles(worldVertices, triangles, triangles.length, uvs);
mesh.indices = Vector.ofArray(clipper.clippedTriangles);
mesh.uvtData = Vector.ofArray(clipper.clippedUvs);
if (angle == 0) {
mesh.vertices = Vector.ofArray(clipper.clippedVertices);
mesh.x = x + offsetX;
mesh.y = y + offsetY;
} else {
var i = 0;
mesh.vertices.length = clipper.clippedVertices.length;
while (i < mesh.vertices.length) {
_tempPoint.setTo(clipper.clippedVertices[i], clipper.clippedVertices[i + 1]);
_tempPoint = _tempMatrix.transformPoint(_tempPoint);
mesh.vertices[i] = _tempPoint.x;
mesh.vertices[i + 1] = _tempPoint.y;
i+=2;
}
}
} else {
var v = 0;
var n = numFloats;
var i = 0;
mesh.vertices.length = numVertices;
while (v < n) {
if (angle == 0) {
mesh.vertices[i] = worldVertices[v];
mesh.vertices[i + 1] = worldVertices[v + 1];
} else {
_tempPoint.setTo(worldVertices[v], worldVertices[v + 1]);
_tempPoint = _tempMatrix.transformPoint(_tempPoint);
mesh.vertices[i] = _tempPoint.x;
mesh.vertices[i + 1] = _tempPoint.y;
}
v += 8;
i += 2;
}
if (angle == 0) {
mesh.x = x + offsetX;
mesh.y = y + offsetY;
}
mesh.indices = Vector.ofArray(triangles);
mesh.uvtData = Vector.ofArray(uvs);
}
mesh.antialiasing = antialiasing;
mesh.blend = SpineTexture.toFlixelBlending(slot.data.blendMode);
// x/y position works for mesh, but angle does not work.
// if the transformation matrix is moved into the FlxStrip draw and used there
// we can just put vertices without doing any transformation
// mesh.x = x + offsetX;
// mesh.y = y + offsetY;
// mesh.angle = angle;
mesh.draw();
}
clipper.clipEndWithSlot(slot);
}
clipper.clipEnd();
}
private function getTransformMatrix():FlxMatrix {
_tempMatrix.identity();
// scale is connected to the skeleton scale - no need to rescale
_tempMatrix.scale(1, 1);
_tempMatrix.rotate(angle * Math.PI / 180);
_tempMatrix.translate(x + offsetX, y + offsetY);
return _tempMatrix;
}
public function skeletonToHaxeWorldCoordinates(point:Array<Float>):Void {
var transform = getTransformMatrix();
var a = transform.a,
b = transform.b,
c = transform.c,
d = transform.d,
tx = transform.tx,
ty = transform.ty;
var x = point[0];
var y = point[1];
point[0] = x * a + y * c + tx;
point[1] = x * b + y * d + ty;
}
public function haxeWorldCoordinatesToSkeleton(point:Array<Float>):Void {
var transform = getTransformMatrix().invert();
var a = transform.a,
b = transform.b,
c = transform.c,
d = transform.d,
tx = transform.tx,
ty = transform.ty;
var x = point[0];
var y = point[1];
point[0] = x * a + y * c + tx;
point[1] = x * b + y * d + ty;
}
public function haxeWorldCoordinatesToBone(point:Array<Float>, bone: Bone):Void {
this.haxeWorldCoordinatesToSkeleton(point);
if (bone.parent != null) {
bone.parent.worldToLocal(point);
} else {
bone.worldToLocal(point);
}
}
private function getFlixelMeshFromRendererAttachment(region: RenderedAttachment) {
if (region.rendererObject == null) {
var skeletonMesh = new SkeletonMesh();
region.rendererObject = skeletonMesh;
skeletonMesh.exists = false;
_meshes.push(skeletonMesh);
}
return region.rendererObject;
}
function set_flipX(value:Bool):Bool
{
if (value != flipX) skeleton.scaleX = -skeleton.scaleX;
return flipX = value;
}
function set_flipY(value:Bool):Bool
{
if (value != flipY) skeleton.scaleY = -skeleton.scaleY;
return flipY = value;
}
function set_scale(value:FlxPoint):FlxPoint {
return value;
}
function get_scaleX():Float {
return skeleton.scaleX;
}
function set_scaleX(value:Float):Float {
return skeleton.scaleX = value;
}
function get_scaleY():Float {
return skeleton.scaleY;
}
function set_scaleY(value:Float):Float {
return skeleton.scaleY = value;
}
}
typedef RenderedAttachment = {
var rendererObject:Dynamic;
var region:TextureRegion;
}

View File

@ -0,0 +1,30 @@
package spine.flixel;
import flixel.FlxG;
import flixel.graphics.FlxGraphic;
import openfl.display.BlendMode;
class SpineTexture extends FlxGraphic
{
public static function from(bitmapData: openfl.display.BitmapData): FlxGraphic {
return FlxG.bitmap.add(bitmapData);
}
public static function toFlixelBlending (blend: spine.BlendMode): BlendMode {
switch (blend) {
case spine.BlendMode.normal:
return BlendMode.NORMAL;
case spine.BlendMode.additive:
return BlendMode.ADD;
case spine.BlendMode.multiply:
return BlendMode.MULTIPLY;
case spine.BlendMode.screen:
return BlendMode.SCREEN;
}
return BlendMode.NORMAL;
}
}