瀏覽代碼

[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.
Davide 5 月之前
父節點
當前提交
7f2e4c1df7
共有 36 個文件被更改,包括 1717 次插入32 次删除
  1. 7 1
      spine-haxe/README.md
  2. 127 6
      spine-haxe/example/src/Main.hx
  3. 45 0
      spine-haxe/example/src/MainFlixel.hx
  4. 57 0
      spine-haxe/example/src/MainStarling.hx
  5. 77 0
      spine-haxe/example/src/flixelExamples/AnimationBoundExample.hx
  6. 87 0
      spine-haxe/example/src/flixelExamples/BasicExample.hx
  7. 75 0
      spine-haxe/example/src/flixelExamples/CelestialCircusExample.hx
  8. 37 0
      spine-haxe/example/src/flixelExamples/CloudPotExample.hx
  9. 108 0
      spine-haxe/example/src/flixelExamples/ControlBonesExample.hx
  10. 77 0
      spine-haxe/example/src/flixelExamples/EventsExample.hx
  11. 195 0
      spine-haxe/example/src/flixelExamples/FlixelState.hx
  12. 55 0
      spine-haxe/example/src/flixelExamples/MixAndMatchExample.hx
  13. 38 0
      spine-haxe/example/src/flixelExamples/SackExample.hx
  14. 39 0
      spine-haxe/example/src/flixelExamples/SequenceExample.hx
  15. 37 0
      spine-haxe/example/src/flixelExamples/SnowglobeExample.hx
  16. 38 0
      spine-haxe/example/src/flixelExamples/TankExample.hx
  17. 38 0
      spine-haxe/example/src/flixelExamples/VineExample.hx
  18. 7 5
      spine-haxe/example/src/starlingExamples/AnimationBoundExample.hx
  19. 3 1
      spine-haxe/example/src/starlingExamples/BasicExample.hx
  20. 3 1
      spine-haxe/example/src/starlingExamples/CelestialCircusExample.hx
  21. 5 3
      spine-haxe/example/src/starlingExamples/CloudPotExample.hx
  22. 5 3
      spine-haxe/example/src/starlingExamples/ControlBonesExample.hx
  23. 3 1
      spine-haxe/example/src/starlingExamples/EventsExample.hx
  24. 3 1
      spine-haxe/example/src/starlingExamples/MixAndMatchExample.hx
  25. 5 3
      spine-haxe/example/src/starlingExamples/SackExample.hx
  26. 2 0
      spine-haxe/example/src/starlingExamples/Scene.hx
  27. 3 1
      spine-haxe/example/src/starlingExamples/SequenceExample.hx
  28. 5 3
      spine-haxe/example/src/starlingExamples/SnowglobeExample.hx
  29. 3 1
      spine-haxe/example/src/starlingExamples/TankExample.hx
  30. 3 1
      spine-haxe/example/src/starlingExamples/Test.hx
  31. 3 1
      spine-haxe/example/src/starlingExamples/VineExample.hx
  32. 3 0
      spine-haxe/project.xml
  33. 72 0
      spine-haxe/spine-haxe/spine/flixel/FlixelTextureLoader.hx
  34. 40 0
      spine-haxe/spine-haxe/spine/flixel/SkeletonMesh.hx
  35. 382 0
      spine-haxe/spine-haxe/spine/flixel/SkeletonSprite.hx
  36. 30 0
      spine-haxe/spine-haxe/spine/flixel/SpineTexture.hx

+ 7 - 1
spine-haxe/README.md

@@ -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.
 
 ## 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:
 
 ```
 haxelib install openfl
 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:
@@ -60,6 +65,7 @@ To setup the development environment install the following:
    haxelib install openfl
    haxelib run openfl setup
    haxelib install starling
+   haxelib install flixel
    ```
 3. Clone the `spine-runtimes` repository, and use `haxelib` to setup a dev library:
    ```

+ 127 - 6
spine-haxe/example/src/Main.hx

@@ -27,24 +27,145 @@
  * SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *****************************************************************************/
 
-import Scene.SceneManager;
+package;
+
+import flixelExamples.FlixelState;
+import starlingExamples.BasicExample;
+import starlingExamples.Scene.SceneManager;
+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.core.Starling;
 import starling.events.Event;
 
 class Main extends Sprite {
-	private var starlingSingleton:Starling;
+    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;
 
-	public function new() {
-		super();
+		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 function onStarlingClick(e:MouseEvent):Void {
+        trace("Launching Starling game");
 		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 {
+		destroyUI();
 		starlingSingleton.removeEventListener(Event.ROOT_CREATED, onStarlingRootCreated);
 		starlingSingleton.start();
 		Starling.current.stage.color = 0x000000;

+ 45 - 0
spine-haxe/example/src/MainFlixel.hx

@@ -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;
+	}
+}

+ 57 - 0
spine-haxe/example/src/MainStarling.hx

@@ -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());
+	}
+}

+ 77 - 0
spine-haxe/example/src/flixelExamples/AnimationBoundExample.hx

@@ -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();
+	}
+}

+ 87 - 0
spine-haxe/example/src/flixelExamples/BasicExample.hx

@@ -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);
+		}
+
+}

+ 75 - 0
spine-haxe/example/src/flixelExamples/CelestialCircusExample.hx

@@ -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;
+		}
+
+	}
+}

+ 37 - 0
spine-haxe/example/src/flixelExamples/CloudPotExample.hx

@@ -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();
+	}
+}

+ 108 - 0
spine-haxe/example/src/flixelExamples/ControlBonesExample.hx

@@ -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;
+		}
+	}
+}

+ 77 - 0
spine-haxe/example/src/flixelExamples/EventsExample.hx

@@ -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;
+		}
+	}
+}

+ 195 - 0
spine-haxe/example/src/flixelExamples/FlixelState.hx

@@ -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);
+	}
+}

+ 55 - 0
spine-haxe/example/src/flixelExamples/MixAndMatchExample.hx

@@ -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();
+	}
+
+}

+ 38 - 0
spine-haxe/example/src/flixelExamples/SackExample.hx

@@ -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();
+	}
+}

+ 39 - 0
spine-haxe/example/src/flixelExamples/SequenceExample.hx

@@ -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();
+	}
+
+}

+ 37 - 0
spine-haxe/example/src/flixelExamples/SnowglobeExample.hx

@@ -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();
+	}
+}

+ 38 - 0
spine-haxe/example/src/flixelExamples/TankExample.hx

@@ -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();
+	}
+}

+ 38 - 0
spine-haxe/example/src/flixelExamples/VineExample.hx

@@ -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();
+	}
+}

+ 7 - 5
spine-haxe/example/src/AnimationBoundExample.hx → spine-haxe/example/src/starlingExamples/AnimationBoundExample.hx

@@ -27,7 +27,9 @@
  * 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 spine.SkeletonData;
 import spine.Physics;
@@ -56,17 +58,17 @@ class AnimationBoundExample extends Scene {
 
 		skeletonSpriteClipping = new SkeletonSprite(skeletondata, animationStateDataClipping);
 		skeletonSpriteClipping.skeleton.updateWorldTransform(Physics.update);
-		
+
 		skeletonSpriteClipping.scale = scale;
 		skeletonSpriteClipping.x = Starling.current.stage.stageWidth / 3 * 2;
 		skeletonSpriteClipping.y = Starling.current.stage.stageHeight / 2;
-		
+
 		var animationClipping = skeletonSpriteClipping.state.setAnimationByName(0, "portal", true).animation;
 		var animationBoundClipping = skeletonSpriteClipping.getAnimationBounds(animationClipping, true);
 		var quad:Quad = new Quad(animationBoundClipping.width * scale, animationBoundClipping.height * scale, 0xc70000);
         quad.x = skeletonSpriteClipping.x + animationBoundClipping.x * scale;
         quad.y = skeletonSpriteClipping.y + animationBoundClipping.y * scale;
-		
+
 		var animationStateDataNoClipping = new AnimationStateData(skeletondata);
 		animationStateDataNoClipping.defaultMix = 0.25;
 		skeletonSpriteNoClipping = new SkeletonSprite(skeletondata, animationStateDataNoClipping);
@@ -83,7 +85,7 @@ class AnimationBoundExample extends Scene {
 
 		addChild(quad);
 		addChild(quadNoClipping);
-		addChild(skeletonSpriteClipping);		
+		addChild(skeletonSpriteClipping);
 		addChild(skeletonSpriteNoClipping);
 		addText("Animation bound without clipping", 75, 350);
 		addText("Animation bound with clipping", 370, 350);

+ 3 - 1
spine-haxe/example/src/BasicExample.hx → spine-haxe/example/src/starlingExamples/BasicExample.hx

@@ -27,7 +27,9 @@
  * 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 spine.SkeletonData;
 import spine.animation.AnimationStateData;

+ 3 - 1
spine-haxe/example/src/CelestialCircusExample.hx → spine-haxe/example/src/starlingExamples/CelestialCircusExample.hx

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

+ 5 - 3
spine-haxe/example/src/CloudPotExample.hx → spine-haxe/example/src/starlingExamples/CloudPotExample.hx

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

+ 5 - 3
spine-haxe/example/src/ControlBonesExample.hx → spine-haxe/example/src/starlingExamples/ControlBonesExample.hx

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

+ 3 - 1
spine-haxe/example/src/EventsExample.hx → spine-haxe/example/src/starlingExamples/EventsExample.hx

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

+ 3 - 1
spine-haxe/example/src/MixAndMatchExample.hx → spine-haxe/example/src/starlingExamples/MixAndMatchExample.hx

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

+ 5 - 3
spine-haxe/example/src/SackExample.hx → spine-haxe/example/src/starlingExamples/SackExample.hx

@@ -27,7 +27,9 @@
  * 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 spine.SkeletonData;
 import spine.Physics;
@@ -53,11 +55,11 @@ class SackExample extends Scene {
 
 		var skeletonSprite = new SkeletonSprite(skeletondata, animationStateData);
 		skeletonSprite.skeleton.updateWorldTransform(Physics.update);
-		
+
 		skeletonSprite.scale = 0.2;
 		skeletonSprite.x = Starling.current.stage.stageWidth / 2;
 		skeletonSprite.y = Starling.current.stage.stageHeight/ 2;
-		
+
 		skeletonSprite.state.setAnimationByName(0, "cape-follow-example", true);
 
 		addChild(skeletonSprite);

+ 2 - 0
spine-haxe/example/src/Scene.hx → spine-haxe/example/src/starlingExamples/Scene.hx

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

+ 3 - 1
spine-haxe/example/src/SequenceExample.hx → spine-haxe/example/src/starlingExamples/SequenceExample.hx

@@ -27,7 +27,9 @@
  * 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 spine.SkeletonData;
 import spine.animation.AnimationStateData;

+ 5 - 3
spine-haxe/example/src/SnowglobeExample.hx → spine-haxe/example/src/starlingExamples/SnowglobeExample.hx

@@ -27,7 +27,9 @@
  * 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 spine.SkeletonData;
 import spine.Physics;
@@ -55,11 +57,11 @@ class SnowglobeExample extends Scene {
 		skeletonSprite.skeleton.updateWorldTransform(Physics.update);
 		var bounds = skeletonSprite.skeleton.getBounds();
 
-		
+
 		skeletonSprite.scale = 0.15;
 		skeletonSprite.x = Starling.current.stage.stageWidth / 2;
 		skeletonSprite.y = Starling.current.stage.stageHeight/ 1.5;
-		
+
 		skeletonSprite.state.setAnimationByName(0, "shake", true);
 
 		addChild(skeletonSprite);

+ 3 - 1
spine-haxe/example/src/TankExample.hx → spine-haxe/example/src/starlingExamples/TankExample.hx

@@ -27,7 +27,9 @@
  * 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 spine.SkeletonData;
 import spine.animation.AnimationStateData;

+ 3 - 1
spine-haxe/example/src/Test.hx → spine-haxe/example/src/starlingExamples/Test.hx

@@ -27,7 +27,9 @@
  * 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 spine.SkeletonData;
 import spine.animation.AnimationStateData;

+ 3 - 1
spine-haxe/example/src/VineExample.hx → spine-haxe/example/src/starlingExamples/VineExample.hx

@@ -27,7 +27,9 @@
  * 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 spine.SkeletonData;
 import spine.Physics;

+ 3 - 0
spine-haxe/project.xml

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

+ 72 - 0
spine-haxe/spine-haxe/spine/flixel/FlixelTextureLoader.hx

@@ -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);
+	}
+}

+ 40 - 0
spine-haxe/spine-haxe/spine/flixel/SkeletonMesh.hx

@@ -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;
+	}
+}

+ 382 - 0
spine-haxe/spine-haxe/spine/flixel/SkeletonSprite.hx

@@ -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;
+}

+ 30 - 0
spine-haxe/spine-haxe/spine/flixel/SpineTexture.hx

@@ -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;
+	}
+
+}