mirror of
https://github.com/EsotericSoftware/spine-runtimes.git
synced 2026-03-26 22:49:01 +08:00
Merge branch '4.1' into 4.2-beta
This commit is contained in:
commit
0d1948a821
@ -178,6 +178,7 @@
|
|||||||
3) Copy the original material, add *_Outline* to its name and set the shader to your outline-only shader like `Universal Render Pipeline/Spine/Outline/Skeleton-OutlineOnly` or `Spine/Outline/OutlineOnly-ZWrite`.
|
3) Copy the original material, add *_Outline* to its name and set the shader to your outline-only shader like `Universal Render Pipeline/Spine/Outline/Skeleton-OutlineOnly` or `Spine/Outline/OutlineOnly-ZWrite`.
|
||||||
4) Assign this *_Outline* material at the new child GameObject's `MeshRenderer` component.
|
4) Assign this *_Outline* material at the new child GameObject's `MeshRenderer` component.
|
||||||
If you are using `SkeletonRenderSeparator` and need to enable and disable the `SkeletonRenderSeparator` component at runtime, you can increase the `RenderCombinedMesh` `Reference Renderers` array by one and assign the `SkeletonRenderer` itself at the last entry after the parts renderers. Disabled `MeshRenderer` components will be skipped when combining the final mesh, so the combined mesh is automatically filled from the desired active renderers.
|
If you are using `SkeletonRenderSeparator` and need to enable and disable the `SkeletonRenderSeparator` component at runtime, you can increase the `RenderCombinedMesh` `Reference Renderers` array by one and assign the `SkeletonRenderer` itself at the last entry after the parts renderers. Disabled `MeshRenderer` components will be skipped when combining the final mesh, so the combined mesh is automatically filled from the desired active renderers.
|
||||||
|
* Timeline extension package: Added static `EditorEvent` callback to allow editor scripts to react to animation events outside of play-mode. Register to the events via `Spine.Unity.Playables.SpineAnimationStateMixerBehaviour.EditorEvent += YourCallback;`.
|
||||||
|
|
||||||
* **Breaking changes**
|
* **Breaking changes**
|
||||||
* Made `SkeletonGraphic.unscaledTime` parameter protected, use the new property `UnscaledTime` instead.
|
* Made `SkeletonGraphic.unscaledTime` parameter protected, use the new property `UnscaledTime` instead.
|
||||||
|
|||||||
7
spine-haxe/.vscode/launch.json
vendored
7
spine-haxe/.vscode/launch.json
vendored
@ -4,13 +4,6 @@
|
|||||||
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
|
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
|
||||||
"version": "0.2.0",
|
"version": "0.2.0",
|
||||||
"configurations": [
|
"configurations": [
|
||||||
{
|
|
||||||
"name": "web",
|
|
||||||
"request": "launch",
|
|
||||||
"type": "chrome",
|
|
||||||
"url": "http://localhost:3000",
|
|
||||||
"webRoot": "${workspaceFolder}"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"name": "lime",
|
"name": "lime",
|
||||||
"type": "lime",
|
"type": "lime",
|
||||||
|
|||||||
28
spine-haxe/example/src/BasicExample.hx
Normal file
28
spine-haxe/example/src/BasicExample.hx
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
import openfl.geom.Rectangle;
|
||||||
|
import spine.SkeletonData;
|
||||||
|
import spine.animation.AnimationStateData;
|
||||||
|
import spine.atlas.TextureAtlas;
|
||||||
|
import spine.starling.SkeletonSprite;
|
||||||
|
import starling.core.Starling;
|
||||||
|
|
||||||
|
class BasicExample extends Scene {
|
||||||
|
var loadBinary = true;
|
||||||
|
|
||||||
|
public function load():Void {
|
||||||
|
var atlas = TextureAtlas.fromAssets("assets/raptor.atlas");
|
||||||
|
var skeletondata = SkeletonData.fromAssets("assets/raptor-pro" + (loadBinary ? ".skel" : ".json"), atlas);
|
||||||
|
var animationStateData = new AnimationStateData(skeletondata);
|
||||||
|
animationStateData.defaultMix = 0.25;
|
||||||
|
|
||||||
|
var skeletonSprite = new SkeletonSprite(skeletondata, animationStateData);
|
||||||
|
var bounds = skeletonSprite.skeleton.getBounds();
|
||||||
|
skeletonSprite.scale = Starling.current.stage.stageWidth / bounds.width * 0.5;
|
||||||
|
skeletonSprite.x = Starling.current.stage.stageWidth / 2;
|
||||||
|
skeletonSprite.y = Starling.current.stage.stageHeight * 0.9;
|
||||||
|
|
||||||
|
skeletonSprite.state.setAnimationByName(0, "walk", true);
|
||||||
|
|
||||||
|
addChild(skeletonSprite);
|
||||||
|
juggler.add(skeletonSprite);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,29 +1,12 @@
|
|||||||
package;
|
package;
|
||||||
|
|
||||||
import starling.display.Image;
|
import Scene.SceneManager;
|
||||||
import haxe.io.Bytes;
|
|
||||||
import openfl.display.Bitmap;
|
|
||||||
import openfl.display.BitmapData;
|
|
||||||
import openfl.display.Sprite;
|
import openfl.display.Sprite;
|
||||||
import openfl.Assets;
|
|
||||||
import openfl.geom.Rectangle;
|
import openfl.geom.Rectangle;
|
||||||
import openfl.utils.ByteArray;
|
|
||||||
import openfl.utils.Endian;
|
|
||||||
import spine.animation.AnimationStateData;
|
|
||||||
import spine.atlas.TextureAtlas;
|
|
||||||
import spine.attachments.AtlasAttachmentLoader;
|
|
||||||
import spine.SkeletonBinary;
|
|
||||||
import spine.SkeletonData;
|
|
||||||
import spine.SkeletonJson;
|
|
||||||
import spine.starling.SkeletonAnimation;
|
|
||||||
import spine.starling.StarlingTextureLoader;
|
|
||||||
import starling.core.Starling;
|
import starling.core.Starling;
|
||||||
import starling.events.Event;
|
import starling.events.Event;
|
||||||
import starling.textures.Texture;
|
|
||||||
|
|
||||||
class Main extends Sprite {
|
class Main extends Sprite {
|
||||||
private static inline var loadBinary:Bool = false;
|
|
||||||
|
|
||||||
private var starlingSingleton:Starling;
|
private var starlingSingleton:Starling;
|
||||||
|
|
||||||
public function new() {
|
public function new() {
|
||||||
@ -39,39 +22,6 @@ class Main extends Sprite {
|
|||||||
starlingSingleton.start();
|
starlingSingleton.start();
|
||||||
Starling.current.stage.color = 0x000000;
|
Starling.current.stage.color = 0x000000;
|
||||||
|
|
||||||
loadSpineAnimation();
|
SceneManager.getInstance().switchScene(new BasicExample());
|
||||||
}
|
|
||||||
|
|
||||||
private function loadSpineAnimation():Void {
|
|
||||||
var textureAtlasBitmapData:BitmapData = Assets.getBitmapData("assets/raptor.png");
|
|
||||||
var stAtlas = Assets.getText("assets/raptor.atlas");
|
|
||||||
var binaryData = Assets.getBytes("assets/raptor-pro.skel");
|
|
||||||
var jsonData = Assets.getText("assets/raptor-pro.json");
|
|
||||||
|
|
||||||
var textureAtlas = Texture.fromBitmapData(textureAtlasBitmapData);
|
|
||||||
var textureloader = new StarlingTextureLoader(textureAtlas);
|
|
||||||
var atlas = new TextureAtlas(stAtlas, textureloader);
|
|
||||||
|
|
||||||
var skeletondata:SkeletonData;
|
|
||||||
if (loadBinary) {
|
|
||||||
var skeletonBinary:SkeletonBinary = new SkeletonBinary(new AtlasAttachmentLoader(atlas));
|
|
||||||
var bytearray:ByteArray = ByteArray.fromBytes(binaryData);
|
|
||||||
bytearray.endian = Endian.BIG_ENDIAN;
|
|
||||||
skeletondata = skeletonBinary.readSkeletonData(bytearray);
|
|
||||||
} else {
|
|
||||||
var skeletonJson:SkeletonJson = new SkeletonJson(new AtlasAttachmentLoader(atlas));
|
|
||||||
skeletondata = skeletonJson.readSkeletonData(jsonData);
|
|
||||||
}
|
|
||||||
|
|
||||||
var stateData:AnimationStateData = new AnimationStateData(skeletondata);
|
|
||||||
stateData.defaultMix = 0.25;
|
|
||||||
|
|
||||||
var skeletonanimation:SkeletonAnimation = new SkeletonAnimation(skeletondata, stateData);
|
|
||||||
skeletonanimation.x = Starling.current.stage.stageWidth / 2;
|
|
||||||
skeletonanimation.y = Starling.current.stage.stageHeight * 0.5;
|
|
||||||
|
|
||||||
Starling.current.stage.addChild(skeletonanimation);
|
|
||||||
Starling.current.juggler.add(skeletonanimation);
|
|
||||||
skeletonanimation.state.setAnimationByName(0, "walk", true);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
46
spine-haxe/example/src/Scene.hx
Normal file
46
spine-haxe/example/src/Scene.hx
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
import starling.core.Starling;
|
||||||
|
import starling.display.Sprite;
|
||||||
|
|
||||||
|
class SceneManager {
|
||||||
|
private static var instance:SceneManager;
|
||||||
|
|
||||||
|
private var currentScene:Sprite;
|
||||||
|
|
||||||
|
private function new() {
|
||||||
|
// Singleton pattern to ensure only one instance of SceneManager
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function getInstance():SceneManager {
|
||||||
|
if (instance == null) {
|
||||||
|
instance = new SceneManager();
|
||||||
|
}
|
||||||
|
return instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function switchScene(newScene:Scene):Void {
|
||||||
|
if (currentScene != null) {
|
||||||
|
currentScene.dispose();
|
||||||
|
currentScene.removeFromParent(true);
|
||||||
|
}
|
||||||
|
currentScene = newScene;
|
||||||
|
starling.core.Starling.current.stage.addChild(currentScene);
|
||||||
|
newScene.load();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract class Scene extends Sprite {
|
||||||
|
var juggler = new starling.animation.Juggler();
|
||||||
|
|
||||||
|
public function new() {
|
||||||
|
super();
|
||||||
|
Starling.current.juggler.add(juggler);
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract public function load():Void;
|
||||||
|
|
||||||
|
public override function dispose():Void {
|
||||||
|
juggler.purge();
|
||||||
|
Starling.current.juggler.remove(juggler);
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -11,5 +11,6 @@
|
|||||||
|
|
||||||
<source path="example/src" />
|
<source path="example/src" />
|
||||||
<assets path="example/assets" rename="assets" />
|
<assets path="example/assets" rename="assets" />
|
||||||
|
<assets path="example/assets" include="*.skel" rename="assets" type="binary" />
|
||||||
|
|
||||||
</project>
|
</project>
|
||||||
@ -76,7 +76,7 @@ class BinaryInput {
|
|||||||
chars += String.fromCharCode(((b & 0x0F) << 12 | (readByte() & 0x3F) << 6 | readByte() & 0x3F));
|
chars += String.fromCharCode(((b & 0x0F) << 12 | (readByte() & 0x3F) << 6 | readByte() & 0x3F));
|
||||||
i += 3;
|
i += 3;
|
||||||
default:
|
default:
|
||||||
chars += String.fromCharCode(b);
|
chars += String.fromCharCode(b & 0xff);
|
||||||
i++;
|
i++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
package spine;
|
package spine;
|
||||||
|
|
||||||
|
import openfl.geom.Rectangle;
|
||||||
import openfl.errors.ArgumentError;
|
import openfl.errors.ArgumentError;
|
||||||
import openfl.utils.Dictionary;
|
import openfl.utils.Dictionary;
|
||||||
import openfl.Vector;
|
import openfl.Vector;
|
||||||
@ -561,11 +562,10 @@ class Skeleton {
|
|||||||
return _data.name != null ? _data.name : "Skeleton?";
|
return _data.name != null ? _data.name : "Skeleton?";
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getBounds(offset:Vector<Float>, size:Vector<Float>, temp:Vector<Float>):Void {
|
private var _tempVertices = new Vector<Float>();
|
||||||
if (offset == null)
|
private var _bounds = new Rectangle();
|
||||||
throw new ArgumentError("offset cannot be null.");
|
|
||||||
if (size == null)
|
public function getBounds():Rectangle {
|
||||||
throw new ArgumentError("size cannot be null.");
|
|
||||||
var minX:Float = Math.POSITIVE_INFINITY;
|
var minX:Float = Math.POSITIVE_INFINITY;
|
||||||
var minY:Float = Math.POSITIVE_INFINITY;
|
var minY:Float = Math.POSITIVE_INFINITY;
|
||||||
var maxX:Float = Math.NEGATIVE_INFINITY;
|
var maxX:Float = Math.NEGATIVE_INFINITY;
|
||||||
@ -576,14 +576,14 @@ class Skeleton {
|
|||||||
var attachment:Attachment = slot.attachment;
|
var attachment:Attachment = slot.attachment;
|
||||||
if (Std.isOfType(attachment, RegionAttachment)) {
|
if (Std.isOfType(attachment, RegionAttachment)) {
|
||||||
verticesLength = 8;
|
verticesLength = 8;
|
||||||
temp.length = verticesLength;
|
_tempVertices.length = verticesLength;
|
||||||
vertices = temp;
|
vertices = _tempVertices;
|
||||||
cast(attachment, RegionAttachment).computeWorldVertices(slot, vertices, 0, 2);
|
cast(attachment, RegionAttachment).computeWorldVertices(slot, vertices, 0, 2);
|
||||||
} else if (Std.isOfType(attachment, MeshAttachment)) {
|
} else if (Std.isOfType(attachment, MeshAttachment)) {
|
||||||
var mesh:MeshAttachment = cast(attachment, MeshAttachment);
|
var mesh:MeshAttachment = cast(attachment, MeshAttachment);
|
||||||
verticesLength = mesh.worldVerticesLength;
|
verticesLength = mesh.worldVerticesLength;
|
||||||
temp.length = verticesLength;
|
_tempVertices.length = verticesLength;
|
||||||
vertices = temp;
|
vertices = _tempVertices;
|
||||||
mesh.computeWorldVertices(slot, 0, verticesLength, vertices, 0, 2);
|
mesh.computeWorldVertices(slot, 0, verticesLength, vertices, 0, 2);
|
||||||
}
|
}
|
||||||
if (vertices != null) {
|
if (vertices != null) {
|
||||||
@ -599,9 +599,10 @@ class Skeleton {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
offset[0] = minX;
|
_bounds.x = minX;
|
||||||
offset[1] = minY;
|
_bounds.y = minY;
|
||||||
size[0] = maxX - minX;
|
_bounds.width = maxX - minX;
|
||||||
size[1] = maxY - minY;
|
_bounds.height = maxY - minY;
|
||||||
|
return _bounds;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,5 +1,7 @@
|
|||||||
package spine;
|
package spine;
|
||||||
|
|
||||||
|
import spine.attachments.AtlasAttachmentLoader;
|
||||||
|
import openfl.utils.Endian;
|
||||||
import spine.animation.SequenceTimeline;
|
import spine.animation.SequenceTimeline;
|
||||||
import openfl.errors.ArgumentError;
|
import openfl.errors.ArgumentError;
|
||||||
import openfl.errors.Error;
|
import openfl.errors.Error;
|
||||||
@ -81,20 +83,17 @@ class SkeletonBinary {
|
|||||||
private static inline var CURVE_STEPPED:Int = 1;
|
private static inline var CURVE_STEPPED:Int = 1;
|
||||||
private static inline var CURVE_BEZIER:Int = 2;
|
private static inline var CURVE_BEZIER:Int = 2;
|
||||||
|
|
||||||
public function new(attachmentLoader:AttachmentLoader = null) {
|
public function new(attachmentLoader:AttachmentLoader) {
|
||||||
this.attachmentLoader = attachmentLoader;
|
this.attachmentLoader = attachmentLoader;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function readSkeletonData(object:ByteArray):SkeletonData {
|
public function readSkeletonData(bytes:ByteArray):SkeletonData {
|
||||||
if (object == null)
|
bytes.endian = Endian.BIG_ENDIAN;
|
||||||
throw new ArgumentError("Object cannot be null");
|
|
||||||
if (!Std.isOfType(object, ByteArrayData))
|
|
||||||
throw new ArgumentError("Object must be ByteArrayData");
|
|
||||||
|
|
||||||
var skeletonData:SkeletonData = new SkeletonData();
|
var skeletonData:SkeletonData = new SkeletonData();
|
||||||
skeletonData.name = null;
|
skeletonData.name = null;
|
||||||
|
|
||||||
var input:BinaryInput = new BinaryInput(object);
|
var input:BinaryInput = new BinaryInput(bytes);
|
||||||
|
|
||||||
var lowHash:Int = input.readInt32();
|
var lowHash:Int = input.readInt32();
|
||||||
var highHash:Int = input.readInt32();
|
var highHash:Int = input.readInt32();
|
||||||
|
|||||||
@ -1,5 +1,8 @@
|
|||||||
package spine;
|
package spine;
|
||||||
|
|
||||||
|
import spine.attachments.AtlasAttachmentLoader;
|
||||||
|
import openfl.utils.Assets;
|
||||||
|
import spine.atlas.TextureAtlas;
|
||||||
import openfl.errors.ArgumentError;
|
import openfl.errors.ArgumentError;
|
||||||
import openfl.Vector;
|
import openfl.Vector;
|
||||||
import spine.animation.Animation;
|
import spine.animation.Animation;
|
||||||
@ -27,6 +30,22 @@ class SkeletonData {
|
|||||||
public var imagesPath:String;
|
public var imagesPath:String;
|
||||||
public var audioPath:String;
|
public var audioPath:String;
|
||||||
|
|
||||||
|
public static function fromAssets(path:String, atlas:TextureAtlas, scale:Float = 1.0):SkeletonData {
|
||||||
|
if (StringTools.endsWith(path, ".skel")) {
|
||||||
|
var byteData = Assets.getBytes(path);
|
||||||
|
var loader = new SkeletonBinary(new AtlasAttachmentLoader(atlas));
|
||||||
|
loader.scale = scale;
|
||||||
|
return loader.readSkeletonData(byteData);
|
||||||
|
} else if (StringTools.endsWith(path, ".json")) {
|
||||||
|
var jsonData = Assets.getText(path);
|
||||||
|
var loader = new SkeletonJson(new AtlasAttachmentLoader(atlas));
|
||||||
|
loader.scale = scale;
|
||||||
|
return loader.readSkeletonData(jsonData);
|
||||||
|
} else {
|
||||||
|
throw new SpineException("Path of skeleton data file must end with .json or .skel");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public function new() {}
|
public function new() {}
|
||||||
|
|
||||||
// --- Bones.
|
// --- Bones.
|
||||||
|
|||||||
@ -54,28 +54,17 @@ class SkeletonJson {
|
|||||||
|
|
||||||
private var linkedMeshes:Vector<LinkedMesh> = new Vector<LinkedMesh>();
|
private var linkedMeshes:Vector<LinkedMesh> = new Vector<LinkedMesh>();
|
||||||
|
|
||||||
public function new(attachmentLoader:AttachmentLoader = null) {
|
public function new(attachmentLoader:AttachmentLoader) {
|
||||||
this.attachmentLoader = attachmentLoader;
|
this.attachmentLoader = attachmentLoader;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @param object A String or ByteArray. */
|
public function readSkeletonData(json:String):SkeletonData {
|
||||||
public function readSkeletonData(object:Object, name:String = null):SkeletonData {
|
if (json == null)
|
||||||
if (object == null)
|
|
||||||
throw new ArgumentError("object cannot be null.");
|
throw new ArgumentError("object cannot be null.");
|
||||||
|
|
||||||
var root:Object;
|
var root = Json.parse(json);
|
||||||
if (Std.isOfType(object, String)) {
|
|
||||||
root = Json.parse(cast(object, String));
|
|
||||||
} else if (Std.isOfType(object, ByteArrayData)) {
|
|
||||||
root = Json.parse(cast(object, ByteArray).readUTFBytes(cast(object, ByteArray).length));
|
|
||||||
} else if (Std.isOfType(object, Dynamic)) {
|
|
||||||
root = object;
|
|
||||||
} else {
|
|
||||||
throw new ArgumentError("object must be a String, ByteArray or Object.");
|
|
||||||
}
|
|
||||||
|
|
||||||
var skeletonData:SkeletonData = new SkeletonData();
|
var skeletonData:SkeletonData = new SkeletonData();
|
||||||
skeletonData.name = name;
|
|
||||||
|
|
||||||
// Skeleton.
|
// Skeleton.
|
||||||
var skeletonMap:Object = getString(root, "skeleton", "");
|
var skeletonMap:Object = getString(root, "skeleton", "");
|
||||||
@ -1172,10 +1161,10 @@ class SkeletonJson {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var i:Int = value << 2;
|
var i:Int = value << 2;
|
||||||
var cx1:Float = curve[Std.string(i)];
|
var cx1:Float = curve[i];
|
||||||
var cy1:Float = curve[Std.string(i + 1)] * scale;
|
var cy1:Float = curve[i + 1] * scale;
|
||||||
var cx2:Float = curve[Std.string(i + 2)];
|
var cx2:Float = curve[i + 2];
|
||||||
var cy2:Float = curve[Std.string(i + 3)] * scale;
|
var cy2:Float = curve[i + 3] * scale;
|
||||||
timeline.setBezier(bezier, frame, value, time1, value1, cx1, cy1, cx2, cy2, time2, value2);
|
timeline.setBezier(bezier, frame, value, time1, value1, cx1, cy1, cx2, cy2, time2, value2);
|
||||||
return bezier + 1;
|
return bezier + 1;
|
||||||
}
|
}
|
||||||
|
|||||||
30
spine-haxe/spine-haxe/spine/atlas/AssetsTextureLoader.hx
Normal file
30
spine-haxe/spine-haxe/spine/atlas/AssetsTextureLoader.hx
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
package spine.atlas;
|
||||||
|
|
||||||
|
import starling.textures.Texture;
|
||||||
|
import spine.atlas.TextureAtlasRegion;
|
||||||
|
import spine.atlas.TextureAtlasPage;
|
||||||
|
import spine.atlas.TextureLoader;
|
||||||
|
|
||||||
|
class AssetsTextureLoader implements TextureLoader {
|
||||||
|
private var basePath:String;
|
||||||
|
|
||||||
|
public function new(basePath:String) {
|
||||||
|
this.basePath = basePath;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function loadPage(page:TextureAtlasPage, path:String) {
|
||||||
|
var bitmapData = openfl.utils.Assets.getBitmapData(basePath + "/" + path);
|
||||||
|
if (bitmapData == null) {
|
||||||
|
throw new SpineException("Could not load atlas page texture " + basePath + "/" + path);
|
||||||
|
}
|
||||||
|
page.texture = Texture.fromBitmapData(bitmapData);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function loadRegion(region:TextureAtlasRegion):Void {
|
||||||
|
region.texture = region.page.texture;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function unloadPage(page:TextureAtlasPage):Void {
|
||||||
|
cast(page.texture, Texture).dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,5 +1,6 @@
|
|||||||
package spine.atlas;
|
package spine.atlas;
|
||||||
|
|
||||||
|
import openfl.utils.Assets;
|
||||||
import openfl.errors.ArgumentError;
|
import openfl.errors.ArgumentError;
|
||||||
import openfl.utils.ByteArray;
|
import openfl.utils.ByteArray;
|
||||||
import openfl.utils.Dictionary;
|
import openfl.utils.Dictionary;
|
||||||
@ -10,6 +11,17 @@ class TextureAtlas {
|
|||||||
private var regions = new Vector<TextureAtlasRegion>();
|
private var regions = new Vector<TextureAtlasRegion>();
|
||||||
private var textureLoader:TextureLoader;
|
private var textureLoader:TextureLoader;
|
||||||
|
|
||||||
|
public static function fromAssets(path:String) {
|
||||||
|
var basePath = "";
|
||||||
|
var slashIndex = path.lastIndexOf("/");
|
||||||
|
if (slashIndex != -1) {
|
||||||
|
basePath = path.substring(0, slashIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
var textureLoader = new AssetsTextureLoader(basePath);
|
||||||
|
return new TextureAtlas(Assets.getText("assets/raptor.atlas"), textureLoader);
|
||||||
|
}
|
||||||
|
|
||||||
/** @param object A String or ByteArray. */
|
/** @param object A String or ByteArray. */
|
||||||
public function new(object:Dynamic, textureLoader:TextureLoader) {
|
public function new(object:Dynamic, textureLoader:TextureLoader) {
|
||||||
if (object == null) {
|
if (object == null) {
|
||||||
@ -132,7 +144,7 @@ class TextureAtlas {
|
|||||||
field();
|
field();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
textureLoader.loadPage(page, line);
|
textureLoader.loadPage(page, page.name);
|
||||||
pages.push(page);
|
pages.push(page);
|
||||||
} else {
|
} else {
|
||||||
region = new TextureAtlasRegion(page, line);
|
region = new TextureAtlasRegion(page, line);
|
||||||
|
|||||||
@ -1,32 +0,0 @@
|
|||||||
package spine.starling;
|
|
||||||
|
|
||||||
import starling.core.Starling;
|
|
||||||
import spine.animation.AnimationState;
|
|
||||||
import spine.animation.AnimationStateData;
|
|
||||||
import spine.SkeletonData;
|
|
||||||
import starling.animation.IAnimatable;
|
|
||||||
|
|
||||||
class SkeletonAnimation extends SkeletonSprite implements IAnimatable {
|
|
||||||
public var state:AnimationState;
|
|
||||||
|
|
||||||
private var functionUpdate:Void->Void;
|
|
||||||
|
|
||||||
public function new(skeletonData:SkeletonData, stateData:AnimationStateData = null) {
|
|
||||||
super(skeletonData);
|
|
||||||
state = new AnimationState(stateData != null ? stateData : new AnimationStateData(skeletonData));
|
|
||||||
}
|
|
||||||
|
|
||||||
public function advanceTime(time:Float):Void {
|
|
||||||
var stage = Starling.current.stage;
|
|
||||||
state.update(time);
|
|
||||||
state.apply(skeleton);
|
|
||||||
skeleton.updateWorldTransform();
|
|
||||||
this.setRequiresRedraw();
|
|
||||||
if (this.functionUpdate != null)
|
|
||||||
this.functionUpdate();
|
|
||||||
}
|
|
||||||
|
|
||||||
public function setFunctionAnimationUpdate(functionUpdate:Void->Void):Void {
|
|
||||||
this.functionUpdate = functionUpdate;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,37 +1,41 @@
|
|||||||
package spine.starling;
|
package spine.starling;
|
||||||
|
|
||||||
import starling.textures.Texture;
|
import starling.animation.IAnimatable;
|
||||||
import starling.utils.Max;
|
import openfl.Vector;
|
||||||
import openfl.geom.Matrix;
|
import openfl.geom.Matrix;
|
||||||
import openfl.geom.Point;
|
import openfl.geom.Point;
|
||||||
import openfl.geom.Rectangle;
|
import openfl.geom.Rectangle;
|
||||||
import openfl.Vector;
|
|
||||||
import spine.atlas.TextureAtlasRegion;
|
|
||||||
import spine.attachments.Attachment;
|
|
||||||
import spine.attachments.ClippingAttachment;
|
|
||||||
import spine.attachments.MeshAttachment;
|
|
||||||
import spine.attachments.RegionAttachment;
|
|
||||||
import spine.Bone;
|
import spine.Bone;
|
||||||
import spine.Skeleton;
|
import spine.Skeleton;
|
||||||
import spine.SkeletonClipping;
|
import spine.SkeletonClipping;
|
||||||
import spine.SkeletonData;
|
import spine.SkeletonData;
|
||||||
import spine.Slot;
|
import spine.Slot;
|
||||||
|
import spine.animation.AnimationState;
|
||||||
|
import spine.animation.AnimationStateData;
|
||||||
|
import spine.attachments.Attachment;
|
||||||
|
import spine.attachments.ClippingAttachment;
|
||||||
|
import spine.attachments.MeshAttachment;
|
||||||
|
import spine.attachments.RegionAttachment;
|
||||||
import starling.display.BlendMode;
|
import starling.display.BlendMode;
|
||||||
import starling.display.DisplayObject;
|
import starling.display.DisplayObject;
|
||||||
import starling.display.Image;
|
|
||||||
import starling.rendering.IndexData;
|
import starling.rendering.IndexData;
|
||||||
import starling.rendering.Painter;
|
import starling.rendering.Painter;
|
||||||
import starling.rendering.VertexData;
|
import starling.rendering.VertexData;
|
||||||
|
import starling.textures.Texture;
|
||||||
import starling.utils.Color;
|
import starling.utils.Color;
|
||||||
import starling.utils.MatrixUtil;
|
import starling.utils.MatrixUtil;
|
||||||
|
import starling.utils.Max;
|
||||||
|
|
||||||
class SkeletonSprite extends DisplayObject {
|
class SkeletonSprite extends DisplayObject implements IAnimatable {
|
||||||
static private var _tempPoint:Point = new Point();
|
static private var _tempPoint:Point = new Point();
|
||||||
static private var _tempMatrix:Matrix = new Matrix();
|
static private var _tempMatrix:Matrix = new Matrix();
|
||||||
static private var _tempVertices:Vector<Float> = new Vector<Float>();
|
static private var _tempVertices:Vector<Float> = new Vector<Float>();
|
||||||
static private var blendModes:Vector<String> = Vector.ofArray([BlendMode.NORMAL, BlendMode.ADD, BlendMode.MULTIPLY, BlendMode.SCREEN]);
|
static private var blendModes:Vector<String> = Vector.ofArray([BlendMode.NORMAL, BlendMode.ADD, BlendMode.MULTIPLY, BlendMode.SCREEN]);
|
||||||
|
|
||||||
private var _skeleton:Skeleton;
|
private var _skeleton:Skeleton;
|
||||||
|
|
||||||
|
public var _state:AnimationState;
|
||||||
|
|
||||||
private var _smoothing:String = "bilinear";
|
private var _smoothing:String = "bilinear";
|
||||||
|
|
||||||
private static var clipper:SkeletonClipping = new SkeletonClipping();
|
private static var clipper:SkeletonClipping = new SkeletonClipping();
|
||||||
@ -40,11 +44,12 @@ class SkeletonSprite extends DisplayObject {
|
|||||||
private var tempLight:spine.Color = new spine.Color(0, 0, 0);
|
private var tempLight:spine.Color = new spine.Color(0, 0, 0);
|
||||||
private var tempDark:spine.Color = new spine.Color(0, 0, 0);
|
private var tempDark:spine.Color = new spine.Color(0, 0, 0);
|
||||||
|
|
||||||
public function new(skeletonData:SkeletonData) {
|
public function new(skeletonData:SkeletonData, animationStateData:AnimationStateData = null) {
|
||||||
super();
|
super();
|
||||||
Bone.yDown = true;
|
Bone.yDown = true;
|
||||||
_skeleton = new Skeleton(skeletonData);
|
_skeleton = new Skeleton(skeletonData);
|
||||||
_skeleton.updateWorldTransform();
|
_skeleton.updateWorldTransform();
|
||||||
|
_state = new AnimationState(animationStateData != null ? animationStateData : new AnimationStateData(skeletonData));
|
||||||
}
|
}
|
||||||
|
|
||||||
override public function render(painter:Painter):Void {
|
override public function render(painter:Painter):Void {
|
||||||
@ -285,6 +290,12 @@ class SkeletonSprite extends DisplayObject {
|
|||||||
return _skeleton;
|
return _skeleton;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public var state(get, never):AnimationState;
|
||||||
|
|
||||||
|
private function get_state():AnimationState {
|
||||||
|
return _state;
|
||||||
|
}
|
||||||
|
|
||||||
public var smoothing(get, set):String;
|
public var smoothing(get, set):String;
|
||||||
|
|
||||||
private function get_smoothing():String {
|
private function get_smoothing():String {
|
||||||
@ -295,4 +306,11 @@ class SkeletonSprite extends DisplayObject {
|
|||||||
_smoothing = smoothing;
|
_smoothing = smoothing;
|
||||||
return _smoothing;
|
return _smoothing;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function advanceTime(time:Float):Void {
|
||||||
|
_state.update(time);
|
||||||
|
_state.apply(skeleton);
|
||||||
|
skeleton.updateWorldTransform();
|
||||||
|
this.setRequiresRedraw();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,70 +0,0 @@
|
|||||||
package spine.starling;
|
|
||||||
|
|
||||||
import openfl.display.Bitmap;
|
|
||||||
import openfl.display.BitmapData;
|
|
||||||
import openfl.errors.ArgumentError;
|
|
||||||
import openfl.utils.Object;
|
|
||||||
import spine.atlas.TextureAtlasPage;
|
|
||||||
import spine.atlas.TextureAtlasRegion;
|
|
||||||
import spine.atlas.TextureLoader;
|
|
||||||
import starling.display.Image;
|
|
||||||
import starling.textures.Texture;
|
|
||||||
|
|
||||||
class StarlingTextureLoader implements TextureLoader {
|
|
||||||
public var bitmapDatasOrTextures:Object = {};
|
|
||||||
public var singleBitmapDataOrTexture:Dynamic;
|
|
||||||
|
|
||||||
/** @param bitmaps A Bitmap or BitmapData or Texture for an atlas that has only one page, or for a multi page atlas an object where the
|
|
||||||
* key is the image path and the value is the Bitmap or BitmapData or Texture. */
|
|
||||||
public function new(bitmapsOrTextures:Dynamic) {
|
|
||||||
if (Std.isOfType(bitmapsOrTextures, BitmapData)) {
|
|
||||||
singleBitmapDataOrTexture = cast(bitmapsOrTextures, BitmapData);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (Std.isOfType(bitmapsOrTextures, Bitmap)) {
|
|
||||||
singleBitmapDataOrTexture = cast(bitmapsOrTextures, Bitmap).bitmapData;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (Std.isOfType(bitmapsOrTextures, Texture)) {
|
|
||||||
singleBitmapDataOrTexture = cast(bitmapsOrTextures, Texture);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (path in Reflect.fields(bitmapsOrTextures)) {
|
|
||||||
var object:Dynamic = Reflect.getProperty(bitmapsOrTextures, path);
|
|
||||||
var bitmapDataOrTexture:Dynamic;
|
|
||||||
if (Std.isOfType(object, BitmapData)) {
|
|
||||||
bitmapDataOrTexture = cast(object, BitmapData);
|
|
||||||
} else if (Std.isOfType(object, Bitmap)) {
|
|
||||||
bitmapDataOrTexture = cast(object, Bitmap).bitmapData;
|
|
||||||
} else if (Std.isOfType(object, Texture)) {
|
|
||||||
bitmapDataOrTexture = cast(object, Texture);
|
|
||||||
} else {
|
|
||||||
throw new ArgumentError("Object for path \"" + path + "\" must be a Bitmap, BitmapData or Texture: " + object);
|
|
||||||
}
|
|
||||||
bitmapDatasOrTextures[path] = bitmapDataOrTexture;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public function loadPage(page:TextureAtlasPage, path:String):Void {
|
|
||||||
var bitmapDataOrTexture:Dynamic = singleBitmapDataOrTexture != null ? singleBitmapDataOrTexture : bitmapDatasOrTextures[path];
|
|
||||||
if (bitmapDataOrTexture == null) {
|
|
||||||
throw new ArgumentError("BitmapData/Texture not found with name: " + path);
|
|
||||||
}
|
|
||||||
if (Std.isOfType(bitmapDataOrTexture, BitmapData)) {
|
|
||||||
var bitmapData:BitmapData = cast(bitmapDataOrTexture, BitmapData);
|
|
||||||
page.texture = Texture.fromBitmapData(bitmapData);
|
|
||||||
} else {
|
|
||||||
var texture:Texture = cast(bitmapDataOrTexture, Texture);
|
|
||||||
page.texture = texture;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public function loadRegion(region:TextureAtlasRegion):Void {
|
|
||||||
region.texture = region.page.texture;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function unloadPage(page:TextureAtlasPage):Void {
|
|
||||||
cast(page.texture, Texture).dispose();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -178,12 +178,12 @@ namespace Spine.Unity.Examples {
|
|||||||
indexBuffer = new ExposedList<int>(combinedIndexCount);
|
indexBuffer = new ExposedList<int>(combinedIndexCount);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (positionBuffer.Count < combinedVertexCount) {
|
if (positionBuffer.Count != combinedVertexCount) {
|
||||||
positionBuffer.Resize(combinedVertexCount);
|
positionBuffer.Resize(combinedVertexCount);
|
||||||
uvBuffer.Resize(combinedVertexCount);
|
uvBuffer.Resize(combinedVertexCount);
|
||||||
colorBuffer.Resize(combinedVertexCount);
|
colorBuffer.Resize(combinedVertexCount);
|
||||||
}
|
}
|
||||||
if (indexBuffer.Count < combinedIndexCount) {
|
if (indexBuffer.Count != combinedIndexCount) {
|
||||||
indexBuffer.Resize(combinedIndexCount);
|
indexBuffer.Resize(combinedIndexCount);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -223,32 +223,27 @@ namespace Spine.Unity.Examples {
|
|||||||
System.Array.Copy(positions, 0, this.positionBuffer.Items, combinedV, vertexCount);
|
System.Array.Copy(positions, 0, this.positionBuffer.Items, combinedV, vertexCount);
|
||||||
System.Array.Copy(uvs, 0, this.uvBuffer.Items, combinedV, vertexCount);
|
System.Array.Copy(uvs, 0, this.uvBuffer.Items, combinedV, vertexCount);
|
||||||
System.Array.Copy(colors, 0, this.colorBuffer.Items, combinedV, vertexCount);
|
System.Array.Copy(colors, 0, this.colorBuffer.Items, combinedV, vertexCount);
|
||||||
combinedV += vertexCount;
|
|
||||||
|
|
||||||
for (int s = 0, submeshCount = mesh.subMeshCount; s < submeshCount; ++s) {
|
for (int s = 0, submeshCount = mesh.subMeshCount; s < submeshCount; ++s) {
|
||||||
int submeshIndexCount = (int)mesh.GetIndexCount(s);
|
int submeshIndexCount = (int)mesh.GetIndexCount(s);
|
||||||
int[] submeshIndices = mesh.GetIndices(s);
|
int[] submeshIndices = mesh.GetIndices(s);
|
||||||
System.Array.Copy(submeshIndices, 0, this.indexBuffer.Items, combinedI, submeshIndexCount);
|
int[] dstIndices = this.indexBuffer.Items;
|
||||||
|
for (int i = 0; i < submeshIndexCount; ++i)
|
||||||
|
dstIndices[i + combinedI] = submeshIndices[i] + combinedV;
|
||||||
combinedI += submeshIndexCount;
|
combinedI += submeshIndexCount;
|
||||||
}
|
}
|
||||||
|
combinedV += vertexCount;
|
||||||
}
|
}
|
||||||
|
|
||||||
Mesh combinedMesh = doubleBufferedMesh.GetNext();
|
Mesh combinedMesh = doubleBufferedMesh.GetNext();
|
||||||
|
combinedMesh.Clear();
|
||||||
#if SET_VERTICES_HAS_LENGTH_PARAMETER
|
#if SET_VERTICES_HAS_LENGTH_PARAMETER
|
||||||
combinedMesh.SetVertices(this.positionBuffer.Items, 0, this.positionBuffer.Count);
|
combinedMesh.SetVertices(this.positionBuffer.Items, 0, this.positionBuffer.Count);
|
||||||
combinedMesh.SetUVs(0, this.uvBuffer.Items, 0, this.uvBuffer.Count);
|
combinedMesh.SetUVs(0, this.uvBuffer.Items, 0, this.uvBuffer.Count);
|
||||||
combinedMesh.SetColors(this.colorBuffer.Items, 0, this.colorBuffer.Count);
|
combinedMesh.SetColors(this.colorBuffer.Items, 0, this.colorBuffer.Count);
|
||||||
combinedMesh.SetTriangles(this.indexBuffer.Items, 0, this.indexBuffer.Count, 0);
|
combinedMesh.SetTriangles(this.indexBuffer.Items, 0, this.indexBuffer.Count, 0);
|
||||||
#else
|
#else
|
||||||
// fill excess with zero positions
|
// Note: excess already contains zero positions and indices after ExposedList.Resize().
|
||||||
{
|
|
||||||
int listCount = this.positionBuffer.Count;
|
|
||||||
Vector3[] positionArray = this.positionBuffer.Items;
|
|
||||||
int arrayLength = positionArray.Length;
|
|
||||||
Vector3 vector3zero = Vector3.zero;
|
|
||||||
for (int i = listCount; i < arrayLength; i++)
|
|
||||||
positionArray[i] = vector3zero;
|
|
||||||
}
|
|
||||||
combinedMesh.vertices = this.positionBuffer.Items;
|
combinedMesh.vertices = this.positionBuffer.Items;
|
||||||
combinedMesh.uv = this.uvBuffer.Items;
|
combinedMesh.uv = this.uvBuffer.Items;
|
||||||
combinedMesh.colors32 = this.colorBuffer.Items;
|
combinedMesh.colors32 = this.colorBuffer.Items;
|
||||||
|
|||||||
@ -2,7 +2,7 @@
|
|||||||
"name": "com.esotericsoftware.spine.spine-unity-examples",
|
"name": "com.esotericsoftware.spine.spine-unity-examples",
|
||||||
"displayName": "spine-unity Runtime Examples",
|
"displayName": "spine-unity Runtime Examples",
|
||||||
"description": "This plugin provides example scenes and scripts for the spine-unity runtime.",
|
"description": "This plugin provides example scenes and scripts for the spine-unity runtime.",
|
||||||
"version": "4.2.21",
|
"version": "4.2.22",
|
||||||
"unity": "2018.3",
|
"unity": "2018.3",
|
||||||
"author": {
|
"author": {
|
||||||
"name": "Esoteric Software",
|
"name": "Esoteric Software",
|
||||||
|
|||||||
@ -31,7 +31,9 @@
|
|||||||
#define SPEED_INCLUDED_IN_CLIP_TIME
|
#define SPEED_INCLUDED_IN_CLIP_TIME
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#if UNITY_EDITOR
|
||||||
#define SPINE_EDITMODEPOSE
|
#define SPINE_EDITMODEPOSE
|
||||||
|
#endif
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
@ -250,8 +252,11 @@ namespace Spine.Unity.Playables {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#if SPINE_EDITMODEPOSE
|
#if SPINE_EDITMODEPOSE
|
||||||
|
/// <summary>Animation event callback for editor scripts when outside of play-mode.</summary>
|
||||||
|
public static event AnimationState.TrackEntryEventDelegate EditorEvent;
|
||||||
|
|
||||||
AnimationState dummyAnimationState;
|
AnimationState dummyAnimationState;
|
||||||
|
ExposedList<Spine.Event> editorAnimationEvents = new ExposedList<Event>();
|
||||||
|
|
||||||
public void PreviewEditModePose (Playable playable,
|
public void PreviewEditModePose (Playable playable,
|
||||||
ISkeletonComponent skeletonComponent, IAnimationStateComponent animationStateComponent,
|
ISkeletonComponent skeletonComponent, IAnimationStateComponent animationStateComponent,
|
||||||
@ -259,6 +264,7 @@ namespace Spine.Unity.Playables {
|
|||||||
|
|
||||||
if (Application.isPlaying) return;
|
if (Application.isPlaying) return;
|
||||||
if (animationStateComponent.IsNullOrDestroyed() || skeletonComponent == null) return;
|
if (animationStateComponent.IsNullOrDestroyed() || skeletonComponent == null) return;
|
||||||
|
editorAnimationEvents.Clear(false);
|
||||||
|
|
||||||
int inputCount = playable.GetInputCount();
|
int inputCount = playable.GetInputCount();
|
||||||
float rootSpeed = GetRootPlayableSpeed(playable);
|
float rootSpeed = GetRootPlayableSpeed(playable);
|
||||||
@ -341,11 +347,19 @@ namespace Spine.Unity.Playables {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Apply Pose
|
// Apply Pose
|
||||||
|
dummyAnimationState.Event += EditorEvent;
|
||||||
dummyAnimationState.Update(0);
|
dummyAnimationState.Update(0);
|
||||||
dummyAnimationState.Apply(skeleton);
|
dummyAnimationState.Apply(skeleton);
|
||||||
|
dummyAnimationState.Event -= EditorEvent;
|
||||||
} else {
|
} else {
|
||||||
if (toAnimation != null)
|
if (toAnimation != null) {
|
||||||
toAnimation.Apply(skeleton, 0, toClipTime, clipData.loop, null, clipData.alpha, MixBlend.Setup, MixDirection.In);
|
toAnimation.Apply(skeleton, 0, toClipTime, clipData.loop, editorAnimationEvents, clipData.alpha, MixBlend.Setup, MixDirection.In);
|
||||||
|
if (EditorEvent != null) {
|
||||||
|
foreach (Spine.Event e in editorAnimationEvents) {
|
||||||
|
EditorEvent(null, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
skeleton.UpdateWorldTransform();
|
skeleton.UpdateWorldTransform();
|
||||||
|
|||||||
@ -2,7 +2,7 @@
|
|||||||
"name": "com.esotericsoftware.spine.timeline",
|
"name": "com.esotericsoftware.spine.timeline",
|
||||||
"displayName": "Spine Timeline Extensions",
|
"displayName": "Spine Timeline Extensions",
|
||||||
"description": "This plugin provides integration of spine-unity for the Unity Timeline.\n\nPrerequisites:\nIt requires a working installation of the spine-unity and spine-csharp runtimes as UPM packages (not as spine-unity unitypackage), version 4.2.\n(See http://esotericsoftware.com/git/spine-runtimes/spine-unity)",
|
"description": "This plugin provides integration of spine-unity for the Unity Timeline.\n\nPrerequisites:\nIt requires a working installation of the spine-unity and spine-csharp runtimes as UPM packages (not as spine-unity unitypackage), version 4.2.\n(See http://esotericsoftware.com/git/spine-runtimes/spine-unity)",
|
||||||
"version": "4.2.12",
|
"version": "4.2.13",
|
||||||
"unity": "2018.3",
|
"unity": "2018.3",
|
||||||
"author": {
|
"author": {
|
||||||
"name": "Esoteric Software",
|
"name": "Esoteric Software",
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user