diff --git a/spine-haxe/README.md b/spine-haxe/README.md index 5050b84dc..d63875ff6 100644 --- a/spine-haxe/README.md +++ b/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: ``` diff --git a/spine-haxe/example/src/Main.hx b/spine-haxe/example/src/Main.hx index 63712f6ab..72fa2cc40 100644 --- a/spine-haxe/example/src/Main.hx +++ b/spine-haxe/example/src/Main.hx @@ -27,24 +27,145 @@ * SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. *****************************************************************************/ -import Scene.SceneManager; -import openfl.display.Sprite; -import openfl.geom.Rectangle; +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.events.Event; 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; - - public function new() { - super(); - + 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; diff --git a/spine-haxe/example/src/MainFlixel.hx b/spine-haxe/example/src/MainFlixel.hx new file mode 100644 index 000000000..4c7334041 --- /dev/null +++ b/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; + } +} diff --git a/spine-haxe/example/src/MainStarling.hx b/spine-haxe/example/src/MainStarling.hx new file mode 100644 index 000000000..f5dfb3636 --- /dev/null +++ b/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()); + } +} diff --git a/spine-haxe/example/src/flixelExamples/AnimationBoundExample.hx b/spine-haxe/example/src/flixelExamples/AnimationBoundExample.hx new file mode 100644 index 000000000..fd15d54ad --- /dev/null +++ b/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(); + } +} diff --git a/spine-haxe/example/src/flixelExamples/BasicExample.hx b/spine-haxe/example/src/flixelExamples/BasicExample.hx new file mode 100644 index 000000000..86b53b9f9 --- /dev/null +++ b/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); + } + +} diff --git a/spine-haxe/example/src/flixelExamples/CelestialCircusExample.hx b/spine-haxe/example/src/flixelExamples/CelestialCircusExample.hx new file mode 100644 index 000000000..5896ec541 --- /dev/null +++ b/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; + } + + } +} diff --git a/spine-haxe/example/src/flixelExamples/CloudPotExample.hx b/spine-haxe/example/src/flixelExamples/CloudPotExample.hx new file mode 100644 index 000000000..7a701cd84 --- /dev/null +++ b/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(); + } +} diff --git a/spine-haxe/example/src/flixelExamples/ControlBonesExample.hx b/spine-haxe/example/src/flixelExamples/ControlBonesExample.hx new file mode 100644 index 000000000..75b4cadf2 --- /dev/null +++ b/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 = []; + 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; + } + } +} diff --git a/spine-haxe/example/src/flixelExamples/EventsExample.hx b/spine-haxe/example/src/flixelExamples/EventsExample.hx new file mode 100644 index 000000000..505902e26 --- /dev/null +++ b/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(); + 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; + } + } +} diff --git a/spine-haxe/example/src/flixelExamples/FlixelState.hx b/spine-haxe/example/src/flixelExamples/FlixelState.hx new file mode 100644 index 000000000..110f9b713 --- /dev/null +++ b/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); + } +} \ No newline at end of file diff --git a/spine-haxe/example/src/flixelExamples/MixAndMatchExample.hx b/spine-haxe/example/src/flixelExamples/MixAndMatchExample.hx new file mode 100644 index 000000000..f179f137a --- /dev/null +++ b/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(); + } + +} diff --git a/spine-haxe/example/src/flixelExamples/SackExample.hx b/spine-haxe/example/src/flixelExamples/SackExample.hx new file mode 100644 index 000000000..beb8a965c --- /dev/null +++ b/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(); + } +} diff --git a/spine-haxe/example/src/flixelExamples/SequenceExample.hx b/spine-haxe/example/src/flixelExamples/SequenceExample.hx new file mode 100644 index 000000000..219a82086 --- /dev/null +++ b/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(); + } + +} diff --git a/spine-haxe/example/src/flixelExamples/SnowglobeExample.hx b/spine-haxe/example/src/flixelExamples/SnowglobeExample.hx new file mode 100644 index 000000000..40b7343e0 --- /dev/null +++ b/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(); + } +} diff --git a/spine-haxe/example/src/flixelExamples/TankExample.hx b/spine-haxe/example/src/flixelExamples/TankExample.hx new file mode 100644 index 000000000..42a7563b2 --- /dev/null +++ b/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(); + } +} diff --git a/spine-haxe/example/src/flixelExamples/VineExample.hx b/spine-haxe/example/src/flixelExamples/VineExample.hx new file mode 100644 index 000000000..97d60bbfb --- /dev/null +++ b/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(); + } +} diff --git a/spine-haxe/example/src/AnimationBoundExample.hx b/spine-haxe/example/src/starlingExamples/AnimationBoundExample.hx similarity index 97% rename from spine-haxe/example/src/AnimationBoundExample.hx rename to spine-haxe/example/src/starlingExamples/AnimationBoundExample.hx index 3a32c3975..3ef52b735 100644 --- a/spine-haxe/example/src/AnimationBoundExample.hx +++ b/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); diff --git a/spine-haxe/example/src/BasicExample.hx b/spine-haxe/example/src/starlingExamples/BasicExample.hx similarity index 97% rename from spine-haxe/example/src/BasicExample.hx rename to spine-haxe/example/src/starlingExamples/BasicExample.hx index 67f53856e..4fbc298d7 100644 --- a/spine-haxe/example/src/BasicExample.hx +++ b/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; diff --git a/spine-haxe/example/src/CelestialCircusExample.hx b/spine-haxe/example/src/starlingExamples/CelestialCircusExample.hx similarity index 98% rename from spine-haxe/example/src/CelestialCircusExample.hx rename to spine-haxe/example/src/starlingExamples/CelestialCircusExample.hx index ece50e7fd..33a82dbbc 100644 --- a/spine-haxe/example/src/CelestialCircusExample.hx +++ b/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; diff --git a/spine-haxe/example/src/CloudPotExample.hx b/spine-haxe/example/src/starlingExamples/CloudPotExample.hx similarity index 97% rename from spine-haxe/example/src/CloudPotExample.hx rename to spine-haxe/example/src/starlingExamples/CloudPotExample.hx index 3aa77c7c4..6c6bc3bf4 100644 --- a/spine-haxe/example/src/CloudPotExample.hx +++ b/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); diff --git a/spine-haxe/example/src/ControlBonesExample.hx b/spine-haxe/example/src/starlingExamples/ControlBonesExample.hx similarity index 98% rename from spine-haxe/example/src/ControlBonesExample.hx rename to spine-haxe/example/src/starlingExamples/ControlBonesExample.hx index 3ab20fca8..e171fe2ee 100644 --- a/spine-haxe/example/src/ControlBonesExample.hx +++ b/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) { diff --git a/spine-haxe/example/src/EventsExample.hx b/spine-haxe/example/src/starlingExamples/EventsExample.hx similarity index 98% rename from spine-haxe/example/src/EventsExample.hx rename to spine-haxe/example/src/starlingExamples/EventsExample.hx index eb4e85a30..203f5cd6f 100644 --- a/spine-haxe/example/src/EventsExample.hx +++ b/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; diff --git a/spine-haxe/example/src/MixAndMatchExample.hx b/spine-haxe/example/src/starlingExamples/MixAndMatchExample.hx similarity index 98% rename from spine-haxe/example/src/MixAndMatchExample.hx rename to spine-haxe/example/src/starlingExamples/MixAndMatchExample.hx index 198bead50..089ab4c09 100644 --- a/spine-haxe/example/src/MixAndMatchExample.hx +++ b/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; diff --git a/spine-haxe/example/src/SackExample.hx b/spine-haxe/example/src/starlingExamples/SackExample.hx similarity index 97% rename from spine-haxe/example/src/SackExample.hx rename to spine-haxe/example/src/starlingExamples/SackExample.hx index 00c4daa6b..110b9be5e 100644 --- a/spine-haxe/example/src/SackExample.hx +++ b/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); diff --git a/spine-haxe/example/src/Scene.hx b/spine-haxe/example/src/starlingExamples/Scene.hx similarity index 99% rename from spine-haxe/example/src/Scene.hx rename to spine-haxe/example/src/starlingExamples/Scene.hx index 801f8667f..e6f356abc 100644 --- a/spine-haxe/example/src/Scene.hx +++ b/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; diff --git a/spine-haxe/example/src/SequenceExample.hx b/spine-haxe/example/src/starlingExamples/SequenceExample.hx similarity index 97% rename from spine-haxe/example/src/SequenceExample.hx rename to spine-haxe/example/src/starlingExamples/SequenceExample.hx index 3eb20360b..f37c134fe 100644 --- a/spine-haxe/example/src/SequenceExample.hx +++ b/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; diff --git a/spine-haxe/example/src/SnowglobeExample.hx b/spine-haxe/example/src/starlingExamples/SnowglobeExample.hx similarity index 97% rename from spine-haxe/example/src/SnowglobeExample.hx rename to spine-haxe/example/src/starlingExamples/SnowglobeExample.hx index f579c8f3f..1a7cc8369 100644 --- a/spine-haxe/example/src/SnowglobeExample.hx +++ b/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); diff --git a/spine-haxe/example/src/TankExample.hx b/spine-haxe/example/src/starlingExamples/TankExample.hx similarity index 97% rename from spine-haxe/example/src/TankExample.hx rename to spine-haxe/example/src/starlingExamples/TankExample.hx index 52e6f39ff..aa8b35885 100644 --- a/spine-haxe/example/src/TankExample.hx +++ b/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; diff --git a/spine-haxe/example/src/Test.hx b/spine-haxe/example/src/starlingExamples/Test.hx similarity index 97% rename from spine-haxe/example/src/Test.hx rename to spine-haxe/example/src/starlingExamples/Test.hx index 4edbc69b0..59e0eb713 100644 --- a/spine-haxe/example/src/Test.hx +++ b/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; diff --git a/spine-haxe/example/src/VineExample.hx b/spine-haxe/example/src/starlingExamples/VineExample.hx similarity index 97% rename from spine-haxe/example/src/VineExample.hx rename to spine-haxe/example/src/starlingExamples/VineExample.hx index 6b174086a..5a317261c 100644 --- a/spine-haxe/example/src/VineExample.hx +++ b/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; diff --git a/spine-haxe/project.xml b/spine-haxe/project.xml index 85f1a9d4b..6b4423862 100644 --- a/spine-haxe/project.xml +++ b/spine-haxe/project.xml @@ -3,10 +3,13 @@ + + + diff --git a/spine-haxe/spine-haxe/spine/flixel/FlixelTextureLoader.hx b/spine-haxe/spine-haxe/spine/flixel/FlixelTextureLoader.hx new file mode 100644 index 000000000..b672b1b14 --- /dev/null +++ b/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); + } +} \ No newline at end of file diff --git a/spine-haxe/spine-haxe/spine/flixel/SkeletonMesh.hx b/spine-haxe/spine-haxe/spine/flixel/SkeletonMesh.hx new file mode 100644 index 000000000..92052745f --- /dev/null +++ b/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; + } +} diff --git a/spine-haxe/spine-haxe/spine/flixel/SkeletonSprite.hx b/spine-haxe/spine-haxe/spine/flixel/SkeletonSprite.hx new file mode 100644 index 000000000..be750d193 --- /dev/null +++ b/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 = new Array(); + var _quadTriangles:Array; + var _meshes(default, null):Array = new Array(); + + private var _tempMatrix = new FlxMatrix(); + private var _tempPoint = new Point(); + + private static var QUAD_INDICES:Array = [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 = skeleton.drawOrder; + var attachmentColor:spine.Color; + var mesh:SkeletonMesh = null; + var numVertices:Int; + var numFloats:Int; + var triangles:Array = null; + var uvs:Array; + 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 = _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):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):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, 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; +} \ No newline at end of file diff --git a/spine-haxe/spine-haxe/spine/flixel/SpineTexture.hx b/spine-haxe/spine-haxe/spine/flixel/SpineTexture.hx new file mode 100644 index 000000000..c1d807024 --- /dev/null +++ b/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; + } + +}