mirror of
https://github.com/EsotericSoftware/spine-runtimes.git
synced 2026-02-09 08:38:43 +08:00
Merge branch '4.2' into 4.3-beta
# Conflicts: # spine-unity/Assets/Spine/package.json
This commit is contained in:
commit
9b596c3856
1
.github/pull_request_template.md
vendored
Normal file
1
.github/pull_request_template.md
vendored
Normal file
@ -0,0 +1 @@
|
||||
* [ ] I confirm this contribution is made under the Esoteric Software LLC [CLA](http://esotericsoftware.com/licenses/cla.txt).
|
||||
@ -49,7 +49,7 @@ jobs:
|
||||
path: spine-godot/example-v4-extension/bin/windows/*.dll
|
||||
|
||||
build-linux-x86_64:
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
|
||||
4
.github/workflows/spine-godot-v4.yml
vendored
4
.github/workflows/spine-godot-v4.yml
vendored
@ -65,7 +65,7 @@ jobs:
|
||||
path: spine-godot/godot/bin/**/*
|
||||
|
||||
godot-editor-linux:
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
@ -161,7 +161,7 @@ jobs:
|
||||
path: spine-godot/godot/bin/macos.zip
|
||||
|
||||
godot-template-linux:
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
@ -221,3 +221,4 @@ spine-godot/build/version.txt
|
||||
spine-godot/vc140.pdb
|
||||
spine-godot/example-v4-extension/bin
|
||||
spine-godot/example-v4-extension/MoltenVK.xcframework
|
||||
spine-flutter/example/android/app/.cxx
|
||||
|
||||
@ -175,6 +175,7 @@
|
||||
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_).
|
||||
- Added option for unsafe direct data loading when loading skeleton binary data to avoid some allocations, enabled via build define `SPINE_ALLOW_UNSAFE`. This define can be set via Spine Preferences, setting `Unsafe Build Defines - Direct data access`. The define is disabled by default to maintain existing behaviour. Changed asmdef setting for spine-unity assembly to allow unsafe code, has no effect other than allowing setting the `SPINE_ALLOW_UNSAFE` define.
|
||||
|
||||
- **Breaking changes**
|
||||
|
||||
|
||||
@ -1,3 +1,6 @@
|
||||
# 4.2.36
|
||||
- Support for 16KB page alignement on Android. You must specify the NDK version in the build.gradle file of your app's Android project. See https://github.com/EsotericSoftware/spine-runtimes/issues/2849
|
||||
|
||||
# 4.2.35
|
||||
- Port of commit f1e0f0f: Fixed animation not being mixed out in some cases.
|
||||
|
||||
|
||||
@ -11,7 +11,7 @@ buildscript {
|
||||
|
||||
dependencies {
|
||||
// The Android Gradle Plugin knows how to build native code with the NDK.
|
||||
classpath 'com.android.tools.build:gradle:7.1.2'
|
||||
classpath 'com.android.tools.build:gradle:8.5.1'
|
||||
}
|
||||
}
|
||||
|
||||
@ -33,7 +33,7 @@ android {
|
||||
|
||||
// Bumping the plugin ndkVersion requires all clients of this plugin to bump
|
||||
// the version in their app and to download a newer version of the NDK.
|
||||
ndkVersion "21.1.6352462"
|
||||
ndkVersion "28.1.13356709"
|
||||
|
||||
// Invoke the shared CMake build with the Android Gradle Plugin.
|
||||
externalNativeBuild {
|
||||
@ -56,6 +56,6 @@ android {
|
||||
}
|
||||
|
||||
defaultConfig {
|
||||
minSdkVersion 16
|
||||
minSdkVersion 21
|
||||
}
|
||||
}
|
||||
|
||||
2
spine-flutter/example/.gitignore
vendored
2
spine-flutter/example/.gitignore
vendored
@ -5,9 +5,11 @@
|
||||
*.swp
|
||||
.DS_Store
|
||||
.atom/
|
||||
.build/
|
||||
.buildlog/
|
||||
.history
|
||||
.svn/
|
||||
.swiftpm/
|
||||
migrate_working_dir/
|
||||
|
||||
# IntelliJ related
|
||||
|
||||
@ -1,3 +1,9 @@
|
||||
plugins {
|
||||
id "com.android.application"
|
||||
id "kotlin-android"
|
||||
id "dev.flutter.flutter-gradle-plugin"
|
||||
}
|
||||
|
||||
def localProperties = new Properties()
|
||||
def localPropertiesFile = rootProject.file('local.properties')
|
||||
if (localPropertiesFile.exists()) {
|
||||
@ -6,11 +12,6 @@ if (localPropertiesFile.exists()) {
|
||||
}
|
||||
}
|
||||
|
||||
def flutterRoot = localProperties.getProperty('flutter.sdk')
|
||||
if (flutterRoot == null) {
|
||||
throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.")
|
||||
}
|
||||
|
||||
def flutterVersionCode = localProperties.getProperty('flutter.versionCode')
|
||||
if (flutterVersionCode == null) {
|
||||
flutterVersionCode = '1'
|
||||
@ -21,13 +22,11 @@ if (flutterVersionName == null) {
|
||||
flutterVersionName = '1.0'
|
||||
}
|
||||
|
||||
apply plugin: 'com.android.application'
|
||||
apply plugin: 'kotlin-android'
|
||||
apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"
|
||||
|
||||
android {
|
||||
compileSdkVersion flutter.compileSdkVersion
|
||||
ndkVersion flutter.ndkVersion
|
||||
// ndkVersion flutter.ndkVersion
|
||||
ndkVersion "28.1.13356709"
|
||||
namespace "com.esotericsoftware.spine.android"
|
||||
|
||||
compileOptions {
|
||||
sourceCompatibility JavaVersion.VERSION_1_8
|
||||
@ -44,7 +43,7 @@ android {
|
||||
|
||||
defaultConfig {
|
||||
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
|
||||
applicationId "com.example.spine_flutter_example"
|
||||
applicationId "com.esotericsoftware.spine.android"
|
||||
// You can update the following values to match your application needs.
|
||||
// For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-build-configuration.
|
||||
minSdkVersion flutter.minSdkVersion
|
||||
@ -65,7 +64,3 @@ android {
|
||||
flutter {
|
||||
source '../..'
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
|
||||
}
|
||||
|
||||
@ -1,5 +1,4 @@
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="com.example.spine_flutter_example">
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<!-- The INTERNET permission is required for development. Specifically,
|
||||
the Flutter tool needs it to communicate with the running application
|
||||
to allow setting breakpoints, to provide hot reload, etc.
|
||||
|
||||
@ -1,5 +1,4 @@
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="com.example.spine_flutter_example">
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<application
|
||||
android:label="spine_flutter_example"
|
||||
android:name="${applicationName}"
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
package com.example.example
|
||||
package com.esotericsoftware.spine.android
|
||||
|
||||
import io.flutter.embedding.android.FlutterActivity
|
||||
|
||||
@ -1,6 +0,0 @@
|
||||
package com.example.spine_flutter_example
|
||||
|
||||
import io.flutter.embedding.android.FlutterActivity
|
||||
|
||||
class MainActivity: FlutterActivity() {
|
||||
}
|
||||
@ -1,5 +1,4 @@
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="com.example.spine_flutter_example">
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<!-- The INTERNET permission is required for development. Specifically,
|
||||
the Flutter tool needs it to communicate with the running application
|
||||
to allow setting breakpoints, to provide hot reload, etc.
|
||||
|
||||
@ -1,16 +1,3 @@
|
||||
buildscript {
|
||||
ext.kotlin_version = '1.6.10'
|
||||
repositories {
|
||||
google()
|
||||
mavenCentral()
|
||||
}
|
||||
|
||||
dependencies {
|
||||
classpath 'com.android.tools.build:gradle:7.1.2'
|
||||
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
|
||||
}
|
||||
}
|
||||
|
||||
allprojects {
|
||||
repositories {
|
||||
google()
|
||||
|
||||
@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-7.4-all.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-all.zip
|
||||
|
||||
@ -1,11 +1,25 @@
|
||||
include ':app'
|
||||
pluginManagement {
|
||||
def flutterSdkPath = {
|
||||
def properties = new Properties()
|
||||
file("local.properties").withInputStream { properties.load(it) }
|
||||
def flutterSdkPath = properties.getProperty("flutter.sdk")
|
||||
assert flutterSdkPath != null, "flutter.sdk not set in local.properties"
|
||||
return flutterSdkPath
|
||||
}()
|
||||
|
||||
def localPropertiesFile = new File(rootProject.projectDir, "local.properties")
|
||||
def properties = new Properties()
|
||||
includeBuild("$flutterSdkPath/packages/flutter_tools/gradle")
|
||||
|
||||
assert localPropertiesFile.exists()
|
||||
localPropertiesFile.withReader("UTF-8") { reader -> properties.load(reader) }
|
||||
repositories {
|
||||
google()
|
||||
mavenCentral()
|
||||
gradlePluginPortal()
|
||||
}
|
||||
}
|
||||
|
||||
def flutterSdkPath = properties.getProperty("flutter.sdk")
|
||||
assert flutterSdkPath != null, "flutter.sdk not set in local.properties"
|
||||
apply from: "$flutterSdkPath/packages/flutter_tools/gradle/app_plugin_loader.gradle"
|
||||
plugins {
|
||||
id "dev.flutter.flutter-plugin-loader" version "1.0.0" // apply true
|
||||
id "com.android.application" version "8.5.1" apply false
|
||||
id "org.jetbrains.kotlin.android" version "2.1.0" apply false
|
||||
}
|
||||
|
||||
include ":app"
|
||||
@ -13,18 +13,18 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: characters
|
||||
sha256: "04a925763edad70e8443c99234dc3328f442e811f1d8fd1a72f1c8ad0f69a605"
|
||||
sha256: f71061c654a3380576a52b451dd5532377954cf9dbd272a78fc8479606670803
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.3.0"
|
||||
version: "1.4.0"
|
||||
collection:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: collection
|
||||
sha256: ee67cb0715911d28db6bf4af1026078bd6f0128b07a5f66fb2ed94ec6783c09a
|
||||
sha256: "2f5709ae4d3d59dd8f7cd309b4e023046b57d8a6c82130785d2b0e5868084e76"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.18.0"
|
||||
version: "1.19.1"
|
||||
crypto:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -53,10 +53,10 @@ packages:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: flame
|
||||
sha256: "2a2352741500ce47823dcf212f06b23e9bdb622454eab90244ee6da58e23b488"
|
||||
sha256: f9e7a100c25f8d6bfd143bf325a9689c509216cd1c8133ce4684955c56770de7
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.15.0"
|
||||
version: "1.28.1"
|
||||
flutter:
|
||||
dependency: "direct main"
|
||||
description: flutter
|
||||
@ -114,26 +114,26 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: material_color_utilities
|
||||
sha256: "0e0a020085b65b6083975e499759762399b4475f766c21668c4ecca34ea74e5a"
|
||||
sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.8.0"
|
||||
version: "0.11.1"
|
||||
meta:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: meta
|
||||
sha256: "7687075e408b093f36e6bbf6c91878cc0d4cd10f409506f7bc996f68220b9136"
|
||||
sha256: e3641ec5d63ebf0d9b41bd43201a66e3fc79a65db5f61fc181f04cd27aab950c
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.12.0"
|
||||
version: "1.16.0"
|
||||
ordered_set:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: ordered_set
|
||||
sha256: "3fedcc9121b3ba24c0a84f32da2989c42e36c159b73feadbc2f402dc55966b81"
|
||||
sha256: dc68b8f1abc7115b81cf890bf7d2ece4ed1d95e0f3e486ab4b64ab3d16d2ea42
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "5.0.1"
|
||||
version: "7.0.0"
|
||||
path:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -154,7 +154,7 @@ packages:
|
||||
dependency: transitive
|
||||
description: flutter
|
||||
source: sdk
|
||||
version: "0.0.99"
|
||||
version: "0.0.0"
|
||||
source_span:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -169,7 +169,7 @@ packages:
|
||||
path: ".."
|
||||
relative: true
|
||||
source: path
|
||||
version: "4.2.34"
|
||||
version: "4.2.35"
|
||||
string_scanner:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -211,5 +211,5 @@ packages:
|
||||
source: hosted
|
||||
version: "0.7.5"
|
||||
sdks:
|
||||
dart: ">=3.3.0-0 <4.0.0"
|
||||
flutter: ">=3.16.0"
|
||||
dart: ">=3.7.0-0 <4.0.0"
|
||||
flutter: ">=3.27.1"
|
||||
|
||||
@ -14,7 +14,7 @@ dependencies:
|
||||
spine_flutter:
|
||||
path: ../
|
||||
cupertino_icons: ^1.0.6
|
||||
flame: ^1.10.1
|
||||
flame: ^1.28.1
|
||||
raw_image_provider: ^0.2.0
|
||||
|
||||
dev_dependencies:
|
||||
|
||||
Binary file not shown.
@ -1,6 +1,6 @@
|
||||
name: spine_flutter
|
||||
description: The official Spine Flutter Runtime to load, display and interact with Spine animations.
|
||||
version: 4.2.35
|
||||
version: 4.2.36
|
||||
homepage: https://esotericsoftware.com
|
||||
repository: https://github.com/esotericsoftware/spine-runtimes
|
||||
issue_tracker: https://github.com/esotericsoftware/spine-runtimes/issues
|
||||
|
||||
@ -132,7 +132,6 @@ namespace Spine {
|
||||
float attachmentColorR, attachmentColorG, attachmentColorB, attachmentColorA;
|
||||
object textureObject = null;
|
||||
int verticesCount = 0;
|
||||
float[] vertices = this.vertices;
|
||||
int indicesCount = 0;
|
||||
int[] indices = null;
|
||||
float[] uvs = null;
|
||||
@ -211,9 +210,10 @@ namespace Spine {
|
||||
darkColor.A = premultipliedAlpha ? (byte)255 : (byte)0;
|
||||
|
||||
// clip
|
||||
float[] usedVertices = vertices;
|
||||
if (clipper.IsClipping) {
|
||||
clipper.ClipTriangles(vertices, indices, indicesCount, uvs);
|
||||
vertices = clipper.ClippedVertices.Items;
|
||||
clipper.ClipTriangles(usedVertices, indices, indicesCount, uvs);
|
||||
usedVertices = clipper.ClippedVertices.Items;
|
||||
verticesCount = clipper.ClippedVertices.Count >> 1;
|
||||
indices = clipper.ClippedTriangles.Items;
|
||||
indicesCount = clipper.ClippedTriangles.Count;
|
||||
@ -240,8 +240,8 @@ namespace Spine {
|
||||
for (int ii = 0, v = 0, nn = verticesCount << 1; v < nn; ii++, v += 2) {
|
||||
itemVertices[ii].Color = color;
|
||||
itemVertices[ii].Color2 = darkColor;
|
||||
itemVertices[ii].Position.X = vertices[v];
|
||||
itemVertices[ii].Position.Y = vertices[v + 1];
|
||||
itemVertices[ii].Position.X = usedVertices[v];
|
||||
itemVertices[ii].Position.Y = usedVertices[v + 1];
|
||||
itemVertices[ii].Position.Z = attachmentZOffset;
|
||||
itemVertices[ii].TextureCoordinate.X = uvs[v];
|
||||
itemVertices[ii].TextureCoordinate.Y = uvs[v + 1];
|
||||
|
||||
1840
spine-ts/package-lock.json
generated
1840
spine-ts/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@esotericsoftware/spine-ts",
|
||||
"version": "4.2.81",
|
||||
"version": "4.2.82",
|
||||
"description": "The official Spine Runtimes for the web.",
|
||||
"type": "module",
|
||||
"files": [
|
||||
@ -85,9 +85,9 @@
|
||||
"@types/offscreencanvas": "^2019.6.4",
|
||||
"concurrently": "^7.6.0",
|
||||
"copyfiles": "^2.4.1",
|
||||
"esbuild": "^0.16.4",
|
||||
"live-server": "^1.2.2",
|
||||
"esbuild": "^0.25.4",
|
||||
"alive-server": "^1.3.0",
|
||||
"rimraf": "^3.0.2",
|
||||
"typescript": "5.6.2"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -22,6 +22,7 @@ sed -i '' "s/$currentVersion/$newVersion/" spine-pixi-v8/package.json
|
||||
sed -i '' "s/$currentVersion/$newVersion/" spine-player/package.json
|
||||
sed -i '' "s/$currentVersion/$newVersion/" spine-threejs/package.json
|
||||
sed -i '' "s/$currentVersion/$newVersion/" spine-webgl/package.json
|
||||
sed -i '' "s/$currentVersion/$newVersion/" spine-webcomponents/package.json
|
||||
|
||||
rm package-lock.json
|
||||
rm -rf node_modules/@esotericsoftware
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@esotericsoftware/spine-canvas",
|
||||
"version": "4.2.81",
|
||||
"version": "4.2.82",
|
||||
"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.81"
|
||||
"@esotericsoftware/spine-core": "4.2.82"
|
||||
}
|
||||
}
|
||||
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@esotericsoftware/spine-canvaskit",
|
||||
"version": "4.2.81",
|
||||
"version": "4.2.82",
|
||||
"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.81",
|
||||
"@esotericsoftware/spine-core": "4.2.82",
|
||||
"canvaskit-wasm": "0.39.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@esotericsoftware/spine-core",
|
||||
"version": "4.2.81",
|
||||
"version": "4.2.82",
|
||||
"description": "The official Spine Runtimes for the web.",
|
||||
"main": "dist/index.js",
|
||||
"types": "dist/index.d.ts",
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@esotericsoftware/spine-phaser-v3",
|
||||
"version": "4.2.81",
|
||||
"version": "4.2.82",
|
||||
"description": "The official Spine Runtimes for the Phaser v3.",
|
||||
"main": "dist/index.js",
|
||||
"types": "dist/index.d.ts",
|
||||
@ -31,9 +31,9 @@
|
||||
},
|
||||
"homepage": "https://github.com/esotericsoftware/spine-runtimes#readme",
|
||||
"dependencies": {
|
||||
"@esotericsoftware/spine-canvas": "4.2.81",
|
||||
"@esotericsoftware/spine-core": "4.2.81",
|
||||
"@esotericsoftware/spine-webgl": "4.2.81"
|
||||
"@esotericsoftware/spine-canvas": "4.2.82",
|
||||
"@esotericsoftware/spine-core": "4.2.82",
|
||||
"@esotericsoftware/spine-webgl": "4.2.82"
|
||||
},
|
||||
"devDependencies": {
|
||||
"phaser": "^3.60.0"
|
||||
|
||||
@ -370,7 +370,10 @@ class SpineAtlasFile extends Phaser.Loader.MultiFile {
|
||||
}
|
||||
}
|
||||
|
||||
let basePath = file.src.match(/^.*\//) ?? "";
|
||||
let basePath = (file.src.match(/^.*\//) ?? "").toString();
|
||||
if (this.loader.path && this.loader.path.length > 0 && basePath.startsWith(this.loader.path))
|
||||
basePath = basePath.slice(this.loader.path.length);
|
||||
|
||||
for (var i = 0; i < textures.length; i++) {
|
||||
var url = basePath + textures[i];
|
||||
var key = file.key + "!" + textures[i];
|
||||
|
||||
@ -27,12 +27,9 @@
|
||||
* SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*****************************************************************************/
|
||||
|
||||
declare global {
|
||||
var require: any;
|
||||
}
|
||||
if (typeof window !== 'undefined' && window.Phaser) {
|
||||
let prevRequire = window.require;
|
||||
window.require = (x: string) => {
|
||||
(window as any).require = (x: string) => {
|
||||
if (prevRequire) return prevRequire(x);
|
||||
else if (x === "Phaser") return window.Phaser;
|
||||
}
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@esotericsoftware/spine-phaser-v4",
|
||||
"version": "4.2.81",
|
||||
"version": "4.2.82",
|
||||
"description": "The official Spine Runtimes for the Phaser v4.",
|
||||
"main": "dist/index.js",
|
||||
"types": "dist/index.d.ts",
|
||||
@ -31,9 +31,9 @@
|
||||
},
|
||||
"homepage": "https://github.com/esotericsoftware/spine-runtimes#readme",
|
||||
"dependencies": {
|
||||
"@esotericsoftware/spine-canvas": "4.2.81",
|
||||
"@esotericsoftware/spine-core": "4.2.81",
|
||||
"@esotericsoftware/spine-webgl": "4.2.81"
|
||||
"@esotericsoftware/spine-canvas": "4.2.82",
|
||||
"@esotericsoftware/spine-core": "4.2.82",
|
||||
"@esotericsoftware/spine-webgl": "4.2.82"
|
||||
},
|
||||
"devDependencies": {
|
||||
"phaser": "^4.0.0-rc.1"
|
||||
|
||||
@ -362,7 +362,10 @@ class SpineAtlasFile extends Phaser.Loader.MultiFile {
|
||||
}
|
||||
}
|
||||
|
||||
let basePath = file.src.match(/^.*\//) ?? "";
|
||||
let basePath = (file.src.match(/^.*\//) ?? "").toString();
|
||||
if (this.loader.path && this.loader.path.length > 0 && basePath.startsWith(this.loader.path))
|
||||
basePath = basePath.slice(this.loader.path.length);
|
||||
|
||||
for (var i = 0; i < textures.length; i++) {
|
||||
var url = basePath + textures[i];
|
||||
var key = file.key + "!" + textures[i];
|
||||
|
||||
@ -27,12 +27,9 @@
|
||||
* THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*****************************************************************************/
|
||||
|
||||
declare global {
|
||||
var require: any;
|
||||
}
|
||||
if (typeof window !== 'undefined' && window.Phaser) {
|
||||
let prevRequire = window.require;
|
||||
window.require = (x: string) => {
|
||||
(window as any).require = (x: string) => {
|
||||
if (prevRequire) return prevRequire(x);
|
||||
else if (x === "Phaser") return window.Phaser;
|
||||
}
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@esotericsoftware/spine-pixi-v7",
|
||||
"version": "4.2.81",
|
||||
"version": "4.2.82",
|
||||
"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.81"
|
||||
"@esotericsoftware/spine-core": "4.2.82"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@pixi/core": "^7.2.4",
|
||||
|
||||
@ -27,16 +27,11 @@
|
||||
* THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*****************************************************************************/
|
||||
|
||||
declare global {
|
||||
var require: any;
|
||||
var PIXI: any;
|
||||
}
|
||||
|
||||
if (typeof window !== 'undefined' && window.PIXI) {
|
||||
if (typeof window !== 'undefined' && (window as any).PIXI) {
|
||||
let prevRequire = window.require;
|
||||
window.require = (x: string) => {
|
||||
(window as any).require = (x: string) => {
|
||||
if (prevRequire) return prevRequire(x);
|
||||
else if (x.startsWith("@pixi/")) return window.PIXI;
|
||||
else if (x.startsWith("@pixi/")) return (window as any).PIXI;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@esotericsoftware/spine-pixi-v8",
|
||||
"version": "4.2.81",
|
||||
"version": "4.2.82",
|
||||
"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.81"
|
||||
"@esotericsoftware/spine-core": "4.2.82"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"pixi.js": "^8.4.0"
|
||||
|
||||
@ -27,16 +27,11 @@
|
||||
* THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*****************************************************************************/
|
||||
|
||||
declare global {
|
||||
var require: any;
|
||||
var PIXI: any;
|
||||
}
|
||||
|
||||
if (typeof window !== 'undefined' && window.PIXI) {
|
||||
if (typeof window !== 'undefined' && (window as any).PIXI) {
|
||||
const prevRequire = window.require;
|
||||
(window as any).require = (x: string) => {
|
||||
if (prevRequire) return prevRequire(x);
|
||||
else if (x.startsWith("@pixi/") || x.startsWith("pixi.js")) return window.PIXI;
|
||||
else if (x.startsWith("@pixi/") || x.startsWith("pixi.js")) return (window as any).PIXI;
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@esotericsoftware/spine-player",
|
||||
"version": "4.2.81",
|
||||
"version": "4.2.82",
|
||||
"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.81"
|
||||
"@esotericsoftware/spine-webgl": "4.2.82"
|
||||
}
|
||||
}
|
||||
@ -151,6 +151,9 @@ export interface SpinePlayerConfig {
|
||||
filter settings from the texture atlas are used. Default: true */
|
||||
mipmaps?: boolean
|
||||
|
||||
/* Optional: Whether the player responds to user click/touch (play/pause, or control bones). Default: true */
|
||||
interactive?: boolean
|
||||
|
||||
/* Optional: List of bone names that the user can drag to position. Default: none */
|
||||
controlBones?: string[]
|
||||
|
||||
@ -239,6 +242,7 @@ export class SpinePlayer implements Disposable {
|
||||
private previousViewport: Viewport = {} as Viewport;
|
||||
private viewportTransitionStart = 0;
|
||||
private eventListeners: Array<{ target: any, event: any, func: any }> = [];
|
||||
private input?: Input;
|
||||
|
||||
constructor (parent: HTMLElement | string, private config: SpinePlayerConfig) {
|
||||
let parentDom = typeof parent === "string" ? document.getElementById(parent) : parent;
|
||||
@ -286,6 +290,7 @@ export class SpinePlayer implements Disposable {
|
||||
var eventListener = this.eventListeners[i];
|
||||
eventListener.target.removeEventListener(eventListener.event, eventListener.func);
|
||||
}
|
||||
this.input?.dispose();
|
||||
this.parent.removeChild(this.dom);
|
||||
this.disposed = true;
|
||||
}
|
||||
@ -312,6 +317,7 @@ export class SpinePlayer implements Disposable {
|
||||
if (config.premultipliedAlpha === void 0) config.premultipliedAlpha = true;
|
||||
if (config.preserveDrawingBuffer === void 0) config.preserveDrawingBuffer = false;
|
||||
if (config.mipmaps === void 0) config.mipmaps = true;
|
||||
if (config.interactive === void 0) config.interactive = true;
|
||||
if (!config.debug) config.debug = {
|
||||
bones: false,
|
||||
clipping: false,
|
||||
@ -592,57 +598,61 @@ export class SpinePlayer implements Disposable {
|
||||
let skeleton = this.skeleton!;
|
||||
let renderer = this.sceneRenderer!;
|
||||
|
||||
let closest = function (x: number, y: number): Bone | null {
|
||||
mouse.set(x, canvas.clientHeight - y, 0)
|
||||
offset.x = offset.y = 0;
|
||||
let bestDistance = 24, index = 0;
|
||||
let best: Bone | null = null;
|
||||
for (let i = 0; i < controlBones.length; i++) {
|
||||
selectedBones[i] = null;
|
||||
let bone = skeleton.findBone(controlBones[i]);
|
||||
if (!bone) continue;
|
||||
let distance = renderer.camera.worldToScreen(
|
||||
coords.set(bone.worldX, bone.worldY, 0),
|
||||
canvas.clientWidth, canvas.clientHeight).distance(mouse);
|
||||
if (distance < bestDistance) {
|
||||
bestDistance = distance;
|
||||
best = bone;
|
||||
index = i;
|
||||
offset.x = coords.x - mouse.x;
|
||||
offset.y = coords.y - mouse.y;
|
||||
}
|
||||
}
|
||||
if (best) selectedBones[index] = best;
|
||||
return best;
|
||||
};
|
||||
|
||||
new Input(canvas).addListener({
|
||||
down: (x, y) => {
|
||||
target = closest(x, y);
|
||||
},
|
||||
up: () => {
|
||||
if (target)
|
||||
target = null;
|
||||
else if (config.showControls)
|
||||
(this.paused ? this.play() : this.pause());
|
||||
},
|
||||
dragged: (x, y) => {
|
||||
if (target) {
|
||||
x = MathUtils.clamp(x + offset.x, 0, canvas.clientWidth)
|
||||
y = MathUtils.clamp(y - offset.y, 0, canvas.clientHeight);
|
||||
renderer.camera.screenToWorld(coords.set(x, y, 0), canvas.clientWidth, canvas.clientHeight);
|
||||
if (target.parent) {
|
||||
target.parent.worldToLocal(position.set(coords.x - skeleton.x, coords.y - skeleton.y));
|
||||
target.x = position.x;
|
||||
target.y = position.y;
|
||||
} else {
|
||||
target.x = coords.x - skeleton.x;
|
||||
target.y = coords.y - skeleton.y;
|
||||
if (config.interactive) {
|
||||
let closest = function (x: number, y: number): Bone | null {
|
||||
mouse.set(x, canvas.clientHeight - y, 0)
|
||||
offset.x = offset.y = 0;
|
||||
let bestDistance = 24, index = 0;
|
||||
let best: Bone | null = null;
|
||||
for (let i = 0; i < controlBones.length; i++) {
|
||||
selectedBones[i] = null;
|
||||
let bone = skeleton.findBone(controlBones[i]);
|
||||
if (!bone) continue;
|
||||
let distance = renderer.camera.worldToScreen(
|
||||
coords.set(bone.worldX, bone.worldY, 0),
|
||||
canvas.clientWidth, canvas.clientHeight).distance(mouse);
|
||||
if (distance < bestDistance) {
|
||||
bestDistance = distance;
|
||||
best = bone;
|
||||
index = i;
|
||||
offset.x = coords.x - mouse.x;
|
||||
offset.y = coords.y - mouse.y;
|
||||
}
|
||||
}
|
||||
},
|
||||
moved: (x, y) => closest(x, y)
|
||||
});
|
||||
if (best) selectedBones[index] = best;
|
||||
return best;
|
||||
};
|
||||
|
||||
this.input = new Input(canvas);
|
||||
this.input.addListener({
|
||||
down: (x, y) => {
|
||||
target = closest(x, y);
|
||||
},
|
||||
up: () => {
|
||||
if (target)
|
||||
target = null;
|
||||
else if (config.showControls)
|
||||
(this.paused ? this.play() : this.pause());
|
||||
},
|
||||
dragged: (x, y) => {
|
||||
if (target) {
|
||||
x = MathUtils.clamp(x + offset.x, 0, canvas.clientWidth)
|
||||
y = MathUtils.clamp(y - offset.y, 0, canvas.clientHeight);
|
||||
renderer.camera.screenToWorld(coords.set(x, y, 0), canvas.clientWidth, canvas.clientHeight);
|
||||
if (target.parent) {
|
||||
target.parent.worldToLocal(position.set(coords.x - skeleton.x, coords.y - skeleton.y));
|
||||
target.x = position.x;
|
||||
target.y = position.y;
|
||||
} else {
|
||||
target.x = coords.x - skeleton.x;
|
||||
target.y = coords.y - skeleton.y;
|
||||
}
|
||||
}
|
||||
},
|
||||
moved: (x, y) => closest(x, y)
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
if (config.showControls) {
|
||||
// For manual hover to work, we need to disable hidding controls if the mouse/touch entered the clickable area of a child of the controls.
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@esotericsoftware/spine-threejs",
|
||||
"version": "4.2.81",
|
||||
"version": "4.2.82",
|
||||
"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.81"
|
||||
"@esotericsoftware/spine-core": "4.2.82"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/three": "0.162.0"
|
||||
|
||||
@ -27,16 +27,11 @@
|
||||
* THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*****************************************************************************/
|
||||
|
||||
declare global {
|
||||
var require: any;
|
||||
var THREE: any;
|
||||
}
|
||||
|
||||
if (typeof window !== 'undefined' && window.THREE) {
|
||||
if (typeof window !== 'undefined' && (window as any).THREE) {
|
||||
let prevRequire = window.require;
|
||||
window.require = (x: string) => {
|
||||
(window as any).require = (x: string) => {
|
||||
if (prevRequire) return prevRequire(x);
|
||||
else if (x === "three") return window.THREE;
|
||||
else if (x === "three") return (window as any).THREE;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -231,11 +231,11 @@
|
||||
></spine-skeleton>
|
||||
</div>
|
||||
<div class="split-right">
|
||||
If you want to preserve the original scale, you can use <code>fit="none"</code>.
|
||||
If you want to preserve the original scale, you can use <code>fit="none"</code> (center the bounds) or <code>fit="origin"</code> (center the skeleton origin).
|
||||
In combination with that, you can use the <code>scale</code> attribute to set your desired scale.
|
||||
<br>
|
||||
<br>
|
||||
Other fit modes are <code>width</code>, <code>height</code>, <code>cover</code>, and <code>scaleDown</code>.
|
||||
Other fit modes are <code>width</code>, <code>height</code>, <code>cover</code>, <code>scaleDown</code>..
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -280,12 +280,20 @@
|
||||
|
||||
<div class="split-top split">
|
||||
<div class="split-left">
|
||||
<style>
|
||||
.custom-class {
|
||||
width: 150px;
|
||||
height: 150px;
|
||||
border: 1px solid green;
|
||||
border-radius: 10px;
|
||||
box-shadow: -5px 5px 3px rgba(255, 0, 0, 0.3);
|
||||
}
|
||||
</style>
|
||||
<spine-skeleton
|
||||
atlas="/assets/spineboy-pma.atlas"
|
||||
skeleton="/assets/spineboy-pro.skel"
|
||||
animation="walk"
|
||||
height="150"
|
||||
width="150"
|
||||
class="custom-class"
|
||||
></spine-skeleton>
|
||||
<spine-skeleton
|
||||
atlas="/assets/spineboy-pma.atlas"
|
||||
@ -301,10 +309,9 @@
|
||||
></spine-skeleton>
|
||||
</div>
|
||||
<div class="split-right">
|
||||
If you want to manually size the Spine widget, specify the <code>width</code> and <code>height</code> attributes in pixels (without the "px" unit).
|
||||
By default, the widget occupy zero width and height.
|
||||
If you want to manually size the Spine widget, you can style the component using the <code>style</code> or <code>class</code> attribute, which provides more styling options.
|
||||
<br>
|
||||
<br>
|
||||
If you prefer, you can style the component using the <code>style</code> attribute, which provides more styling options.
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -356,11 +363,6 @@
|
||||
|
||||
<div class="split-top split">
|
||||
<div class="split-left">
|
||||
The <code>origin</code> mode centers the animation's world origin with the center of the HTML element.
|
||||
<br>
|
||||
You are responsible for scaling the skeleton when using this mode.
|
||||
<br>
|
||||
<br>
|
||||
Move the origin by a percentage of the div's width and height using the <code>x-axis</code> and <code>y-axis</code> attributes, respectively.
|
||||
</div>
|
||||
<div class="split-right">
|
||||
@ -368,7 +370,7 @@
|
||||
atlas="/assets/vine-pma.atlas"
|
||||
skeleton="/assets/vine-pro.skel"
|
||||
animation="grow"
|
||||
mode="origin"
|
||||
fit="origin"
|
||||
scale=".5"
|
||||
y-axis="-.5"
|
||||
></spine-skeleton>
|
||||
@ -383,7 +385,7 @@
|
||||
atlas="/assets/vine-pma.atlas"
|
||||
skeleton="/assets/vine-pro.skel"
|
||||
animation="grow"
|
||||
mode="origin"
|
||||
fit="origin"
|
||||
scale=".5"
|
||||
y-axis="-.5"
|
||||
></spine-skeleton>
|
||||
@ -765,10 +767,11 @@
|
||||
<li><code>mixDuration</code>: the mix duration between this animation and the previous one (not used for the first animation on a track)</li>
|
||||
</ol>
|
||||
|
||||
<p>To loop a track once it reaches the end, add the special group <code>[loop, trackNumber]</code>, where:</p>
|
||||
<p>To loop a track once it reaches the end, add the special group <code>[loop, trackNumber, repeatDelay]</code>, where:</p>
|
||||
<ul>
|
||||
<li><code>loop</code>: identifies this as a loop instruction</li>
|
||||
<li><code>trackNumber</code>: the number of the track to loop</li>
|
||||
<li><code>repeatDelay</code>: the number of seconds to wait after the last animation is completed before repeating the loop</li>
|
||||
</ul>
|
||||
|
||||
<p>The parameters of the first group on each track are passed to the <code>setAnimation</code> method, while the remaining groups use <code>addAnimation</code>.</p>
|
||||
@ -1346,7 +1349,7 @@ function removeDiv() {
|
||||
Click the button below to toggle the spinner.
|
||||
<br>
|
||||
<br>
|
||||
<input type="button" value="Spinner ON" onclick="toggleSpinner(this)">
|
||||
<input type="button" value="Spinner OFF" onclick="toggleSpinner(this)">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -1355,7 +1358,6 @@ function removeDiv() {
|
||||
async function reloadWidget(element) {
|
||||
element.disabled = true;
|
||||
await widget.whenReady;
|
||||
const skeleton = widget.skeleton;
|
||||
widget.loading = true;
|
||||
setTimeout(() => {
|
||||
element.disabled = false;
|
||||
@ -1364,7 +1366,7 @@ function removeDiv() {
|
||||
}
|
||||
function toggleSpinner(element) {
|
||||
widget.noSpinner = !widget.noSpinner;
|
||||
element.value = widget.noSpinner ? "Spinner ON" : "Spinner OFF";
|
||||
element.value = widget.noSpinner ? "Spinner OFF" : "Spinner ON";
|
||||
}
|
||||
</script>
|
||||
|
||||
@ -1420,6 +1422,8 @@ function toggleSpinner(element) {
|
||||
|
||||
<div class="split-left" style="width: 80%; box-sizing: border-box; min-height: 0;">
|
||||
It's very easy to display your different skins and animations. Simply create a table and use the <code>skin</code> and <code>animation</code> attributes.
|
||||
<br>
|
||||
<code>skin</code> accepts a comma separated list of skin names. The skins will be combined in a new one, from the first to the last. If multiple skins set the same slot, the latest in the list will be used.
|
||||
</div>
|
||||
|
||||
<div class="skin-grid">
|
||||
@ -2042,19 +2046,16 @@ skins.forEach((skin, i) => {
|
||||
<div class="split" style="width: 100%; flex-direction: column;">
|
||||
|
||||
<div class="split-left" style="width: 80%; box-sizing: border-box; min-height: 0;">
|
||||
When the widget (or the parent element) enters the viewport, the callback <code>onScreenFunction</code> is invoked.
|
||||
<br>
|
||||
<br>
|
||||
By default, the callback does two things:
|
||||
<ul>
|
||||
<li>sets <code>onScreenAtLeastOnce</code> to <code>true</code> when the widget enters the viewport for the first time</li>
|
||||
<li>if <code>start-when-visible</code> is set, the widget's <code>start</code> method is invoked the first time the widget enters the viewport, and the assets are loaded at that moment.</li>
|
||||
</ul>
|
||||
<br>
|
||||
The assets of the coin below are loaded only when the widget enters the viewport.
|
||||
<br>
|
||||
<br>
|
||||
You can overwrite the <code>onScreenFunction</code> behavior. For example, the raptor below changes its animation every time the widget enters the viewport.
|
||||
When the widget (or its parent element) enters the viewport, two things happen:<br>
|
||||
<ul>
|
||||
<li>the widget's <code>onScreenAtLeastOnce</code> property is set to <code>true</code></li>
|
||||
<li>the widget's <code>onScreenFunction</code> callback is invoked</li>
|
||||
</ul>
|
||||
By default, <code>onScreenFunction</code> invokes the widget's <code>start</code> method if the widget has the <code>start-when-visible</code> attribute set, and this occurs only the first time it enters the viewport.<br>
|
||||
<br>
|
||||
The assets of the coin below are loaded only when the widget enters the viewport.<br>
|
||||
<br>
|
||||
You can override the <code>onScreenFunction</code> behavior. For example, the raptor below changes its animation every time the widget enters the viewport.
|
||||
</div>
|
||||
|
||||
<div class="skin-grid">
|
||||
@ -2077,7 +2078,6 @@ skins.forEach((skin, i) => {
|
||||
<script>
|
||||
(async () => {
|
||||
const raptorWidget = await spine.getSpineWidget("coin-with-raptor").whenReady;
|
||||
|
||||
let raptorWalking = true;
|
||||
raptorWidget.onScreenFunction = widget => {
|
||||
raptorWalking = !raptorWalking;
|
||||
@ -2096,7 +2096,14 @@ skins.forEach((skin, i) => {
|
||||
<script>
|
||||
escapeHTMLandInject(`
|
||||
<spine-skeleton
|
||||
identifier="coin"
|
||||
atlas="/assets/coin-pma.atlas"
|
||||
skeleton="/assets/coin-pro.skel"
|
||||
animation="animation"
|
||||
start-when-visible
|
||||
></spine-skeleton>
|
||||
|
||||
<spine-skeleton
|
||||
identifier="coin-with-raptor"
|
||||
atlas="/assets/raptor-pma.atlas"
|
||||
skeleton="/assets/raptor-pro.skel"
|
||||
animation="walk"
|
||||
@ -2689,8 +2696,6 @@ tank.beforeUpdateWorldTransforms = (delta, skeleton, state) => {
|
||||
<div class="split-left" style="overflow-y: auto; width: 100px; height: 200px; transform: translateZ(0);">
|
||||
<spine-overlay
|
||||
overlay-id="scroll"
|
||||
scrollable
|
||||
no-auto-parent-transform
|
||||
overflow-top=".2"
|
||||
overflow-bottom=".2"
|
||||
overflow-left=".2"
|
||||
@ -3271,7 +3276,7 @@ const darkPicker = document.getElementById("dark-picker");
|
||||
<li><code>followOpacity</code>: the element opacity is connected to the slot alpha</li>
|
||||
<li><code>followScale</code>: the element scale is connected to the slot scale</li>
|
||||
<li><code>followRotation</code>: the element rotation is connected to the slot rotation</li>
|
||||
<li><code>followAttachmentAttach</code>: the element is shown/hidden depending if the slot contains an attachment or not</li>
|
||||
<li><code>followVisibility</code>: the element is shown/hidden depending if the slot contains an attachment or not</li>
|
||||
<li><code>hideAttachment</code>: the slot attachment is hidden as if the element replaced the attachment</li>
|
||||
</ul>
|
||||
</li>
|
||||
@ -3296,10 +3301,10 @@ const darkPicker = document.getElementById("dark-picker");
|
||||
<script>
|
||||
(async () => {
|
||||
const widget = await spine.getSpineWidget("potty").whenReady;
|
||||
widget.followSlot("rain/rain-color", document.getElementById("rain/rain-color"), { followAttachmentAttach: false, hideAttachment: true });
|
||||
widget.followSlot("rain/rain-white", document.getElementById("rain/rain-white"), { followAttachmentAttach: false, hideAttachment: true });
|
||||
widget.followSlot("rain/rain-blue", document.getElementById("rain/rain-blue"), { followAttachmentAttach: false, hideAttachment: true });
|
||||
widget.followSlot("rain/rain-green", document.getElementById("rain/rain-green"), { followAttachmentAttach: false, hideAttachment: true });
|
||||
widget.followSlot("rain/rain-color", document.getElementById("rain/rain-color"), { followVisibility: false, hideAttachment: true });
|
||||
widget.followSlot("rain/rain-white", document.getElementById("rain/rain-white"), { followVisibility: false, hideAttachment: true });
|
||||
widget.followSlot("rain/rain-blue", document.getElementById("rain/rain-blue"), { followVisibility: false, hideAttachment: true });
|
||||
widget.followSlot("rain/rain-green", document.getElementById("rain/rain-green"), { followVisibility: false, hideAttachment: true });
|
||||
})();
|
||||
</script>
|
||||
|
||||
@ -3323,10 +3328,10 @@ const darkPicker = document.getElementById("dark-picker");
|
||||
|
||||
(async () => {
|
||||
const widget = await spine.getSpineWidget("potty").whenReady;
|
||||
widget.followSlot("rain/rain-color", document.getElementById("rain/rain-color"), { followAttachmentAttach: false, hideAttachment: true });
|
||||
widget.followSlot("rain/rain-white", document.getElementById("rain/rain-white"), { followAttachmentAttach: false, hideAttachment: true });
|
||||
widget.followSlot("rain/rain-blue", document.getElementById("rain/rain-blue"), { followAttachmentAttach: false, hideAttachment: true });
|
||||
widget.followSlot("rain/rain-green", document.getElementById("rain/rain-green"), { followAttachmentAttach: false, hideAttachment: true });
|
||||
widget.followSlot("rain/rain-color", document.getElementById("rain/rain-color"), { followVisibility: false, hideAttachment: true });
|
||||
widget.followSlot("rain/rain-white", document.getElementById("rain/rain-white"), { followVisibility: false, hideAttachment: true });
|
||||
widget.followSlot("rain/rain-blue", document.getElementById("rain/rain-blue"), { followVisibility: false, hideAttachment: true });
|
||||
widget.followSlot("rain/rain-green", document.getElementById("rain/rain-green"), { followVisibility: false, hideAttachment: true });
|
||||
})();`);</script>
|
||||
</code></pre>
|
||||
</div>
|
||||
@ -3370,10 +3375,10 @@ const darkPicker = document.getElementById("dark-picker");
|
||||
<script>
|
||||
(async () => {
|
||||
const widget = await spine.getSpineWidget("potty2").whenReady;
|
||||
widget.followSlot("rain/rain-color", spine.getSpineWidget("potty2-1"), { followAttachmentAttach: false, hideAttachment: true });
|
||||
widget.followSlot("rain/rain-white", spine.getSpineWidget("potty2-2"), { followAttachmentAttach: false, hideAttachment: true });
|
||||
widget.followSlot("rain/rain-blue", spine.getSpineWidget("potty2-3"), { followAttachmentAttach: false, hideAttachment: true });
|
||||
widget.followSlot("rain/rain-green", spine.getSpineWidget("potty2-4"), { followAttachmentAttach: false, hideAttachment: true });
|
||||
widget.followSlot("rain/rain-color", spine.getSpineWidget("potty2-1"), { followVisibility: false, hideAttachment: true });
|
||||
widget.followSlot("rain/rain-white", spine.getSpineWidget("potty2-2"), { followVisibility: false, hideAttachment: true });
|
||||
widget.followSlot("rain/rain-blue", spine.getSpineWidget("potty2-3"), { followVisibility: false, hideAttachment: true });
|
||||
widget.followSlot("rain/rain-green", spine.getSpineWidget("potty2-4"), { followVisibility: false, hideAttachment: true });
|
||||
})();
|
||||
</script>
|
||||
|
||||
@ -3397,10 +3402,10 @@ const darkPicker = document.getElementById("dark-picker");
|
||||
|
||||
(async () => {
|
||||
const widget = await spine.getSpineWidget("potty2").whenReady;
|
||||
widget.followSlot("rain/rain-color", spine.getSpineWidget("potty2-1"), { followAttachmentAttach: false, hideAttachment: true });
|
||||
widget.followSlot("rain/rain-white", spine.getSpineWidget("potty2-2"), { followAttachmentAttach: false, hideAttachment: true });
|
||||
widget.followSlot("rain/rain-blue", spine.getSpineWidget("potty2-3"), { followAttachmentAttach: false, hideAttachment: true });
|
||||
widget.followSlot("rain/rain-green", spine.getSpineWidget("potty2-4"), { followAttachmentAttach: false, hideAttachment: true });
|
||||
widget.followSlot("rain/rain-color", spine.getSpineWidget("potty2-1"), { followVisibility: false, hideAttachment: true });
|
||||
widget.followSlot("rain/rain-white", spine.getSpineWidget("potty2-2"), { followVisibility: false, hideAttachment: true });
|
||||
widget.followSlot("rain/rain-blue", spine.getSpineWidget("potty2-3"), { followVisibility: false, hideAttachment: true });
|
||||
widget.followSlot("rain/rain-green", spine.getSpineWidget("potty2-4"), { followVisibility: false, hideAttachment: true });
|
||||
})();`);</script>
|
||||
</code></pre>
|
||||
</div>
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@esotericsoftware/spine-webcomponents",
|
||||
"version": "4.2.80",
|
||||
"version": "4.2.82",
|
||||
"description": "The official Spine webcomponents.",
|
||||
"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.80"
|
||||
"@esotericsoftware/spine-webgl": "4.2.82"
|
||||
}
|
||||
}
|
||||
@ -149,6 +149,8 @@ export class SpineWebComponentOverlay extends HTMLElement implements OverlayAttr
|
||||
private lastCanvasBaseWidth = 0;
|
||||
private lastCanvasBaseHeight = 0;
|
||||
|
||||
private zIndex?: number;
|
||||
|
||||
private disposed = false;
|
||||
private loaded = false;
|
||||
|
||||
@ -164,13 +166,14 @@ export class SpineWebComponentOverlay extends HTMLElement implements OverlayAttr
|
||||
*
|
||||
* In order to fix this behaviour, it is necessary to insert a dedicated `spine-overlay` webcomponent as a direct child of the container.
|
||||
* Moreover, it is necessary to perform the following actions:
|
||||
* 1) The scrollable container must have a `transform` css attribute. If it hasn't this attribute the `spine-overlay` will add it for you.
|
||||
* If your scrollable container has already this css attribute, or if you prefer to add it by yourself (example: `transform: translateZ(0);`), set the `no-auto-parent-transform` to the `spine-overlay`.
|
||||
* 1) The appendedToBody container must have a `transform` css attribute. If it hasn't this attribute the `spine-overlay` will add it for you.
|
||||
* If your appendedToBody container has already this css attribute, or if you prefer to add it by yourself (example: `transform: translateZ(0);`), set the `no-auto-parent-transform` to the `spine-overlay`.
|
||||
* 2) The `spine-overlay` must have an `overlay-id` attribute. Choose the value you prefer.
|
||||
* 3) Each `spine-skeleton` must have an `overlay-id` attribute. The same as the hosting `spine-overlay`.
|
||||
* Connected to `scrollable` attribute.
|
||||
* Connected to `appendedToBody` attribute.
|
||||
*/
|
||||
private appendedToBody = true;
|
||||
private hasParentTransform = true;
|
||||
|
||||
readonly time = new TimeKeeper();
|
||||
|
||||
@ -257,6 +260,7 @@ export class SpineWebComponentOverlay extends HTMLElement implements OverlayAttr
|
||||
widget.onScreen = isIntersecting;
|
||||
if (isIntersecting) {
|
||||
widget.onScreenFunction(widget);
|
||||
widget.onScreenAtLeastOnce = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -267,8 +271,10 @@ export class SpineWebComponentOverlay extends HTMLElement implements OverlayAttr
|
||||
// Alternatively, we can store the body size, check the current body size in the loop (like the translateCanvas), and
|
||||
// if they differs call the resizeCallback. I already tested it, and it works. ResizeObserver should be more efficient.
|
||||
if (this.appendedToBody) {
|
||||
// if the element is scrollable, the user does not disable translate tweak, and the parent did not have already a transform, add the tweak
|
||||
if (this.appendedToBody && !this.noAutoParentTransform && getComputedStyle(this.parentElement!).transform === "none") {
|
||||
// if the element is appendedToBody, the user does not disable translate tweak, and the parent did not have already a transform, add the tweak
|
||||
if (this.hasCssTweakOff()) {
|
||||
this.hasParentTransform = false;
|
||||
} else {
|
||||
this.parentElement!.style.transform = `translateZ(0)`;
|
||||
}
|
||||
this.resizeObserver = new ResizeObserver(this.resizedCallback);
|
||||
@ -376,6 +382,8 @@ export class SpineWebComponentOverlay extends HTMLElement implements OverlayAttr
|
||||
this.parentElement!.appendChild(this);
|
||||
}
|
||||
}
|
||||
|
||||
this.updateZIndexIfNecessary(widget);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -475,7 +483,7 @@ export class SpineWebComponentOverlay extends HTMLElement implements OverlayAttr
|
||||
|
||||
const tempVector = new Vector3();
|
||||
for (const widget of this.widgets) {
|
||||
const { skeleton, pma, bounds, mode, debug, offsetX, offsetY, xAxis, yAxis, dragX, dragY, fit, noSpinner, onScreen, loading, clip, isDraggable } = widget;
|
||||
const { skeleton, pma, bounds, debug, offsetX, offsetY, dragX, dragY, fit, noSpinner, loading, clip, isDraggable } = widget;
|
||||
|
||||
if (widget.isOffScreenAndWasMoved()) continue;
|
||||
const elementRef = widget.getHostElement();
|
||||
@ -489,7 +497,7 @@ export class SpineWebComponentOverlay extends HTMLElement implements OverlayAttr
|
||||
divBounds.y -= offsetTopForOverlay;
|
||||
}
|
||||
|
||||
const { padLeft, padRight, padTop, padBottom } = widget
|
||||
const { padLeft, padRight, padTop, padBottom, xAxis, yAxis } = widget
|
||||
const paddingShiftHorizontal = (padLeft - padRight) / 2;
|
||||
const paddingShiftVertical = (padTop - padBottom) / 2;
|
||||
|
||||
@ -508,7 +516,7 @@ export class SpineWebComponentOverlay extends HTMLElement implements OverlayAttr
|
||||
if (clip) startScissor(divBounds);
|
||||
|
||||
if (loading) {
|
||||
if (noSpinner) {
|
||||
if (!noSpinner) {
|
||||
if (!widget.loadingScreen) widget.loadingScreen = new LoadingScreen(renderer);
|
||||
widget.loadingScreen!.drawInCoordinates(divOriginX, divOriginY);
|
||||
}
|
||||
@ -517,7 +525,7 @@ export class SpineWebComponentOverlay extends HTMLElement implements OverlayAttr
|
||||
}
|
||||
|
||||
if (skeleton) {
|
||||
if (mode === "inside") {
|
||||
if (fit !== "origin") {
|
||||
let { x: ax, y: ay, width: aw, height: ah } = bounds;
|
||||
if (aw <= 0 || ah <= 0) continue;
|
||||
|
||||
@ -583,8 +591,9 @@ export class SpineWebComponentOverlay extends HTMLElement implements OverlayAttr
|
||||
}
|
||||
}
|
||||
|
||||
const worldOffsetX = divOriginX + offsetX + dragX;
|
||||
const worldOffsetY = divOriginY + offsetY + dragY;
|
||||
// const worldOffsetX = divOriginX + offsetX + dragX;
|
||||
const worldOffsetX = divOriginX + offsetX * window.devicePixelRatio + dragX;
|
||||
const worldOffsetY = divOriginY + offsetY * window.devicePixelRatio + dragY;
|
||||
|
||||
widget.worldX = worldOffsetX;
|
||||
widget.worldY = worldOffsetY;
|
||||
@ -626,12 +635,10 @@ export class SpineWebComponentOverlay extends HTMLElement implements OverlayAttr
|
||||
renderer.circle(true, root.x + worldOffsetX, root.y + worldOffsetY, 10, red);
|
||||
|
||||
// show shifted origin
|
||||
const originX = worldOffsetX - dragX - offsetX;
|
||||
const originY = worldOffsetY - dragY - offsetY;
|
||||
renderer.circle(true, originX, originY, 10, green);
|
||||
renderer.circle(true, divOriginX, divOriginY, 10, green);
|
||||
|
||||
// show line from origin to bounds center
|
||||
renderer.line(originX, originY, bbCenterX, bbCenterY, green);
|
||||
renderer.line(divOriginX, divOriginY, bbCenterX, bbCenterY, green);
|
||||
}
|
||||
|
||||
if (clip) endScissor();
|
||||
@ -646,7 +653,7 @@ export class SpineWebComponentOverlay extends HTMLElement implements OverlayAttr
|
||||
if (widget.isOffScreenAndWasMoved() || !widget.skeleton) continue;
|
||||
|
||||
for (const boneFollower of widget.boneFollowerList) {
|
||||
const { slot, bone, element, followAttachmentAttach, followRotation, followOpacity, followScale } = boneFollower;
|
||||
const { slot, bone, element, followVisibility, followRotation, followOpacity, followScale } = boneFollower;
|
||||
const { worldX, worldY } = widget;
|
||||
this.worldToScreen(this.tempFollowBoneVector, bone.worldX + worldX, bone.worldY + worldY);
|
||||
|
||||
@ -667,7 +674,7 @@ export class SpineWebComponentOverlay extends HTMLElement implements OverlayAttr
|
||||
|
||||
element.style.display = ""
|
||||
|
||||
if (followAttachmentAttach && !slot.attachment) {
|
||||
if (followVisibility && !slot.attachment) {
|
||||
element.style.opacity = "0";
|
||||
} else if (followOpacity) {
|
||||
element.style.opacity = `${slot.color.a}`;
|
||||
@ -943,7 +950,7 @@ export class SpineWebComponentOverlay extends HTMLElement implements OverlayAttr
|
||||
private updateWidgetScales () {
|
||||
for (const widget of this.widgets) {
|
||||
// inside mode scale automatically to fit the skeleton within its parent
|
||||
if (widget.mode !== "origin" && widget.fit !== "none") continue;
|
||||
if (widget.fit !== "origin" && widget.fit !== "none") continue;
|
||||
|
||||
const skeleton = widget.skeleton;
|
||||
if (!skeleton) continue;
|
||||
@ -958,6 +965,7 @@ export class SpineWebComponentOverlay extends HTMLElement implements OverlayAttr
|
||||
}
|
||||
}
|
||||
|
||||
// this function is invoked each frame - pay attention to what you add here
|
||||
private translateCanvas () {
|
||||
let scrollPositionX = -this.overflowLeftSize;
|
||||
let scrollPositionY = -this.overflowTopSize;
|
||||
@ -967,9 +975,9 @@ export class SpineWebComponentOverlay extends HTMLElement implements OverlayAttr
|
||||
scrollPositionY += window.scrollY;
|
||||
} else {
|
||||
|
||||
// Ideally this should be the only scrollable case (no-auto-parent-transform not enabled or at least an ancestor has transform)
|
||||
// I'd like to get rid of the code below
|
||||
if (!this.hasCssTweakOff()) {
|
||||
// Ideally this should be the only appendedToBody case (no-auto-parent-transform not enabled or at least an ancestor has transform)
|
||||
// I'd like to get rid of the else case
|
||||
if (this.hasParentTransform) {
|
||||
scrollPositionX += this.parentElement!.scrollLeft;
|
||||
scrollPositionY += this.parentElement!.scrollTop;
|
||||
} else {
|
||||
@ -979,7 +987,7 @@ export class SpineWebComponentOverlay extends HTMLElement implements OverlayAttr
|
||||
|
||||
let offsetParent = this.offsetParent;
|
||||
do {
|
||||
if (offsetParent === document.body) break;
|
||||
if (offsetParent === null || offsetParent === document.body) break;
|
||||
|
||||
const htmlOffsetParentElement = offsetParent as HTMLElement;
|
||||
if (htmlOffsetParentElement.style.position === "fixed" || htmlOffsetParentElement.style.position === "sticky" || htmlOffsetParentElement.style.position === "absolute") {
|
||||
@ -1000,6 +1008,23 @@ export class SpineWebComponentOverlay extends HTMLElement implements OverlayAttr
|
||||
this.canvas.style.transform = `translate(${scrollPositionX}px,${scrollPositionY}px)`;
|
||||
}
|
||||
|
||||
private updateZIndexIfNecessary (element: HTMLElement) {
|
||||
let parent: HTMLElement | null = element;
|
||||
let zIndex: undefined | number;
|
||||
do {
|
||||
let currentZIndex = parseInt(getComputedStyle(parent).zIndex);
|
||||
|
||||
// searching the shallowest z-index
|
||||
if (!isNaN(currentZIndex)) zIndex = currentZIndex;
|
||||
parent = parent.parentElement;
|
||||
} while (parent && parent !== document.body)
|
||||
|
||||
if (zIndex && (!this.zIndex || this.zIndex < zIndex)) {
|
||||
this.zIndex = zIndex;
|
||||
this.div.style.zIndex = `${this.zIndex}`;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Other utilities
|
||||
*/
|
||||
|
||||
@ -49,6 +49,7 @@ import {
|
||||
RegionAttachment,
|
||||
MeshAttachment,
|
||||
Bone,
|
||||
Skin,
|
||||
} from "@esotericsoftware/spine-webgl";
|
||||
import { AttributeTypes, castValue, isBase64, Rectangle } from "./wcUtils.js";
|
||||
import { SpineWebComponentOverlay } from "./SpineWebComponentOverlay.js";
|
||||
@ -56,9 +57,12 @@ import { SpineWebComponentOverlay } from "./SpineWebComponentOverlay.js";
|
||||
type UpdateSpineWidgetFunction = (delta: number, skeleton: Skeleton, state: AnimationState) => void;
|
||||
|
||||
export type OffScreenUpdateBehaviourType = "pause" | "update" | "pose";
|
||||
export type ModeType = "inside" | "origin";
|
||||
export type FitType = "fill" | "width" | "height" | "contain" | "cover" | "none" | "scaleDown";
|
||||
export type AnimationsInfo = Record<string, { cycle?: boolean, animations: Array<AnimationsType> }>;
|
||||
export type FitType = "fill" | "width" | "height" | "contain" | "cover" | "none" | "scaleDown" | "origin";
|
||||
export type AnimationsInfo = Record<string, {
|
||||
cycle?: boolean,
|
||||
repeatDelay?: number;
|
||||
animations: Array<AnimationsType>
|
||||
}>;
|
||||
export type AnimationsType = { animationName: string | "#EMPTY#", loop?: boolean, delay?: number, mixDuration?: number };
|
||||
export type CursorEventType = "down" | "up" | "enter" | "leave" | "move" | "drag";
|
||||
export type CursorEventTypesInput = Exclude<CursorEventType, "enter" | "leave">;
|
||||
@ -73,9 +77,8 @@ interface WidgetAttributes {
|
||||
animation?: string
|
||||
animations?: AnimationsInfo
|
||||
defaultMix?: number
|
||||
skin?: string
|
||||
skin?: string[]
|
||||
fit: FitType
|
||||
mode: ModeType
|
||||
xAxis: number
|
||||
yAxis: number
|
||||
offsetX: number
|
||||
@ -217,14 +220,14 @@ export class SpineWebComponentSkeleton extends HTMLElement implements Disposable
|
||||
* Optional: The name of the skin to be set
|
||||
* Connected to `skin` attribute.
|
||||
*/
|
||||
public get skin (): string | undefined {
|
||||
public get skin (): string[] | undefined {
|
||||
return this._skin;
|
||||
}
|
||||
public set skin (value: string | undefined) {
|
||||
public set skin (value: string[] | undefined) {
|
||||
this._skin = value;
|
||||
this.initWidget();
|
||||
}
|
||||
private _skin?: string
|
||||
private _skin?: string[]
|
||||
|
||||
/**
|
||||
* Specify the way the skeleton is sized within the element automatically changing its `scaleX` and `scaleY`.
|
||||
@ -236,19 +239,11 @@ export class SpineWebComponentSkeleton extends HTMLElement implements Disposable
|
||||
* - `cover`: as small as possible while still covering the entire element container.
|
||||
* - `scaleDown`: scale the skeleton down to ensure that the skeleton fits within the element container.
|
||||
* - `none`: display the skeleton without autoscaling it.
|
||||
* - `origin`: the skeleton origin is centered with the element container regardless of the bounds.
|
||||
* Connected to `fit` attribute.
|
||||
*/
|
||||
public fit: FitType = "contain";
|
||||
|
||||
/**
|
||||
* Specify the way the skeleton is centered within the element container:
|
||||
* - `inside`: the skeleton bounds center is centered with the element container (Default)
|
||||
* - `origin`: the skeleton origin is centered with the element container regardless of the bounds.
|
||||
* Origin does not allow to specify any {@link fit} type and guarantee the skeleton to not be autoscaled.
|
||||
* Connected to `mode` attribute.
|
||||
*/
|
||||
public mode: ModeType = "inside";
|
||||
|
||||
/**
|
||||
* The x offset of the skeleton world origin x axis as a percentage of the element container width
|
||||
* Connected to `x-axis` attribute.
|
||||
@ -562,16 +557,11 @@ export class SpineWebComponentSkeleton extends HTMLElement implements Disposable
|
||||
/**
|
||||
* A callback invoked each time the element container enters the screen viewport.
|
||||
* By default, the callback call the {@link start} method the first time the widget
|
||||
* enters the screen viewport.
|
||||
* enters the screen viewport and {@link startWhenVisible} is `true`.
|
||||
*/
|
||||
public onScreenFunction: (widget: SpineWebComponentSkeleton) => void = async (widget) => {
|
||||
if (widget.loading && !widget.onScreenAtLeastOnce) {
|
||||
widget.onScreenAtLeastOnce = true;
|
||||
|
||||
if (widget.manualStart && widget.startWhenVisible) {
|
||||
widget.start();
|
||||
}
|
||||
}
|
||||
if (widget.loading && !widget.onScreenAtLeastOnce && widget.manualStart && widget.startWhenVisible)
|
||||
widget.start()
|
||||
}
|
||||
|
||||
/**
|
||||
@ -706,7 +696,7 @@ export class SpineWebComponentSkeleton extends HTMLElement implements Disposable
|
||||
animations: { propertyName: "animations", type: "animationsInfo", defaultValue: undefined },
|
||||
"animation-bounds": { propertyName: "animationsBound", type: "array-string", defaultValue: undefined },
|
||||
"default-mix": { propertyName: "defaultMix", type: "number", defaultValue: 0 },
|
||||
skin: { propertyName: "skin", type: "string" },
|
||||
skin: { propertyName: "skin", type: "array-string" },
|
||||
width: { propertyName: "width", type: "number", defaultValue: -1 },
|
||||
height: { propertyName: "height", type: "number", defaultValue: -1 },
|
||||
isdraggable: { propertyName: "isDraggable", type: "boolean" },
|
||||
@ -732,7 +722,6 @@ export class SpineWebComponentSkeleton extends HTMLElement implements Disposable
|
||||
clip: { propertyName: "clip", type: "boolean" },
|
||||
pages: { propertyName: "pages", type: "array-number" },
|
||||
fit: { propertyName: "fit", type: "fitType", defaultValue: "contain" },
|
||||
mode: { propertyName: "mode", type: "modeType", defaultValue: "inside" },
|
||||
offscreen: { propertyName: "offScreenUpdateBehaviour", type: "offScreenUpdateBehaviourType", defaultValue: "pause" },
|
||||
}
|
||||
|
||||
@ -876,7 +865,7 @@ export class SpineWebComponentSkeleton extends HTMLElement implements Disposable
|
||||
* @returns The `HTMLElement` where the widget is hosted.
|
||||
*/
|
||||
public getHostElement (): HTMLElement {
|
||||
return (this.width <= 0 || this.width <= 0) && !this.getAttribute("style")
|
||||
return (this.width <= 0 || this.width <= 0) && !this.getAttribute("style") && !this.getAttribute("class")
|
||||
? this.parentElement!
|
||||
: this;
|
||||
}
|
||||
@ -1009,18 +998,28 @@ export class SpineWebComponentSkeleton extends HTMLElement implements Disposable
|
||||
// skeleton.scaleX = this.dprScale;
|
||||
// skeleton.scaleY = this.dprScale;
|
||||
|
||||
this.loading = false;
|
||||
|
||||
// the bounds are calculated the first time, if no custom bound is provided
|
||||
this.initWidget(this.bounds.width <= 0 || this.bounds.height <= 0);
|
||||
|
||||
this.loading = false;
|
||||
return this;
|
||||
}
|
||||
|
||||
private initWidget (forceRecalculate = false) {
|
||||
if (this.loading) return;
|
||||
|
||||
const { skeleton, state, animation, animations: animationsInfo, skin, defaultMix } = this;
|
||||
|
||||
if (skin) {
|
||||
skeleton?.setSkinByName(skin);
|
||||
if (skin.length === 1) {
|
||||
skeleton?.setSkinByName(skin[0]);
|
||||
} else {
|
||||
const customSkin = new Skin("custom");
|
||||
for (const s of skin) customSkin.addSkin(skeleton?.data.findSkin(s) as Skin);
|
||||
skeleton?.setSkin(customSkin);
|
||||
}
|
||||
|
||||
skeleton?.setSlotsToSetupPose();
|
||||
}
|
||||
|
||||
@ -1028,7 +1027,7 @@ export class SpineWebComponentSkeleton extends HTMLElement implements Disposable
|
||||
state.data.defaultMix = defaultMix;
|
||||
|
||||
if (animationsInfo) {
|
||||
for (const [trackIndexString, { cycle, animations }] of Object.entries(animationsInfo)) {
|
||||
for (const [trackIndexString, { cycle, animations, repeatDelay }] of Object.entries(animationsInfo)) {
|
||||
const cycleFn = () => {
|
||||
const trackIndex = Number(trackIndexString);
|
||||
for (const [index, { animationName, delay, loop, mixDuration }] of animations.entries()) {
|
||||
@ -1050,7 +1049,15 @@ export class SpineWebComponentSkeleton extends HTMLElement implements Disposable
|
||||
if (mixDuration) track.mixDuration = mixDuration;
|
||||
|
||||
if (cycle && index === animations.length - 1) {
|
||||
track.listener = { complete: () => cycleFn() };
|
||||
track.listener = {
|
||||
complete: () => {
|
||||
if (repeatDelay)
|
||||
setTimeout(() => cycleFn(), 1000 * repeatDelay);
|
||||
else
|
||||
cycleFn();
|
||||
delete track.listener?.complete;
|
||||
}
|
||||
};
|
||||
};
|
||||
}
|
||||
}
|
||||
@ -1068,22 +1075,13 @@ export class SpineWebComponentSkeleton extends HTMLElement implements Disposable
|
||||
}
|
||||
|
||||
private render (): void {
|
||||
let width;
|
||||
let height;
|
||||
if (this.width === -1 || this.height === -1) {
|
||||
width = "0";
|
||||
height = "0";
|
||||
} else {
|
||||
width = `${this.width}px`
|
||||
height = `${this.height}px`
|
||||
}
|
||||
let noSize = (!this.getAttribute("style") && !this.getAttribute("class"));
|
||||
this.root.innerHTML = `
|
||||
<style>
|
||||
:host {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
width: ${width};
|
||||
height: ${height};
|
||||
${noSize ? "width: 0; height: 0;" : ""}
|
||||
}
|
||||
</style>
|
||||
`;
|
||||
@ -1233,10 +1231,10 @@ export class SpineWebComponentSkeleton extends HTMLElement implements Disposable
|
||||
* Other utilities
|
||||
*/
|
||||
|
||||
public boneFollowerList: Array<{ slot: Slot, bone: Bone, element: HTMLElement, followAttachmentAttach: boolean, followRotation: boolean, followOpacity: boolean, followScale: boolean, hideAttachment: boolean }> = [];
|
||||
public followSlot (slotName: string | Slot, element: HTMLElement, options: { followAttachmentAttach?: boolean, followRotation?: boolean, followOpacity?: boolean, followScale?: boolean, hideAttachment?: boolean } = {}) {
|
||||
public boneFollowerList: Array<{ slot: Slot, bone: Bone, element: HTMLElement, followVisibility: boolean, followRotation: boolean, followOpacity: boolean, followScale: boolean, hideAttachment: boolean }> = [];
|
||||
public followSlot (slotName: string | Slot, element: HTMLElement, options: { followVisibility?: boolean, followRotation?: boolean, followOpacity?: boolean, followScale?: boolean, hideAttachment?: boolean } = {}) {
|
||||
const {
|
||||
followAttachmentAttach = false,
|
||||
followVisibility = false,
|
||||
followRotation = true,
|
||||
followOpacity = true,
|
||||
followScale = true,
|
||||
@ -1255,7 +1253,7 @@ export class SpineWebComponentSkeleton extends HTMLElement implements Disposable
|
||||
element.style.left = '0px';
|
||||
element.style.display = 'none';
|
||||
|
||||
this.boneFollowerList.push({ slot, bone: slot.bone, element, followAttachmentAttach, followRotation, followOpacity, followScale, hideAttachment });
|
||||
this.boneFollowerList.push({ slot, bone: slot.bone, element, followVisibility, followRotation, followOpacity, followScale, hideAttachment });
|
||||
this.overlay.addSlotFollowerElement(element);
|
||||
}
|
||||
public unfollowSlot (element: HTMLElement): HTMLElement | undefined {
|
||||
|
||||
@ -27,10 +27,10 @@
|
||||
* SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*****************************************************************************/
|
||||
|
||||
import { AnimationsInfo, FitType, ModeType, OffScreenUpdateBehaviourType } from "./SpineWebComponentSkeleton.js";
|
||||
import { AnimationsInfo, FitType, OffScreenUpdateBehaviourType } from "./SpineWebComponentSkeleton.js";
|
||||
|
||||
const animatonTypeRegExp = /\[([^\]]+)\]/g;
|
||||
export type AttributeTypes = "string" | "number" | "boolean" | "array-number" | "array-string" | "object" | "fitType" | "modeType" | "offScreenUpdateBehaviourType" | "animationsInfo";
|
||||
export type AttributeTypes = "string" | "number" | "boolean" | "array-number" | "array-string" | "object" | "fitType" | "offScreenUpdateBehaviourType" | "animationsInfo";
|
||||
|
||||
export function castValue (type: AttributeTypes, value: string | null, defaultValue?: any) {
|
||||
switch (type) {
|
||||
@ -48,8 +48,6 @@ export function castValue (type: AttributeTypes, value: string | null, defaultVa
|
||||
return castObject(value, defaultValue);
|
||||
case "fitType":
|
||||
return isFitType(value) ? value : defaultValue;
|
||||
case "modeType":
|
||||
return isModeType(value) ? value : defaultValue;
|
||||
case "offScreenUpdateBehaviourType":
|
||||
return isOffScreenUpdateBehaviourType(value) ? value : defaultValue;
|
||||
case "animationsInfo":
|
||||
@ -104,7 +102,7 @@ function castToAnimationsInfo (value: string | null): AnimationsInfo | undefined
|
||||
if (!matches) return undefined;
|
||||
|
||||
return matches.reduce((obj, group) => {
|
||||
const [trackIndexStringOrLoopDefinition, animationNameOrTrackIndexStringCycle, loop, delayString, mixDurationString] = group.slice(1, -1).split(',').map(v => v.trim());
|
||||
const [trackIndexStringOrLoopDefinition, animationNameOrTrackIndexStringCycle, loopOrRepeatDelay, delayString, mixDurationString] = group.slice(1, -1).split(',').map(v => v.trim());
|
||||
|
||||
if (trackIndexStringOrLoopDefinition === "loop") {
|
||||
if (!Number.isInteger(Number(animationNameOrTrackIndexStringCycle))) {
|
||||
@ -112,6 +110,15 @@ function castToAnimationsInfo (value: string | null): AnimationsInfo | undefined
|
||||
}
|
||||
const animationInfoObject = obj[animationNameOrTrackIndexStringCycle] ||= { animations: [] };
|
||||
animationInfoObject.cycle = true;
|
||||
|
||||
if (loopOrRepeatDelay !== undefined) {
|
||||
const repeatDelay = Number(loopOrRepeatDelay);
|
||||
if (Number.isNaN(repeatDelay)) {
|
||||
throw new Error(`If present, duration of last animation of cycle in ${group} must be a positive integer number, instead it is ${loopOrRepeatDelay}. Original value: ${value}`);
|
||||
}
|
||||
animationInfoObject.repeatDelay = repeatDelay;
|
||||
}
|
||||
|
||||
return obj;
|
||||
}
|
||||
|
||||
@ -139,7 +146,7 @@ function castToAnimationsInfo (value: string | null): AnimationsInfo | undefined
|
||||
const animationInfoObject = obj[trackIndexStringOrLoopDefinition] ||= { animations: [] };
|
||||
animationInfoObject.animations.push({
|
||||
animationName: animationNameOrTrackIndexStringCycle,
|
||||
loop: loop.trim().toLowerCase() === "true",
|
||||
loop: (loopOrRepeatDelay || "").trim().toLowerCase() === "true",
|
||||
delay,
|
||||
mixDuration,
|
||||
});
|
||||
@ -155,7 +162,8 @@ function isFitType (value: string | null): value is FitType {
|
||||
value === "contain" ||
|
||||
value === "cover" ||
|
||||
value === "none" ||
|
||||
value === "scaleDown"
|
||||
value === "scaleDown" ||
|
||||
value === "origin"
|
||||
);
|
||||
}
|
||||
|
||||
@ -167,12 +175,6 @@ function isOffScreenUpdateBehaviourType (value: string | null): value is OffScre
|
||||
);
|
||||
}
|
||||
|
||||
function isModeType (value: string | null): value is ModeType {
|
||||
return (
|
||||
value === "inside" ||
|
||||
value === "origin"
|
||||
);
|
||||
}
|
||||
const base64RegExp = /^(([A-Za-z0-9+/]{4})*([A-Za-z0-9+/]{4}|[A-Za-z0-9+/]{3}=|[A-Za-z0-9+/]{2}==))$/;
|
||||
export function isBase64 (str: string) {
|
||||
return base64RegExp.test(str);
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@esotericsoftware/spine-webgl",
|
||||
"version": "4.2.81",
|
||||
"version": "4.2.82",
|
||||
"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.81"
|
||||
"@esotericsoftware/spine-core": "4.2.82"
|
||||
}
|
||||
}
|
||||
@ -83,6 +83,8 @@ namespace Spine.Unity.Editor {
|
||||
}
|
||||
|
||||
public static class SpineBuildEnvUtility {
|
||||
public const string SPINE_ALLOW_UNSAFE_CODE = "SPINE_ALLOW_UNSAFE";
|
||||
|
||||
static bool IsInvalidGroup (BuildTargetGroup group) {
|
||||
int gi = (int)group;
|
||||
return
|
||||
@ -99,15 +101,18 @@ namespace Spine.Unity.Editor {
|
||||
if (IsInvalidGroup(group))
|
||||
continue;
|
||||
|
||||
string defines = PlayerSettings.GetScriptingDefineSymbolsForGroup(group);
|
||||
if (!defines.Contains(define)) {
|
||||
wasDefineAdded = true;
|
||||
if (defines.EndsWith(";", System.StringComparison.Ordinal))
|
||||
defines += define;
|
||||
else
|
||||
defines += ";" + define;
|
||||
try {
|
||||
string defines = PlayerSettings.GetScriptingDefineSymbolsForGroup(group);
|
||||
if (!defines.Contains(define)) {
|
||||
wasDefineAdded = true;
|
||||
if (defines.EndsWith(";", System.StringComparison.Ordinal))
|
||||
defines += define;
|
||||
else
|
||||
defines += ";" + define;
|
||||
|
||||
PlayerSettings.SetScriptingDefineSymbolsForGroup(group, defines);
|
||||
PlayerSettings.SetScriptingDefineSymbolsForGroup(group, defines);
|
||||
}
|
||||
} catch (System.Exception) {
|
||||
}
|
||||
}
|
||||
Debug.LogWarning("Please ignore errors \"PlayerSettings Validation: Requested build target group doesn't exist\" above");
|
||||
@ -127,15 +132,18 @@ namespace Spine.Unity.Editor {
|
||||
if (IsInvalidGroup(group))
|
||||
continue;
|
||||
|
||||
string defines = PlayerSettings.GetScriptingDefineSymbolsForGroup(group);
|
||||
if (defines.Contains(define)) {
|
||||
wasDefineRemoved = true;
|
||||
if (defines.Contains(define + ";"))
|
||||
defines = defines.Replace(define + ";", "");
|
||||
else
|
||||
defines = defines.Replace(define, "");
|
||||
try {
|
||||
string defines = PlayerSettings.GetScriptingDefineSymbolsForGroup(group);
|
||||
if (defines.Contains(define)) {
|
||||
wasDefineRemoved = true;
|
||||
if (defines.Contains(define + ";"))
|
||||
defines = defines.Replace(define + ";", "");
|
||||
else
|
||||
defines = defines.Replace(define, "");
|
||||
|
||||
PlayerSettings.SetScriptingDefineSymbolsForGroup(group, defines);
|
||||
PlayerSettings.SetScriptingDefineSymbolsForGroup(group, defines);
|
||||
}
|
||||
} catch (System.Exception) {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -39,6 +39,14 @@
|
||||
#define HAS_ON_POSTPROCESS_PREFAB
|
||||
#endif
|
||||
|
||||
#if UNITY_2021_2_OR_NEWER
|
||||
#define TEXT_ASSET_HAS_GET_DATA_BYTES
|
||||
#endif
|
||||
|
||||
#if TEXT_ASSET_HAS_GET_DATA_BYTES
|
||||
#define HAS_ANY_UNSAFE_OPTIONS
|
||||
#endif
|
||||
|
||||
using System.Threading;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
@ -356,6 +364,18 @@ namespace Spine.Unity.Editor {
|
||||
}
|
||||
#endif
|
||||
|
||||
#if HAS_ANY_UNSAFE_OPTIONS
|
||||
GUILayout.Space(20);
|
||||
EditorGUILayout.LabelField("Unsafe Build Defines", EditorStyles.boldLabel);
|
||||
using (new GUILayout.HorizontalScope()) {
|
||||
EditorGUILayout.PrefixLabel(new GUIContent("Direct data access", "Allow unsafe direct data access. Currently affects reading .skel.bytes files, reading with fewer allocations."));
|
||||
if (GUILayout.Button("Enable", GUILayout.Width(64)))
|
||||
SpineBuildEnvUtility.EnableBuildDefine(SpineBuildEnvUtility.SPINE_ALLOW_UNSAFE_CODE);
|
||||
if (GUILayout.Button("Disable", GUILayout.Width(64)))
|
||||
SpineBuildEnvUtility.DisableBuildDefine(SpineBuildEnvUtility.SPINE_ALLOW_UNSAFE_CODE);
|
||||
}
|
||||
#endif
|
||||
|
||||
#if SPINE_TK2D_DEFINE
|
||||
bool isTK2DDefineSet = true;
|
||||
#else
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
{
|
||||
"name": "spine-unity",
|
||||
"references": [ "spine-csharp" ]
|
||||
"references": [ "spine-csharp" ],
|
||||
"allowUnsafeCode": true
|
||||
}
|
||||
|
||||
@ -27,14 +27,39 @@
|
||||
* THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*****************************************************************************/
|
||||
|
||||
//#define SPINE_ALLOW_UNSAFE // note: this define can be set via Edit - Preferences - Spine.
|
||||
|
||||
#if UNITY_2021_2_OR_NEWER
|
||||
#define TEXT_ASSET_HAS_GET_DATA_BYTES
|
||||
#endif
|
||||
|
||||
#if SPINE_ALLOW_UNSAFE && TEXT_ASSET_HAS_GET_DATA_BYTES
|
||||
#define UNSAFE_DIRECT_ACCESS_TEXT_ASSET_DATA
|
||||
#endif
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
#if UNSAFE_DIRECT_ACCESS_TEXT_ASSET_DATA
|
||||
using Unity.Collections;
|
||||
#endif
|
||||
using UnityEngine;
|
||||
|
||||
using CompatibilityProblemInfo = Spine.Unity.SkeletonDataCompatibility.CompatibilityProblemInfo;
|
||||
|
||||
namespace Spine.Unity {
|
||||
#if UNSAFE_DIRECT_ACCESS_TEXT_ASSET_DATA
|
||||
public static class TextAssetExtensions {
|
||||
public static Stream GetStreamUnsafe (this TextAsset textAsset) {
|
||||
NativeArray<byte> dataNativeArray = textAsset.GetData<byte>();
|
||||
return dataNativeArray.GetUnmanagedMemoryStream();
|
||||
}
|
||||
|
||||
public static unsafe UnmanagedMemoryStream GetUnmanagedMemoryStream<T> (this NativeArray<T> nativeArray) where T : struct {
|
||||
return new UnmanagedMemoryStream((byte*)global::Unity.Collections.LowLevel.Unsafe.
|
||||
NativeArrayUnsafeUtility.GetUnsafeReadOnlyPtr(nativeArray), nativeArray.Length);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
[CreateAssetMenu(fileName = "New SkeletonDataAsset", menuName = "Spine/SkeletonData Asset")]
|
||||
public class SkeletonDataAsset : ScriptableObject {
|
||||
@ -188,9 +213,15 @@ namespace Spine.Unity {
|
||||
SkeletonData loadedSkeletonData = null;
|
||||
|
||||
try {
|
||||
if (hasBinaryExtension)
|
||||
if (hasBinaryExtension) {
|
||||
#if UNSAFE_DIRECT_ACCESS_TEXT_ASSET_DATA
|
||||
using (Stream stream = skeletonJSON.GetStreamUnsafe()) {
|
||||
loadedSkeletonData = SkeletonDataAsset.ReadSkeletonData(stream, attachmentLoader, skeletonDataScale);
|
||||
}
|
||||
#else
|
||||
loadedSkeletonData = SkeletonDataAsset.ReadSkeletonData(skeletonJSON.bytes, attachmentLoader, skeletonDataScale);
|
||||
else
|
||||
#endif
|
||||
} else
|
||||
loadedSkeletonData = SkeletonDataAsset.ReadSkeletonData(skeletonJSON.text, attachmentLoader, skeletonDataScale);
|
||||
} catch (Exception ex) {
|
||||
if (!quiet)
|
||||
@ -287,6 +318,13 @@ namespace Spine.Unity {
|
||||
}
|
||||
}
|
||||
|
||||
internal static SkeletonData ReadSkeletonData (Stream assetStream, AttachmentLoader attachmentLoader, float scale) {
|
||||
SkeletonBinary binary = new SkeletonBinary(attachmentLoader) {
|
||||
Scale = scale
|
||||
};
|
||||
return binary.ReadSkeletonData(assetStream);
|
||||
}
|
||||
|
||||
internal static SkeletonData ReadSkeletonData (string text, AttachmentLoader attachmentLoader, float scale) {
|
||||
StringReader input = new StringReader(text);
|
||||
SkeletonJson json = new SkeletonJson(attachmentLoader) {
|
||||
|
||||
@ -27,6 +27,16 @@
|
||||
* THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*****************************************************************************/
|
||||
|
||||
//#define SPINE_ALLOW_UNSAFE // note: this define can be set via Edit - Preferences - Spine.
|
||||
|
||||
#if UNITY_2021_2_OR_NEWER
|
||||
#define TEXT_ASSET_HAS_GET_DATA_BYTES
|
||||
#endif
|
||||
|
||||
#if SPINE_ALLOW_UNSAFE && TEXT_ASSET_HAS_GET_DATA_BYTES
|
||||
#define UNSAFE_DIRECT_ACCESS_TEXT_ASSET_DATA
|
||||
#endif
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
@ -106,8 +116,12 @@ namespace Spine.Unity {
|
||||
|
||||
if (fileVersion.sourceType == SourceType.Binary) {
|
||||
try {
|
||||
using (MemoryStream memStream = new MemoryStream(asset.bytes)) {
|
||||
fileVersion.rawVersion = SkeletonBinary.GetVersionString(memStream);
|
||||
#if UNSAFE_DIRECT_ACCESS_TEXT_ASSET_DATA
|
||||
using (Stream stream = asset.GetStreamUnsafe()) {
|
||||
#else
|
||||
using (MemoryStream stream = new MemoryStream(asset.bytes)) {
|
||||
#endif
|
||||
fileVersion.rawVersion = SkeletonBinary.GetVersionString(stream);
|
||||
}
|
||||
} catch (System.Exception e) {
|
||||
problemDescription = string.Format("Failed to read '{0}'. It is likely not a binary Spine SkeletonData file.\n{1}", asset.name, e);
|
||||
@ -162,8 +176,11 @@ namespace Spine.Unity {
|
||||
}
|
||||
|
||||
public static bool IsJsonFile (TextAsset file) {
|
||||
#if TEXT_ASSET_HAS_GET_DATA_BYTES
|
||||
var content = file.GetData<byte>();
|
||||
#else
|
||||
byte[] content = file.bytes;
|
||||
|
||||
#endif
|
||||
// check for binary skeleton version number string, starts after 8 byte hash
|
||||
char majorVersionChar = compatibleBinaryVersions[0][0].ToString()[0];
|
||||
if (content.Length > 10 && content[9] == majorVersionChar && content[10] == '.')
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user