Merge branch '4.2' into 4.3

This commit is contained in:
Nathan Sweet 2025-03-13 16:04:43 -04:00
commit 7a50323479
117 changed files with 2322 additions and 220 deletions

View File

@ -169,6 +169,12 @@
- SkeletonMecanim: Added `Scene Preview` option to preview an Animation Clip for e.g. easier event placement. When enabled, the Animation Clip selected in the Animation window is previewed in the Scene and Game views. Lock the `SkeletonMecanim` Inspector window, open the Animation window and select the Animation Clip. Then in the Animation window scrub through the timeline to see the current animation frame previewed.
- `Universal Render Pipeline/Spine/Skeleton Lit` shader now supports [Adaptive Probe Volumes (APV)](https://docs.unity3d.com/6000.0/Documentation/Manual/urp/probevolumes-concept.html) introduced in Unity 6. The shader also provides a new material property `APV per Pixel` to either calculate APV lighting contribution per pixel (the default) or per vertex.
- `Universal Render Pipeline/Spine/Sprite` shader now also supports [Adaptive Probe Volumes (APV)](https://docs.unity3d.com/6000.0/Documentation/Manual/urp/probevolumes-concept.html) introduced in Unity 6. APV lighting contribution is automatically calculated per pixel.
- All Spine Outline shaders, including the URP outline shaders, now provide an additional parameter `Fill`. Enable it to also fill the opaque area inside the outline with the outline color. Prevents a semi-transparent gap between outline and skeleton. Defaults to `disabled` to maintain existing behaviour.
- Added example component `RenderExistingMeshGraphic` (similar to `RenderExistingMesh`) to render a `SkeletonGraphic` mesh again with different materials. This might be required by e.g. URP and SkeletonGraphic outline shaders skipping additional render passes. To add a second outline variant of your SkeletonGraphic:
1. Add a GameObject at the same hierarchy level as the reference SkeletonGraphic and move it before the reference SkeletonGraphic to render behind.
2. Add a `RenderExistingMeshGraphic` component.
3. In the `RenderExistingMeshGraphic` component Inspector at `Reference Skeleton Graphic` assign the original `SkeletonGraphic` object.
4. At `Replacement Material` assign e.g. the included _SkeletonGraphicDefaultOutline_ material to replace all materials with this material. Alternatively, if `Multiple CanvasRenderers` is enabled at the reference SkeletonGraphic, you can add entries to the `Replacement Materials` list and at each entry assign the original SkeletonGraphic material (e.g. _SkeletonGraphicDefault_) to be replaced and the respective `Replacement Material` (e.g. _SkeletonGraphicDefaultOutline_).
- **Breaking changes**

View File

@ -1341,9 +1341,9 @@ spSkeletonData *spSkeletonBinary_readSkeletonData(spSkeletonBinary *self, const
} else {
if (!string_starts_with(skeletonData->version, SPINE_VERSION_STRING)) {
FREE(input);
spSkeletonData_dispose(skeletonData);
char errorMsg[255];
snprintf(errorMsg, 255, "Skeleton version %s does not match runtime version %s", skeletonData->version, SPINE_VERSION_STRING);
spSkeletonData_dispose(skeletonData);
_spSkeletonBinary_setError(self, errorMsg, NULL);
return NULL;
}

View File

@ -217,8 +217,6 @@ void SpineAtlasResource::_bind_methods() {
ADD_PROPERTY(PropertyInfo(Variant::STRING, "source_path"), "", "get_source_path");
ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "textures"), "", "get_textures");
ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "normal_maps"), "", "get_normal_maps");
ADD_SIGNAL(MethodInfo("skeleton_atlas_changed"));
}
SpineAtlasResource::SpineAtlasResource() : atlas(nullptr), texture_loader(nullptr), normal_map_prefix("n") {

View File

@ -32,10 +32,26 @@
#ifdef SPINE_GODOT_EXTENSION
#include <godot_cpp/classes/encoded_object_as_id.hpp>
#include <godot_cpp/classes/engine.hpp>
#include <godot_cpp/classes/editor_interface.hpp>
#else
#if VERSION_MAJOR > 3
#include "core/config/engine.h"
#include "editor/editor_interface.h"
#else
#include "core/engine.h"
#endif
#include <core/io/marshalls.h>
#endif
#ifdef TOOLS_ENABLED
#ifdef SPINE_GODOT_EXTENSION
#include <godot_cpp/classes/editor_file_system.hpp>
#else
#include "editor/editor_file_system.h"
#endif
#endif
void SpineAnimationMix::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_from", "from"),
&SpineAnimationMix::set_from);
@ -175,16 +191,113 @@ void SpineSkeletonDataResource::_bind_methods() {
#endif
ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "animation_mixes"),
"set_animation_mixes", "get_animation_mixes");
#ifdef TOOLS_ENABLED
#if VERSION_MAJOR > 3
ClassDB::bind_method(D_METHOD("_on_resources_reimported", "resources"),
&SpineSkeletonDataResource::_on_resources_reimported);
#else
ClassDB::bind_method(D_METHOD("_on_resources_reimported", "resources"),
&SpineSkeletonDataResource::_on_resources_reimported);
#endif
#endif
}
#ifdef TOOLS_ENABLED
EditorFileSystem *get_editor_file_system() {
#ifdef SPINE_GODOT_EXTENSION
EditorInterface *editor_interface = EditorInterface::get_singleton();
if (editor_interface) {
return editor_interface->get_resource_filesystem();
}
return nullptr;
#else
return EditorFileSystem::get_singleton();
#endif
}
#endif
SpineSkeletonDataResource::SpineSkeletonDataResource()
: default_mix(0), skeleton_data(nullptr), animation_state_data(nullptr) {}
: default_mix(0), skeleton_data(nullptr), animation_state_data(nullptr) {
#ifdef TOOLS_ENABLED
#if VERSION_MAJOR > 3
if (Engine::get_singleton()->is_editor_hint()) {
EditorFileSystem *efs = get_editor_file_system();
if (efs) {
efs->connect("resources_reimported", callable_mp(this, &SpineSkeletonDataResource::_on_resources_reimported));
}
}
#else
if (Engine::get_singleton()->is_editor_hint()) {
EditorFileSystem *efs = EditorFileSystem::get_singleton();
if (efs) {
efs->connect("resources_reimported", this, "_on_resources_reimported");
}
}
#endif
#endif
}
SpineSkeletonDataResource::~SpineSkeletonDataResource() {
#ifdef TOOLS_ENABLED
#if VERSION_MAJOR > 3
if (Engine::get_singleton()->is_editor_hint()) {
EditorFileSystem *efs = get_editor_file_system();
if (efs && efs->is_connected("resources_reimported", callable_mp(this, &SpineSkeletonDataResource::_on_resources_reimported))) {
efs->disconnect("resources_reimported", callable_mp(this, &SpineSkeletonDataResource::_on_resources_reimported));
}
}
#else
if (Engine::get_singleton()->is_editor_hint()) {
EditorFileSystem *efs = EditorFileSystem::get_singleton();
if (efs && efs->is_connected("resources_reimported", this, "_on_resources_reimported")) {
efs->disconnect("resources_reimported", this, "_on_resources_reimported");
}
}
#endif
#endif
delete skeleton_data;
delete animation_state_data;
}
#ifdef TOOLS_ENABLED
#if VERSION_MAJOR > 3
void SpineSkeletonDataResource::_on_resources_reimported(const PackedStringArray &resources) {
for (int i = 0; i < resources.size(); i++) {
if (atlas_res.is_valid() && atlas_res->get_path() == resources[i]) {
#ifdef SPINE_GODOT_EXTENSION
atlas_res = ResourceLoader::get_singleton()->load(resources[i], "SpineAtlasResource", ResourceLoader::CACHE_MODE_IGNORE);
#else
atlas_res = ResourceLoader::load(resources[i], "SpineAtlasResource", ResourceFormatLoader::CACHE_MODE_IGNORE);
#endif
update_skeleton_data();
} else if (skeleton_file_res.is_valid() && skeleton_file_res->get_path() == resources[i]) {
#ifdef SPINE_GODOT_EXTENSION
skeleton_file_res = ResourceLoader::get_singleton()->load(resources[i], "SpineSkeletonFileResource", ResourceLoader::CACHE_MODE_IGNORE);
#else
skeleton_file_res = ResourceLoader::load(resources[i], "SpineSkeletonFileResource", ResourceFormatLoader::CACHE_MODE_IGNORE);
#endif
update_skeleton_data();
}
}
}
#else
void SpineSkeletonDataResource::_on_resources_reimported(const PoolStringArray &resources) {
for (int i = 0; i < resources.size(); i++) {
if (atlas_res.is_valid() && atlas_res->get_path() == resources[i]) {
atlas_res = ResourceLoader::load(resources[i]);
update_skeleton_data();
} else if (skeleton_file_res.is_valid() && skeleton_file_res->get_path() == resources[i]) {
skeleton_file_res = ResourceLoader::load(resources[i]);
update_skeleton_data();
}
}
}
#endif
#endif
void SpineSkeletonDataResource::update_skeleton_data() {
if (skeleton_data) {
delete skeleton_data;
@ -249,22 +362,6 @@ bool SpineSkeletonDataResource::is_skeleton_data_loaded() const {
void SpineSkeletonDataResource::set_atlas_res(
const Ref<SpineAtlasResource> &atlas) {
atlas_res = atlas;
if (atlas_res.is_valid()) {
#if VERSION_MAJOR > 3
if (!atlas_res->is_connected(
SNAME("skeleton_atlas_changed"),
callable_mp(this,
&SpineSkeletonDataResource::update_skeleton_data)))
atlas_res->connect(
SNAME("skeleton_atlas_changed"),
callable_mp(this, &SpineSkeletonDataResource::update_skeleton_data));
#else
if (!atlas_res->is_connected(SNAME("skeleton_atlas_changed"), this,
SNAME("update_skeleton_data")))
atlas_res->connect(SNAME("skeleton_atlas_changed"), this,
SNAME("update_skeleton_data"));
#endif
}
update_skeleton_data();
}
@ -275,22 +372,6 @@ Ref<SpineAtlasResource> SpineSkeletonDataResource::get_atlas_res() {
void SpineSkeletonDataResource::set_skeleton_file_res(
const Ref<SpineSkeletonFileResource> &skeleton_file) {
skeleton_file_res = skeleton_file;
if (skeleton_file_res.is_valid()) {
#if VERSION_MAJOR > 3
if (!skeleton_file_res->is_connected(
SNAME("skeleton_file_changed"),
callable_mp(this,
&SpineSkeletonDataResource::update_skeleton_data)))
skeleton_file_res->connect(
SNAME("skeleton_file_changed"),
callable_mp(this, &SpineSkeletonDataResource::update_skeleton_data));
#else
if (!skeleton_file_res->is_connected(SNAME("skeleton_file_changed"), this,
SNAME("update_skeleton_data")))
skeleton_file_res->connect(SNAME("skeleton_file_changed"), this,
SNAME("update_skeleton_data"));
#endif
}
update_skeleton_data();
}

View File

@ -209,4 +209,12 @@ public:
float get_reference_scale() const;
void set_reference_scale(float reference_scale);
#ifdef TOOLS_ENABLED
#if VERSION_MAJOR > 3
void _on_resources_reimported(const PackedStringArray &resources);
#else
void _on_resources_reimported(const PoolStringArray &resources);
#endif
#endif
};

View File

@ -95,7 +95,6 @@ static char *readString(BinaryInput *input) {
void SpineSkeletonFileResource::_bind_methods() {
ClassDB::bind_method(D_METHOD("load_from_file", "path"), &SpineSkeletonFileResource::load_from_file);
ADD_SIGNAL(MethodInfo("skeleton_file_changed"));
}
static bool checkVersion(const char *version) {

View File

@ -1216,8 +1216,9 @@ void SpineSprite::draw() {
}
#if TOOLS_ENABLED
float editor_scale = 1.0;
if (Engine::get_singleton()->is_editor_hint()) editor_scale = EditorInterface::get_singleton()->get_editor_scale();
float editor_scale = EditorInterface::get_singleton()->get_editor_scale();
float inverse_zoom = 1 / get_viewport()->get_global_canvas_transform().get_scale().x * editor_scale;
Vector<String> hover_text_lines;
if (hovered_slot) {

View File

@ -1,18 +0,0 @@
[configuration]
entry_symbol = "spine_godot_library_init"
compatibility_minimum = "4.1"
[libraries]
macos.editor = "res://bin/macos/macos.framework/libspine_godot.macos.dev.editor"
macos.debug = "res://bin/macos/macos.framework/libspine_godot.macos.dev.template_debug"
macos.release = "res://bin/macos/macos.framework/libspine_godot.macos.template_release"
windows.editor.x86_64 = "res://bin/windows/libspine_godot.windows.editor.dev.x86_64.dll"
windows.debug.x86_64 = "res://bin/windows/libspine_godot.windows.template_debug.dev.x86_64.dll"
windows.release.x86_64 = "res://bin/windows/libspine_godot.windows.template_release.dev.x86_64.dll"
linux.editor.x86_64 = "res://bin/linux/libspine_godot.linux.editor.dev.x86_64.so"
linux.debug.x86_64 = "res://bin/linux/libspine_godot.linux.template_debug.dev.x86_64.so"
linux.release.x86_64 = "res://bin/linux/libspine_godot.linux.template_release.dev.x86_64.so"

View File

@ -23,12 +23,17 @@ spine-haxe works with data exported from Spine 4.2.xx.
spine-haxe supports all Spine features except premultiplied alpha atlases and two color tinting.
## 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:
```

View File

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

View File

@ -0,0 +1,45 @@
/******************************************************************************
* Spine Runtimes License Agreement
* Last updated July 28, 2023. Replaces all prior versions.
*
* Copyright (c) 2013-2023, Esoteric Software LLC
*
* Integration of the Spine Runtimes into software or otherwise creating
* derivative works of the Spine Runtimes is permitted under the terms and
* conditions of Section 2 of the Spine Editor License Agreement:
* http://esotericsoftware.com/spine-editor-license
*
* Otherwise, it is permitted to integrate the Spine Runtimes into software or
* otherwise create derivative works of the Spine Runtimes (collectively,
* "Products"), provided that each user of the Products must obtain their own
* Spine Editor license and redistribution of the Products in any form must
* include this license and copyright notice.
*
* THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES,
* BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THE
* SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*****************************************************************************/
package;
import flixelExamples.FlixelState;
import flixel.FlxG;
import flixel.FlxGame;
import openfl.display.Sprite;
class MainFlixel extends Sprite
{
public function new()
{
super();
addChild(new FlxGame(640, 480, FlixelState));
FlxG.autoPause = false;
}
}

View File

@ -0,0 +1,57 @@
/******************************************************************************
* Spine Runtimes License Agreement
* Last updated July 28, 2023. Replaces all prior versions.
*
* Copyright (c) 2013-2023, Esoteric Software LLC
*
* Integration of the Spine Runtimes into software or otherwise creating
* derivative works of the Spine Runtimes is permitted under the terms and
* conditions of Section 2 of the Spine Editor License Agreement:
* http://esotericsoftware.com/spine-editor-license
*
* Otherwise, it is permitted to integrate the Spine Runtimes into software or
* otherwise create derivative works of the Spine Runtimes (collectively,
* "Products"), provided that each user of the Products must obtain their own
* Spine Editor license and redistribution of the Products in any form must
* include this license and copyright notice.
*
* THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES,
* BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THE
* SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*****************************************************************************/
package;
import starlingExamples.BasicExample;
import starlingExamples.Scene.SceneManager;
import openfl.display.Sprite;
import openfl.geom.Rectangle;
import starling.core.Starling;
import starling.events.Event;
class MainStarling extends Sprite {
private var starlingSingleton:Starling;
public function new() {
super();
starlingSingleton = new Starling(starling.display.Sprite, stage, new Rectangle(0, 0, 800, 600));
starlingSingleton.supportHighResolutions = true;
starlingSingleton.addEventListener(Event.ROOT_CREATED, onStarlingRootCreated);
}
private function onStarlingRootCreated(event:Event):Void {
starlingSingleton.removeEventListener(Event.ROOT_CREATED, onStarlingRootCreated);
starlingSingleton.start();
Starling.current.stage.color = 0x000000;
SceneManager.getInstance().switchScene(new BasicExample());
}
}

View File

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

View File

@ -0,0 +1,87 @@
/******************************************************************************
* Spine Runtimes License Agreement
* Last updated July 28, 2023. Replaces all prior versions.
*
* Copyright (c) 2013-2023, Esoteric Software LLC
*
* Integration of the Spine Runtimes into software or otherwise creating
* derivative works of the Spine Runtimes is permitted under the terms and
* conditions of Section 2 of the Spine Editor License Agreement:
* http://esotericsoftware.com/spine-editor-license
*
* Otherwise, it is permitted to integrate the Spine Runtimes into software or
* otherwise create derivative works of the Spine Runtimes (collectively,
* "Products"), provided that each user of the Products must obtain their own
* Spine Editor license and redistribution of the Products in any form must
* include this license and copyright notice.
*
* THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES,
* BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THE
* SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*****************************************************************************/
package flixelExamples;
import flixel.ui.FlxButton;
import flixel.FlxG;
import spine.flixel.SkeletonSprite;
import spine.flixel.FlixelTextureLoader;
import flixel.FlxState;
import openfl.utils.Assets;
import spine.SkeletonData;
import spine.animation.AnimationStateData;
import spine.atlas.TextureAtlas;
class BasicExample extends FlxState {
var loadBinary = true;
var skeletonSprite:SkeletonSprite;
override public function create():Void {
FlxG.cameras.bgColor = 0xffa1b2b0;
var button = new FlxButton(0, 0, "Next scene", () -> FlxG.switchState(() -> new SequenceExample()));
button.setPosition(FlxG.width * .75, FlxG.height / 10);
add(button);
var atlas = new TextureAtlas(Assets.getText("assets/raptor.atlas"), new FlixelTextureLoader("assets/raptor-pro.atlas"));
var skeletondata = SkeletonData.from(loadBinary ? Assets.getBytes("assets/raptor-pro.skel") : Assets.getText("assets/raptor-pro.json"), atlas, .25);
var animationStateData = new AnimationStateData(skeletondata);
animationStateData.defaultMix = 0.25;
skeletonSprite = new SkeletonSprite(skeletondata, animationStateData);
var animation = skeletonSprite.state.setAnimationByName(0, "walk", true).animation;
skeletonSprite.setBoundingBox(animation);
skeletonSprite.screenCenter();
add(skeletonSprite);
super.create();
trace("loaded");
}
override public function update(elapsed:Float):Void
{
if (FlxG.keys.anyPressed([RIGHT])) {
skeletonSprite.x += 15;
}
if (FlxG.keys.anyPressed([LEFT])) {
skeletonSprite.x -= 15;
}
if (FlxG.keys.anyPressed([DOWN])) {
skeletonSprite.y += 15;
}
if (FlxG.keys.anyPressed([UP])) {
skeletonSprite.y -= 15;
}
super.update(elapsed);
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -27,7 +27,9 @@
* SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*****************************************************************************/
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);

View File

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

View File

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

View File

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

View File

@ -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) {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -17,8 +17,8 @@
"cpp"
],
"description": "The official Spine Runtime for Haxe",
"version": "4.2.3",
"releasenote": "Update to 4.2.3",
"version": "4.2.5",
"releasenote": "Update to 4.2.5",
"contributors": [
"esotericsoftware"
],

View File

@ -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" />

View File

@ -228,13 +228,15 @@ class Skin {
var slotAttachment:Attachment = slot.attachment;
if (slotAttachment != null && slotIndex < oldSkin.attachments.length) {
var dictionary:StringMap<Attachment> = oldSkin.attachments[slotIndex];
for (name in dictionary.keys()) {
var skinAttachment:Attachment = dictionary.get(name);
if (slotAttachment == skinAttachment) {
var attachment:Attachment = getAttachment(slotIndex, name);
if (attachment != null)
slot.attachment = attachment;
break;
if (null != dictionary) {
for (name in dictionary.keys()) {
var skinAttachment:Attachment = dictionary.get(name);
if (slotAttachment == skinAttachment) {
var attachment:Attachment = getAttachment(slotIndex, name);
if (attachment != null)
slot.attachment = attachment;
break;
}
}
}
}

View File

@ -0,0 +1,72 @@
/******************************************************************************
* Spine Runtimes License Agreement
* Last updated July 28, 2023. Replaces all prior versions.
*
* Copyright (c) 2013-2023, Esoteric Software LLC
*
* Integration of the Spine Runtimes into software or otherwise creating
* derivative works of the Spine Runtimes is permitted under the terms and
* conditions of Section 2 of the Spine Editor License Agreement:
* http://esotericsoftware.com/spine-editor-license
*
* Otherwise, it is permitted to integrate the Spine Runtimes into software or
* otherwise create derivative works of the Spine Runtimes (collectively,
* "Products"), provided that each user of the Products must obtain their own
* Spine Editor license and redistribution of the Products in any form must
* include this license and copyright notice.
*
* THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES,
* BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THE
* SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*****************************************************************************/
package spine.flixel;
import flixel.graphics.FlxGraphic;
import flixel.FlxG;
import spine.atlas.TextureAtlasPage;
import spine.atlas.TextureAtlasRegion;
import spine.atlas.TextureLoader;
import spine.flixel.SpineTexture;
class FlixelTextureLoader implements TextureLoader
{
private var basePath:String;
public function new(prefix:String) {
basePath = "";
var slashIndex = prefix.lastIndexOf("/");
if (slashIndex != -1) {
basePath = prefix.substring(0, slashIndex);
}
}
public function loadPage(page:TextureAtlasPage, path:String):Void
{
var bitmapData = openfl.utils.Assets.getBitmapData(basePath + "/" + path);
if (bitmapData == null) {
throw new SpineException("Could not load atlas page texture " + basePath + "/" + path);
}
var texture:FlxGraphic = SpineTexture.from(bitmapData);
// TODO: reset this value to true when destroy skeleton
// this is needed for sequence, otherwise the previous texture would be detroyed
texture.destroyOnNoUse = false;
page.texture = texture;
}
public function loadRegion(region:TextureAtlasRegion):Void {
region.texture = region.page.texture;
}
public function unloadPage(page:TextureAtlasPage):Void
{
FlxG.bitmap.remove(cast page.texture);
}
}

View File

@ -0,0 +1,40 @@
/******************************************************************************
* Spine Runtimes License Agreement
* Last updated July 28, 2023. Replaces all prior versions.
*
* Copyright (c) 2013-2023, Esoteric Software LLC
*
* Integration of the Spine Runtimes into software or otherwise creating
* derivative works of the Spine Runtimes is permitted under the terms and
* conditions of Section 2 of the Spine Editor License Agreement:
* http://esotericsoftware.com/spine-editor-license
*
* Otherwise, it is permitted to integrate the Spine Runtimes into software or
* otherwise create derivative works of the Spine Runtimes (collectively,
* "Products"), provided that each user of the Products must obtain their own
* Spine Editor license and redistribution of the Products in any form must
* include this license and copyright notice.
*
* THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES,
* BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THE
* SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*****************************************************************************/
package spine.flixel;
import flixel.FlxStrip;
// this class is just to make the implementation coherent with the starling implementation
class SkeletonMesh extends FlxStrip {
public function new(/*texture:FlxGraphicAsset*/) {
super();
// graphic = texture;
}
}

View File

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

View File

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

View File

@ -59,7 +59,7 @@ public final class SkinAndAnimationBounds: NSObject, BoundsProvider {
/// the bounding box of the skeleton. If no skins are given, the default skin is used.
/// The `stepTime`, given in seconds, defines at what interval the bounds should be sampled
/// across the entire animation.
public init(animation: String? = nil, skins: [String]? = nil, let stepTime: TimeInterval = 0.1) {
public init(animation: String? = nil, skins: [String]? = nil, stepTime: TimeInterval = 0.1) {
self.animation = animation
if let skins, !skins.isEmpty {
self.skins = skins

View File

@ -27,7 +27,7 @@ public extension SkeletonDrawableWrapper {
spineView.delegate?.draw(in: spineView)
guard let texture = spineView.currentDrawable?.texture else {
throw "Could not read texture."
throw SpineError("Could not read texture.")
}
let width = texture.width
let height = texture.height
@ -47,7 +47,7 @@ public extension SkeletonDrawableWrapper {
let colorSpace = CGColorSpaceCreateDeviceRGB()
guard let context = CGContext(data: data, width: width, height: height, bitsPerComponent: 8, bytesPerRow: rowBytes, space: colorSpace, bitmapInfo: bitmapInfo.rawValue),
let cgImage = context.makeImage() else {
throw "Could not create image."
throw SpineError("Could not create image.")
}
return cgImage
}

View File

@ -94,22 +94,22 @@ public final class SkeletonDrawableWrapper: NSObject {
self.skeletonData = skeletonData
guard let nativeSkeletonDrawable = spine_skeleton_drawable_create(skeletonData.wrappee) else {
throw "Could not load native skeleton drawable"
throw SpineError("Could not load native skeleton drawable")
}
skeletonDrawable = SkeletonDrawable(nativeSkeletonDrawable)
guard let nativeSkeleton = spine_skeleton_drawable_get_skeleton(skeletonDrawable.wrappee) else {
throw "Could not load native skeleton"
throw SpineError("Could not load native skeleton")
}
skeleton = Skeleton(nativeSkeleton)
guard let nativeAnimationStateData = spine_skeleton_drawable_get_animation_state_data(skeletonDrawable.wrappee) else {
throw "Could not load native animation state data"
throw SpineError("Could not load native animation state data")
}
animationStateData = AnimationStateData(nativeAnimationStateData)
guard let nativeAnimationState = spine_skeleton_drawable_get_animation_state(skeletonDrawable.wrappee) else {
throw "Could not load native animation state"
throw SpineError("Could not load native animation state")
}
animationState = AnimationState(nativeAnimationState)
animationStateWrapper = AnimationStateWrapper(

View File

@ -57,18 +57,19 @@ public extension Atlas {
}
private static func fromData(data: Data, loadFile: (_ name: String) async throws -> Data) async throws -> (Atlas, [UIImage]) {
guard let atlasData = String(data: data, encoding: .utf8) as? NSString else {
throw "Couldn't read atlas bytes as utf8 string"
guard let atlasData = String(data: data, encoding: .utf8) else {
throw SpineError("Couldn't read atlas bytes as utf8 string")
}
let atlasDataNative = UnsafeMutablePointer<CChar>(mutating: atlasData.utf8String)
guard let atlas = spine_atlas_load(atlasDataNative) else {
throw "Couldn't load atlas data"
let atlas = try atlasData.utf8CString.withUnsafeBufferPointer {
guard let atlas = spine_atlas_load($0.baseAddress) else {
throw SpineError("Couldn't load atlas data")
}
return atlas
}
if let error = spine_atlas_get_error(atlas) {
let message = String(cString: error)
spine_atlas_dispose(atlas)
throw "Couldn't load atlas: \(message)"
throw SpineError("Couldn't load atlas: \(message)")
}
var atlasPages = [UIImage]()
@ -130,26 +131,31 @@ public extension SkeletonData {
///
/// Throws an `Error` in case the skeleton data could not be loaded.
static func fromData(atlas: Atlas, data: Data) throws -> SkeletonData {
let binaryNative = try data.withUnsafeBytes { unsafeBytes in
guard let bytes = unsafeBytes.bindMemory(to: UInt8.self).baseAddress else {
throw "Couldn't read atlas binary"
let result = try data.withUnsafeBytes{
try $0.withMemoryRebound(to: UInt8.self) { buffer in
guard let ptr = buffer.baseAddress else {
throw SpineError("Couldn't read atlas binary")
}
return spine_skeleton_data_load_binary(
atlas.wrappee,
ptr,
Int32(buffer.count)
)
}
return (data: bytes, length: Int32(unsafeBytes.count))
}
let result = spine_skeleton_data_load_binary(
atlas.wrappee,
binaryNative.data,
binaryNative.length
)
guard let result else {
throw SpineError("Couldn't load skeleton data")
}
defer {
spine_skeleton_data_result_dispose(result)
}
if let error = spine_skeleton_data_result_get_error(result) {
let message = String(cString: error)
spine_skeleton_data_result_dispose(result)
throw "Couldn't load skeleton data: \(message)"
throw SpineError("Couldn't load skeleton data: \(message)")
}
guard let data = spine_skeleton_data_result_get_data(result) else {
throw "Couldn't load skeleton data from result"
throw SpineError("Couldn't load skeleton data from result")
}
spine_skeleton_data_result_dispose(result)
return SkeletonData(data)
}
@ -158,26 +164,31 @@ public extension SkeletonData {
///
/// Throws an `Error` in case the atlas could not be loaded.
static func fromJson(atlas: Atlas, json: String) throws -> SkeletonData {
let jsonNative = UnsafeMutablePointer<CChar>(mutating: (json as NSString).utf8String)
guard let result = spine_skeleton_data_load_json(atlas.wrappee, jsonNative) else {
throw "Couldn't load skeleton data json"
let result = try json.utf8CString.withUnsafeBufferPointer { buffer in
guard
let basePtr = buffer.baseAddress,
let result = spine_skeleton_data_load_json(atlas.wrappee, basePtr) else {
throw SpineError("Couldn't load skeleton data json")
}
return result
}
defer {
spine_skeleton_data_result_dispose(result)
}
if let error = spine_skeleton_data_result_get_error(result) {
let message = String(cString: error)
spine_skeleton_data_result_dispose(result)
throw "Couldn't load skeleton data: \(message)"
throw SpineError("Couldn't load skeleton data: \(message)")
}
guard let data = spine_skeleton_data_result_get_data(result) else {
throw "Couldn't load skeleton data from result"
throw SpineError("Couldn't load skeleton data from result")
}
spine_skeleton_data_result_dispose(result)
return SkeletonData(data)
}
private static func fromData(atlas: Atlas, data: Data, isJson: Bool) throws -> SkeletonData {
if isJson {
guard let json = String(data: data, encoding: .utf8) else {
throw "Couldn't read skeleton data json string"
throw SpineError("Couldn't read skeleton data json string")
}
return try fromJson(atlas: atlas, json: json)
} else {
@ -271,12 +282,12 @@ internal enum FileSource {
case .bundle(let fileName, let bundle):
let components = fileName.split(separator: ".")
guard components.count > 1, let ext = components.last else {
throw "Provide both file name and file extension"
throw SpineError("Provide both file name and file extension")
}
let name = components.dropLast(1).joined(separator: ".")
guard let fileUrl = bundle.url(forResource: name, withExtension: String(ext)) else {
throw "Could not load file with name \(name) from bundle"
throw SpineError("Could not load file with name \(name) from bundle")
}
return try Data(contentsOf: fileUrl, options: [])
case .file(let fileUrl):
@ -289,34 +300,63 @@ internal enum FileSource {
}
return try Data(contentsOf: temp, options: [])
} else {
return try await withCheckedThrowingContinuation { continuation in
let task = URLSession.shared.downloadTask(with: url) { temp, response, error in
if let error {
continuation.resume(throwing: error)
} else {
guard let httpResponse = response as? HTTPURLResponse, httpResponse.statusCode == 200 else {
continuation.resume(throwing: URLError(.badServerResponse))
return
}
guard let temp else {
continuation.resume(throwing: "Could not download file.")
return
}
do {
continuation.resume(returning: try Data(contentsOf: temp, options: []))
} catch {
let lock = NSRecursiveLock()
nonisolated(unsafe)
var isCancelled = false
nonisolated(unsafe)
var taskHolder:URLSessionDownloadTask? = nil
return try await withTaskCancellationHandler {
try await withCheckedThrowingContinuation { continuation in
let task = URLSession.shared.downloadTask(with: url) { temp, response, error in
if let error {
continuation.resume(throwing: error)
} else {
guard let httpResponse = response as? HTTPURLResponse, httpResponse.statusCode == 200 else {
continuation.resume(throwing: URLError(.badServerResponse))
return
}
guard let temp else {
continuation.resume(throwing: SpineError("Could not download file."))
return
}
do {
continuation.resume(returning: try Data(contentsOf: temp, options: []))
} catch {
continuation.resume(throwing: error)
}
}
}
task.resume()
let shouldCancel = lock.withLock {
if !isCancelled {
taskHolder = task
}
return isCancelled
}
if shouldCancel {
task.cancel()
}
}
task.resume()
} onCancel: {
lock.withLock {
isCancelled = true
let value = taskHolder
taskHolder = nil
return value
}?.cancel()
}
}
}
}
}
extension String: Error {
public struct SpineError: Error, CustomStringConvertible {
public let description: String
internal init(_ description: String) {
self.description = description
}
}

View File

@ -180,18 +180,16 @@ public class AnimationState {
from.animationLast = from.nextAnimationLast;
from.trackLast = from.nextTrackLast;
if (to.nextTrackLast != -1) { // The from entry was applied at least once.
boolean discard = to.mixTime == 0 && from.mixTime == 0; // Discard the from entry when neither have advanced yet.
if (to.mixTime >= to.mixDuration || discard) {
// Require totalAlpha == 0 to ensure mixing is complete or the transition is a single frame or discarded.
if (from.totalAlpha == 0 || to.mixDuration == 0 || discard) {
to.mixingFrom = from.mixingFrom;
if (from.mixingFrom != null) from.mixingFrom.mixingTo = to;
to.interruptAlpha = from.interruptAlpha;
queue.end(from);
}
return finished;
// The from entry was applied at least once and the mix is complete.
if (to.nextTrackLast != -1 && to.mixTime >= to.mixDuration) {
// Mixing is complete for all entries before the from entry or the mix is instantaneous.
if (from.totalAlpha == 0 || to.mixDuration == 0) {
to.mixingFrom = from.mixingFrom;
if (from.mixingFrom != null) from.mixingFrom.mixingTo = to;
to.interruptAlpha = from.interruptAlpha;
queue.end(from);
}
return finished;
}
from.trackTime += delta * from.timeScale;

View File

@ -1,12 +1,12 @@
{
"name": "@esotericsoftware/spine-ts",
"version": "4.2.73",
"version": "4.2.76",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "@esotericsoftware/spine-ts",
"version": "4.2.73",
"version": "4.2.76",
"license": "LicenseRef-LICENSE",
"workspaces": [
"spine-core",
@ -3142,18 +3142,18 @@
},
"spine-canvas": {
"name": "@esotericsoftware/spine-canvas",
"version": "4.2.73",
"version": "4.2.76",
"license": "LicenseRef-LICENSE",
"dependencies": {
"@esotericsoftware/spine-core": "4.2.73"
"@esotericsoftware/spine-core": "4.2.76"
}
},
"spine-canvaskit": {
"name": "@esotericsoftware/spine-canvaskit",
"version": "4.2.73",
"version": "4.2.76",
"license": "LicenseRef-LICENSE",
"dependencies": {
"@esotericsoftware/spine-core": "4.2.73",
"@esotericsoftware/spine-core": "4.2.76",
"canvaskit-wasm": "0.39.1"
},
"devDependencies": {
@ -3163,17 +3163,17 @@
},
"spine-core": {
"name": "@esotericsoftware/spine-core",
"version": "4.2.73",
"version": "4.2.76",
"license": "LicenseRef-LICENSE"
},
"spine-phaser": {
"name": "@esotericsoftware/spine-phaser",
"version": "4.2.73",
"version": "4.2.76",
"license": "LicenseRef-LICENSE",
"dependencies": {
"@esotericsoftware/spine-canvas": "4.2.73",
"@esotericsoftware/spine-core": "4.2.73",
"@esotericsoftware/spine-webgl": "4.2.73"
"@esotericsoftware/spine-canvas": "4.2.76",
"@esotericsoftware/spine-core": "4.2.76",
"@esotericsoftware/spine-webgl": "4.2.76"
},
"peerDependencies": {
"phaser": "^3.60.0"
@ -3181,10 +3181,10 @@
},
"spine-pixi-v7": {
"name": "@esotericsoftware/spine-pixi-v7",
"version": "4.2.73",
"version": "4.2.76",
"license": "LicenseRef-LICENSE",
"dependencies": {
"@esotericsoftware/spine-core": "4.2.73"
"@esotericsoftware/spine-core": "4.2.76"
},
"peerDependencies": {
"@pixi/assets": "^7.2.4",
@ -3198,10 +3198,10 @@
},
"spine-pixi-v8": {
"name": "@esotericsoftware/spine-pixi-v8",
"version": "4.2.73",
"version": "4.2.76",
"license": "LicenseRef-LICENSE",
"dependencies": {
"@esotericsoftware/spine-core": "4.2.73"
"@esotericsoftware/spine-core": "4.2.76"
},
"peerDependencies": {
"pixi.js": "^8.4.0"
@ -3209,18 +3209,18 @@
},
"spine-player": {
"name": "@esotericsoftware/spine-player",
"version": "4.2.73",
"version": "4.2.76",
"license": "LicenseRef-LICENSE",
"dependencies": {
"@esotericsoftware/spine-webgl": "4.2.73"
"@esotericsoftware/spine-webgl": "4.2.76"
}
},
"spine-threejs": {
"name": "@esotericsoftware/spine-threejs",
"version": "4.2.73",
"version": "4.2.76",
"license": "LicenseRef-LICENSE",
"dependencies": {
"@esotericsoftware/spine-core": "4.2.73"
"@esotericsoftware/spine-core": "4.2.76"
},
"devDependencies": {
"@types/three": "0.162.0"
@ -3231,10 +3231,10 @@
},
"spine-webgl": {
"name": "@esotericsoftware/spine-webgl",
"version": "4.2.73",
"version": "4.2.76",
"license": "LicenseRef-LICENSE",
"dependencies": {
"@esotericsoftware/spine-core": "4.2.73"
"@esotericsoftware/spine-core": "4.2.76"
}
}
}

View File

@ -1,6 +1,6 @@
{
"name": "@esotericsoftware/spine-ts",
"version": "4.2.73",
"version": "4.2.76",
"description": "The official Spine Runtimes for the web.",
"type": "module",
"files": [

View File

@ -1,6 +1,6 @@
{
"name": "@esotericsoftware/spine-canvas",
"version": "4.2.73",
"version": "4.2.76",
"description": "The official Spine Runtimes for the web.",
"main": "dist/index.js",
"types": "dist/index.d.ts",
@ -31,6 +31,6 @@
},
"homepage": "https://github.com/esotericsoftware/spine-runtimes#readme",
"dependencies": {
"@esotericsoftware/spine-core": "4.2.73"
"@esotericsoftware/spine-core": "4.2.76"
}
}

View File

@ -1,6 +1,6 @@
{
"name": "@esotericsoftware/spine-canvaskit",
"version": "4.2.73",
"version": "4.2.76",
"description": "The official Spine Runtimes for CanvasKit for NodeJS",
"main": "dist/index.js",
"types": "dist/index.d.ts",
@ -31,7 +31,7 @@
},
"homepage": "https://github.com/esotericsoftware/spine-runtimes#readme",
"dependencies": {
"@esotericsoftware/spine-core": "4.2.73",
"@esotericsoftware/spine-core": "4.2.76",
"canvaskit-wasm": "0.39.1"
},
"devDependencies": {

View File

@ -1,6 +1,6 @@
{
"name": "@esotericsoftware/spine-core",
"version": "4.2.73",
"version": "4.2.76",
"description": "The official Spine Runtimes for the web.",
"main": "dist/index.js",
"types": "dist/index.d.ts",

View File

@ -1,6 +1,6 @@
{
"name": "@esotericsoftware/spine-phaser",
"version": "4.2.73",
"version": "4.2.76",
"description": "The official Spine Runtimes for the Phaser.",
"main": "dist/index.js",
"types": "dist/index.d.ts",
@ -31,9 +31,9 @@
},
"homepage": "https://github.com/esotericsoftware/spine-runtimes#readme",
"dependencies": {
"@esotericsoftware/spine-core": "4.2.73",
"@esotericsoftware/spine-webgl": "4.2.73",
"@esotericsoftware/spine-canvas": "4.2.73"
"@esotericsoftware/spine-core": "4.2.76",
"@esotericsoftware/spine-webgl": "4.2.76",
"@esotericsoftware/spine-canvas": "4.2.76"
},
"peerDependencies": {
"phaser": "^3.60.0"

View File

@ -122,22 +122,42 @@ export class SpinePlugin extends Phaser.Plugins.ScenePlugin {
};
pluginManager.registerFileType("spineAtlas", atlasFileCallback, scene);
let self = this;
let addSpineGameObject = function (this: Phaser.GameObjects.GameObjectFactory, x: number, y: number, dataKey: string, atlasKey: string, boundsProvider: SpineGameObjectBoundsProvider) {
let gameObject = new SpineGameObject(this.scene, self, x, y, dataKey, atlasKey, boundsProvider);
if (this.scene.sys.renderer instanceof Phaser.Renderer.WebGL.WebGLRenderer) {
this.scene.sys.renderer.pipelines.clear();
}
const spinePlugin = (this.scene.sys as any)[pluginKey] as SpinePlugin;
let gameObject = new SpineGameObject(this.scene, spinePlugin, x, y, dataKey, atlasKey, boundsProvider);
this.displayList.add(gameObject);
this.updateList.add(gameObject);
if (this.scene.sys.renderer instanceof Phaser.Renderer.WebGL.WebGLRenderer) {
this.scene.sys.renderer.pipelines.rebind();
}
return gameObject;
};
let makeSpineGameObject = function (this: Phaser.GameObjects.GameObjectFactory, config: SpineGameObjectConfig, addToScene: boolean = false) {
if (this.scene.sys.renderer instanceof Phaser.Renderer.WebGL.WebGLRenderer) {
this.scene.sys.renderer.pipelines.clear();
}
let x = config.x ? config.x : 0;
let y = config.y ? config.y : 0;
let boundsProvider = config.boundsProvider ? config.boundsProvider : undefined;
let gameObject = new SpineGameObject(this.scene, self, x, y, config.dataKey, config.atlasKey, boundsProvider);
const spinePlugin = (this.scene.sys as any)[pluginKey] as SpinePlugin;
let gameObject = new SpineGameObject(this.scene, spinePlugin, x, y, config.dataKey, config.atlasKey, boundsProvider);
if (addToScene !== undefined) {
config.add = addToScene;
}
if (this.scene.sys.renderer instanceof Phaser.Renderer.WebGL.WebGLRenderer) {
this.scene.sys.renderer.pipelines.rebind();
}
return Phaser.GameObjects.BuildGameObject(this.scene, gameObject, config);
}
pluginManager.registerGameObject((window as any).SPINE_GAME_OBJECT_TYPE ? (window as any).SPINE_GAME_OBJECT_TYPE : SPINE_GAME_OBJECT_TYPE, addSpineGameObject, makeSpineGameObject);

View File

@ -59,7 +59,7 @@ export function createMixin<
};
}
type ComputedSizeMixin = Mixin<Phaser.GameObjects.Components.Transform, Phaser.GameObjects.GameObject>;
type ComputedSizeMixin = Mixin<Phaser.GameObjects.Components.ComputedSize, Phaser.GameObjects.GameObject>;
export const ComputedSizeMixin: ComputedSizeMixin = createMixin<Phaser.GameObjects.Components.ComputedSize>(ComputedSize);
type DepthMixin = Mixin<Phaser.GameObjects.Components.Depth, Phaser.GameObjects.GameObject>;

View File

@ -1,6 +1,6 @@
{
"name": "@esotericsoftware/spine-pixi-v7",
"version": "4.2.73",
"version": "4.2.76",
"description": "The official Spine Runtimes for the web PixiJS v7.",
"main": "dist/index.js",
"types": "dist/index.d.ts",
@ -31,7 +31,7 @@
},
"homepage": "https://github.com/esotericsoftware/spine-runtimes#readme",
"dependencies": {
"@esotericsoftware/spine-core": "4.2.73"
"@esotericsoftware/spine-core": "4.2.76"
},
"peerDependencies": {
"@pixi/core": "^7.2.4",

View File

@ -1,6 +1,6 @@
{
"name": "@esotericsoftware/spine-pixi-v8",
"version": "4.2.73",
"version": "4.2.76",
"description": "The official Spine Runtimes for PixiJS v8.",
"main": "dist/index.js",
"types": "dist/index.d.ts",
@ -31,7 +31,7 @@
},
"homepage": "https://github.com/esotericsoftware/spine-runtimes#readme",
"dependencies": {
"@esotericsoftware/spine-core": "4.2.73"
"@esotericsoftware/spine-core": "4.2.76"
},
"peerDependencies": {
"pixi.js": "^8.4.0"

View File

@ -560,6 +560,7 @@ export class Spine extends ViewContainer {
this.spineAttachmentsDirty ||= spineAttachmentsDirty;
}
private currentClippingSlot: SlotsToClipping | undefined;
private updateAndSetPixiMask (slot: Slot, last: boolean) {
// assign/create the currentClippingSlot
const attachment = slot.attachment;
@ -614,10 +615,10 @@ export class Spine extends ViewContainer {
clippingSlotToPixiMask.mask = undefined;
}
}
this.currentClippingSlot = undefined;
}
}
private currentClippingSlot: SlotsToClipping | undefined;
private transformAttachments () {
const currentDrawOrder = this.skeleton.drawOrder;

View File

@ -1,6 +1,6 @@
{
"name": "@esotericsoftware/spine-player",
"version": "4.2.73",
"version": "4.2.76",
"description": "The official Spine Runtimes for the web.",
"main": "dist/index.js",
"types": "dist/index.d.ts",
@ -31,6 +31,6 @@
},
"homepage": "https://github.com/esotericsoftware/spine-runtimes#readme",
"dependencies": {
"@esotericsoftware/spine-webgl": "4.2.73"
"@esotericsoftware/spine-webgl": "4.2.76"
}
}

View File

@ -347,7 +347,7 @@ export class SpinePlayer implements Disposable {
this.sceneRenderer = new SceneRenderer(this.canvas, this.context, true);
if (config.showLoading) this.loadingScreen = new LoadingScreen(this.sceneRenderer);
} catch (e) {
this.showError("Sorry, your browser does not support WebG, or you have disabled WebGL in your browser settings.\nPlease use the latest version of Firefox, Chrome, Edge, or Safari.", e as any);
this.showError("Sorry, your browser does not support WebGL, or you have disabled WebGL in your browser settings.\nPlease use the latest version of Firefox, Chrome, Edge, or Safari.", e as any);
return null;
}

View File

@ -1,6 +1,6 @@
{
"name": "@esotericsoftware/spine-threejs",
"version": "4.2.73",
"version": "4.2.76",
"description": "The official Spine Runtimes for the web.",
"main": "dist/index.js",
"types": "dist/index.d.ts",
@ -31,7 +31,7 @@
},
"homepage": "https://github.com/esotericsoftware/spine-runtimes#readme",
"dependencies": {
"@esotericsoftware/spine-core": "4.2.73"
"@esotericsoftware/spine-core": "4.2.76"
},
"devDependencies": {
"@types/three": "0.162.0"

View File

@ -309,7 +309,7 @@ const spineOnBeforeCompile = (shader: THREE.WebGLProgramParametersWithUniforms)
#ifdef USE_SPINE_DARK_TINT
#ifdef USE_COLOR_ALPHA
diffuseColor.a *= vColor.a;
diffuseColor.rgb *= (1.0 - diffuseColor.rgb) * v_dark.rgb + diffuseColor.rgb * vColor.rgb;
diffuseColor.rgb = (diffuseColor.a - diffuseColor.rgb) * v_dark.rgb + diffuseColor.rgb * vColor.rgb;
#endif
#else
#ifdef USE_COLOR_ALPHA

View File

@ -294,7 +294,7 @@ export class SkeletonMesh extends THREE.Object3D {
let darkColor = this.tempDarkColor;
if (!slot.darkColor)
darkColor.set(1, 1, 1, 0);
darkColor.set(0, 0, 0, 1);
else {
darkColor.r = slot.darkColor.r * alpha;
darkColor.g = slot.darkColor.g * alpha;

View File

@ -1,6 +1,6 @@
{
"name": "@esotericsoftware/spine-webgl",
"version": "4.2.73",
"version": "4.2.76",
"description": "The official Spine Runtimes for the web.",
"main": "dist/index.js",
"types": "dist/index.d.ts",
@ -31,6 +31,6 @@
},
"homepage": "https://github.com/esotericsoftware/spine-runtimes#readme",
"dependencies": {
"@esotericsoftware/spine-core": "4.2.73"
"@esotericsoftware/spine-core": "4.2.76"
}
}

View File

@ -98,7 +98,7 @@
function render() {
var start = Date.now()
timeKeeper.update();
var delta = 0.016; // timeKeeper.delta;
var delta = timeKeeper.delta;
for (var i = 0; i < skeletons.length; i++) {
var state = skeletons[i].state;

View File

@ -0,0 +1,224 @@
/******************************************************************************
* Spine Runtimes License Agreement
* Last updated July 28, 2023. Replaces all prior versions.
*
* Copyright (c) 2013-2025, 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.
*****************************************************************************/
#if UNITY_2018_3 || UNITY_2019 || UNITY_2018_3_OR_NEWER
#define NEW_PREFAB_SYSTEM
#endif
#if UNITY_2018_2_OR_NEWER
#define HAS_CULL_TRANSPARENT_MESH
#endif
using System;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
namespace Spine.Unity.Examples {
using MaterialReplacement = RenderExistingMesh.MaterialReplacement;
#if NEW_PREFAB_SYSTEM
[ExecuteAlways]
#else
[ExecuteInEditMode]
#endif
public class RenderExistingMeshGraphic : MonoBehaviour {
public SkeletonGraphic referenceSkeletonGraphic;
public Material replacementMaterial;
public MaterialReplacement[] replacementMaterials = new MaterialReplacement[0];
SkeletonSubmeshGraphic ownGraphic;
public List<SkeletonSubmeshGraphic> ownSubmeshGraphics;
#if UNITY_EDITOR
private void Reset () {
Awake();
LateUpdate();
}
#endif
void Awake () {
// subscribe to OnMeshAndMaterialsUpdated
if (referenceSkeletonGraphic) {
referenceSkeletonGraphic.OnMeshAndMaterialsUpdated -= UpdateOnCallback;
referenceSkeletonGraphic.OnMeshAndMaterialsUpdated += UpdateOnCallback;
}
ownGraphic = this.GetComponent<SkeletonSubmeshGraphic>();
if (referenceSkeletonGraphic) {
if (referenceSkeletonGraphic.allowMultipleCanvasRenderers)
EnsureCanvasRendererCount(referenceSkeletonGraphic.canvasRenderers.Count);
else
SetupSubmeshGraphic();
}
}
protected void OnDisable () {
if (referenceSkeletonGraphic) {
referenceSkeletonGraphic.OnMeshAndMaterialsUpdated -= UpdateOnCallback;
}
}
protected void OnEnable () {
#if UNITY_EDITOR
// handle disabled scene reload
if (Application.isPlaying) {
Awake();
return;
}
#endif
if (referenceSkeletonGraphic) {
referenceSkeletonGraphic.OnMeshAndMaterialsUpdated -= UpdateOnCallback;
referenceSkeletonGraphic.OnMeshAndMaterialsUpdated += UpdateOnCallback;
}
}
void SetupSubmeshGraphic () {
if (ownGraphic == null)
ownGraphic = this.gameObject.AddComponent<SkeletonSubmeshGraphic>();
ownGraphic.maskable = referenceSkeletonGraphic.maskable;
#if HAS_CULL_TRANSPARENT_MESH
ownGraphic.canvasRenderer.cullTransparentMesh = referenceSkeletonGraphic.canvasRenderer.cullTransparentMesh;
#endif
ownGraphic.canvasRenderer.SetMaterial(replacementMaterial, referenceSkeletonGraphic.mainTexture);
}
protected void EnsureCanvasRendererCount (int targetCount) {
if (ownSubmeshGraphics == null)
ownSubmeshGraphics = new List<SkeletonSubmeshGraphic>();
#if HAS_CULL_TRANSPARENT_MESH
bool cullTransparentMesh = referenceSkeletonGraphic.canvasRenderer.cullTransparentMesh;
#endif
Vector2 pivot = referenceSkeletonGraphic.rectTransform.pivot;
int currentCount = ownSubmeshGraphics.Count;
for (int i = currentCount; i < targetCount; ++i) {
GameObject go = new GameObject(string.Format("Renderer{0}", i), typeof(RectTransform));
go.transform.SetParent(this.transform, false);
go.transform.localPosition = Vector3.zero;
CanvasRenderer canvasRenderer = go.AddComponent<CanvasRenderer>();
#if HAS_CULL_TRANSPARENT_MESH
canvasRenderer.cullTransparentMesh = cullTransparentMesh;
#endif
SkeletonSubmeshGraphic submeshGraphic = go.AddComponent<SkeletonSubmeshGraphic>();
ownSubmeshGraphics.Add(submeshGraphic);
submeshGraphic.maskable = referenceSkeletonGraphic.maskable;
submeshGraphic.raycastTarget = false;
submeshGraphic.rectTransform.pivot = pivot;
submeshGraphic.rectTransform.anchorMin = Vector2.zero;
submeshGraphic.rectTransform.anchorMax = Vector2.one;
submeshGraphic.rectTransform.sizeDelta = Vector2.zero;
}
}
protected void UpdateCanvasRenderers () {
Mesh[] referenceMeshes = referenceSkeletonGraphic.MeshesMultipleCanvasRenderers.Items;
Material[] referenceMaterials = referenceSkeletonGraphic.MaterialsMultipleCanvasRenderers.Items;
Texture[] referenceTextures = referenceSkeletonGraphic.TexturesMultipleCanvasRenderers.Items;
int end = Math.Min(ownSubmeshGraphics.Count, referenceSkeletonGraphic.TexturesMultipleCanvasRenderers.Count);
for (int i = 0; i < end; i++) {
SkeletonSubmeshGraphic submeshGraphic = ownSubmeshGraphics[i];
CanvasRenderer reference = referenceSkeletonGraphic.canvasRenderers[i];
if (reference.gameObject.activeInHierarchy) {
Material usedMaterial = replacementMaterial != null ?
replacementMaterial : GetReplacementMaterialFor(referenceMaterials[i]);
if (usedMaterial == null)
usedMaterial = referenceMaterials[i];
usedMaterial = referenceSkeletonGraphic.GetModifiedMaterial(usedMaterial);
submeshGraphic.canvasRenderer.SetMaterial(usedMaterial, referenceTextures[i]);
submeshGraphic.canvasRenderer.SetMesh(referenceMeshes[i]);
submeshGraphic.gameObject.SetActive(true);
} else {
submeshGraphic.canvasRenderer.Clear();
submeshGraphic.gameObject.SetActive(false);
}
}
}
protected void DisableCanvasRenderers () {
for (int i = 0; i < ownSubmeshGraphics.Count; i++) {
SkeletonSubmeshGraphic submeshGraphic = ownSubmeshGraphics[i];
submeshGraphic.canvasRenderer.Clear();
submeshGraphic.gameObject.SetActive(false);
}
}
protected Material GetReplacementMaterialFor (Material originalMaterial) {
for (int i = 0; i < replacementMaterials.Length; ++i) {
MaterialReplacement entry = replacementMaterials[i];
if (entry.originalMaterial != null && entry.originalMaterial.shader == originalMaterial.shader)
return entry.replacementMaterial;
}
return null;
}
#if UNITY_EDITOR
void LateUpdate () {
if (!Application.isPlaying) {
UpdateMesh();
}
}
#endif
void UpdateOnCallback (SkeletonGraphic g) {
UpdateMesh();
}
void UpdateMesh () {
if (!referenceSkeletonGraphic) return;
if (referenceSkeletonGraphic.allowMultipleCanvasRenderers) {
EnsureCanvasRendererCount(referenceSkeletonGraphic.canvasRenderers.Count);
UpdateCanvasRenderers();
if (ownGraphic)
ownGraphic.canvasRenderer.Clear();
} else {
if (ownGraphic == null)
ownGraphic = this.gameObject.AddComponent<SkeletonSubmeshGraphic>();
DisableCanvasRenderers();
Material referenceMaterial = referenceSkeletonGraphic.materialForRendering;
Material usedMaterial = replacementMaterial != null ? replacementMaterial : GetReplacementMaterialFor(referenceMaterial);
if (usedMaterial == null)
usedMaterial = referenceMaterial;
usedMaterial = referenceSkeletonGraphic.GetModifiedMaterial(usedMaterial);
ownGraphic.canvasRenderer.SetMaterial(usedMaterial, referenceSkeletonGraphic.mainTexture);
Mesh mesh = referenceSkeletonGraphic.GetLastMesh();
ownGraphic.canvasRenderer.SetMesh(mesh);
}
}
}
}

View File

@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: ff6ce4ce6b9336a479c6bf5af81fa80a

View File

@ -2,7 +2,7 @@
"name": "com.esotericsoftware.spine.spine-unity-examples",
"displayName": "spine-unity Runtime Examples",
"description": "This plugin provides example scenes and scripts for the spine-unity runtime.",
"version": "4.2.37",
"version": "4.2.38",
"unity": "2018.3",
"author": {
"name": "Esoteric Software",

View File

@ -41,6 +41,7 @@ public class SpineShaderWithOutlineGUI : ShaderGUI {
MaterialProperty _OutlineWidth = null;
MaterialProperty _UseScreenSpaceOutlineWidth = null;
MaterialProperty _OutlineColor = null;
MaterialProperty _Fill = null;
MaterialProperty _OutlineReferenceTexWidth = null;
MaterialProperty _ThresholdEnd = null;
MaterialProperty _OutlineSmoothness = null;
@ -54,6 +55,7 @@ public class SpineShaderWithOutlineGUI : ShaderGUI {
static GUIContent _OutlineWidthText = new GUIContent("Outline Width", "");
static GUIContent _UseScreenSpaceOutlineWidthText = new GUIContent("Width in Screen Space", "Enable to keep the outline width constant in screen space instead of texture space. Requires more expensive computations.");
static GUIContent _OutlineColorText = new GUIContent("Outline Color", "");
static GUIContent _FillText = new GUIContent("Fill", "Enable to also fill the opaque area inside the outline with the outline color. Prevents a semi-transparent gap between outline and skeleton.");
static GUIContent _OutlineReferenceTexWidthText = new GUIContent("Reference Texture Width", "");
static GUIContent _ThresholdEndText = new GUIContent("Outline Threshold", "");
static GUIContent _OutlineSmoothnessText = new GUIContent("Outline Smoothness", "");
@ -92,6 +94,7 @@ public class SpineShaderWithOutlineGUI : ShaderGUI {
_UseScreenSpaceOutlineWidth = FindProperty("_UseScreenSpaceOutlineWidth", props, false);
_OutlineReferenceTexWidth = FindProperty("_OutlineReferenceTexWidth", props, false);
_OutlineColor = FindProperty("_OutlineColor", props, false);
_Fill = FindProperty("_Fill", props, false);
_ThresholdEnd = FindProperty("_ThresholdEnd", props, false);
_OutlineSmoothness = FindProperty("_OutlineSmoothness", props, false);
_Use8Neighbourhood = FindProperty("_Use8Neighbourhood", props, false);
@ -157,6 +160,8 @@ public class SpineShaderWithOutlineGUI : ShaderGUI {
if (_UseScreenSpaceOutlineWidth != null)
_materialEditor.ShaderProperty(_UseScreenSpaceOutlineWidth, _UseScreenSpaceOutlineWidthText);
_materialEditor.ShaderProperty(_OutlineColor, _OutlineColorText);
if (_Fill != null)
_materialEditor.ShaderProperty(_Fill, _FillText);
_showAdvancedOutlineSettings = EditorGUILayout.Foldout(_showAdvancedOutlineSettings, _OutlineAdvancedText);
if (_showAdvancedOutlineSettings) {

View File

@ -18,6 +18,7 @@ Shader "Spine/Blend Modes/Skeleton PMA Additive" {
[HideInInspector] _OutlineWidth("Outline Width", Range(0,8)) = 3.0
[HideInInspector][MaterialToggle(_USE_SCREENSPACE_OUTLINE_WIDTH)] _UseScreenSpaceOutlineWidth("Width in Screen Space", Float) = 0
[HideInInspector] _OutlineColor("Outline Color", Color) = (1,1,0,1)
[HideInInspector][MaterialToggle(_OUTLINE_FILL_INSIDE)]_Fill("Fill", Float) = 0
[HideInInspector] _OutlineReferenceTexWidth("Reference Texture Width", Int) = 1024
[HideInInspector] _ThresholdEnd("Outline Threshold", Range(0,1)) = 0.25
[HideInInspector] _OutlineSmoothness("Outline Smoothness", Range(0,1)) = 1.0

View File

@ -18,6 +18,7 @@ Shader "Spine/Blend Modes/Skeleton PMA Multiply" {
[HideInInspector] _OutlineWidth("Outline Width", Range(0,8)) = 3.0
[HideInInspector][MaterialToggle(_USE_SCREENSPACE_OUTLINE_WIDTH)] _UseScreenSpaceOutlineWidth("Width in Screen Space", Float) = 0
[HideInInspector] _OutlineColor("Outline Color", Color) = (1,1,0,1)
[HideInInspector][MaterialToggle(_OUTLINE_FILL_INSIDE)]_Fill("Fill", Float) = 0
[HideInInspector] _OutlineReferenceTexWidth("Reference Texture Width", Int) = 1024
[HideInInspector] _ThresholdEnd("Outline Threshold", Range(0,1)) = 0.25
[HideInInspector] _OutlineSmoothness("Outline Smoothness", Range(0,1)) = 1.0

View File

@ -18,6 +18,7 @@ Shader "Spine/Blend Modes/Skeleton PMA Screen" {
[HideInInspector] _OutlineWidth("Outline Width", Range(0,8)) = 3.0
[HideInInspector][MaterialToggle(_USE_SCREENSPACE_OUTLINE_WIDTH)] _UseScreenSpaceOutlineWidth("Width in Screen Space", Float) = 0
[HideInInspector] _OutlineColor("Outline Color", Color) = (1,1,0,1)
[HideInInspector][MaterialToggle(_OUTLINE_FILL_INSIDE)]_Fill("Fill", Float) = 0
[HideInInspector] _OutlineReferenceTexWidth("Reference Texture Width", Int) = 1024
[HideInInspector] _ThresholdEnd("Outline Threshold", Range(0,1)) = 0.25
[HideInInspector] _OutlineSmoothness("Outline Smoothness", Range(0,1)) = 1.0

View File

@ -46,8 +46,13 @@ float4 computeOutlinePixel(sampler2D mainTexture, float2 mainTextureTexelSize,
float average = (pixelTop + pixelBottom + pixelLeft + pixelRight) * vertexColorAlpha / numSamples;
#endif
float thresholdStart = ThresholdEnd * (1.0 - OutlineSmoothness);
float outlineAlpha = saturate(saturate((average - thresholdStart) / (ThresholdEnd - thresholdStart)) - pixelCenter);
outlineAlpha = pixelCenter > OutlineOpaqueAlpha ? 0 : outlineAlpha;
float outlineAlpha = saturate((average - thresholdStart) / (ThresholdEnd - thresholdStart));
#if !_OUTLINE_FILL_INSIDE
outlineAlpha = saturate(outlineAlpha - pixelCenter);
outlineAlpha = pixelCenter > OutlineOpaqueAlpha ? 0.0 : outlineAlpha;
#else
outlineAlpha = pixelCenter > OutlineOpaqueAlpha ? 1.0 : outlineAlpha;
#endif
return lerp(texColor, OutlineColor, outlineAlpha);
}

View File

@ -13,6 +13,7 @@ Shader "Spine/Outline/Blend Modes/Skeleton PMA Additive" {
[HideInInspector] _OutlineWidth("Outline Width", Range(0,8)) = 3.0
[HideInInspector][MaterialToggle(_USE_SCREENSPACE_OUTLINE_WIDTH)] _UseScreenSpaceOutlineWidth("Width in Screen Space", Float) = 0
[HideInInspector] _OutlineColor("Outline Color", Color) = (1,1,0,1)
[HideInInspector][MaterialToggle(_OUTLINE_FILL_INSIDE)]_Fill("Fill", Float) = 0
[HideInInspector] _OutlineReferenceTexWidth("Reference Texture Width", Int) = 1024
[HideInInspector] _ThresholdEnd("Outline Threshold", Range(0,1)) = 0.25
[HideInInspector] _OutlineSmoothness("Outline Smoothness", Range(0,1)) = 1.0

View File

@ -13,6 +13,7 @@ Shader "Spine/Outline/Blend Modes/Skeleton PMA Multiply" {
[HideInInspector] _OutlineWidth("Outline Width", Range(0,8)) = 3.0
[HideInInspector][MaterialToggle(_USE_SCREENSPACE_OUTLINE_WIDTH)] _UseScreenSpaceOutlineWidth("Width in Screen Space", Float) = 0
[HideInInspector] _OutlineColor("Outline Color", Color) = (1,1,0,1)
[HideInInspector][MaterialToggle(_OUTLINE_FILL_INSIDE)]_Fill("Fill", Float) = 0
[HideInInspector] _OutlineReferenceTexWidth("Reference Texture Width", Int) = 1024
[HideInInspector] _ThresholdEnd("Outline Threshold", Range(0,1)) = 0.25
[HideInInspector] _OutlineSmoothness("Outline Smoothness", Range(0,1)) = 1.0

View File

@ -13,6 +13,7 @@ Shader "Spine/Outline/Blend Modes/Skeleton PMA Screen" {
[HideInInspector] _OutlineWidth("Outline Width", Range(0,8)) = 3.0
[HideInInspector][MaterialToggle(_USE_SCREENSPACE_OUTLINE_WIDTH)] _UseScreenSpaceOutlineWidth("Width in Screen Space", Float) = 0
[HideInInspector] _OutlineColor("Outline Color", Color) = (1,1,0,1)
[HideInInspector][MaterialToggle(_OUTLINE_FILL_INSIDE)]_Fill("Fill", Float) = 0
[HideInInspector] _OutlineReferenceTexWidth("Reference Texture Width", Int) = 1024
[HideInInspector] _ThresholdEnd("Outline Threshold", Range(0,1)) = 0.25
[HideInInspector] _OutlineSmoothness("Outline Smoothness", Range(0,1)) = 1.0

View File

@ -23,6 +23,7 @@ Shader "Spine/Outline/SkeletonGraphic"
[HideInInspector] _OutlineWidth("Outline Width", Range(0,8)) = 3.0
[HideInInspector][MaterialToggle(_USE_SCREENSPACE_OUTLINE_WIDTH)] _UseScreenSpaceOutlineWidth("Width in Screen Space", Float) = 0
[HideInInspector] _OutlineColor("Outline Color", Color) = (1,1,0,1)
[HideInInspector][MaterialToggle(_OUTLINE_FILL_INSIDE)]_Fill("Fill", Float) = 0
[HideInInspector] _OutlineReferenceTexWidth("Reference Texture Width", Int) = 1024
[HideInInspector] _ThresholdEnd("Outline Threshold", Range(0,1)) = 0.25
[HideInInspector] _OutlineSmoothness("Outline Smoothness", Range(0,1)) = 1.0

View File

@ -25,6 +25,7 @@ Shader "Spine/Outline/SkeletonGraphic Tint Black"
[HideInInspector] _OutlineWidth("Outline Width", Range(0,8)) = 3.0
[HideInInspector][MaterialToggle(_USE_SCREENSPACE_OUTLINE_WIDTH)] _UseScreenSpaceOutlineWidth("Width in Screen Space", Float) = 0
[HideInInspector] _OutlineColor("Outline Color", Color) = (1,1,0,1)
[HideInInspector][MaterialToggle(_OUTLINE_FILL_INSIDE)]_Fill("Fill", Float) = 0
[HideInInspector] _OutlineReferenceTexWidth("Reference Texture Width", Int) = 1024
[HideInInspector] _ThresholdEnd("Outline Threshold", Range(0,1)) = 0.25
[HideInInspector] _OutlineSmoothness("Outline Smoothness", Range(0,1)) = 1.0

View File

@ -14,6 +14,7 @@ Shader "Spine/Outline/Skeleton Fill" {
[HideInInspector] _OutlineWidth("Outline Width", Range(0,8)) = 3.0
[HideInInspector][MaterialToggle(_USE_SCREENSPACE_OUTLINE_WIDTH)] _UseScreenSpaceOutlineWidth("Width in Screen Space", Float) = 0
[HideInInspector] _OutlineColor("Outline Color", Color) = (1,1,0,1)
[HideInInspector][MaterialToggle(_OUTLINE_FILL_INSIDE)]_Fill("Fill", Float) = 0
[HideInInspector] _OutlineReferenceTexWidth("Reference Texture Width", Int) = 1024
[HideInInspector] _ThresholdEnd("Outline Threshold", Range(0,1)) = 0.25
[HideInInspector] _OutlineSmoothness("Outline Smoothness", Range(0,1)) = 1.0

View File

@ -13,6 +13,7 @@ Shader "Spine/Outline/Skeleton Lit" {
[HideInInspector] _OutlineWidth("Outline Width", Range(0,8)) = 3.0
[HideInInspector][MaterialToggle(_USE_SCREENSPACE_OUTLINE_WIDTH)] _UseScreenSpaceOutlineWidth("Width in Screen Space", Float) = 0
[HideInInspector] _OutlineColor("Outline Color", Color) = (1,1,0,1)
[HideInInspector][MaterialToggle(_OUTLINE_FILL_INSIDE)]_Fill("Fill", Float) = 0
[HideInInspector] _OutlineReferenceTexWidth("Reference Texture Width", Int) = 1024
[HideInInspector] _ThresholdEnd("Outline Threshold", Range(0,1)) = 0.25
[HideInInspector] _OutlineSmoothness("Outline Smoothness", Range(0,1)) = 1.0

View File

@ -14,6 +14,7 @@ Shader "Spine/Outline/Skeleton Lit ZWrite" {
[HideInInspector] _OutlineWidth("Outline Width", Range(0,8)) = 3.0
[HideInInspector][MaterialToggle(_USE_SCREENSPACE_OUTLINE_WIDTH)] _UseScreenSpaceOutlineWidth("Width in Screen Space", Float) = 0
[HideInInspector] _OutlineColor("Outline Color", Color) = (1,1,0,1)
[HideInInspector][MaterialToggle(_OUTLINE_FILL_INSIDE)]_Fill("Fill", Float) = 0
[HideInInspector] _OutlineReferenceTexWidth("Reference Texture Width", Int) = 1024
[HideInInspector] _ThresholdEnd("Outline Threshold", Range(0,1)) = 0.25
[HideInInspector] _OutlineSmoothness("Outline Smoothness", Range(0,1)) = 1.0

View File

@ -12,6 +12,7 @@ Shader "Spine/Outline/Skeleton" {
[HideInInspector] _OutlineWidth("Outline Width", Range(0,8)) = 3.0
[HideInInspector][MaterialToggle(_USE_SCREENSPACE_OUTLINE_WIDTH)] _UseScreenSpaceOutlineWidth("Width in Screen Space", Float) = 0
[HideInInspector] _OutlineColor("Outline Color", Color) = (1,1,0,1)
[HideInInspector][MaterialToggle(_OUTLINE_FILL_INSIDE)]_Fill("Fill", Float) = 0
[HideInInspector] _OutlineReferenceTexWidth("Reference Texture Width", Int) = 1024
[HideInInspector] _ThresholdEnd("Outline Threshold", Range(0,1)) = 0.25
[HideInInspector] _OutlineSmoothness("Outline Smoothness", Range(0,1)) = 1.0
@ -42,6 +43,7 @@ Shader "Spine/Outline/Skeleton" {
#pragma fragment fragOutline
#pragma shader_feature _ _USE8NEIGHBOURHOOD_ON
#pragma shader_feature _ _USE_SCREENSPACE_OUTLINE_WIDTH
#pragma shader_feature _ _OUTLINE_FILL_INSIDE
#include "CGIncludes/Spine-Outline-Pass.cginc"
ENDCG
}

View File

@ -11,6 +11,7 @@ Shader "Spine/Outline/OutlineOnly-ZWrite" {
[HideInInspector] _OutlineWidth("Outline Width", Range(0,8)) = 3.0
[HideInInspector][MaterialToggle(_USE_SCREENSPACE_OUTLINE_WIDTH)] _UseScreenSpaceOutlineWidth("Width in Screen Space", Float) = 0
[HideInInspector] _OutlineColor("Outline Color", Color) = (1,1,0,1)
[HideInInspector][MaterialToggle(_OUTLINE_FILL_INSIDE)]_Fill("Fill", Float) = 0
[HideInInspector] _OutlineReferenceTexWidth("Reference Texture Width", Int) = 1024
[HideInInspector] _ThresholdEnd("Outline Threshold", Range(0,1)) = 0.25
[HideInInspector] _OutlineSmoothness("Outline Smoothness", Range(0,1)) = 1.0
@ -56,6 +57,7 @@ Shader "Spine/Outline/OutlineOnly-ZWrite" {
#pragma fragment fragOutline
#pragma shader_feature _ _USE8NEIGHBOURHOOD_ON
#pragma shader_feature _ _USE_SCREENSPACE_OUTLINE_WIDTH
#pragma shader_feature _ _OUTLINE_FILL_INSIDE
#include "CGIncludes/Spine-Outline-Pass.cginc"
ENDCG
}

View File

@ -14,6 +14,7 @@ Shader "Spine/Outline/Skeleton Tint" {
[HideInInspector] _OutlineWidth("Outline Width", Range(0,8)) = 3.0
[HideInInspector][MaterialToggle(_USE_SCREENSPACE_OUTLINE_WIDTH)] _UseScreenSpaceOutlineWidth("Width in Screen Space", Float) = 0
[HideInInspector] _OutlineColor("Outline Color", Color) = (1,1,0,1)
[HideInInspector][MaterialToggle(_OUTLINE_FILL_INSIDE)]_Fill("Fill", Float) = 0
[HideInInspector] _OutlineReferenceTexWidth("Reference Texture Width", Int) = 1024
[HideInInspector] _ThresholdEnd("Outline Threshold", Range(0,1)) = 0.25
[HideInInspector] _OutlineSmoothness("Outline Smoothness", Range(0,1)) = 1.0

View File

@ -14,6 +14,7 @@ Shader "Spine/Outline/Skeleton Tint Black" {
[HideInInspector] _OutlineWidth("Outline Width", Range(0,8)) = 3.0
[HideInInspector][MaterialToggle(_USE_SCREENSPACE_OUTLINE_WIDTH)] _UseScreenSpaceOutlineWidth("Width in Screen Space", Float) = 0
[HideInInspector] _OutlineColor("Outline Color", Color) = (1,1,0,1)
[HideInInspector][MaterialToggle(_OUTLINE_FILL_INSIDE)]_Fill("Fill", Float) = 0
[HideInInspector] _OutlineReferenceTexWidth("Reference Texture Width", Int) = 1024
[HideInInspector] _ThresholdEnd("Outline Threshold", Range(0,1)) = 0.25
[HideInInspector] _OutlineSmoothness("Outline Smoothness", Range(0,1)) = 1.0

View File

@ -13,6 +13,7 @@ Shader "Spine/Outline/Special/Skeleton Grayscale" {
[HideInInspector] _OutlineWidth("Outline Width", Range(0,8)) = 3.0
[HideInInspector][MaterialToggle(_USE_SCREENSPACE_OUTLINE_WIDTH)] _UseScreenSpaceOutlineWidth("Width in Screen Space", Float) = 0
[HideInInspector] _OutlineColor("Outline Color", Color) = (1,1,0,1)
[HideInInspector][MaterialToggle(_OUTLINE_FILL_INSIDE)]_Fill("Fill", Float) = 0
[HideInInspector] _OutlineReferenceTexWidth("Reference Texture Width", Int) = 1024
[HideInInspector] _ThresholdEnd("Outline Threshold", Range(0,1)) = 0.25
[HideInInspector] _OutlineSmoothness("Outline Smoothness", Range(0,1)) = 1.0

View File

@ -50,6 +50,7 @@ Shader "Spine/Outline/Sprite/Pixel Lit"
[HideInInspector] _OutlineWidth("Outline Width", Range(0,8)) = 3.0
[HideInInspector][MaterialToggle(_USE_SCREENSPACE_OUTLINE_WIDTH)] _UseScreenSpaceOutlineWidth("Width in Screen Space", Float) = 0
[HideInInspector] _OutlineColor("Outline Color", Color) = (1,1,0,1)
[HideInInspector][MaterialToggle(_OUTLINE_FILL_INSIDE)]_Fill("Fill", Float) = 0
[HideInInspector] _OutlineReferenceTexWidth("Reference Texture Width", Int) = 1024
[HideInInspector] _ThresholdEnd("Outline Threshold", Range(0,1)) = 0.25
[HideInInspector] _OutlineSmoothness("Outline Smoothness", Range(0,1)) = 1.0

View File

@ -33,6 +33,7 @@ Shader "Spine/Outline/Sprite/Unlit"
[HideInInspector] _OutlineWidth("Outline Width", Range(0,8)) = 3.0
[HideInInspector][MaterialToggle(_USE_SCREENSPACE_OUTLINE_WIDTH)] _UseScreenSpaceOutlineWidth("Width in Screen Space", Float) = 0
[HideInInspector] _OutlineColor("Outline Color", Color) = (1,1,0,1)
[HideInInspector][MaterialToggle(_OUTLINE_FILL_INSIDE)]_Fill("Fill", Float) = 0
[HideInInspector] _OutlineReferenceTexWidth("Reference Texture Width", Int) = 1024
[HideInInspector] _ThresholdEnd("Outline Threshold", Range(0,1)) = 0.25
[HideInInspector] _OutlineSmoothness("Outline Smoothness", Range(0,1)) = 1.0

View File

@ -51,6 +51,7 @@ Shader "Spine/Outline/Sprite/Vertex Lit"
[HideInInspector] _OutlineWidth("Outline Width", Range(0,8)) = 3.0
[HideInInspector][MaterialToggle(_USE_SCREENSPACE_OUTLINE_WIDTH)] _UseScreenSpaceOutlineWidth("Width in Screen Space", Float) = 0
[HideInInspector] _OutlineColor("Outline Color", Color) = (1,1,0,1)
[HideInInspector][MaterialToggle(_OUTLINE_FILL_INSIDE)]_Fill("Fill", Float) = 0
[HideInInspector] _OutlineReferenceTexWidth("Reference Texture Width", Int) = 1024
[HideInInspector] _ThresholdEnd("Outline Threshold", Range(0,1)) = 0.25
[HideInInspector] _OutlineSmoothness("Outline Smoothness", Range(0,1)) = 1.0

View File

@ -23,6 +23,7 @@ Shader "Spine/SkeletonGraphic Additive"
[HideInInspector] _OutlineWidth("Outline Width", Range(0,8)) = 3.0
[HideInInspector][MaterialToggle(_USE_SCREENSPACE_OUTLINE_WIDTH)] _UseScreenSpaceOutlineWidth("Width in Screen Space", Float) = 0
[HideInInspector] _OutlineColor("Outline Color", Color) = (1,1,0,1)
[HideInInspector][MaterialToggle(_OUTLINE_FILL_INSIDE)]_Fill("Fill", Float) = 0
[HideInInspector] _OutlineReferenceTexWidth("Reference Texture Width", Int) = 1024
[HideInInspector] _ThresholdEnd("Outline Threshold", Range(0,1)) = 0.25
[HideInInspector] _OutlineSmoothness("Outline Smoothness", Range(0,1)) = 1.0

View File

@ -23,6 +23,7 @@ Shader "Spine/SkeletonGraphic Fill"
[HideInInspector] _OutlineWidth("Outline Width", Range(0,8)) = 3.0
[HideInInspector][MaterialToggle(_USE_SCREENSPACE_OUTLINE_WIDTH)] _UseScreenSpaceOutlineWidth("Width in Screen Space", Float) = 0
[HideInInspector] _OutlineColor("Outline Color", Color) = (1,1,0,1)
[HideInInspector][MaterialToggle(_OUTLINE_FILL_INSIDE)]_Fill("Fill", Float) = 0
[HideInInspector] _OutlineReferenceTexWidth("Reference Texture Width", Int) = 1024
[HideInInspector] _ThresholdEnd("Outline Threshold", Range(0,1)) = 0.25
[HideInInspector] _OutlineSmoothness("Outline Smoothness", Range(0,1)) = 1.0

View File

@ -21,6 +21,7 @@ Shader "Spine/SkeletonGraphic Grayscale"
[HideInInspector] _OutlineWidth("Outline Width", Range(0,8)) = 3.0
[HideInInspector][MaterialToggle(_USE_SCREENSPACE_OUTLINE_WIDTH)] _UseScreenSpaceOutlineWidth("Width in Screen Space", Float) = 0
[HideInInspector] _OutlineColor("Outline Color", Color) = (1,1,0,1)
[HideInInspector][MaterialToggle(_OUTLINE_FILL_INSIDE)]_Fill("Fill", Float) = 0
[HideInInspector] _OutlineReferenceTexWidth("Reference Texture Width", Int) = 1024
[HideInInspector] _ThresholdEnd("Outline Threshold", Range(0,1)) = 0.25
[HideInInspector] _OutlineSmoothness("Outline Smoothness", Range(0,1)) = 1.0

View File

@ -23,6 +23,7 @@ Shader "Spine/SkeletonGraphic Multiply"
[HideInInspector] _OutlineWidth("Outline Width", Range(0,8)) = 3.0
[HideInInspector][MaterialToggle(_USE_SCREENSPACE_OUTLINE_WIDTH)] _UseScreenSpaceOutlineWidth("Width in Screen Space", Float) = 0
[HideInInspector] _OutlineColor("Outline Color", Color) = (1,1,0,1)
[HideInInspector][MaterialToggle(_OUTLINE_FILL_INSIDE)]_Fill("Fill", Float) = 0
[HideInInspector] _OutlineReferenceTexWidth("Reference Texture Width", Int) = 1024
[HideInInspector] _ThresholdEnd("Outline Threshold", Range(0,1)) = 0.25
[HideInInspector] _OutlineSmoothness("Outline Smoothness", Range(0,1)) = 1.0

View File

@ -23,6 +23,7 @@ Shader "Spine/SkeletonGraphic Screen"
[HideInInspector] _OutlineWidth("Outline Width", Range(0,8)) = 3.0
[HideInInspector][MaterialToggle(_USE_SCREENSPACE_OUTLINE_WIDTH)] _UseScreenSpaceOutlineWidth("Width in Screen Space", Float) = 0
[HideInInspector] _OutlineColor("Outline Color", Color) = (1,1,0,1)
[HideInInspector][MaterialToggle(_OUTLINE_FILL_INSIDE)]_Fill("Fill", Float) = 0
[HideInInspector] _OutlineReferenceTexWidth("Reference Texture Width", Int) = 1024
[HideInInspector] _ThresholdEnd("Outline Threshold", Range(0,1)) = 0.25
[HideInInspector] _OutlineSmoothness("Outline Smoothness", Range(0,1)) = 1.0

View File

@ -23,6 +23,7 @@ Shader "Spine/SkeletonGraphic"
[HideInInspector] _OutlineWidth("Outline Width", Range(0,8)) = 3.0
[HideInInspector][MaterialToggle(_USE_SCREENSPACE_OUTLINE_WIDTH)] _UseScreenSpaceOutlineWidth("Width in Screen Space", Float) = 0
[HideInInspector] _OutlineColor("Outline Color", Color) = (1,1,0,1)
[HideInInspector][MaterialToggle(_OUTLINE_FILL_INSIDE)]_Fill("Fill", Float) = 0
[HideInInspector] _OutlineReferenceTexWidth("Reference Texture Width", Int) = 1024
[HideInInspector] _ThresholdEnd("Outline Threshold", Range(0,1)) = 0.25
[HideInInspector] _OutlineSmoothness("Outline Smoothness", Range(0,1)) = 1.0

View File

@ -24,6 +24,7 @@ Shader "Spine/SkeletonGraphic Tint Black Additive"
[HideInInspector] _OutlineWidth("Outline Width", Range(0,8)) = 3.0
[HideInInspector][MaterialToggle(_USE_SCREENSPACE_OUTLINE_WIDTH)] _UseScreenSpaceOutlineWidth("Width in Screen Space", Float) = 0
[HideInInspector] _OutlineColor("Outline Color", Color) = (1,1,0,1)
[HideInInspector][MaterialToggle(_OUTLINE_FILL_INSIDE)]_Fill("Fill", Float) = 0
[HideInInspector] _OutlineReferenceTexWidth("Reference Texture Width", Int) = 1024
[HideInInspector] _ThresholdEnd("Outline Threshold", Range(0,1)) = 0.25
[HideInInspector] _OutlineSmoothness("Outline Smoothness", Range(0,1)) = 1.0

Some files were not shown because too many files have changed in this diff Show More