Initial commit: Client Doc docs Server Tools
Co-authored-by: Cursor <cursoragent@cursor.com>
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 6b6125af1514a5e4e85028f30a7a0170
|
||||
timeCreated: 1780087327
|
||||
licenseType: Free
|
||||
DefaultImporter:
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,9 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 51f28f46cad34314f8f275f5e4434487
|
||||
folderAsset: yes
|
||||
timeCreated: 1759784347
|
||||
licenseType: Free
|
||||
DefaultImporter:
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,371 @@
|
||||
# spine-unity 4.3 分離コンポーネントへのアップグレードガイド
|
||||
## コンポーネントアーキテクチャの再構築
|
||||
|
||||
---
|
||||
|
||||
## 📋 本ドキュメントの対象範囲
|
||||
|
||||
**本ドキュメントでは、spine-unityにおける分離コンポーネントへの移行のみを扱います。** バージョン 4.2 から 4.3 への変更では、分離コンポーネント関係以外にも、spine-csharp および spine-unity の両方で破壊的変更が加えられています。分離コンポーネントへの対応を行う前に、先に以下を処理する必要があります:
|
||||
|
||||
- **spine-csharp API の変更:** ボーン、スロット、コンストレイントのプロパティに影響を与える新しいポーズシステム
|
||||
|
||||
4.2 から 4.3 へのすべての変更を網羅する完全な移行手順については、以下を参照してください:
|
||||
- [CHANGELOG.md](https://github.com/EsotericSoftware/spine-runtimes/blob/4.3/CHANGELOG.md#c-2) ドキュメント(「C#」および「Unity」セクション)
|
||||
- フォーラム投稿 ["Spine-Unity 4.2 to 4.3 Upgrade Guide"](https://esotericsoftware.com/forum) (こちらは包括的な移行ガイドです)
|
||||
|
||||
**重要:** spine-csharp API の移行を最初に完了してから、本ドキュメントで説明されている分離コンポーネントへの移行に進んでください。
|
||||
|
||||
---
|
||||
|
||||
## ⚠️ 重要:アップグレード前の必読事項
|
||||
|
||||
**この警告は、アップグレードを行う既存のプロジェクトのみに適用されます。新規プロジェクトの場合はこのセクションを安全に無視できます。**
|
||||
|
||||
### 何が起こるか
|
||||
|
||||
以下のコンポーネントが**自動的にアップグレード**され、アニメーションコンポーネントとレンダリングコンポーネントに分離されます:
|
||||
- `SkeletonAnimation` → `SkeletonAnimation` + `SkeletonRenderer` コンポーネント
|
||||
- `SkeletonMecanim` → `SkeletonMecanim` + `SkeletonRenderer` コンポーネント
|
||||
- `SkeletonGraphic` → `SkeletonAnimation` + `SkeletonGraphic` コンポーネント
|
||||
|
||||
すべてのコンポーネント設定とフィールドは自動的に転送されますので、それらが失われることはありません。
|
||||
|
||||
**ただし:** 今回の型の変更により、コンポーネントタイプが一致しなくなるため、カスタムスクリプトの既存の参照が失われる可能性があります(例えば、`SkeletonAnimation` はもはや `SkeletonRenderer` のサブクラスではなくなっています)。
|
||||
|
||||
### 必要なアップグレード手順(上から順番に行ってください):
|
||||
|
||||
1. **🔒 プロジェクトのバックアップを作成する**
|
||||
アップグレード前に完全なバックアップを作成してください。これらの変更によって、シーンとプレハブが変更されることになります。
|
||||
|
||||
2. **📖 このドキュメント全文を読む**
|
||||
続行する前にすべての破壊的変更の内容を把握してください。
|
||||
|
||||
3. **✏️ コードのテストと更新を行う**
|
||||
- **スクリプトを更新する:** このドキュメントに従って、Spineコンポーネントを参照するカスタムスクリプトを修正してください。メンバーが分離されたクラスに移動されると、コードがコンパイルできなくなる可能性があります。
|
||||
- **テストを行う:** いくつかのテストシーンを開いてみて、どのコンポーネント参照が失われるかを確認してください。未保存のシーンには古いコンポーネントデータが含まれており、セーフティバックアップとして機能します。保存すると、分離されたコンポーネントが古いものを置き換え、参照が失われます。
|
||||
**⚠️ 警告:** プレハブの場合は動作が異なります!「Prefab Edit Mode」でプレハブを開くと、Unityのプレハブ自動保存機能により、コンポーネントの移行が自動的に保存されてしまいます。まずはシーンでテストし、プレハブでテストしないようにしてください。
|
||||
- **対応を決める:** 失われた参照を手動で再割り当てするか、移行コードを記述して自動的に処理してください(詳しくは後述)。
|
||||
|
||||
**⚠️ 重要な注意事項:** 自動コンポーネント分離は、Unityエディターでシーン/プレハブが開かれたときにのみ発生します。ゲームをビルドする前に必ず、 `Upgrade All` を選択するか、各シーン/プレハブを保存してください。そうしないと、古い単一コンポーネントが分離されず、ビルドで必要なコンポーネントの半分が欠落してしまう可能性があります!
|
||||
|
||||
**💡 ヒント:** Consoleログに「SendMessage cannot be called during Awake, CheckConsistency, or OnValidate」という出力が表示された場合、これは古い未移行のアセットがシーンに存在し、自動アップグレードされたことを示しています。これらのメッセージの後に、古いコンポーネントが分離コンポーネントに自動移行されたことを確認するログメッセージが続きます。
|
||||
|
||||
4. **🔄 アップグレード方法を選択する**
|
||||
|
||||
**オプション A - 手動再割り当て(小規模プロジェクトの場合に推奨):**
|
||||
- 各シーンとプレハブを個別に開く
|
||||
- インスペクターで失われた参照を手動で再割り当てする
|
||||
- 問題が起きていないことを確認したら保存
|
||||
|
||||
**オプション B - 自動移行(多数のシーン/プレハブを持つ大規模プロジェクトの場合):**
|
||||
- 最初に移行コードを記述:
|
||||
- 参照移行パターンを実装(後述の「コンポーネント参照の損失を防ぐには」セクションを参照してください)
|
||||
- `SkeletonGraphic` 参照については、「既存の参照を SkeletonAnimation に変更」セクションを参照してください
|
||||
- いくつかのファイルで移行コードをテスト
|
||||
- その後 `Upgrade All` を使用:
|
||||
- `Edit → Preferences → Spine` に移動
|
||||
- `Upgrade Scenes & Prefabs` の下で
|
||||
- `Upgrade All` ボタンをクリック
|
||||
|
||||
### 準備しないと何が壊れるか:
|
||||
- シーンまたはプレハブを保存すると、シーンまたはプレハブ内のコンポーネント参照が失われる可能性があります(null に設定されます)
|
||||
- すべてのシーン/プレハブをアップグレードする前にビルドしてしまうと、ビルドでコンポーネントが欠落します
|
||||
|
||||
**ステップ 1-3 を完了せずに続行しないでください。プロジェクトが壊れるリスクがあります。**
|
||||
|
||||
---
|
||||
|
||||
## 📦 (任意)4.2 からの2ステップ移行
|
||||
|
||||
spine-unity 4.2から移行する場合、潜在的な問題を分離するために2ステップでアップグレードする方が簡単な場合があります:
|
||||
|
||||
### ステップ 1:コンポーネント分離前の4.3バージョンへアップグレードする
|
||||
まず、コンポーネント分離の変更が加わる前の4.3バージョンにアップグレードしてください:
|
||||
- **コミットハッシュ**: `a07b1de9ce972d4e38898ccf2cc84de16ff66c9b`
|
||||
- **Git URL でパッケージを追加**: `https://github.com/EsotericSoftware/spine-runtimes.git?path=spine-csharp/src#a07b1de9ce972d4e38898ccf2cc84de16ff66c9b`
|
||||
- または **unitypackage** を利用: https://esotericsoftware.com/files/runtimes/unity/spine-unity-4.2-2025-09-26.unitypackage
|
||||
|
||||
この中間ステップで、次の作業を行ってください:
|
||||
- 4.2 → 4.3 spine-csharp API 変更に関連する問題を修正し、プロジェクトが正常に動作することを確認してください
|
||||
- 続行する前にプロジェクトが安定していることを検証してください
|
||||
- この 4.3 バージョンでプロジェクトが正常に動作したら、ステップ2に進む前に別のバックアップを作成してください
|
||||
|
||||
### ステップ 2:最新の4.3バージョン(分離コンポーネントに対応したバージョン)へアップグレードする
|
||||
4.3でプロジェクトが正常に動作したら:
|
||||
- 最新の4.3パッケージにアップグレードしてください
|
||||
- 先述のコンポーネント分離移行ガイドに従ってください
|
||||
- 分離されたアニメーションコンポーネントとレンダリングコンポーネントを処理してください
|
||||
|
||||
この2ステップアプローチは、問題の原因を分離するのに役立ちます。つまり、何か問題が発生した場合に、それが 4.2 → 4.3 spine-csharp の変更によるものか、コンポーネント分離によるものかを判断できます。
|
||||
|
||||
---
|
||||
|
||||
## 📋 概要
|
||||
|
||||
spine-unity 4.3 ランタイムでは、大きなアーキテクチャ変更として、**コンポーネント継承ではなく、2つの独立したコンポーネントでアニメーションとレンダリングを分離する仕組みが導入されました**。これにより、以前は不可能だった `SkeletonMecanim` をアニメーションに、`SkeletonGraphic` をレンダリングに使用するなどの柔軟な組み合わせが可能になりました。
|
||||
|
||||
### 主な変更点:
|
||||
- **コンポーネント分離**: 以前の一体型コンポーネントが、レンダリングとアニメーションでそれぞれ別のコンポーネントに分離されました。
|
||||
- **個別のコンポーネント**: `SkeletonAnimation` と `SkeletonMecanim` は `SkeletonRenderer` から継承されなくなり、`SkeletonRenderer` や `SkeletonGraphic` などの個別のレンダラーコンポーネント(`ISkeletonRenderer` のサブクラス)と連携して動作します。
|
||||
- **インターフェースの更新**: プロパティ名が更新された新しい `ISkeletonRenderer` および `ISkeletonAnimation` インターフェース。
|
||||
- **設定のグループ化**: メッシュ生成設定が `MeshSettings` プロパティにまとめられました。
|
||||
- **自動移行**: `AUTO_UPGRADE_TO_43_COMPONENTS` が定義されている場合(*デフォルト*)、Unityエディターがコンポーネントを新しい分離コンポーネントに自動アップグレードし、古いフィールドを移行します。
|
||||
- **すべてのシーンとプレハブをアップグレード**: すべてのシーンとプレハブを一度にアップグレードするには、`Edit - Preferences - Spine` に移動し、`Automatic Component Upgrade` の下の `Upgrade Scenes & Prefabs` - `Upgrade All` をクリックします。
|
||||
|
||||
### コンポーネントの関係:
|
||||
|
||||
| **旧アーキテクチャ** | **新アーキテクチャ** |
|
||||
|---------------------|---------------------|
|
||||
| `SkeletonAnimation` は `SkeletonRenderer` から継承 | `SkeletonAnimation` + 個別の `SkeletonRenderer` コンポーネント |
|
||||
| `SkeletonMecanim` は `SkeletonRenderer` から継承 | `SkeletonMecanim` + 個別の `SkeletonRenderer` コンポーネント |
|
||||
| `SkeletonGraphic` は埋め込み AnimationState を提供 | `SkeletonGraphic` + 個別の `SkeletonAnimation` コンポーネント |
|
||||
|
||||
---
|
||||
|
||||
## コードの適応
|
||||
|
||||
各コンポーネントの変更点:
|
||||
|
||||
## ▶️ SkeletonRenderer
|
||||
|
||||
### 破壊的変更
|
||||
|
||||
#### 1. インターフェースの変更
|
||||
- `IHasSkeletonRenderer` インターフェースのプロパティ名が `SkeletonRenderer` から `Renderer` に、型が `SkeletonRenderer` から `ISkeletonRenderer` に変更されました。
|
||||
|
||||
#### 2. イベントの変更
|
||||
- `SkeletonRendererDelegate` 型は `SkeletonRenderer` のネストされた型ではなくなりました。
|
||||
コンパイルエラーを修正するには、`SkeletonRenderer.SkeletonRendererDelegate` を `SkeletonRendererDelegate` に置き換えてください。
|
||||
- `BeforeApply` デリゲート型が `SkeletonRendererDelegate` から `SkeletonAnimationDelegate` に変更されました。
|
||||
- `SkeletonRendererDelegate` 型が `SkeletonRendererDelegate(SkeletonRenderer)` から `SkeletonRendererDelegate(ISkeletonRenderer)` に変更されました。これは `OnRebuild` および `OnMeshAndMaterialsUpdated` イベントに影響します。
|
||||
コンパイルエラーを修正するには、メソッドパラメータを `SkeletonRenderer` から `ISkeletonRenderer` に変更してください。
|
||||
- ボーンイベント `UpdateLocal`、`UpdateWorld`、`UpdateComplete` を `ISkeletonAnimation` クラス(`SkeletonAnimation`、`SkeletonMecanim`)から `ISkeletonRenderer` クラス(SkeletonRenderer、SkeletonGraphic)に移動しました。デリゲート型を `UpdateBonesDelegate` から `SkeletonRendererDelegate` に変更し、パラメータを `ISkeletonAnimation` から `ISkeletonRenderer` に変更しました。
|
||||
|
||||
#### 2. メソッドの変更
|
||||
- `LateUpdateMesh()` が `UpdateMesh()` に変更されました。
|
||||
- `MeshGenerator.TryReplaceMaterials` を削除しました。
|
||||
|
||||
#### 3. 実行順序
|
||||
- `SkeletonRenderer` と `SkeletonGraphic` コンポーネントは `DefaultExecutionOrder(1)]` を受け取り、デフォルト*(order=0)*スクリプトの後に実行されます。これにより、`UpdateTiming` が `InLateUpdate` に設定されていても、スケルトンが更新される前にアニメーションが適用されます。
|
||||
|
||||
#### 4. 動作の変更
|
||||
- `singleSubmesh` が有効な場合、`generateMeshOverride` も呼び出されるようになりました。
|
||||
|
||||
### フィールドとプロパティの移行
|
||||
|
||||
#### メッシュ生成設定
|
||||
| **旧 API** | **新 API** | **備考** |
|
||||
|-------------------|-------------------|-----------|
|
||||
| `skeletonRenderer.zSpacing` | `skeletonRenderer.MeshSettings.zSpacing` | MeshSettings に移動 |
|
||||
| `skeletonRenderer.useClipping` | `skeletonRenderer.MeshSettings.useClipping` | MeshSettings に移動 |
|
||||
| `skeletonRenderer.immutableTriangles` | `skeletonRenderer.MeshSettings.immutableTriangles` | MeshSettings に移動 |
|
||||
| `skeletonRenderer.pmaVertexColors` | `skeletonRenderer.MeshSettings.pmaVertexColors` | MeshSettings に移動 |
|
||||
| `skeletonRenderer.tintBlack` | `skeletonRenderer.MeshSettings.tintBlack` | MeshSettings に移動 |
|
||||
| `skeletonRenderer.addNormals` | `skeletonRenderer.MeshSettings.addNormals` | MeshSettings に移動 |
|
||||
| `skeletonRenderer.calculateTangents` | `skeletonRenderer.MeshSettings.calculateTangents` | MeshSettings に移動 |
|
||||
|
||||
#### 非公開の小文字属性
|
||||
- `skeletonRenderer.maskInteraction` は、プロパティ `skeletonRenderer.MaskInteraction` に置き換えられました。
|
||||
|
||||
#### 非推奨の小文字属性
|
||||
- 小文字の属性 `initialFlipX`、`initialFlipY`、`initialSkinName` は非推奨となりました。将来のランタイムバージョンで削除される予定です。代わりに同じ名前の大文字のプロパティ、`InitialFlipX`、`InitialFlipY`、`InitialSkinName` を使用してください。
|
||||
|
||||
---
|
||||
|
||||
## ▶️ SkeletonAnimation
|
||||
|
||||
### 破壊的変更
|
||||
|
||||
#### 1. コンポーネントアーキテクチャ
|
||||
- **SkeletonAnimation は SkeletonRenderer とは別のコンポーネントになりました**、もはやサブクラスではありません。
|
||||
- レンダラーにアクセスしたい場合は `skeletonAnimation.Renderer` を使用してください。
|
||||
- レンダラーからアニメーションにアクセスしたい場合は `skeletonRenderer.Animation` を使用してください。
|
||||
|
||||
#### 2. プロパティの変更
|
||||
- `state` はパブリックではなくなりました。代わりに `AnimationState` プロパティを使用します。
|
||||
- `valid` は削除されました。代わりに `IsValid` を使用します。
|
||||
|
||||
#### 3. メソッドの変更
|
||||
- `UpdateOncePerFrame()` を追加 - このフレームで `Update` が呼び出されていない場合に更新します。既存の `Update(float time)` は呼び出されると常に更新します。
|
||||
- パラメータなしの `Update()` はパブリックではなくなりました。このフレームで更新が実行されていない場合は `UpdateOncePerFrame()` を使用し、
|
||||
アニメーションの更新を強制する場合は `Update(0)` を使用します。
|
||||
|
||||
### フィールドとプロパティの移行
|
||||
|
||||
`SkeletonAnimation` は独立したコンポーネントになったため、`SkeletonRenderer` が提供するメソッドとプロパティは `SkeletonAnimation` オブジェクトから直接アクセスできなくなりました。`SkeletonAnimation` オブジェクトから対応する `ISkeletonRenderer` にアクセスしたい場合は、`skeletonAnimation.Renderer` プロパティを使用してください。
|
||||
また、`SkeletonRenderer` や `SkeletonGraphic` オブジェクトから関連する `ISkeletonAnimation` にアクセスしたい場合は、`skeletonRenderer.Animation` プロパティを使用してください。
|
||||
`ISkeletonRenderer` インターフェースで公開されていないメンバーにアクセスする必要がある場合は、`skeletonAnimation.Renderer` を `SkeletonRenderer` または `SkeletonGraphic` にキャストしてレンダラーメンバー変数にアクセスできます。
|
||||
|
||||
### コード例
|
||||
```csharp
|
||||
// 旧式の SkeletonAnimation から SkeletonRenderer プロパティへのアクセス方法
|
||||
skeletonAnimation.zSpacing = 0.1f;
|
||||
skeletonAnimation.AnySkeletonRendererProperty;
|
||||
|
||||
// 新しくなった SkeletonAnimation から SkeletonRenderer プロパティへのアクセス方法
|
||||
skeletonAnimation.Renderer.MeshSettings.zSpacing = 0.1f; // ISkeletonRenderer インターフェースで公開
|
||||
var skeletonRenderer = (SkeletonRenderer)skeletonAnimation.Renderer;
|
||||
skeletonRenderer.AnySkeletonRendererProperty; // ISkeletonRenderer インターフェースで公開されていない
|
||||
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## ▶️ SkeletonMecanim
|
||||
|
||||
### 破壊的変更
|
||||
|
||||
#### 1. コンポーネントアーキテクチャ
|
||||
- **SkeletonMecanim は SkeletonRenderer とは別のコンポーネントになりました**、もはやサブクラスではありません。
|
||||
- アクセスパターンは[SkeletonAnimation](#▶️-skeletonanimation) と同じです。
|
||||
|
||||
#### 2. メソッドの変更
|
||||
- `Update()` はパブリックではなくなりました。
|
||||
- 強制更新には `UpdateIfNecessary()` または `Update(0)` を使用します。
|
||||
|
||||
### フィールドとプロパティの移行
|
||||
|
||||
先述の [SkeletonAnimation](#▶️-skeletonanimation) と同じです。
|
||||
---
|
||||
|
||||
## ▶️ SkeletonGraphic
|
||||
|
||||
### 破壊的変更
|
||||
|
||||
#### 1. コンポーネントアーキテクチャ
|
||||
- **SkeletonGraphic はアニメーションを扱わなくなりました。個別のアニメーションコンポーネントとして SkeletonAnimation を追加してください**。
|
||||
- アニメーションにアクセスしたい場合は `skeletonGraphic.Animation` を使用してください。
|
||||
|
||||
#### 2. 既存の参照を SkeletonAnimation に変更
|
||||
|
||||
コンポーネントがアニメーションプロパティを変更するためだけに `SkeletonGraphic` への参照を保持している場合は、参照タイプを `SkeletonAnimation` に変更することをお勧めします。
|
||||
これにより、`((SkeletonAnimation)skeletonGraphic.Animation).AnimationState` のようにキャストする代わりに
|
||||
`skeletonAnimation.AnimationState` のようにAnimationStateにアクセスできます。シリアライズされたコンポーネント変数の名前を変更したい場合は、変数定義の前に `[FormerlySerializedAs("previousName")]` 属性を付与することで、既存のシリアライズ値を自動的に再割り当てできます。
|
||||
|
||||
#### コード例
|
||||
```csharp
|
||||
// 以前のシリアライズされた値を自動的に再割り当て
|
||||
[FormerlySerializedAs("skeletonGraphic")]
|
||||
public SkeletonAnimation skeletonAnimation; // アップグレード後も参照を維持
|
||||
```
|
||||
|
||||
#### 3. プロパティの変更
|
||||
- プロパティ `AnimationState` を削除しました。 代わりに `SkeletonAnimation` コンポーネントから照会します。
|
||||
- `MeshGenerator` はパブリックではなくなりました。代わりに `MeshSettings` プロパティと `SetMeshSettings()` を使用します。
|
||||
- `MaterialsMultipleCanvasRenderers` の型が `ExposedList<Material>` から `Material[]` に変更。
|
||||
|
||||
#### 4. 作成ヘルパーメソッド
|
||||
- `NewSkeletonGraphicGameObject` は既存の動作を維持するために `SkeletonGraphic` と `SkeletonAnimation` の両方を作成するようになりました。戻り値の型が変更され、両方のコンポーネント参照を単一の構造体で返します。
|
||||
- `AddSkeletonGraphicComponent` は削除され、次のものに置き換えられました:
|
||||
- `AddSkeletonGraphicAnimationComponents` - `SkeletonGraphic` と `SkeletonAnimation` コンポーネントの両方を作成。
|
||||
- `AddSkeletonGraphicRenderingComponent` - `SkeletonGraphic` コンポーネントのみを作成。
|
||||
|
||||
#### 5. イベントの変更
|
||||
- デリゲートシグネチャが `SkeletonRendererDelegate(SkeletonGraphic)` から `SkeletonRendererDelegate(ISkeletonRenderer)` に変更されました。これは、`OnRebuild` および `OnMeshAndMaterialsUpdated` イベントに影響します:
|
||||
コンパイルエラーを修正するには、メソッドパラメータを SkeletonGraphic から ISkeletonRenderer に変更してください。
|
||||
|
||||
#### 6. 実行順序
|
||||
- `SkeletonRenderer` と `SkeletonGraphic` コンポーネントは `DefaultExecutionOrder(1)]` を受け取り、デフォルト*(order=0)*スクリプトの後に実行されます。これにより、`UpdateTiming` が `InLateUpdate` に設定されていても、スケルトンが更新される前にアニメーションが適用されます。
|
||||
|
||||
#### 7. 動作の変更 - マテリアルの更新
|
||||
- 各 `CanvasRenderer` のマテリアルは、毎回 `LateUpdate` で更新されなくなりました。代わりに次の場合に更新されます:
|
||||
- a) 更新されたスケルトンがマテリアルの変更を必要とする場合、または
|
||||
- b) `CustomMaterialOverride` または `CustomTextureOverride` がアクセスされ、潜在的に変更された場合。
|
||||
|
||||
### フィールドとプロパティの移行
|
||||
|
||||
#### アニメーションプロパティ
|
||||
| **旧 API** | **新 API** | **備考** |
|
||||
|-------------------|-------------------|-----------|
|
||||
| `skeletonGraphic.AnimationState` | `((SkeletonAnimation)skeletonGraphic.Animation).AnimationState` | キャストが必要 |
|
||||
| `skeletonGraphic.startingAnimation` | `skeletonAnimation.AnimationName` | アニメーションコンポーネント経由 |
|
||||
| `skeletonGraphic.startingLoop` | `skeletonAnimation.loop` | アニメーションコンポーネント経由 |
|
||||
| `skeletonGraphic.timeScale` | `skeletonAnimation.timeScale` | アニメーションコンポーネント経由 |
|
||||
| `skeletonGraphic.unscaledTime` | `skeletonAnimation.unscaledTime` | アニメーションコンポーネント経由 |
|
||||
|
||||
#### メッシュジェネレーター設定
|
||||
| **旧 API** | **新 API** | **備考** |
|
||||
|-------------------|-------------------|-----------|
|
||||
| `skeletonGraphic.MeshGenerator.settings` | `skeletonGraphic.MeshSettings` | 設定への直接アクセス |
|
||||
|
||||
---
|
||||
|
||||
## ⚠️ その他の重要な注意事項
|
||||
|
||||
### コンポーネント参照の損失を防ぐには
|
||||
`SkeletonRenderer` コンポーネントを参照し、`SkeletonAnimation` または `SkeletonMecanim` ターゲットを持っていた他のコンポーネント(例:`SkeletonRenderSeparator`)は、アップグレード後に *null* を指すようになります。`SkeletonAnimation` と `SkeletonMecanim` コンポーネントはもはや `SkeletonRenderer` のサブクラスではなく、有効な参照ではないためです。手動の解決策は、型を `SkeletonRenderer` のままにして `SkeletonAnimation` への参照を失うことです(*none* に設定されます)。その後、シーンとプレハブで失われた参照を手動で再割り当てする必要があります。半自動の代替解決策は次のとおりです:`SkeletonRenderer` 変数の型を `Component` に変更して(オブジェクト参照をキャプチャして失わないようにする)、`SkeletonRenderer`(または `SkeletonAnimation`)型の 2 番目の変数を追加し、プログラム的に `Component` 変数から読み取って新しく追加された変数に割り当てます。たとえば、コンポーネントに `[ExecuteAlways]` タグを追加して、Unityエディターの `Awake()` で自動的にこれを実行できます。
|
||||
|
||||
#### コード例
|
||||
```csharp
|
||||
// アップグレード前の古いクラス
|
||||
public class TestMigrateReferences : MonoBehaviour {
|
||||
public SkeletonRenderer skeletonRenderer; // アップグレード後、ここに割り当てられた SkeletonAnimation 参照は失われます。
|
||||
|
||||
public void Foo () {
|
||||
skeletonRenderer.skeleton.ScaleX = -1;
|
||||
}
|
||||
}
|
||||
|
||||
// アップグレード後の新しいクラス
|
||||
[ExecuteAlways] // または [ExecuteInEditMode]
|
||||
public class TestMigrateReferences : MonoBehaviour {
|
||||
#if UNITY_EDITOR
|
||||
[SerializeField, HideInInspector, FormerlySerializedAs("skeletonRenderer")] Component previousSkeletonRenderer; // これは skeletonRenderer という名前で割り当てられた古い SkeletonAnimation 参照をキャプチャします。
|
||||
#endif
|
||||
public SkeletonRenderer skeletonRenderer;
|
||||
|
||||
public void Foo () {
|
||||
skeletonRenderer.skeleton.ScaleX = -1;
|
||||
}
|
||||
|
||||
#if UNITY_EDITOR
|
||||
public void Awake () {
|
||||
AutoUpgradeReferences();
|
||||
}
|
||||
|
||||
public void AutoUpgradeReferences () {
|
||||
if (previousSkeletonRenderer != null && skeletonRenderer == null) {
|
||||
skeletonRenderer = previousSkeletonRenderer.GetComponent<SkeletonRenderer>();
|
||||
if (skeletonRenderer != null)
|
||||
Debug.Log("Upgraded SkeletonRenderer reference.");
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
```
|
||||
|
||||
### コンポーネントの有効化/無効化
|
||||
`ISkeletonRenderer` と `ISkeletonAnimation` コンポーネントが分離されたため、これらのコンポーネントのいずれかを有効/無効にするスクリプトは、**両方を有効/無効にする**ように調整する必要があります。
|
||||
|
||||
### SkeletonUtilityBone の動作変更
|
||||
Overrideモードでは、`SkeletonUtilityBone` は `UpdatePhase.World` でTransformを調整しなくなりました。`UpdatePhase.Complete` でのみ調整します(冗長な更新を削除)。
|
||||
|
||||
### 自動移行
|
||||
- `AUTO_UPGRADE_TO_43_COMPONENTS` が定義されている場合、Unityエディターは、廃止されたフィールドを自動的に転送します。
|
||||
- すべてのシーンとプレハブを一度にアップグレードするには、`Edit - Preferences - Spine` に移動し、`Upgrade Scenes & Prefabs` - `Upgrade All` を選択します。
|
||||
- 各クラスの `UpgradeTo43` と `TransferDeprecatedFields()` メソッドがシリアライズされたデータの移行を処理します。
|
||||
- ランタイムアクセスには手動のコード更新が必要です。
|
||||
|
||||
### 最も一般的な変更の要約
|
||||
1. アニメーションコンポーネントからレンダリングプロパティにアクセスする場合は、プレフィックス **`.Renderer.`** を付けてアクセスしてください。
|
||||
2. メッシュ生成設定にアクセスするには **`.MeshSettings.`** を付けてアクセスしてください。
|
||||
3. SkeletonGraphic から AnimationState にアクセスする場合は、 **SkeletonAnimation にキャストしてください**。
|
||||
4. 具体的な型からインターフェースへの**デリゲートメソッドシグネチャ更新**してください。
|
||||
5. アップグレード後は、上記の移行パターンを使用して**失われた参照を再割り当て**してください。
|
||||
|
||||
---
|
||||
|
||||
## 自動アップグレードチェックの無効化
|
||||
|
||||
すべてのSpineアセット、シーン、プレハブの移行が完了したら、エディターのパフォーマンスを向上させるために自動アップグレードチェックを無効にしてください:
|
||||
|
||||
1. `Edit → Preferences → Spine` に移動
|
||||
2. `Automatic Component Upgrade` の下で、`Split Component Upgrade` → `Disable` をクリック
|
||||
|
||||
これにより、Unityエディターがシーンまたはプレハブのロード時にコンポーネントの移行が必要かどうかを判断するためのエディター内チェックの実行を停止します。追加のアセットを移行する必要がある場合は、いつでも再度有効にできます。
|
||||
|
||||
---
|
||||
|
||||
## ヘルプが必要な場合
|
||||
|
||||
移行中に予期しない問題が発生した場合や、コンポーネントプロパティが正しく移行されない場合は、どうぞ遠慮なく[Spineフォーラム](https://esotericsoftware.com/forum)で質問してください。できるだけスムーズに自動移行できるように、喜んで問題解決のお手伝いをします。
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 84b6c350ead7f64469652d531034991c
|
||||
timeCreated: 1759835002
|
||||
licenseType: Free
|
||||
DefaultImporter:
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,418 @@
|
||||
# spine-unity 4.3 模块化组件升级指南
|
||||
|
||||
## 组件架构重构
|
||||
|
||||
---
|
||||
|
||||
## 📋 本指南的适用范围
|
||||
|
||||
**本文档仅涵盖 spine-unity 模块化组件迁移流程.** 当从 4.2 升级至 4.3 时, spine-csharp 和 spine-unity 中仍包含其他破坏性更改, 在着手模块化组件升级前应先解决这些问题:
|
||||
|
||||
* **spine-csharp API 变更:** 全新引入的姿态(pose)系统显著地改变了访问骨骼、槽位和约束属性的方式
|
||||
|
||||
如需获取涵盖 4.2 至 4.3 所有变更的完整迁移步骤, 请参阅以下官方文档:
|
||||
|
||||
* 运行时 [CHANGELOG.md](https://github.com/EsotericSoftware/spine-runtimes/blob/4.3/CHANGELOG.md#c-2) 的 C# 和 Unity 一节
|
||||
* 论坛置顶帖 ["Spine-Unity 4.2 to 4.3升级指南"](https://zh.esotericsoftware.com/forum/d/29263-zh-cnspine-unity-42-to-43%E5%8D%87%E7%BA%A7%E6%8C%87%E5%8D%97)
|
||||
|
||||
**重要提示:** 请务必优先完成 spine-csharp API 的迁移, 再依据本指南执行模块化组件迁移操作.
|
||||
|
||||
---
|
||||
|
||||
## ⚠️ 重要提醒: 升级前须知
|
||||
|
||||
**本提醒仅用于升级现有项目, 新项目可忽略本节内容.**
|
||||
|
||||
### 升级后将发生的变化
|
||||
|
||||
组件将**自动升级**并分离为相互独立的动画与渲染组件:
|
||||
|
||||
* `SkeletonAnimation` → `SkeletonAnimation` + `SkeletonRenderer` 组件
|
||||
* `SkeletonMecanim` → `SkeletonMecanim` + `SkeletonRenderer` 组件
|
||||
* `SkeletonGraphic` → `SkeletonAnimation` + `SkeletonGraphic` 组件
|
||||
|
||||
所有组件设置与字段将被自动迁移 - 不会造成数据丢失.
|
||||
|
||||
**但请注意:** 由于变更了组件类型, 项目脚本中对原组件的引用可能失效 (例如, `SkeletonAnimation` 不再继承自 `SkeletonRenderer`).
|
||||
|
||||
### 必须****依序****执行的项目升级步骤
|
||||
|
||||
1. **🔒 备份项目**
|
||||
在升级前请完整备份项目. 升级操作将修改场景与预制件文件.
|
||||
|
||||
2. **📖 通读本指南全文**
|
||||
在执行升级操作前, 务必充分理解全部的破坏性变更点.
|
||||
|
||||
3. **✏️ 测试并更新你的代码**
|
||||
* **更新代码:** 根据本指南修正所有引用了 Spine 组件的脚本, 由于类成员已迁移至模块化类, 项目代码将可能无法编译.
|
||||
* **测试:** 打开若干测试场景, 检查丢了失哪些引用. 未保存的场景仍会保留旧组件数据, 不妨将其作为安全备份——仅当你保存场景后旧组件才会被替换为模块化组件, 此时旧的组件引用便会失效.
|
||||
**⚠️ 警告:** 小心对待预制件(Prefab)! 在预制件编辑模式中打开预制件的话, 它会因 Unity 的自动保存机制立即触发组件迁移. 请优先在场景范围内测试, 避免直接操作预制体.
|
||||
* **可选操作:** 也可以一个个地手动重新分配丢失的引用, 或编写一套自动化迁移脚本(详见下文)来自动迁移组件.
|
||||
|
||||
**⚠️ 重要提示:** 自动组件升级仅在 Unity 编辑器打开了场景或预制件时触发. 在构建项目前, 你必须选择 'Upgrade All' 或逐一保存所有场景与预制件来确保没有残留旧组件, 否则可能导致构建中缺失必要组件而失败!
|
||||
|
||||
**💡 提示:** 若控制台输出信息中包含 "SendMessage cannot be called during Awake, CheckConsistency, or OnValidate" 字样,则表明场景中的旧资产已完成自动升级. 此消息将后附日志信息来确认旧组件已成功迁移至模块化组件.
|
||||
|
||||
4. **🔄 选择升级方式**
|
||||
|
||||
**选项 A - 手动重分配 (推荐小型项目采用):**
|
||||
* 逐一打开各场景与预制件
|
||||
* 在检查器(Inspector)中手动分配缺失的引用
|
||||
* 确认无误后保存
|
||||
|
||||
**选项 B - 自动化迁移 (推荐含大量场景/预制件的大型项目采用):**
|
||||
* 首先编写迁移代码:
|
||||
* 实现引用迁移模式 (详见下文中的 "防止丢失组件引用" 一节)
|
||||
* 对于 `SkeletonGraphic` 的引用, 请见 "将已有引用迁至 SkeletonAnimation" 一节
|
||||
* 在少量文件上测试迁移代码
|
||||
* 使用 `Upgrade All` 按钮:
|
||||
* 打开 `Edit → Preferences → Spine`
|
||||
* 在 "Upgrade Scenes & Prefabs" 中
|
||||
* 点击 `Upgrade All` 按钮
|
||||
|
||||
### 若仓促升级可能会导致以下后果
|
||||
|
||||
* 场景或预制件中的 Spine 组件引用在保存后将丢失 (引用被置null)
|
||||
|
||||
* 在未能升级全部场景/预制件便匆忙执行构建, 可能导致构建过程中缺失必要组件而失败
|
||||
|
||||
**请勿跳过步骤 1-3, 否则项目极有可能遭到破坏.**
|
||||
|
||||
---
|
||||
|
||||
## 📦 可选方案: 4.2 版的两阶段迁移
|
||||
|
||||
若你正从 spine-unity 4.2 升级至最新版, 建议遵循两阶段迁移方案来隔离潜在问题:
|
||||
|
||||
### 阶段 1: 升级至 4.3 非模块化组件版本
|
||||
|
||||
第一步是升级至 4.3 非模块化组件版本:
|
||||
|
||||
* **Commit Hash**: `a07b1de9ce972d4e38898ccf2cc84de16ff66c9b`
|
||||
* **使用 Git URL 添加包**: `https://github.com/EsotericSoftware/spine-runtimes.git?path=spine-csharp/src#a07b1de9ce972d4e38898ccf2cc84de16ff66c9b`
|
||||
* 或使用 **unitypackage**: <https://esotericsoftware.com/files/runtimes/unity/spine-unity-4.2-2025-09-26.unitypackage>
|
||||
|
||||
这一阶段可以:
|
||||
|
||||
* 优先解决 4.2 → 4.3 spine-csharp API 变更问题, 同时确保项目正常
|
||||
* 在升级模块化组件前验证项目稳定性
|
||||
* 确认项目在 4.3 下稳定后, 有机会再次备份完整项目, 为阶段 2的迁移工作作出充分准备
|
||||
|
||||
### 阶段 2: 升级至最新的 4.3 模块化组件版本
|
||||
|
||||
当项目能够在 4.3 非模块化组件版下稳定工作后:
|
||||
|
||||
* Up升级至最新 4.3 包
|
||||
* 按照本指南所述执行模块化组件迁移
|
||||
* 处理独立的动画与渲染组件
|
||||
|
||||
这种两阶段策略可有效隔离问题源头: 项目出现故障时, 可以明确地判断出是 4.2 → 4.3 的 spine-csharp API 变更导致的, 还是模块化组件所致.
|
||||
|
||||
---
|
||||
|
||||
## 📋 简介
|
||||
|
||||
spine-unity 4.3 运行时引入了重大架构变更: **相互独立的动画与渲染组件取代原有的继承结构**. 该设计让用户可灵活地选用组件, 例如在项目中让 `SkeletonMecanim` 负责控制动画, 同时采用 `SkeletonGraphic` 执行渲染——这是此前无法实现的.
|
||||
|
||||
### 关键更改
|
||||
|
||||
* **模块化组件**: 旧组件被拆分为了相互独立的渲染与动画组件.
|
||||
|
||||
* **独立组件架构**: `SkeletonAnimation` 和 `SkeletonMecanim` 不再继承自 `SkeletonRenderer`, 而是与独立渲染器组件(`ISkeletonRenderer` 的子类)协同工作, 例如 `SkeletonRenderer` 和 `SkeletonGraphic`.
|
||||
* **接口更新**: 新增了命名统一的 `ISkeletonRenderer` 和 `ISkeletonAnimation` 接口.
|
||||
* **设置分组**: 网格生成的相关设置集中到了 `MeshSettings` 属性中.
|
||||
* **自动化迁移**: 当启用 `AUTO_UPGRADE_TO_43_COMPONENTS` (*此版本默认启用*)后, Unity 编辑器将自动升级组件并迁移已废弃的字段.
|
||||
* **升级全部场景和预制件**: 如需一键升级全部场景与预制件, 请打开 `Edit - Preferences - Spine`, 并在 `Automatic Component Upgrade` 中点击 `Upgrade Scenes & Prefabs` - `Upgrade All` 即可.
|
||||
|
||||
### 各组件关系
|
||||
|
||||
| **旧架构** | **新架构** |
|
||||
|---------------------|---------------------|
|
||||
| `SkeletonAnimation` 继承自 `SkeletonRenderer` | `SkeletonAnimation` + 独立的 `SkeletonRenderer` 组件 |
|
||||
| `SkeletonMecanim` 继承自 `SkeletonRenderer` | `SkeletonMecanim` + 独立的 `SkeletonRenderer` 组件 |
|
||||
| `SkeletonGraphic` 内置 AnimationState | `SkeletonGraphic` + 独立的 `SkeletonAnimation` 组件 |
|
||||
|
||||
---
|
||||
|
||||
## 适配你的代码
|
||||
|
||||
以下为各组件的具体变更说明:
|
||||
|
||||
## ▶️ SkeletonRenderer
|
||||
|
||||
### 破坏性变更
|
||||
|
||||
#### 1. 接口变更
|
||||
|
||||
* 接口 `IHasSkeletonRenderer` 中的属性名 `SkeletonRenderer` 改为 `Renderer`, 类型由 `SkeletonRenderer` 改为 `ISkeletonRenderer`.
|
||||
|
||||
#### 2. 事件变更
|
||||
|
||||
* `SkeletonRendererDelegate` 类型不再为 `SkeletonRenderer` 的嵌套类型.
|
||||
如需修复编译错误, 只需将 `SkeletonRenderer.SkeletonRendererDelegate` 替换为 `SkeletonRendererDelegate`.
|
||||
|
||||
* `BeforeApply` 委托类型由 `SkeletonRendererDelegate` 更改为 `SkeletonAnimationDelegate`.
|
||||
* `SkeletonRendererDelegate` 委托签名由 `SkeletonRendererDelegate(SkeletonRenderer)` 更改为 `SkeletonRendererDelegate(ISkeletonRenderer)`. 该更改会影响以下事件: `OnRebuild`, `OnMeshAndMaterialsUpdated`.
|
||||
如需修复编译错误, 只需将方法实参从 `SkeletonRenderer` 更改为 `ISkeletonRenderer`.
|
||||
* 骨骼事件 `UpdateLocal`, `UpdateWorld`, 和 `UpdateComplete` 从 `ISkeletonAnimation` 类 (`SkeletonAnimation`, `SkeletonMecanim`) 迁移至 `ISkeletonRenderer (SkeletonRenderer, SkeletonGraphic)` 类中. 委托类型由 `UpdateBonesDelegate` 改为 `SkeletonRendererDelegate`, 参数由 `ISkeletonRenderer` 改为了 `ISkeletonAnimation`.
|
||||
|
||||
#### 2. 方法变更
|
||||
|
||||
* `LateUpdateMesh()` 改为了 `UpdateMesh()`.
|
||||
|
||||
* 移除了 `MeshGenerator.TryReplaceMaterials`.
|
||||
|
||||
#### 3. 执行顺序
|
||||
|
||||
* `SkeletonRenderer` 和 `SkeletonGraphic` 组件接受 `DefaultExecutionOrder(1)]` 参数, 以确保其在默认脚本*(即 order=0)*之后执行. 此举保证了即使 `UpdateTiming` 置为 `InLateUpdate`, 动画仍能先于 skeleton 更新.
|
||||
|
||||
#### 4. 行为变更
|
||||
|
||||
* 在启用 `singleSubmesh` 后, 现在也会调用 `generateMeshOverride`.
|
||||
|
||||
### 字段与属性迁移
|
||||
|
||||
#### 网格生成器设置
|
||||
|
||||
| **旧 API** | **新 API** | **说明** |
|
||||
|-------------------|-------------------|-----------|
|
||||
| `skeletonRenderer.zSpacing` | `skeletonRenderer.MeshSettings.zSpacing` | 已迁移至 MeshSettings |
|
||||
| `skeletonRenderer.useClipping` | `skeletonRenderer.MeshSettings.useClipping` | 已迁移至 MeshSettings |
|
||||
| `skeletonRenderer.immutableTriangles` | `skeletonRenderer.MeshSettings.immutableTriangles` | 已迁移至 MeshSettings |
|
||||
| `skeletonRenderer.pmaVertexColors` | `skeletonRenderer.MeshSettings.pmaVertexColors` | 已迁移至 MeshSettings |
|
||||
| `skeletonRenderer.tintBlack` | `skeletonRenderer.MeshSettings.tintBlack` | 已迁移至 MeshSettings |
|
||||
| `skeletonRenderer.addNormals` | `skeletonRenderer.MeshSettings.addNormals` | 已迁移至 MeshSettings |
|
||||
| `skeletonRenderer.calculateTangents` | `skeletonRenderer.MeshSettings.calculateTangents` | 已迁移至 MeshSettings |
|
||||
|
||||
#### 已更改的小写属性
|
||||
|
||||
* `skeletonRenderer.maskInteraction` 改为了 `skeletonRenderer.MaskInteraction`.
|
||||
|
||||
#### 已弃用的小写属性
|
||||
|
||||
* 小写属性 `initialFlipX`, `initialFlipY` 和 `initialSkinName` 已被弃用并将于后续版本中移除. 请转用同名大写属性 `InitialFlipX`, `InitialFlipY` 和 `InitialSkinName`.
|
||||
|
||||
---
|
||||
|
||||
## ▶️ SkeletonAnimation
|
||||
|
||||
### 破坏性变更
|
||||
|
||||
#### 1. 组件架构
|
||||
|
||||
* **SkeletonAnimation 现为独立于 SkeletonRenderer 的组件**, 不再继承自 SkeletonRenderer.
|
||||
|
||||
* 如需访问渲染器: `skeletonAnimation.Renderer`.
|
||||
* 如需通过渲染器访问动画: `skeletonRenderer.Animation`.
|
||||
|
||||
#### 2. 属性变更
|
||||
|
||||
* `state` 不再是公开属性. 请转用 `AnimationState` 属性.
|
||||
|
||||
* 移除了 `valid`. 请转用 `IsValid` 属性.
|
||||
|
||||
#### 3. 方法变更
|
||||
|
||||
* 新增 `UpdateOncePerFrame()` - 仅在该帧未调用 `Update` 时执行更新. 已有的 `Update(float time)` 保持行为不变.
|
||||
|
||||
* 不再公开无参 `Update()` 方法, 如需仅在该帧无更新时执行更新, 请使用 `UpdateOncePerFrame()`, 而 `Update(0)` 可用于强制执行一次动画更新.
|
||||
|
||||
### 字段与属性迁移
|
||||
|
||||
由于 `SkeletonAnimation` 现已成为独立组件, 曾由 `SkeletonRenderer` 提供的方法与属性将无法直接通过 `SkeletonAnimation` 实例访问. 请通过 `SkeletonAnimation` 实例的 `ISkeletonRenderer` 访问渲染器属性 `skeletonAnimation.Renderer`, 而 `skeletonRenderer.Animation` 属性则可通过 `SkeletonRenderer` 或 `SkeletonGraphic` 实例中的 `ISkeletonAnimation` 访问. 对于未在 `ISkeletonRenderer` 接口中暴露的成员, 可将 `skeletonAnimation.Renderer` 显式转换为 `SkeletonRenderer` 或 `SkeletonGraphic` 以访问渲染器成员变量.
|
||||
|
||||
### 示例代码
|
||||
|
||||
```csharp
|
||||
// 升级前: 直接访问 SkeletonRenderer 属性
|
||||
skeletonAnimation.zSpacing = 0.1f;
|
||||
skeletonAnimation.AnySkeletonRendererProperty;
|
||||
|
||||
// 升级后: 通过 Renderer 对象访问
|
||||
skeletonAnimation.Renderer.MeshSettings.zSpacing = 0.1f; // 访问 ISkeletonRenderer 接口暴露出的成员
|
||||
var skeletonRenderer = (SkeletonRenderer)skeletonAnimation.Renderer;
|
||||
skeletonRenderer.AnySkeletonRendererProperty; // 访问 ISkeletonRenderer 接口未暴露的成员
|
||||
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## ▶️ SkeletonMecanim
|
||||
|
||||
### 破坏性变更
|
||||
|
||||
#### 1. 组件架构
|
||||
|
||||
* **SkeletonMecanim 现为独立于 SkeletonRenderer 的组件**, 不再继承自 SkeletonRenderer.
|
||||
|
||||
* 访问方式同 [SkeletonAnimation](#▶️-skeletonanimation).
|
||||
|
||||
#### 2. 方法变更
|
||||
|
||||
* 不再公开 `Update()`.
|
||||
|
||||
* 请改用 `UpdateIfNecessary()`, 如需强制更新请使用 `Update(0)`.
|
||||
|
||||
### 字段与属性迁移
|
||||
|
||||
与上文中的 [SkeletonAnimation](#▶️-skeletonanimation) 相同
|
||||
---
|
||||
|
||||
## ▶️ SkeletonGraphic
|
||||
|
||||
### 破坏性变更
|
||||
|
||||
#### 1. 组件架构
|
||||
|
||||
* **SkeletonGraphic 不再涉及动画逻辑, 请额外添加 SkeletonAnimation 组件以使用动画**.
|
||||
|
||||
* 如需访问动画: `skeletonGraphic.Animation`.
|
||||
|
||||
#### 2. 将已有引用迁至 SkeletonAnimation
|
||||
|
||||
若你组件仅通过引用 `SkeletonGraphic` 来修改其动画属性, 那么建议将引用类型更改为 `SkeletonAnimation`.
|
||||
此举便可直接访问动画状态而无需像 `((SkeletonAnimation)skeletonGraphic.Animation).AnimationState` 这样进行显式转换, 例如: `skeletonAnimation.AnimationState`. 请注意, 若希望重命名序列化组件变量, 可在变量声明前添加 `[FormerlySerializedAs("previousName")]` 特性, Unity 将自动将原字段中保存的序列化值迁移至新变量, 从而保留你在场景或预制件中的已有配置.
|
||||
|
||||
#### 示例代码
|
||||
|
||||
```csharp
|
||||
// Automatically reassign previous serialized values
|
||||
[FormerlySerializedAs("skeletonGraphic")]
|
||||
public SkeletonAnimation skeletonAnimation; // Will maintain the reference after upgrade
|
||||
```
|
||||
|
||||
#### 3. 属性变更
|
||||
|
||||
* 已移除属性 `AnimationState` - 请通过 `SkeletonAnimation` 组件查询该属性.
|
||||
|
||||
* 不再公开 `MeshGenerator` - 请改用 `MeshSettings` 属性和 `SetMeshSettings()` 方法.
|
||||
* `MaterialsMultipleCanvasRenderers` 类型由 `ExposedList<Material>` 改为 `Material[]`.
|
||||
|
||||
#### 4. 创建辅助器(Creation Helper)方法
|
||||
|
||||
* `NewSkeletonGraphicGameObject` 现在默认同时创建 `SkeletonGraphic` 和 `SkeletonAnimation`, 以对齐原有行为. 返回类型改为了一个包含了这两个组件引用的结构体.
|
||||
|
||||
* 已移除 `AddSkeletonGraphicComponent`, 可用替换为:
|
||||
* `AddSkeletonGraphicAnimationComponents` - 同时创建 `SkeletonGraphic` 和 `SkeletonAnimation` 组件.
|
||||
* `AddSkeletonGraphicRenderingComponent` - 仅创建 `SkeletonGraphic` 组件.
|
||||
|
||||
#### 5. 事件更改
|
||||
|
||||
* 委托签名从 `SkeletonRendererDelegate(SkeletonGraphic)` 改为 `SkeletonRendererDelegate(ISkeletonRenderer)`. 该更改会影响以下事件: `OnRebuild`, `OnMeshAndMaterialsUpdated`.
|
||||
如需修复编译错误, 只需将方法实参从 `SkeletonGraphic` 更改为 `ISkeletonRenderer`.
|
||||
|
||||
#### 6. 执行顺序
|
||||
|
||||
* `SkeletonRenderer` 和 `SkeletonGraphic` 组件现可接收 `DefaultExecutionOrder(1)]` 参数,, 以确保其在默认脚本*(即 order=0)*之后执行. 此举保证了即使 `UpdateTiming` 置为 `InLateUpdate`, 动画仍能先于 skeleton 更新.
|
||||
|
||||
#### 7. 行为变更 - Material 更新
|
||||
|
||||
* 各 `CanvasRenderer` 的 material 不再于每个 `LateUpdate` 中更新, 而仅在以下情况时更新:
|
||||
* a) 当 skeleton 更新需更换 materials, 或者
|
||||
* b) 代码访问了 `CustomMaterialOverride` 或 `CustomTextureOverride` 并可能修改它们.
|
||||
|
||||
### 字段与属性迁移
|
||||
|
||||
#### A动画属性
|
||||
|
||||
| **旧 API** | **新 API** | **说明** |
|
||||
|-------------------|-------------------|-----------|
|
||||
| `skeletonGraphic.AnimationState` | `((SkeletonAnimation)skeletonGraphic.Animation).AnimationState` | 需显式转换 |
|
||||
| `skeletonGraphic.startingAnimation` | `skeletonAnimation.AnimationName` | 通过 Animation 组件访问 |
|
||||
| `skeletonGraphic.startingLoop` | `skeletonAnimation.loop` | 通过 Animation 组件访问 |
|
||||
| `skeletonGraphic.timeScale` | `skeletonAnimation.timeScale` | 通过 Animation 组件访问 |
|
||||
| `skeletonGraphic.unscaledTime` | `skeletonAnimation.unscaledTime` | 通过 Animation 组件访问 |
|
||||
|
||||
#### 网格生成器设置
|
||||
|
||||
| **旧 API** | **新 API** | **说明** |
|
||||
|-------------------|-------------------|-----------|
|
||||
| `skeletonGraphic.MeshGenerator.settings` | `skeletonGraphic.MeshSettings` | 直接访问设置 |
|
||||
|
||||
---
|
||||
|
||||
## ⚠️ 其他重要注意事项
|
||||
|
||||
### 防止丢失组件引用
|
||||
|
||||
其他组件(例如 `SkeletonRenderSeparator`)若持有对 `SkeletonRenderer` 的引用, 且其目标引用曾为 `SkeletonAnimation` 或 `SkeletonMecanim`, 则升级后该引用将被置为 *null*. 这是由于 `SkeletonAnimation` 与 `SkeletonMecanim` 不再继承自 `SkeletonRenderer`, 因而不再构成有效的类型引用. 对此, 手动解决方案是直接不管, 变量类型保持 `SkeletonRenderer` 不变, 任凭 `SkeletonAnimation` 的引用被置为 *none*. 随后再在各场景与预制件中手动分配丢失的引用. 而半自动方案则是: 将项目中 `SkeletonRenderer` 变量的类型改为 `Component` (保持对象引用避免丢失目标), 新增一个类型为 `SkeletonRenderer`(或 `SkeletonAnimation`)的变量,然后在代码中从 `Component` 类型变量读取目标对象, 并将其赋给新变量. 例如, 在 Unity 编辑器中给组件添加 [ExecuteAlways] 特性标记, 并在 `Awake()` 方法中自动执行此操作.
|
||||
|
||||
#### 示例代码
|
||||
|
||||
```csharp
|
||||
// 升级前的类代码
|
||||
public class TestMigrateReferences : MonoBehaviour {
|
||||
public SkeletonRenderer skeletonRenderer; // 赋给这个 SkeletonAnimation 的引用将在升级后丢失.
|
||||
|
||||
public void Foo () {
|
||||
skeletonRenderer.skeleton.ScaleX = -1;
|
||||
}
|
||||
}
|
||||
|
||||
// 升级后的类代码
|
||||
[ExecuteAlways] // 或者用 [ExecuteInEditMode]
|
||||
public class TestMigrateReferences : MonoBehaviour {
|
||||
#if UNITY_EDITOR
|
||||
[SerializeField, HideInInspector, FormerlySerializedAs("skeletonRenderer")] Component previousSkeletonRenderer; // 如此便能捕获原名为 skeletonRenderer 的 `SkeletonAnimation` 引用.
|
||||
#endif
|
||||
public SkeletonRenderer skeletonRenderer;
|
||||
|
||||
public void Foo () {
|
||||
skeletonRenderer.skeleton.ScaleX = -1;
|
||||
}
|
||||
|
||||
#if UNITY_EDITOR
|
||||
public void Awake () {
|
||||
AutoUpgradeReferences();
|
||||
}
|
||||
|
||||
public void AutoUpgradeReferences () {
|
||||
if (previousSkeletonRenderer != null && skeletonRenderer == null) {
|
||||
skeletonRenderer = previousSkeletonRenderer.GetComponent<SkeletonRenderer>();
|
||||
if (skeletonRenderer != null)
|
||||
Debug.Log("Upgraded SkeletonRenderer reference.");
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
```
|
||||
|
||||
### 组件的启用/禁用
|
||||
|
||||
由于 `ISkeletonRenderer` 和 `ISkeletonAnimation` 组件现已一分为二, 任何控制组件启用/禁用的脚本都必须**同时启用/禁用这两个组件**.
|
||||
|
||||
### SkeletonUtilityBone 行为更改
|
||||
|
||||
在 Override 模式下, `SkeletonUtilityBone` 仅在 `UpdatePhase.Complete` 阶段调整变换(Transform), 不再于 `UpdatePhase.World` 阶段重复更新, 以减少冗余操作.
|
||||
|
||||
### 自动化迁移
|
||||
|
||||
* 当启用了 `AUTO_UPGRADE_TO_43_COMPONENTS` 后, Unity 编辑器将自动迁移废弃字段.
|
||||
* 如需一键升级全部场景与预制件, 请打开 `Edit - Preferences - Spine`, 并在 `Automatic Component Upgrade` 中点击 `Upgrade Scenes & Prefabs` - `Upgrade All` 即可.
|
||||
* 各类中的 `UpgradeTo43` 和 `TransferDeprecatedFields()` 方法负责其各自的序列化数据的迁移.
|
||||
* 运行时访问仍需手动更新代码.
|
||||
|
||||
### 常见变更总结
|
||||
|
||||
1. **添加 `.Renderer.`** 前缀便可从动画组件访问渲染属性.
|
||||
2. **添加 `.MeshSettings.`** 前缀可以访问网格生成设置.
|
||||
3. 从 SkeletonGraphic 访问 AnimationState 时, 需 **显式类型转换为 SkeletonAnimation**.
|
||||
4. **更新委托方法签名**: 签名中的具体类型应改为接口类型.
|
||||
5. 使用上述迁移模式升级后要**重新绑定丢失的引用**.
|
||||
|
||||
---
|
||||
|
||||
## 禁用自动升级检查
|
||||
|
||||
当完成了 Spine 资源、场景和预制件的全部迁移工作后, 可禁用自动升级检查来提升编辑器性能:
|
||||
|
||||
1. 转到 `Edit → Preferences → Spine`
|
||||
2. 在 `Automatic Component Upgrade` 中点击 `Split Component Upgrade` → `Disable`
|
||||
|
||||
此操作将停止 Unity 编辑器在加载场景或预制件时的迁移检测. 如有新资源需要迁移, 可随时重启此功能.
|
||||
|
||||
---
|
||||
|
||||
## 需要帮助?
|
||||
|
||||
若在迁移过程中遇到棘手问题, 或发现组件迁移有误, 请前往 [Spine 论坛](https://esotericsoftware.com/forum) 发帖反馈. 我们将全力协助,确保自动迁移过程尽可能顺畅无痛.
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 6c12bf9dde3ca4146b5d422361d438de
|
||||
timeCreated: 1759835002
|
||||
licenseType: Free
|
||||
DefaultImporter:
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,375 @@
|
||||
# spine-unity 4.3 Split Component Upgrade Guide
|
||||
## Component Architecture Restructuring
|
||||
|
||||
> **Note:** You can find Chinese and Japanese translations of this upgrade guide right next to this file.
|
||||
>
|
||||
> * [中文版 4.3 模块化组件升级指南 - Chinese 4.3 Split Component Upgrade Guide](https://github.com/EsotericSoftware/spine-runtimes/tree/4.3/spine-unity/Assets/Spine/Documentation/4.3-split-component-upgrade-guide-zh.md)
|
||||
> * [日本語版 4.3 分割コンポーネントアップグレードガイド - Japanese 4.3 Split Component Upgrade Guide](https://github.com/EsotericSoftware/spine-runtimes/tree/4.3/spine-unity/Assets/Spine/Documentation/4.3-split-component-upgrade-guide-ja.md)
|
||||
|
||||
---
|
||||
|
||||
## 📋 Scope of This Document
|
||||
|
||||
**This document covers only the spine-unity component split migration.** From version 4.2 to 4.3, there have been additional breaking changes in both spine-csharp and spine-unity that you need to address before handling the component split:
|
||||
|
||||
- **spine-csharp API changes:** New pose system affecting bone, slot, and constraint properties
|
||||
|
||||
For complete migration steps covering all 4.2 to 4.3 changes, please refer to:
|
||||
- The [CHANGELOG.md](https://github.com/EsotericSoftware/spine-runtimes/blob/4.3/CHANGELOG.md#c-2) document (sections C# and Unity)
|
||||
- The forum post ["Spine-Unity 4.2 to 4.3 Upgrade Guide"](https://esotericsoftware.com/forum) for a comprehensive migration guide
|
||||
|
||||
**Important:** Complete the spine-csharp API migration first, then proceed with the component split migration described in this document.
|
||||
|
||||
---
|
||||
|
||||
## ⚠️ IMPORTANT: Before You Upgrade
|
||||
|
||||
**This warning only applies to existing projects being upgraded. New projects can safely ignore this section.**
|
||||
|
||||
### What Will Happen
|
||||
|
||||
Components will be **automatically upgraded** and split into separate animation and rendering components:
|
||||
- `SkeletonAnimation` → `SkeletonAnimation` + `SkeletonRenderer` components
|
||||
- `SkeletonMecanim` → `SkeletonMecanim` + `SkeletonRenderer` components
|
||||
- `SkeletonGraphic` → `SkeletonAnimation` + `SkeletonGraphic` components
|
||||
|
||||
All component settings and fields will be automatically transferred - nothing will be lost.
|
||||
|
||||
**However:** Due to these type changes, existing references in your custom scripts may be lost because the component types no longer match (e.g., `SkeletonAnimation` is no longer a subclass of `SkeletonRenderer`).
|
||||
|
||||
### Required Upgrade Steps (In Order)
|
||||
|
||||
1. **🔒 BACKUP YOUR PROJECT**
|
||||
Create a complete backup before upgrading. These changes will modify your scenes and prefabs.
|
||||
|
||||
2. **📖 READ THIS ENTIRE DOCUMENT**
|
||||
Understand all breaking changes before proceeding.
|
||||
|
||||
3. **✏️ TEST AND UPDATE YOUR CODE**
|
||||
- **Update your scripts:** Fix custom scripts that reference Spine components according to this document, as your code may no longer compile when members were moved to the split classes
|
||||
- **Test:** Open a few test scenes to see which component references are lost. Unsaved scenes still contain the old component data and serve as a safety backup - only when you save will the split components replace the old ones and references be lost.
|
||||
**⚠️ WARNING:** Prefabs behave differently! Opening a prefab in Prefab Edit Mode will automatically save the component migration due to Unity's prefab auto-save feature. Always test with scenes first, not prefabs.
|
||||
- **Decide:** Either manually re-assign lost references, or write migration code (explained below) to handle this automatically
|
||||
|
||||
**⚠️ IMPORTANT:** Automatic component splitting only happens when scenes/prefabs are opened in the Unity Editor. You must choose 'Upgrade All' or save each scene/prefab before building your game, otherwise the old single components won't be split and half the required components might be missing in the build!
|
||||
|
||||
**💡 HINT:** If you encounter the Console log output "SendMessage cannot be called during Awake, CheckConsistency, or OnValidate", this is a sign that old un-migrated assets were present in the scene which were just auto-upgraded. These messages will be followed by a log message confirming that an old component was auto-migrated to split components.
|
||||
|
||||
4. **🔄 CHOOSE YOUR UPGRADE PATH**
|
||||
|
||||
**Option A - Manual re-assignment (recommended for small projects):**
|
||||
- Open each scene and prefab individually
|
||||
- Manually re-assign lost references in the Inspector
|
||||
- Save when satisfied
|
||||
|
||||
**Option B - Automatic migration (for large projects with many scenes/prefabs):**
|
||||
- Write migration code first:
|
||||
- Implement the reference migration pattern (see "Preventing Lost Component References" section below)
|
||||
- For `SkeletonGraphic` references, see "Changing Existing References to SkeletonAnimation" section
|
||||
- Test migration code on a few files
|
||||
- Then use `Upgrade All`:
|
||||
- Go to `Edit → Preferences → Spine`
|
||||
- Under "Upgrade Scenes & Prefabs"
|
||||
- Click `Upgrade All` button
|
||||
|
||||
### What Will Break If You Don't Prepare
|
||||
- Component references in scenes or prefabs may be lost (set to null) once you save your scenes or prefabs
|
||||
- Building before upgrading all scenes/prefabs may result in missing components in the build
|
||||
|
||||
**Do not proceed without completing steps 1-3, or you risk breaking your project.**
|
||||
|
||||
---
|
||||
|
||||
## 📦 Optional Two-Step Migration from 4.2
|
||||
|
||||
If you're migrating from spine-unity 4.2, you may find it easier to upgrade in two steps to isolate potential issues:
|
||||
|
||||
### Step 1: Upgrade to a 4.3 pre-split version
|
||||
First upgrade to a 4.3 version before the component split changes:
|
||||
- **Commit Hash**: `a07b1de9ce972d4e38898ccf2cc84de16ff66c9b`
|
||||
- **add package via Git URL**: `https://github.com/EsotericSoftware/spine-runtimes.git?path=spine-csharp/src#a07b1de9ce972d4e38898ccf2cc84de16ff66c9b`
|
||||
- or **unitypackage**: https://esotericsoftware.com/files/runtimes/unity/spine-unity-4.2-2025-09-26.unitypackage
|
||||
|
||||
This intermediate step allows you to:
|
||||
- Fix any issues related to the 4.2 → 4.3 spine-csharp API changes first and ensures your project works
|
||||
- Verify your project is stable before proceeding
|
||||
- Once your project is working with this 4.3 version, create another backup before proceeding to step 2
|
||||
|
||||
### Step 2: Upgrade to latest 4.3 version (with split components)
|
||||
Once your project is working with 4.3:
|
||||
- Upgrade to the latest 4.3 package
|
||||
- Follow the component split migration guide above
|
||||
- Handle the separated animation and rendering components
|
||||
|
||||
This two-step approach helps isolate issues - if something breaks, you'll know whether it's due to the 4.2 → 4.3 spine-csharp changes or the component split specifically.
|
||||
|
||||
---
|
||||
|
||||
## 📋 Introduction
|
||||
|
||||
The spine-unity 4.3 runtime introduces a major architectural change: **separation of animation from rendering by using two separate components instead of component inheritance**. This enables flexible combinations like using `SkeletonMecanim` for animation with `SkeletonGraphic` for rendering, which was previously not possible.
|
||||
|
||||
### Key Changes
|
||||
- **Component Split**: The previously monolithic components have been split into separate rendering and animation components.
|
||||
- **Separate Components**: `SkeletonAnimation` and `SkeletonMecanim` no longer inherit from `SkeletonRenderer`. They now work alongside a separate renderer component (a subclass of `ISkeletonRenderer`), such as `SkeletonRenderer` and `SkeletonGraphic`.
|
||||
- **Interface Updates**: New `ISkeletonRenderer` and `ISkeletonAnimation` interfaces with updated property names.
|
||||
- **Settings Grouping**: Mesh generation settings are now grouped under `MeshSettings` property.
|
||||
- **Automatic Migration**: The Unity Editor will automatically upgrade your components to the new split components and transfer deprecated fields when `AUTO_UPGRADE_TO_43_COMPONENTS` is defined (*the default*).
|
||||
- **Upgrade all Scenes and Prefabs**: To upgrade all scenes and prefabs at once, go to `Edit - Preferences - Spine` and under `Automatic Component Upgrade` hit `Upgrade Scenes & Prefabs` - `Upgrade All`.
|
||||
|
||||
### Component Relationships
|
||||
|
||||
| **Old Architecture** | **New Architecture** |
|
||||
|---------------------|---------------------|
|
||||
| `SkeletonAnimation` inherits from `SkeletonRenderer` | `SkeletonAnimation` + separate `SkeletonRenderer` component |
|
||||
| `SkeletonMecanim` inherits from `SkeletonRenderer` | `SkeletonMecanim` + separate `SkeletonRenderer` component |
|
||||
| `SkeletonGraphic` provides embedded AnimationState | `SkeletonGraphic` + separate `SkeletonAnimation` component |
|
||||
|
||||
---
|
||||
|
||||
## Adapting Your Code
|
||||
|
||||
Here's what changes for each component:
|
||||
|
||||
## ▶️ SkeletonRenderer
|
||||
|
||||
### Breaking Changes
|
||||
|
||||
#### 1. Interface Changes
|
||||
- `IHasSkeletonRenderer` interface changed property name from `SkeletonRenderer` to `Renderer`, type from `SkeletonRenderer` to `ISkeletonRenderer`.
|
||||
|
||||
#### 2. Event Changes
|
||||
- `SkeletonRendererDelegate` type is no longer a nested type in `SkeletonRenderer`.
|
||||
To fix any compile errors, replace `SkeletonRenderer.SkeletonRendererDelegate` with `SkeletonRendererDelegate`.
|
||||
- `BeforeApply` delegate type changed from `SkeletonRendererDelegate` to `SkeletonAnimationDelegate`.
|
||||
- `SkeletonRendererDelegate` type changed from `SkeletonRendererDelegate(SkeletonRenderer)` to `SkeletonRendererDelegate(ISkeletonRenderer)`. This affects events: `OnRebuild`, `OnMeshAndMaterialsUpdated`.
|
||||
To fix any compile errors, change your method parameter from `SkeletonRenderer` to `ISkeletonRenderer`.
|
||||
- Moved bone events `UpdateLocal`, `UpdateWorld`, and `UpdateComplete` from `ISkeletonAnimation` classes (`SkeletonAnimation`, `SkeletonMecanim`) to `ISkeletonRenderer classes (SkeletonRenderer, SkeletonGraphic)`. Changed delegate type from `UpdateBonesDelegate` to `SkeletonRendererDelegate`, with parameter `ISkeletonRenderer` instead of `ISkeletonAnimation`.
|
||||
|
||||
#### 2. Method Changes
|
||||
- `LateUpdateMesh()` has been changed to `UpdateMesh()`.
|
||||
- Removed `MeshGenerator.TryReplaceMaterials`.
|
||||
|
||||
#### 3. Execution Order
|
||||
- `SkeletonRenderer` and `SkeletonGraphic` components received `DefaultExecutionOrder(1)]` which makes them run after default *(order=0)* scripts. This ensures animations are applied before the skeleton is updated even if `UpdateTiming` is set to `InLateUpdate`.
|
||||
|
||||
#### 4. Behaviour Changes
|
||||
- `generateMeshOverride` is now also called when `singleSubmesh` is enabled.
|
||||
|
||||
### Field and Property Migration
|
||||
|
||||
#### Mesh Generation Settings
|
||||
| **Old API** | **New API** | **Notes** |
|
||||
|-------------------|-------------------|-----------|
|
||||
| `skeletonRenderer.zSpacing` | `skeletonRenderer.MeshSettings.zSpacing` | Moved to MeshSettings |
|
||||
| `skeletonRenderer.useClipping` | `skeletonRenderer.MeshSettings.useClipping` | Moved to MeshSettings |
|
||||
| `skeletonRenderer.immutableTriangles` | `skeletonRenderer.MeshSettings.immutableTriangles` | Moved to MeshSettings |
|
||||
| `skeletonRenderer.pmaVertexColors` | `skeletonRenderer.MeshSettings.pmaVertexColors` | Moved to MeshSettings |
|
||||
| `skeletonRenderer.tintBlack` | `skeletonRenderer.MeshSettings.tintBlack` | Moved to MeshSettings |
|
||||
| `skeletonRenderer.addNormals` | `skeletonRenderer.MeshSettings.addNormals` | Moved to MeshSettings |
|
||||
| `skeletonRenderer.calculateTangents` | `skeletonRenderer.MeshSettings.calculateTangents` | Moved to MeshSettings |
|
||||
|
||||
#### Hidden Lowercase Attributes
|
||||
- `skeletonRenderer.maskInteraction` is replaced by Property `skeletonRenderer.MaskInteraction`.
|
||||
|
||||
#### Deprecated Lowercase Attributes
|
||||
- Lowercase attributes `initialFlipX`, `initialFlipY` and `initialSkinName` are now deprecated and will be removed in future runtime versions. Use the added properties of the same name but uppercase instead: `InitialFlipX`, `InitialFlipY` and `InitialSkinName`.
|
||||
|
||||
---
|
||||
|
||||
## ▶️ SkeletonAnimation
|
||||
|
||||
### Breaking Changes
|
||||
|
||||
#### 1. Component Architecture
|
||||
- **SkeletonAnimation is now a separate component from SkeletonRenderer**, no longer a subclass.
|
||||
- Access renderer via: `skeletonAnimation.Renderer`.
|
||||
- Access animation from renderer via: `skeletonRenderer.Animation`.
|
||||
|
||||
#### 2. Property Changes
|
||||
- `state` is no longer public. Use `AnimationState` property instead.
|
||||
- `valid` removed. Use `IsValid` instead.
|
||||
|
||||
#### 3. Method Changes
|
||||
- Added `UpdateOncePerFrame()` - updates if `Update` has not been called this frame. The existing `Update(float time)` still always updates when called.
|
||||
- `Update()` without parameters is no longer public, use either `UpdateOncePerFrame()` to update when no update has been performed this frame,
|
||||
or `Update(0)` to force an animation update.
|
||||
|
||||
### Field and Property Migration
|
||||
|
||||
As `SkeletonAnimation` is now a separate component, methods and properties provided by `SkeletonRenderer` can no longer be accessed directly via a `SkeletonAnimation` object. There is a `skeletonAnimation.Renderer` property available to access the associated `ISkeletonRenderer` from a `SkeletonAnimation` object,
|
||||
and an `skeletonRenderer.Animation` property to access the associated `ISkeletonAnimation` from a `SkeletonRenderer` or `SkeletonGraphic` object. For members that are not exposed by the `ISkeletonRenderer` interface, you can cast `skeletonAnimation.Renderer` to `SkeletonRenderer` or `SkeletonGraphic` respectively to access the renderer member variables.
|
||||
|
||||
### Example Code
|
||||
```csharp
|
||||
// Old SkeletonRenderer property access from SkeletonAnimation
|
||||
skeletonAnimation.zSpacing = 0.1f;
|
||||
skeletonAnimation.AnySkeletonRendererProperty;
|
||||
|
||||
// New SkeletonRenderer property access from SkeletonAnimation
|
||||
skeletonAnimation.Renderer.MeshSettings.zSpacing = 0.1f; // exposed in ISkeletonRenderer interface
|
||||
var skeletonRenderer = (SkeletonRenderer)skeletonAnimation.Renderer;
|
||||
skeletonRenderer.AnySkeletonRendererProperty; // not exposed in ISkeletonRenderer interface
|
||||
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## ▶️ SkeletonMecanim
|
||||
|
||||
### Breaking Changes
|
||||
|
||||
#### 1. Component Architecture
|
||||
- **SkeletonMecanim is now a separate component from SkeletonRenderer**, no longer a subclass.
|
||||
- Same access pattern as [SkeletonAnimation](#▶️-skeletonanimation).
|
||||
|
||||
#### 2. Method Changes
|
||||
- `Update()` is no longer public.
|
||||
- Use `UpdateIfNecessary()` or `Update(0)` to force update.
|
||||
|
||||
### Field and Property Migration
|
||||
|
||||
Same as [SkeletonAnimation](#▶️-skeletonanimation) above.
|
||||
---
|
||||
|
||||
## ▶️ SkeletonGraphic
|
||||
|
||||
### Breaking Changes
|
||||
|
||||
#### 1. Component Architecture
|
||||
- **SkeletonGraphic no longer covers animation, add SkeletonAnimation as a separate animation component**.
|
||||
- Animation accessed via: `skeletonGraphic.Animation`.
|
||||
|
||||
#### 2. Changing Existing References to SkeletonAnimation
|
||||
|
||||
If your components are holding a reference to `SkeletonGraphic` only to modify its animation properties, it is recommended to change the reference type to `SkeletonAnimation`.
|
||||
This way you can access animation state like `skeletonAnimation.AnimationState`
|
||||
instead of having to cast it like `((SkeletonAnimation)skeletonGraphic.Animation).AnimationState`. Note that if you want to change the name of serialized component variables, you can use the `[FormerlySerializedAs("previousName")]` attribute in front of a variable definition to automatically reassign your existing serialized value.
|
||||
|
||||
#### Example Code
|
||||
```csharp
|
||||
// Automatically reassign previous serialized values
|
||||
[FormerlySerializedAs("skeletonGraphic")]
|
||||
public SkeletonAnimation skeletonAnimation; // Will maintain the reference after upgrade
|
||||
```
|
||||
|
||||
#### 3. Property Changes
|
||||
- Removed property `AnimationState` - query it from the `SkeletonAnimation` component instead.
|
||||
- `MeshGenerator` is no longer public - use `MeshSettings` property and `SetMeshSettings()` instead.
|
||||
- `MaterialsMultipleCanvasRenderers` type changed from `ExposedList<Material>` to `Material[]`.
|
||||
|
||||
#### 4. Creation Helper Methods
|
||||
- `NewSkeletonGraphicGameObject` now creates both `SkeletonGraphic` and `SkeletonAnimation` to maintain existing behaviour. Return type changed to return both component references in a single struct.
|
||||
- `AddSkeletonGraphicComponent` is removed and replaced by:
|
||||
- `AddSkeletonGraphicAnimationComponents` - creates both `SkeletonGraphic` and `SkeletonAnimation` components.
|
||||
- `AddSkeletonGraphicRenderingComponent` - creates only `SkeletonGraphic` component.
|
||||
|
||||
#### 5. Event Changes
|
||||
- Delegate signature changed from `SkeletonRendererDelegate(SkeletonGraphic)` to `SkeletonRendererDelegate(ISkeletonRenderer)`. This affects events: `OnRebuild`, `OnMeshAndMaterialsUpdated`.
|
||||
To fix any compile errors, change your method parameter from SkeletonGraphic to ISkeletonRenderer.
|
||||
|
||||
#### 6. Execution Order
|
||||
- `SkeletonRenderer` and `SkeletonGraphic` components received `DefaultExecutionOrder(1)]` which makes them run after default *(order=0)* scripts. This ensures animations are applied before the skeleton is updated even if `UpdateTiming` is set to `InLateUpdate`.
|
||||
|
||||
#### 7. Behaviour Changes - Material Updates
|
||||
- Materials at each `CanvasRenderer` are no longer updated every `LateUpdate`, instead they are updated when either:
|
||||
- a) the updated skeleton requires a change of materials, or
|
||||
- b) when `CustomMaterialOverride` or `CustomTextureOverride` were accessed and thus potentially modified.
|
||||
|
||||
### Field and Property Migration
|
||||
|
||||
#### Animation Properties
|
||||
| **Old API** | **New API** | **Notes** |
|
||||
|-------------------|-------------------|-----------|
|
||||
| `skeletonGraphic.AnimationState` | `((SkeletonAnimation)skeletonGraphic.Animation).AnimationState` | Cast required |
|
||||
| `skeletonGraphic.startingAnimation` | `skeletonAnimation.AnimationName` | Via Animation component |
|
||||
| `skeletonGraphic.startingLoop` | `skeletonAnimation.loop` | Via Animation component |
|
||||
| `skeletonGraphic.timeScale` | `skeletonAnimation.timeScale` | Via Animation component |
|
||||
| `skeletonGraphic.unscaledTime` | `skeletonAnimation.unscaledTime` | Via Animation component |
|
||||
|
||||
#### Mesh Generator Settings
|
||||
| **Old API** | **New API** | **Notes** |
|
||||
|-------------------|-------------------|-----------|
|
||||
| `skeletonGraphic.MeshGenerator.settings` | `skeletonGraphic.MeshSettings` | Direct access to settings |
|
||||
|
||||
---
|
||||
|
||||
## ⚠️ Additional Important Notes
|
||||
|
||||
### Preventing Lost Component References
|
||||
Any references by other components (e.g. `SkeletonRenderSeparator`) that reference a `SkeletonRenderer` component and had a `SkeletonAnimation` or `SkeletonMecanim` target will be pointing to *null* after the upgrade, since the `SkeletonAnimation` and `SkeletonMecanim` components are no longer subclasses of `SkeletonRenderer` and thus no valid reference. The manual solution is to leave the type as `SkeletonRenderer` and lose references to `SkeletonAnimation` (will be set to *none*). Then you need to manually re-assign the lost references in your scenes and prefabs. A semi-automatic alternative solution is as follows: change the type of your `SkeletonRenderer` variable to `Component` (to capture the object reference and not lose it) and add a second variable of type `SkeletonRenderer` (or `SkeletonAnimation`) and then programmatically read it from the `Component` variable and assign it to the newly added variable. You can e.g. do this automatically in the Unity Editor in `Awake()` with an `[ExecuteAlways]` tag added to your component.
|
||||
|
||||
#### Example code
|
||||
```csharp
|
||||
// Old class before upgrading
|
||||
public class TestMigrateReferences : MonoBehaviour {
|
||||
public SkeletonRenderer skeletonRenderer; // this SkeletonAnimation reference assigned here would be lost after upgrading.
|
||||
|
||||
public void Foo () {
|
||||
skeletonRenderer.skeleton.ScaleX = -1;
|
||||
}
|
||||
}
|
||||
|
||||
// New class after upgrading
|
||||
[ExecuteAlways] // or [ExecuteInEditMode]
|
||||
public class TestMigrateReferences : MonoBehaviour {
|
||||
#if UNITY_EDITOR
|
||||
[SerializeField, HideInInspector, FormerlySerializedAs("skeletonRenderer")] Component previousSkeletonRenderer; // this captures the old SkeletonAnimation reference assigned at the name skeletonRenderer.
|
||||
#endif
|
||||
public SkeletonRenderer skeletonRenderer;
|
||||
|
||||
public void Foo () {
|
||||
skeletonRenderer.skeleton.ScaleX = -1;
|
||||
}
|
||||
|
||||
#if UNITY_EDITOR
|
||||
public void Awake () {
|
||||
AutoUpgradeReferences();
|
||||
}
|
||||
|
||||
public void AutoUpgradeReferences () {
|
||||
if (previousSkeletonRenderer != null && skeletonRenderer == null) {
|
||||
skeletonRenderer = previousSkeletonRenderer.GetComponent<SkeletonRenderer>();
|
||||
if (skeletonRenderer != null)
|
||||
Debug.Log("Upgraded SkeletonRenderer reference.");
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
```
|
||||
|
||||
### Component Enable/Disable
|
||||
Since `ISkeletonRenderer` and `ISkeletonAnimation` components are now separate, scripts that enable/disable any of these components need adjustment to **enable/disable both**.
|
||||
|
||||
### SkeletonUtilityBone Behaviour Change
|
||||
In mode Override, `SkeletonUtilityBone` no longer adjusts the Transform in `UpdatePhase.World`, only in `UpdatePhase.Complete` (removes redundant update).
|
||||
|
||||
### Automatic Migration
|
||||
- Unity Editor automatically transfers deprecated fields when `AUTO_UPGRADE_TO_43_COMPONENTS` is defined.
|
||||
- To upgrade all scenes and prefabs at once, go to `Edit - Preferences - Spine` and select `Upgrade Scenes & Prefabs` - `Upgrade All`.
|
||||
- The `UpgradeTo43` and `TransferDeprecatedFields()` methods in each class handles serialized data migration.
|
||||
- Manual code updates are still required for runtime access.
|
||||
|
||||
### Summary of Most Common Changes
|
||||
1. **Add `.Renderer.`** prefix to access rendering properties from animation components.
|
||||
2. **Add `.MeshSettings.`** to access mesh generation settings.
|
||||
3. **Cast to SkeletonAnimation** when accessing AnimationState from SkeletonGraphic.
|
||||
4. **Update delegate method signatures** from concrete types to interfaces.
|
||||
5. **Re-assign lost references** after upgrade using the migration pattern above.
|
||||
|
||||
---
|
||||
|
||||
## Disabling Automatic Upgrade Checks
|
||||
|
||||
Once you have completed the migration of all your Spine assets, scenes, and prefabs, you can disable the automatic upgrade checks to improve editor performance:
|
||||
|
||||
1. Go to `Edit → Preferences → Spine`
|
||||
2. Under `Automatic Component Upgrade`, click `Split Component Upgrade` → `Disable`
|
||||
|
||||
This will stop the Unity Editor from performing in-editor checks upon scene or prefab loading to determine whether components need to be migrated. You can re-enable it at any time if you need to migrate additional assets.
|
||||
|
||||
---
|
||||
|
||||
## Need Help?
|
||||
|
||||
If you encounter any unexpected problems during migration or find that component properties are incorrectly migrated, please post on the [Spine forum](https://esotericsoftware.com/forum). We're happy to help and fix any issues to make automatic migration as painless as possible.
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 3c30cad54c352c542a01a50ad3b6b8fe
|
||||
timeCreated: 1759519802
|
||||
licenseType: Free
|
||||
DefaultImporter:
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,9 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ad14d5a4cd7a0444286d315541ee0495
|
||||
folderAsset: yes
|
||||
timeCreated: 1527569319
|
||||
licenseType: Free
|
||||
DefaultImporter:
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"name": "spine-unity-editor",
|
||||
"references": [
|
||||
"spine-csharp",
|
||||
"spine-unity"
|
||||
],
|
||||
"optionalUnityReferences": [],
|
||||
"includePlatforms": [
|
||||
"Editor"
|
||||
],
|
||||
"excludePlatforms": [],
|
||||
"allowUnsafeCode": false
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 173464ddf4cdb6640a4dfa8a9281ad69
|
||||
AssemblyDefinitionImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,9 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 83fbec88df35fe34bab43a5dde6788af
|
||||
folderAsset: yes
|
||||
timeCreated: 1527569675
|
||||
licenseType: Free
|
||||
DefaultImporter:
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,5 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f0e95036e72b08544a9d295dd4366f40
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
userData:
|
||||
@@ -0,0 +1,9 @@
|
||||
fileFormatVersion: 2
|
||||
guid: eb646ac6e394e534b80d5cac61478488
|
||||
folderAsset: yes
|
||||
timeCreated: 1563305058
|
||||
licenseType: Free
|
||||
DefaultImporter:
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,227 @@
|
||||
/******************************************************************************
|
||||
* Spine Runtimes License Agreement
|
||||
* Last updated April 5, 2025. Replaces all prior versions.
|
||||
*
|
||||
* Copyright (c) 2013-2026, Esoteric Software LLC
|
||||
*
|
||||
* Integration of the Spine Runtimes into software or otherwise creating
|
||||
* derivative works of the Spine Runtimes is permitted under the terms and
|
||||
* conditions of Section 2 of the Spine Editor License Agreement:
|
||||
* http://esotericsoftware.com/spine-editor-license
|
||||
*
|
||||
* Otherwise, it is permitted to integrate the Spine Runtimes into software
|
||||
* or otherwise create derivative works of the Spine Runtimes (collectively,
|
||||
* "Products"), provided that each user of the Products must obtain their own
|
||||
* Spine Editor license and redistribution of the Products in any form must
|
||||
* include this license and copyright notice.
|
||||
*
|
||||
* THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY
|
||||
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY
|
||||
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES,
|
||||
* BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND
|
||||
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
||||
* THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*****************************************************************************/
|
||||
|
||||
using System.IO;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Spine.Unity.Editor {
|
||||
|
||||
[CustomPropertyDrawer(typeof(AnimationReferenceAsset))]
|
||||
public class AnimationReferenceAssetDrawer : PropertyDrawer {
|
||||
|
||||
const string NoneString = "<None>";
|
||||
const string ReferenceAssetsFolderName = SpineEditorUtilities.ReferenceAssetsFolderName;
|
||||
|
||||
static GUIStyle errorPopupStyle;
|
||||
GUIStyle ErrorPopupStyle {
|
||||
get {
|
||||
if (errorPopupStyle == null) errorPopupStyle = new GUIStyle(EditorStyles.popup);
|
||||
errorPopupStyle.normal.textColor = Color.red;
|
||||
errorPopupStyle.hover.textColor = Color.red;
|
||||
errorPopupStyle.focused.textColor = Color.red;
|
||||
errorPopupStyle.active.textColor = Color.red;
|
||||
return errorPopupStyle;
|
||||
}
|
||||
}
|
||||
|
||||
SkeletonDataAsset resolvedSkeletonDataAsset;
|
||||
|
||||
public override void OnGUI (Rect position, SerializedProperty property, GUIContent label) {
|
||||
if (property.propertyType != SerializedPropertyType.ObjectReference) {
|
||||
EditorGUI.LabelField(position, label.text, "ERROR: Must be an object reference.");
|
||||
return;
|
||||
}
|
||||
|
||||
bool isSkeletonDataMismatch;
|
||||
SkeletonDataAsset skeletonDataAsset = ResolveSkeletonDataAsset(property, out isSkeletonDataMismatch);
|
||||
SkeletonData skeletonData = skeletonDataAsset != null ? skeletonDataAsset.GetSkeletonData(true) : null;
|
||||
label = EditorGUI.BeginProperty(position, label, property);
|
||||
|
||||
if (skeletonData == null || skeletonData.Animations.Count == 0) {
|
||||
EditorGUI.PropertyField(position, property, label);
|
||||
} else {
|
||||
Rect objectFieldRect;
|
||||
Rect dropdownRect;
|
||||
SplitRect(position, out objectFieldRect, out dropdownRect);
|
||||
|
||||
position = EditorGUI.PrefixLabel(position, label);
|
||||
SplitRect(position, out objectFieldRect, out dropdownRect);
|
||||
|
||||
EditorGUI.PropertyField(objectFieldRect, property, GUIContent.none);
|
||||
|
||||
string currentAnimationName = GetAnimationName(property);
|
||||
GUIContent dropdownLabel = string.IsNullOrEmpty(currentAnimationName) ?
|
||||
new GUIContent(NoneString, SpineEditorUtilities.Icons.animation) :
|
||||
new GUIContent(currentAnimationName, SpineEditorUtilities.Icons.animation);
|
||||
|
||||
GUIStyle usedStyle =
|
||||
(isSkeletonDataMismatch && SpineEditorUtilities.Preferences.skeletonDataAssetMismatchWarning) ?
|
||||
ErrorPopupStyle : EditorStyles.popup;
|
||||
if (GUI.Button(dropdownRect, dropdownLabel, usedStyle)) {
|
||||
ShowAnimationMenu(property, skeletonDataAsset, skeletonData);
|
||||
}
|
||||
}
|
||||
EditorGUI.EndProperty();
|
||||
}
|
||||
|
||||
static void SplitRect (Rect position, out Rect left, out Rect right) {
|
||||
float dropdownWidth = Mathf.Min(position.width * 0.5f, 200);
|
||||
left = new Rect(position.x, position.y, position.width - dropdownWidth - 2, position.height);
|
||||
right = new Rect(position.x + position.width - dropdownWidth, position.y, dropdownWidth, position.height);
|
||||
}
|
||||
|
||||
SkeletonDataAsset ResolveSkeletonDataAsset (SerializedProperty property, out bool skeletonDataAssetMismatch) {
|
||||
skeletonDataAssetMismatch = false;
|
||||
SkeletonDataAsset expectedSkeletonDataAsset = GetSkeletonDataAssetFromGameObject(property);
|
||||
|
||||
AnimationReferenceAsset currentAsset = property.objectReferenceValue as AnimationReferenceAsset;
|
||||
if (currentAsset != null && currentAsset.SkeletonDataAsset != null) {
|
||||
resolvedSkeletonDataAsset = currentAsset.SkeletonDataAsset;
|
||||
// If other SkeletonDataAsset than expected, use assigned asset but show warning color in Inspector.
|
||||
if (expectedSkeletonDataAsset && resolvedSkeletonDataAsset != expectedSkeletonDataAsset)
|
||||
skeletonDataAssetMismatch = true;
|
||||
} else {
|
||||
resolvedSkeletonDataAsset = expectedSkeletonDataAsset;
|
||||
}
|
||||
return resolvedSkeletonDataAsset;
|
||||
}
|
||||
|
||||
SkeletonDataAsset GetSkeletonDataAssetFromGameObject (SerializedProperty property) {
|
||||
Object targetObject = property.serializedObject.targetObject;
|
||||
IHasSkeletonDataAsset skeletonDataAssetComponent = targetObject as IHasSkeletonDataAsset;
|
||||
if (skeletonDataAssetComponent == null) {
|
||||
Component component = targetObject as Component;
|
||||
if (component != null)
|
||||
skeletonDataAssetComponent = component.GetComponentInParent<IHasSkeletonDataAsset>();
|
||||
}
|
||||
if (skeletonDataAssetComponent != null) {
|
||||
return skeletonDataAssetComponent.SkeletonDataAsset;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
static string GetAnimationName (SerializedProperty property) {
|
||||
AnimationReferenceAsset asset = property.objectReferenceValue as AnimationReferenceAsset;
|
||||
if (asset == null) return null;
|
||||
return asset.AnimationName;
|
||||
}
|
||||
|
||||
void ShowAnimationMenu (SerializedProperty property, SkeletonDataAsset skeletonDataAsset, SkeletonData skeletonData) {
|
||||
GenericMenu menu = new GenericMenu();
|
||||
|
||||
menu.AddItem(new GUIContent(NoneString), property.objectReferenceValue == null, () => {
|
||||
property.objectReferenceValue = null;
|
||||
property.serializedObject.ApplyModifiedProperties();
|
||||
});
|
||||
|
||||
ExposedList<Animation> animations = skeletonData.Animations;
|
||||
string currentAnimationName = GetAnimationName(property);
|
||||
|
||||
for (int i = 0; i < animations.Count; i++) {
|
||||
string animName = animations.Items[i].Name;
|
||||
bool isSelected = animName == currentAnimationName;
|
||||
menu.AddItem(new GUIContent(animName), isSelected, HandleAnimationSelected,
|
||||
new AnimationSelectContext(property, skeletonDataAsset, animName));
|
||||
}
|
||||
|
||||
menu.ShowAsContext();
|
||||
}
|
||||
|
||||
static void HandleAnimationSelected (object contextObj) {
|
||||
AnimationSelectContext context = (AnimationSelectContext)contextObj;
|
||||
AnimationReferenceAsset asset = FindAnimationReferenceAsset(context.skeletonDataAsset, context.animationName);
|
||||
if (asset == null) {
|
||||
Debug.LogWarning(string.Format("AnimationReferenceAsset for animation '{0}' not found. " +
|
||||
"Please select the SkeletonDataAsset and in the Inspector hit 'Create Animation Reference " +
|
||||
"Assets', or manually assign an AnimationReferenceAsset if using shared AnimationReferenceAssets.",
|
||||
context.animationName), context.skeletonDataAsset);
|
||||
return;
|
||||
}
|
||||
context.property.objectReferenceValue = asset;
|
||||
context.property.serializedObject.ApplyModifiedProperties();
|
||||
}
|
||||
|
||||
static AnimationReferenceAsset FindAnimationReferenceAsset (SkeletonDataAsset targetSkeletonDataAsset,
|
||||
string targetAnimationName) {
|
||||
|
||||
string skeletonDataAssetPath = AssetDatabase.GetAssetPath(targetSkeletonDataAsset);
|
||||
string parentFolder = Path.GetDirectoryName(skeletonDataAssetPath);
|
||||
|
||||
// Search AnimationReferenceAssetContainer sub-assets
|
||||
string skeletonDataAssetName = Path.GetFileNameWithoutExtension(skeletonDataAssetPath);
|
||||
string baseName = skeletonDataAssetName.Replace(AssetUtility.SkeletonDataSuffix, "");
|
||||
string containerPath = string.Format("{0}/{1}{2}.asset", parentFolder, baseName,
|
||||
SpineEditorUtilities.AnimationReferenceContainerSuffix);
|
||||
AnimationReferenceAsset foundAsset = FindAnimationReferenceInSubAssets(containerPath, targetSkeletonDataAsset, targetAnimationName);
|
||||
if (foundAsset != null) return foundAsset;
|
||||
|
||||
// Search standalone files in same asset directory
|
||||
string dataPath = parentFolder + "/" + ReferenceAssetsFolderName;
|
||||
string safeName = AssetUtility.GetPathSafeName(targetAnimationName);
|
||||
string assetPath = string.Format("{0}/{1}.asset", dataPath, safeName);
|
||||
AnimationReferenceAsset existingAsset = AssetDatabase.LoadAssetAtPath<AnimationReferenceAsset>(assetPath);
|
||||
if (existingAsset != null) return existingAsset;
|
||||
|
||||
// Global fallback: search the project for matching AnimationReferenceAsset including sub-assets
|
||||
string[] guids = AssetDatabase.FindAssets("t:AnimationReferenceAsset");
|
||||
foreach (string guid in guids) {
|
||||
string path = AssetDatabase.GUIDToAssetPath(guid);
|
||||
foundAsset = FindAnimationReferenceInSubAssets(path, targetSkeletonDataAsset, targetAnimationName);
|
||||
if (foundAsset != null) return foundAsset;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
static AnimationReferenceAsset FindAnimationReferenceInSubAssets (string assetPath,
|
||||
SkeletonDataAsset targetSkeletonDataAsset, string targetAnimationName) {
|
||||
|
||||
UnityEngine.Object[] allAssets = AssetDatabase.LoadAllAssetsAtPath(assetPath);
|
||||
foreach (UnityEngine.Object obj in allAssets) {
|
||||
AnimationReferenceAsset asset = obj as AnimationReferenceAsset;
|
||||
if (asset == null) continue;
|
||||
if (asset.SkeletonDataAsset == targetSkeletonDataAsset && asset.AnimationName == targetAnimationName)
|
||||
return asset;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
struct AnimationSelectContext {
|
||||
public SerializedProperty property;
|
||||
public SkeletonDataAsset skeletonDataAsset;
|
||||
public string animationName;
|
||||
|
||||
public AnimationSelectContext (SerializedProperty property, SkeletonDataAsset skeletonDataAsset, string animationName) {
|
||||
this.property = property;
|
||||
this.skeletonDataAsset = skeletonDataAsset;
|
||||
this.animationName = animationName;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: df65d8f8319dac74296b5ef428050221
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,182 @@
|
||||
/******************************************************************************
|
||||
* Spine Runtimes License Agreement
|
||||
* Last updated April 5, 2025. Replaces all prior versions.
|
||||
*
|
||||
* Copyright (c) 2013-2026, Esoteric Software LLC
|
||||
*
|
||||
* Integration of the Spine Runtimes into software or otherwise creating
|
||||
* derivative works of the Spine Runtimes is permitted under the terms and
|
||||
* conditions of Section 2 of the Spine Editor License Agreement:
|
||||
* http://esotericsoftware.com/spine-editor-license
|
||||
*
|
||||
* Otherwise, it is permitted to integrate the Spine Runtimes into software
|
||||
* or otherwise create derivative works of the Spine Runtimes (collectively,
|
||||
* "Products"), provided that each user of the Products must obtain their own
|
||||
* Spine Editor license and redistribution of the Products in any form must
|
||||
* include this license and copyright notice.
|
||||
*
|
||||
* THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY
|
||||
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY
|
||||
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES,
|
||||
* BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND
|
||||
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
||||
* THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*****************************************************************************/
|
||||
|
||||
using System;
|
||||
using System.Reflection;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Spine.Unity.Editor {
|
||||
using Editor = UnityEditor.Editor;
|
||||
|
||||
[CustomEditor(typeof(AnimationReferenceAsset))]
|
||||
public class AnimationReferenceAssetEditor : Editor {
|
||||
|
||||
const string InspectorHelpText = "This is a Spine-Unity Animation Reference Asset. It serializes a reference to a SkeletonData asset and an animationName. It does not contain actual animation data. At runtime, it stores a reference to a Spine.Animation.\n\n" +
|
||||
"You can use this in your AnimationState calls instead of a string animation name or a Spine.Animation reference. Use its implicit conversion into Spine.Animation or its .Animation property.\n\n" +
|
||||
"Use AnimationReferenceAssets as an alternative to storing strings or finding animations and caching per component. This only does the lookup by string once, and allows you to store and manage animations via asset references.";
|
||||
|
||||
readonly SkeletonInspectorPreview preview = new SkeletonInspectorPreview();
|
||||
FieldInfo skeletonDataAssetField = typeof(AnimationReferenceAsset).GetField("skeletonDataAsset", BindingFlags.NonPublic | BindingFlags.Instance);
|
||||
FieldInfo nameField = typeof(AnimationReferenceAsset).GetField("animationName", BindingFlags.NonPublic | BindingFlags.Instance);
|
||||
|
||||
AnimationReferenceAsset ThisAnimationReferenceAsset { get { return target as AnimationReferenceAsset; } }
|
||||
SkeletonDataAsset ThisSkeletonDataAsset { get { return skeletonDataAssetField.GetValue(ThisAnimationReferenceAsset) as SkeletonDataAsset; } }
|
||||
string ThisAnimationName { get { return nameField.GetValue(ThisAnimationReferenceAsset) as string; } }
|
||||
|
||||
bool changeNextFrame = false;
|
||||
SerializedProperty animationNameProperty;
|
||||
SkeletonDataAsset lastSkeletonDataAsset;
|
||||
SkeletonData lastSkeletonData;
|
||||
|
||||
void OnEnable () { HandleOnEnablePreview(); }
|
||||
void OnDestroy () {
|
||||
HandleOnDestroyPreview();
|
||||
AppDomain.CurrentDomain.DomainUnload -= OnDomainUnload;
|
||||
EditorApplication.update -= preview.HandleEditorUpdate;
|
||||
}
|
||||
|
||||
public override void OnInspectorGUI () {
|
||||
animationNameProperty = animationNameProperty ?? serializedObject.FindProperty("animationName");
|
||||
string animationName = animationNameProperty.stringValue;
|
||||
|
||||
Animation animation = null;
|
||||
if (ThisSkeletonDataAsset != null) {
|
||||
SkeletonData skeletonData = ThisSkeletonDataAsset.GetSkeletonData(true);
|
||||
if (skeletonData != null) {
|
||||
animation = skeletonData.FindAnimation(animationName);
|
||||
}
|
||||
}
|
||||
bool animationNotFound = (animation == null);
|
||||
|
||||
if (changeNextFrame) {
|
||||
changeNextFrame = false;
|
||||
|
||||
if (ThisSkeletonDataAsset != lastSkeletonDataAsset || ThisSkeletonDataAsset.GetSkeletonData(true) != lastSkeletonData) {
|
||||
preview.Clear();
|
||||
preview.Initialize(Repaint, ThisSkeletonDataAsset, LastSkinName);
|
||||
|
||||
if (animationNotFound) {
|
||||
animationNameProperty.stringValue = "";
|
||||
preview.ClearAnimationSetupPose();
|
||||
}
|
||||
}
|
||||
|
||||
preview.ClearAnimationSetupPose();
|
||||
|
||||
if (!string.IsNullOrEmpty(animationNameProperty.stringValue))
|
||||
preview.PlayPauseAnimation(animationNameProperty.stringValue, true);
|
||||
}
|
||||
|
||||
//EditorGUILayout.HelpBox(AnimationReferenceAssetEditor.InspectorHelpText, MessageType.Info, true);
|
||||
EditorGUILayout.Space();
|
||||
EditorGUI.BeginChangeCheck();
|
||||
DrawDefaultInspector();
|
||||
if (EditorGUI.EndChangeCheck()) {
|
||||
changeNextFrame = true;
|
||||
}
|
||||
|
||||
// Draw extra info below default inspector.
|
||||
EditorGUILayout.Space();
|
||||
if (ThisSkeletonDataAsset == null) {
|
||||
EditorGUILayout.HelpBox("SkeletonDataAsset is missing.", MessageType.Error);
|
||||
} else if (string.IsNullOrEmpty(animationName)) {
|
||||
EditorGUILayout.HelpBox("No animation selected.", MessageType.Warning);
|
||||
} else if (animationNotFound) {
|
||||
EditorGUILayout.HelpBox(string.Format("Animation named {0} was not found for this Skeleton.", animationNameProperty.stringValue), MessageType.Warning);
|
||||
} else {
|
||||
using (new SpineInspectorUtility.BoxScope()) {
|
||||
if (!string.Equals(AssetUtility.GetPathSafeName(animationName), ThisAnimationReferenceAsset.name, System.StringComparison.OrdinalIgnoreCase))
|
||||
EditorGUILayout.HelpBox("Animation name value does not match this asset's name. Inspectors using this asset may be misleading.", MessageType.None);
|
||||
|
||||
EditorGUILayout.LabelField(SpineInspectorUtility.TempContent(animationName, SpineEditorUtilities.Icons.animation));
|
||||
if (animation != null) {
|
||||
EditorGUILayout.LabelField(string.Format("Timelines: {0}", animation.Timelines.Count));
|
||||
EditorGUILayout.LabelField(string.Format("Duration: {0} sec", animation.Duration));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
lastSkeletonDataAsset = ThisSkeletonDataAsset;
|
||||
lastSkeletonData = ThisSkeletonDataAsset.GetSkeletonData(true);
|
||||
}
|
||||
|
||||
#region Preview Handlers
|
||||
string TargetAssetGUID { get { return AssetDatabase.AssetPathToGUID(AssetDatabase.GetAssetPath(ThisSkeletonDataAsset)); } }
|
||||
string LastSkinKey { get { return TargetAssetGUID + "_lastSkin"; } }
|
||||
string LastSkinName { get { return EditorPrefs.GetString(LastSkinKey, ""); } }
|
||||
|
||||
void HandleOnEnablePreview () {
|
||||
if (ThisSkeletonDataAsset != null && ThisSkeletonDataAsset.skeletonJSON == null)
|
||||
return;
|
||||
SpineEditorUtilities.ConfirmInitialization();
|
||||
|
||||
// This handles the case where the managed editor assembly is unloaded before recompilation when code changes.
|
||||
AppDomain.CurrentDomain.DomainUnload -= OnDomainUnload;
|
||||
AppDomain.CurrentDomain.DomainUnload += OnDomainUnload;
|
||||
|
||||
preview.Initialize(this.Repaint, ThisSkeletonDataAsset, LastSkinName);
|
||||
preview.PlayPauseAnimation(ThisAnimationName, true);
|
||||
preview.OnSkinChanged -= HandleOnSkinChanged;
|
||||
preview.OnSkinChanged += HandleOnSkinChanged;
|
||||
EditorApplication.update -= preview.HandleEditorUpdate;
|
||||
EditorApplication.update += preview.HandleEditorUpdate;
|
||||
}
|
||||
|
||||
private void OnDomainUnload (object sender, EventArgs e) {
|
||||
OnDestroy();
|
||||
}
|
||||
|
||||
private void HandleOnSkinChanged (string skinName) {
|
||||
EditorPrefs.SetString(LastSkinKey, skinName);
|
||||
preview.PlayPauseAnimation(ThisAnimationName, true);
|
||||
}
|
||||
|
||||
void HandleOnDestroyPreview () {
|
||||
EditorApplication.update -= preview.HandleEditorUpdate;
|
||||
preview.OnDestroy();
|
||||
}
|
||||
|
||||
override public bool HasPreviewGUI () {
|
||||
if (serializedObject.isEditingMultipleObjects) return false;
|
||||
return ThisSkeletonDataAsset != null && ThisSkeletonDataAsset.GetSkeletonData(true) != null;
|
||||
}
|
||||
|
||||
override public void OnInteractivePreviewGUI (Rect r, GUIStyle background) {
|
||||
preview.Initialize(this.Repaint, ThisSkeletonDataAsset);
|
||||
preview.HandleInteractivePreviewGUI(r, background);
|
||||
}
|
||||
|
||||
public override GUIContent GetPreviewTitle () { return SpineInspectorUtility.TempContent("Preview"); }
|
||||
public override void OnPreviewSettings () { preview.HandleDrawSettings(); }
|
||||
public override Texture2D RenderStaticPreview (string assetPath, UnityEngine.Object[] subAssets, int width, int height) { return preview.GetStaticPreview(width, height); }
|
||||
#endregion
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 9511532e80feed24881a5863f5485446
|
||||
timeCreated: 1523316585
|
||||
licenseType: Free
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,90 @@
|
||||
/******************************************************************************
|
||||
* Spine Runtimes License Agreement
|
||||
* Last updated April 5, 2025. Replaces all prior versions.
|
||||
*
|
||||
* Copyright (c) 2013-2026, Esoteric Software LLC
|
||||
*
|
||||
* Integration of the Spine Runtimes into software or otherwise creating
|
||||
* derivative works of the Spine Runtimes is permitted under the terms and
|
||||
* conditions of Section 2 of the Spine Editor License Agreement:
|
||||
* http://esotericsoftware.com/spine-editor-license
|
||||
*
|
||||
* Otherwise, it is permitted to integrate the Spine Runtimes into software
|
||||
* or otherwise create derivative works of the Spine Runtimes (collectively,
|
||||
* "Products"), provided that each user of the Products must obtain their own
|
||||
* Spine Editor license and redistribution of the Products in any form must
|
||||
* include this license and copyright notice.
|
||||
*
|
||||
* THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY
|
||||
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY
|
||||
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES,
|
||||
* BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND
|
||||
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
||||
* THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*****************************************************************************/
|
||||
|
||||
using Spine;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Spine.Unity.Editor {
|
||||
using Event = UnityEngine.Event;
|
||||
|
||||
[CustomPropertyDrawer(typeof(MaterialOverrideSet))]
|
||||
public class MaterialOverrideSetDrawer : PropertyDrawer {
|
||||
private const float Padding = 5f;
|
||||
private const float ButtonWidth = 20f;
|
||||
|
||||
public override float GetPropertyHeight (SerializedProperty property, GUIContent label) {
|
||||
SerializedProperty dictionaryKeysProp = property.FindPropertyRelative("dictionaryKeys");
|
||||
return EditorGUIUtility.singleLineHeight * (dictionaryKeysProp.arraySize + 2);
|
||||
}
|
||||
|
||||
public override void OnGUI (Rect position, SerializedProperty property, GUIContent label) {
|
||||
EditorGUI.BeginProperty(position, label, property);
|
||||
|
||||
SerializedProperty nameProperty = property.FindPropertyRelative("name");
|
||||
SerializedProperty dictionaryKeysProperty = property.FindPropertyRelative("dictionaryKeys");
|
||||
SerializedProperty dictionaryValuesProperty = property.FindPropertyRelative("dictionaryValues");
|
||||
|
||||
Rect labelPosition = new Rect(position.x, position.y, position.width * 0.5f, EditorGUIUtility.singleLineHeight);
|
||||
Rect namePosition = new Rect(position.x + position.width * 0.5f, position.y, position.width * 0.5f, EditorGUIUtility.singleLineHeight);
|
||||
|
||||
EditorGUI.LabelField(labelPosition, label);
|
||||
nameProperty.stringValue = EditorGUI.TextField(namePosition, GUIContent.none, nameProperty.stringValue);
|
||||
|
||||
Rect contentPosition = EditorGUI.IndentedRect(new Rect(position.x, position.y + EditorGUIUtility.singleLineHeight, position.width, position.height - EditorGUIUtility.singleLineHeight));
|
||||
float lineHeight = EditorGUIUtility.singleLineHeight;
|
||||
|
||||
for (int i = 0; i < dictionaryKeysProperty.arraySize; i++) {
|
||||
Rect keyRect = new Rect(contentPosition.x, contentPosition.y + i * lineHeight, contentPosition.width * 0.5f, lineHeight);
|
||||
Rect valueRect = new Rect(contentPosition.x + contentPosition.width * 0.5f, contentPosition.y + i * lineHeight, contentPosition.width * 0.5f - ButtonWidth, lineHeight);
|
||||
Rect removeButtonRect = new Rect(contentPosition.xMax - ButtonWidth, contentPosition.y + i * lineHeight, ButtonWidth, lineHeight);
|
||||
|
||||
EditorGUI.PropertyField(keyRect, dictionaryKeysProperty.GetArrayElementAtIndex(i), GUIContent.none);
|
||||
EditorGUI.PropertyField(valueRect, dictionaryValuesProperty.GetArrayElementAtIndex(i), GUIContent.none);
|
||||
|
||||
if (GUI.Button(removeButtonRect, "-")) {
|
||||
dictionaryKeysProperty.DeleteArrayElementAtIndex(i);
|
||||
dictionaryValuesProperty.DeleteArrayElementAtIndex(i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
int indent = 15;
|
||||
Rect addButtonRect = new Rect(contentPosition.x + indent, contentPosition.y + dictionaryKeysProperty.arraySize * lineHeight, contentPosition.width - indent, lineHeight);
|
||||
if (GUI.Button(addButtonRect, "+ Add Entry")) {
|
||||
dictionaryKeysProperty.InsertArrayElementAtIndex(dictionaryKeysProperty.arraySize);
|
||||
dictionaryValuesProperty.InsertArrayElementAtIndex(dictionaryValuesProperty.arraySize);
|
||||
}
|
||||
|
||||
EditorGUI.EndProperty();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 90d399311223a9040a5e7d32742a1bbe
|
||||
timeCreated: 1686129585
|
||||
licenseType: Free
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 01cbef8f24d105f4bafa9668d669e040
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
@@ -0,0 +1,477 @@
|
||||
/******************************************************************************
|
||||
* Spine Runtimes License Agreement
|
||||
* Last updated April 5, 2025. Replaces all prior versions.
|
||||
*
|
||||
* Copyright (c) 2013-2026, Esoteric Software LLC
|
||||
*
|
||||
* Integration of the Spine Runtimes into software or otherwise creating
|
||||
* derivative works of the Spine Runtimes is permitted under the terms and
|
||||
* conditions of Section 2 of the Spine Editor License Agreement:
|
||||
* http://esotericsoftware.com/spine-editor-license
|
||||
*
|
||||
* Otherwise, it is permitted to integrate the Spine Runtimes into software
|
||||
* or otherwise create derivative works of the Spine Runtimes (collectively,
|
||||
* "Products"), provided that each user of the Products must obtain their own
|
||||
* Spine Editor license and redistribution of the Products in any form must
|
||||
* include this license and copyright notice.
|
||||
*
|
||||
* THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY
|
||||
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY
|
||||
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES,
|
||||
* BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND
|
||||
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
||||
* THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*****************************************************************************/
|
||||
|
||||
#if UNITY_6000_3_OR_NEWER
|
||||
#define USES_ENTITY_ID
|
||||
#endif
|
||||
|
||||
#if UNITY_2022_2_OR_NEWER
|
||||
#define TEXTUREIMPORTER_SPRITESHEET_OBSOLETE
|
||||
#endif
|
||||
|
||||
//#define BAKE_ALL_BUTTON
|
||||
//#define REGION_BAKING_MESH
|
||||
|
||||
using Spine;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Reflection;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Spine.Unity.Editor {
|
||||
using Event = UnityEngine.Event;
|
||||
|
||||
[CustomEditor(typeof(SpineAtlasAsset)), CanEditMultipleObjects]
|
||||
public class SpineAtlasAssetInspector : UnityEditor.Editor {
|
||||
SerializedProperty atlasFile, materials, materialOverrides, textureLoadingMode, onDemandTextureLoader;
|
||||
SpineAtlasAsset atlasAsset;
|
||||
|
||||
GUIContent spriteSlicesLabel;
|
||||
GUIContent SpriteSlicesLabel {
|
||||
get {
|
||||
if (spriteSlicesLabel == null) {
|
||||
spriteSlicesLabel = new GUIContent(
|
||||
"Apply Regions as Texture Sprite Slices",
|
||||
SpineEditorUtilities.Icons.unity,
|
||||
"Adds Sprite slices to atlas texture(s). " +
|
||||
"Updates existing slices if ones with matching names exist. \n\n" +
|
||||
"If your atlas was exported with Premultiply Alpha, " +
|
||||
"your SpriteRenderer should use the generated Spine _Material asset (or any Material with a PMA shader) instead of Sprites-Default.");
|
||||
}
|
||||
return spriteSlicesLabel;
|
||||
}
|
||||
}
|
||||
|
||||
static List<AtlasRegion> GetRegions (Atlas atlas) {
|
||||
FieldInfo regionsField = SpineInspectorUtility.GetNonPublicField(typeof(Atlas), "regions");
|
||||
return (List<AtlasRegion>)regionsField.GetValue(atlas);
|
||||
}
|
||||
|
||||
void OnEnable () {
|
||||
SpineEditorUtilities.ConfirmInitialization();
|
||||
atlasFile = serializedObject.FindProperty("atlasFile");
|
||||
materials = serializedObject.FindProperty("materials");
|
||||
textureLoadingMode = serializedObject.FindProperty("textureLoadingMode");
|
||||
onDemandTextureLoader = serializedObject.FindProperty("onDemandTextureLoader");
|
||||
materials.isExpanded = true;
|
||||
materialOverrides = serializedObject.FindProperty("serializedMaterialOverrides");
|
||||
atlasAsset = (SpineAtlasAsset)target;
|
||||
#if REGION_BAKING_MESH
|
||||
UpdateBakedList();
|
||||
#endif
|
||||
}
|
||||
|
||||
#if REGION_BAKING_MESH
|
||||
private List<bool> baked;
|
||||
private List<GameObject> bakedObjects;
|
||||
|
||||
void UpdateBakedList () {
|
||||
AtlasAsset asset = (AtlasAsset)target;
|
||||
baked = new List<bool>();
|
||||
bakedObjects = new List<GameObject>();
|
||||
if (atlasFile.objectReferenceValue != null) {
|
||||
List<AtlasRegion> regions = this.Regions;
|
||||
string atlasAssetPath = AssetDatabase.GetAssetPath(atlasAsset);
|
||||
string atlasAssetDirPath = Path.GetDirectoryName(atlasAssetPath);
|
||||
string bakedDirPath = Path.Combine(atlasAssetDirPath, atlasAsset.name);
|
||||
for (int i = 0; i < regions.Count; i++) {
|
||||
AtlasRegion region = regions[i];
|
||||
string bakedPrefabPath = Path.Combine(bakedDirPath, AssetUtility.GetPathSafeRegionName(region) + ".prefab").Replace("\\", "/");
|
||||
GameObject prefab = (GameObject)AssetDatabase.LoadAssetAtPath(bakedPrefabPath, typeof(GameObject));
|
||||
baked.Add(prefab != null);
|
||||
bakedObjects.Add(prefab);
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
override public void OnInspectorGUI () {
|
||||
if (serializedObject.isEditingMultipleObjects) {
|
||||
DrawDefaultInspector();
|
||||
return;
|
||||
}
|
||||
|
||||
serializedObject.Update();
|
||||
atlasAsset = (atlasAsset == null) ? (SpineAtlasAsset)target : atlasAsset;
|
||||
EditorGUI.BeginChangeCheck();
|
||||
EditorGUILayout.PropertyField(atlasFile);
|
||||
EditorGUILayout.PropertyField(materials, true);
|
||||
if (EditorGUI.EndChangeCheck()) {
|
||||
serializedObject.ApplyModifiedProperties();
|
||||
atlasAsset.Clear();
|
||||
atlasAsset.GetAtlas();
|
||||
}
|
||||
|
||||
if (materials.arraySize == 0) {
|
||||
EditorGUILayout.HelpBox("No materials", MessageType.Error);
|
||||
return;
|
||||
}
|
||||
|
||||
for (int i = 0; i < materials.arraySize; i++) {
|
||||
SerializedProperty prop = materials.GetArrayElementAtIndex(i);
|
||||
Material material = (Material)prop.objectReferenceValue;
|
||||
if (material == null) {
|
||||
EditorGUILayout.HelpBox("Materials cannot be null.", MessageType.Error);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
EditorGUILayout.PropertyField(materialOverrides, true);
|
||||
|
||||
if (textureLoadingMode != null) {
|
||||
EditorGUILayout.Space();
|
||||
EditorGUILayout.PropertyField(textureLoadingMode);
|
||||
EditorGUILayout.PropertyField(onDemandTextureLoader);
|
||||
}
|
||||
|
||||
EditorGUILayout.Space();
|
||||
if (SpineInspectorUtility.LargeCenteredButton(SpineInspectorUtility.TempContent("Set Mipmap Bias to " + SpinePreferences.DEFAULT_MIPMAPBIAS, tooltip: "This may help textures with mipmaps be less blurry when used for 2D sprites."))) {
|
||||
foreach (Material m in atlasAsset.materials) {
|
||||
Texture texture = m.mainTexture;
|
||||
#if USES_ENTITY_ID
|
||||
string texturePath = AssetDatabase.GetAssetPath(texture.GetEntityId());
|
||||
#else
|
||||
string texturePath = AssetDatabase.GetAssetPath(texture.GetInstanceID());
|
||||
#endif
|
||||
TextureImporter importer = (TextureImporter)TextureImporter.GetAtPath(texturePath);
|
||||
importer.mipMapBias = SpinePreferences.DEFAULT_MIPMAPBIAS;
|
||||
EditorUtility.SetDirty(texture);
|
||||
}
|
||||
Debug.Log("Texture mipmap bias set to " + SpinePreferences.DEFAULT_MIPMAPBIAS);
|
||||
}
|
||||
|
||||
EditorGUILayout.Space();
|
||||
if (atlasFile.objectReferenceValue != null) {
|
||||
if (SpineInspectorUtility.LargeCenteredButton(SpriteSlicesLabel)) {
|
||||
Atlas atlas = atlasAsset.GetAtlas();
|
||||
foreach (Material m in atlasAsset.materials)
|
||||
UpdateSpriteSlices(m.mainTexture, atlas);
|
||||
}
|
||||
}
|
||||
|
||||
EditorGUILayout.Space();
|
||||
|
||||
#if REGION_BAKING_MESH
|
||||
if (atlasFile.objectReferenceValue != null) {
|
||||
Atlas atlas = asset.GetAtlas();
|
||||
FieldInfo field = typeof(Atlas).GetField("regions", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.NonPublic);
|
||||
List<AtlasRegion> regions = (List<AtlasRegion>)field.GetValue(atlas);
|
||||
EditorGUILayout.LabelField(new GUIContent("Region Baking", SpineEditorUtilities.Icons.unityIcon));
|
||||
EditorGUI.indentLevel++;
|
||||
AtlasPage lastPage = null;
|
||||
for (int i = 0; i < regions.Count; i++) {
|
||||
if (lastPage != regions[i].page) {
|
||||
if (lastPage != null) {
|
||||
EditorGUILayout.Separator();
|
||||
EditorGUILayout.Separator();
|
||||
}
|
||||
lastPage = regions[i].page;
|
||||
Material mat = ((Material)lastPage.rendererObject);
|
||||
if (mat != null) {
|
||||
GUILayout.BeginHorizontal();
|
||||
{
|
||||
EditorGUI.BeginDisabledGroup(true);
|
||||
EditorGUILayout.ObjectField(mat, typeof(Material), false, GUILayout.Width(250));
|
||||
EditorGUI.EndDisabledGroup();
|
||||
}
|
||||
GUILayout.EndHorizontal();
|
||||
|
||||
} else {
|
||||
EditorGUILayout.LabelField(new GUIContent("Page missing material!", SpineEditorUtilities.Icons.warning));
|
||||
}
|
||||
}
|
||||
GUILayout.BeginHorizontal();
|
||||
{
|
||||
//EditorGUILayout.ToggleLeft(baked[i] ? "" : regions[i].name, baked[i]);
|
||||
bool result = baked[i] ? EditorGUILayout.ToggleLeft("", baked[i], GUILayout.Width(24)) : EditorGUILayout.ToggleLeft(" " + regions[i].name, baked[i]);
|
||||
if(baked[i]){
|
||||
EditorGUILayout.ObjectField(bakedObjects[i], typeof(GameObject), false, GUILayout.Width(250));
|
||||
}
|
||||
if (result && !baked[i]) {
|
||||
//bake
|
||||
baked[i] = true;
|
||||
bakedObjects[i] = SpineEditorUtilities.BakeRegion(atlasAsset, regions[i]);
|
||||
EditorGUIUtility.PingObject(bakedObjects[i]);
|
||||
} else if (!result && baked[i]) {
|
||||
//unbake
|
||||
bool unbakeResult = EditorUtility.DisplayDialog("Delete Baked Region", "Do you want to delete the prefab for " + regions[i].name, "Yes", "Cancel");
|
||||
switch (unbakeResult) {
|
||||
case true:
|
||||
//delete
|
||||
string atlasAssetPath = AssetDatabase.GetAssetPath(atlasAsset);
|
||||
string atlasAssetDirPath = Path.GetDirectoryName(atlasAssetPath);
|
||||
string bakedDirPath = Path.Combine(atlasAssetDirPath, atlasAsset.name);
|
||||
string bakedPrefabPath = Path.Combine(bakedDirPath, SpineEditorUtilities.GetPathSafeRegionName(regions[i]) + ".prefab").Replace("\\", "/");
|
||||
AssetDatabase.DeleteAsset(bakedPrefabPath);
|
||||
baked[i] = false;
|
||||
break;
|
||||
case false:
|
||||
//do nothing
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
GUILayout.EndHorizontal();
|
||||
}
|
||||
EditorGUI.indentLevel--;
|
||||
|
||||
#if BAKE_ALL_BUTTON
|
||||
// Check state
|
||||
bool allBaked = true;
|
||||
bool allUnbaked = true;
|
||||
for (int i = 0; i < regions.Count; i++) {
|
||||
allBaked &= baked[i];
|
||||
allUnbaked &= !baked[i];
|
||||
}
|
||||
|
||||
if (!allBaked && GUILayout.Button("Bake All")) {
|
||||
for (int i = 0; i < regions.Count; i++) {
|
||||
if (!baked[i]) {
|
||||
baked[i] = true;
|
||||
bakedObjects[i] = SpineEditorUtilities.BakeRegion(atlasAsset, regions[i]);
|
||||
}
|
||||
}
|
||||
|
||||
} else if (!allUnbaked && GUILayout.Button("Unbake All")) {
|
||||
bool unbakeResult = EditorUtility.DisplayDialog("Delete All Baked Regions", "Are you sure you want to unbake all region prefabs? This cannot be undone.", "Yes", "Cancel");
|
||||
switch (unbakeResult) {
|
||||
case true:
|
||||
//delete
|
||||
for (int i = 0; i < regions.Count; i++) {
|
||||
if (baked[i]) {
|
||||
string atlasAssetPath = AssetDatabase.GetAssetPath(atlasAsset);
|
||||
string atlasAssetDirPath = Path.GetDirectoryName(atlasAssetPath);
|
||||
string bakedDirPath = Path.Combine(atlasAssetDirPath, atlasAsset.name);
|
||||
string bakedPrefabPath = Path.Combine(bakedDirPath, SpineEditorUtilities.GetPathSafeRegionName(regions[i]) + ".prefab").Replace("\\", "/");
|
||||
AssetDatabase.DeleteAsset(bakedPrefabPath);
|
||||
baked[i] = false;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case false:
|
||||
//do nothing
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
#endif
|
||||
|
||||
}
|
||||
#else
|
||||
if (atlasFile.objectReferenceValue != null) {
|
||||
|
||||
|
||||
int baseIndent = EditorGUI.indentLevel;
|
||||
|
||||
List<AtlasRegion> regions = SpineAtlasAssetInspector.GetRegions(atlasAsset.GetAtlas());
|
||||
int regionsCount = regions.Count;
|
||||
using (new EditorGUILayout.HorizontalScope()) {
|
||||
EditorGUILayout.LabelField("Atlas Regions", EditorStyles.boldLabel);
|
||||
EditorGUILayout.LabelField(string.Format("{0} regions total", regionsCount));
|
||||
}
|
||||
AtlasPage lastPage = null;
|
||||
for (int i = 0; i < regionsCount; i++) {
|
||||
if (lastPage != regions[i].page) {
|
||||
if (lastPage != null) {
|
||||
EditorGUILayout.Separator();
|
||||
EditorGUILayout.Separator();
|
||||
}
|
||||
lastPage = regions[i].page;
|
||||
Material mat = ((Material)lastPage.rendererObject);
|
||||
if (mat != null) {
|
||||
EditorGUI.indentLevel = baseIndent;
|
||||
using (new GUILayout.HorizontalScope())
|
||||
using (new EditorGUI.DisabledGroupScope(true))
|
||||
EditorGUILayout.ObjectField(mat, typeof(Material), false, GUILayout.Width(250));
|
||||
EditorGUI.indentLevel = baseIndent + 1;
|
||||
} else {
|
||||
EditorGUILayout.HelpBox("Page missing material!", MessageType.Warning);
|
||||
}
|
||||
}
|
||||
|
||||
string regionName = regions[i].name;
|
||||
Texture2D icon = SpineEditorUtilities.Icons.image;
|
||||
if (regionName.EndsWith(" ")) {
|
||||
regionName = string.Format("'{0}'", regions[i].name);
|
||||
icon = SpineEditorUtilities.Icons.warning;
|
||||
EditorGUILayout.LabelField(SpineInspectorUtility.TempContent(regionName, icon, "Region name ends with whitespace. This may cause errors. Please check your source image filenames."));
|
||||
} else {
|
||||
EditorGUILayout.LabelField(SpineInspectorUtility.TempContent(regionName, icon));
|
||||
}
|
||||
|
||||
}
|
||||
EditorGUI.indentLevel = baseIndent;
|
||||
}
|
||||
#endif
|
||||
|
||||
if (serializedObject.ApplyModifiedProperties() || SpineInspectorUtility.UndoRedoPerformed(Event.current))
|
||||
atlasAsset.Clear();
|
||||
}
|
||||
|
||||
static public void UpdateSpriteSlices (Texture texture, Atlas atlas) {
|
||||
#if USES_ENTITY_ID
|
||||
string texturePath = AssetDatabase.GetAssetPath(texture.GetEntityId());
|
||||
#else
|
||||
string texturePath = AssetDatabase.GetAssetPath(texture.GetInstanceID());
|
||||
#endif
|
||||
TextureImporter t = (TextureImporter)TextureImporter.GetAtPath(texturePath);
|
||||
t.spriteImportMode = SpriteImportMode.Multiple;
|
||||
|
||||
List<AtlasRegion> regions = SpineAtlasAssetInspector.GetRegions(atlas);
|
||||
int updatedCount = 0;
|
||||
int addedCount = 0;
|
||||
|
||||
#if TEXTUREIMPORTER_SPRITESHEET_OBSOLETE
|
||||
// Avoid assembly reference to Unity.2D.Sprite.Editor which causes an error on Unity 2018.3.
|
||||
Type factoryType = null;
|
||||
foreach (Assembly assembly in AppDomain.CurrentDomain.GetAssemblies()) {
|
||||
factoryType = assembly.GetType("UnityEditor.U2D.Sprites.SpriteDataProviderFactories");
|
||||
if (factoryType != null) break;
|
||||
}
|
||||
if (factoryType == null) {
|
||||
Debug.LogWarning("SpriteDataProviderFactories type not found. Sprite slices could not be applied.");
|
||||
return;
|
||||
}
|
||||
// The following reflection code is equivalent to this:
|
||||
// SpriteDataProviderFactories factory = new SpriteDataProviderFactories();
|
||||
// factory.Init();
|
||||
// ISpriteEditorDataProvider dataProvider = factory.GetSpriteEditorDataProviderFromObject(t);
|
||||
// dataProvider.InitSpriteEditorDataProvider();
|
||||
//
|
||||
// SpriteRect[] spriteRects = dataProvider.GetSpriteRects();
|
||||
// List<SpriteRect> sprites = new List<SpriteRect>(spriteRects);
|
||||
object factory = Activator.CreateInstance(factoryType);
|
||||
factoryType.GetMethod("Init").Invoke(factory, null);
|
||||
object dataProvider = factoryType.GetMethod("GetSpriteEditorDataProviderFromObject").Invoke(factory, new object[] { t });
|
||||
Type providerInterface = dataProvider.GetType().GetInterface("ISpriteEditorDataProvider");
|
||||
providerInterface.GetMethod("InitSpriteEditorDataProvider").Invoke(dataProvider, null);
|
||||
Array spriteRectsArray = (Array)providerInterface.GetMethod("GetSpriteRects").Invoke(dataProvider, null);
|
||||
Type spriteRectType = spriteRectsArray.GetType().GetElementType();
|
||||
// Find a base type with a default constructor for creating new instances,
|
||||
// since the array element type may be a derived type without one.
|
||||
Type spriteRectBaseType = spriteRectType;
|
||||
while (spriteRectBaseType != null && spriteRectBaseType != typeof(object)
|
||||
&& spriteRectBaseType.GetConstructor(Type.EmptyTypes) == null)
|
||||
spriteRectBaseType = spriteRectBaseType.BaseType;
|
||||
PropertyInfo nameProperty = spriteRectBaseType.GetProperty("name");
|
||||
PropertyInfo rectProperty = spriteRectBaseType.GetProperty("rect");
|
||||
PropertyInfo pivotProperty = spriteRectBaseType.GetProperty("pivot");
|
||||
|
||||
List<object> sprites = new List<object>();
|
||||
for (int i = 0; i < spriteRectsArray.Length; i++)
|
||||
sprites.Add(spriteRectsArray.GetValue(i));
|
||||
|
||||
foreach (AtlasRegion r in regions) {
|
||||
string pageName = System.IO.Path.GetFileNameWithoutExtension(r.page.name);
|
||||
string textureName = texture.name;
|
||||
bool pageMatch = string.Equals(pageName, textureName, StringComparison.Ordinal);
|
||||
|
||||
int spriteIndex = pageMatch ? sprites.FindIndex(
|
||||
(s) => string.Equals((string)nameProperty.GetValue(s), r.name, StringComparison.Ordinal)
|
||||
) : -1;
|
||||
bool spriteNameMatchExists = spriteIndex >= 0;
|
||||
|
||||
if (pageMatch) {
|
||||
Rect spriteRect = new Rect();
|
||||
spriteRect.width = r.width;
|
||||
spriteRect.height = r.height;
|
||||
spriteRect.x = r.x;
|
||||
spriteRect.y = r.page.height - spriteRect.height - r.y;
|
||||
|
||||
if (spriteNameMatchExists) {
|
||||
object s = sprites[spriteIndex];
|
||||
rectProperty.SetValue(s, spriteRect);
|
||||
updatedCount++;
|
||||
} else {
|
||||
object newSpriteRect = Activator.CreateInstance(spriteRectBaseType);
|
||||
nameProperty.SetValue(newSpriteRect, r.name);
|
||||
rectProperty.SetValue(newSpriteRect, spriteRect);
|
||||
pivotProperty.SetValue(newSpriteRect, new Vector2(0.5f, 0.5f));
|
||||
sprites.Add(newSpriteRect);
|
||||
addedCount++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Array resultArray = Array.CreateInstance(spriteRectBaseType, sprites.Count);
|
||||
for (int i = 0; i < sprites.Count; i++)
|
||||
resultArray.SetValue(sprites[i], i);
|
||||
// The following reflection code is equivalent to this:
|
||||
// dataProvider.SetSpriteRects(spriteRects);
|
||||
// dataProvider.Apply();
|
||||
providerInterface.GetMethod("SetSpriteRects").Invoke(dataProvider, new object[] { resultArray });
|
||||
providerInterface.GetMethod("Apply").Invoke(dataProvider, null);
|
||||
#else
|
||||
SpriteMetaData[] spriteSheet = t.spritesheet;
|
||||
List<SpriteMetaData> sprites = new List<SpriteMetaData>(spriteSheet);
|
||||
|
||||
foreach (AtlasRegion r in regions) {
|
||||
string pageName = System.IO.Path.GetFileNameWithoutExtension(r.page.name);
|
||||
string textureName = texture.name;
|
||||
bool pageMatch = string.Equals(pageName, textureName, StringComparison.Ordinal);
|
||||
|
||||
int spriteIndex = pageMatch ? sprites.FindIndex(
|
||||
(s) => string.Equals(s.name, r.name, StringComparison.Ordinal)
|
||||
) : -1;
|
||||
bool spriteNameMatchExists = spriteIndex >= 0;
|
||||
|
||||
if (pageMatch) {
|
||||
Rect spriteRect = new Rect();
|
||||
spriteRect.width = r.width;
|
||||
spriteRect.height = r.height;
|
||||
spriteRect.x = r.x;
|
||||
spriteRect.y = r.page.height - spriteRect.height - r.y;
|
||||
|
||||
if (spriteNameMatchExists) {
|
||||
SpriteMetaData s = sprites[spriteIndex];
|
||||
s.rect = spriteRect;
|
||||
sprites[spriteIndex] = s;
|
||||
updatedCount++;
|
||||
} else {
|
||||
sprites.Add(new SpriteMetaData {
|
||||
name = r.name,
|
||||
pivot = new Vector2(0.5f, 0.5f),
|
||||
rect = spriteRect
|
||||
});
|
||||
addedCount++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
t.spritesheet = sprites.ToArray();
|
||||
#endif
|
||||
EditorUtility.SetDirty(t);
|
||||
AssetDatabase.ImportAsset(texturePath, ImportAssetOptions.ForceUpdate);
|
||||
EditorGUIUtility.PingObject(texture);
|
||||
Debug.Log(string.Format("Applied sprite slices to {2}. {0} added. {1} updated.", addedCount, updatedCount, texture.name));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ca9b3ce36d70a05408e3bdd5e92c7f64
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,155 @@
|
||||
/******************************************************************************
|
||||
* Spine Runtimes License Agreement
|
||||
* Last updated April 5, 2025. Replaces all prior versions.
|
||||
*
|
||||
* Copyright (c) 2013-2026, Esoteric Software LLC
|
||||
*
|
||||
* Integration of the Spine Runtimes into software or otherwise creating
|
||||
* derivative works of the Spine Runtimes is permitted under the terms and
|
||||
* conditions of Section 2 of the Spine Editor License Agreement:
|
||||
* http://esotericsoftware.com/spine-editor-license
|
||||
*
|
||||
* Otherwise, it is permitted to integrate the Spine Runtimes into software
|
||||
* or otherwise create derivative works of the Spine Runtimes (collectively,
|
||||
* "Products"), provided that each user of the Products must obtain their own
|
||||
* Spine Editor license and redistribution of the Products in any form must
|
||||
* include this license and copyright notice.
|
||||
*
|
||||
* THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY
|
||||
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY
|
||||
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES,
|
||||
* BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND
|
||||
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
||||
* THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*****************************************************************************/
|
||||
|
||||
using Spine;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Reflection;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Spine.Unity.Editor {
|
||||
using Event = UnityEngine.Event;
|
||||
|
||||
[CustomEditor(typeof(SpineSpriteAtlasAsset)), CanEditMultipleObjects]
|
||||
public class SpineSpriteAtlasAssetInspector : UnityEditor.Editor {
|
||||
SerializedProperty atlasFile, materials, materialOverrides;
|
||||
SpineSpriteAtlasAsset atlasAsset;
|
||||
|
||||
static List<AtlasRegion> GetRegions (Atlas atlas) {
|
||||
FieldInfo regionsField = SpineInspectorUtility.GetNonPublicField(typeof(Atlas), "regions");
|
||||
return (List<AtlasRegion>)regionsField.GetValue(atlas);
|
||||
}
|
||||
|
||||
void OnEnable () {
|
||||
SpineEditorUtilities.ConfirmInitialization();
|
||||
atlasFile = serializedObject.FindProperty("spriteAtlasFile");
|
||||
materials = serializedObject.FindProperty("materials");
|
||||
materials.isExpanded = true;
|
||||
materialOverrides = serializedObject.FindProperty("materialOverrides");
|
||||
atlasAsset = (SpineSpriteAtlasAsset)target;
|
||||
|
||||
if (!SpineSpriteAtlasAsset.AnySpriteAtlasNeedsRegionsLoaded())
|
||||
return;
|
||||
EditorApplication.update -= SpineSpriteAtlasAsset.UpdateWhenEditorPlayModeStarted;
|
||||
EditorApplication.update += SpineSpriteAtlasAsset.UpdateWhenEditorPlayModeStarted;
|
||||
}
|
||||
|
||||
void OnDisable () {
|
||||
EditorApplication.update -= SpineSpriteAtlasAsset.UpdateWhenEditorPlayModeStarted;
|
||||
}
|
||||
|
||||
override public void OnInspectorGUI () {
|
||||
if (serializedObject.isEditingMultipleObjects) {
|
||||
DrawDefaultInspector();
|
||||
return;
|
||||
}
|
||||
|
||||
serializedObject.Update();
|
||||
atlasAsset = (atlasAsset == null) ? (SpineSpriteAtlasAsset)target : atlasAsset;
|
||||
|
||||
if (atlasAsset.RegionsNeedLoading) {
|
||||
if (GUILayout.Button(SpineInspectorUtility.TempContent("Load regions by entering Play mode"), GUILayout.Height(20))) {
|
||||
EditorApplication.isPlaying = true;
|
||||
}
|
||||
}
|
||||
|
||||
EditorGUI.BeginChangeCheck();
|
||||
EditorGUILayout.PropertyField(atlasFile);
|
||||
EditorGUILayout.PropertyField(materials, true);
|
||||
EditorGUILayout.PropertyField(materialOverrides, true);
|
||||
if (EditorGUI.EndChangeCheck()) {
|
||||
serializedObject.ApplyModifiedProperties();
|
||||
atlasAsset.Clear();
|
||||
atlasAsset.GetAtlas();
|
||||
atlasAsset.updateRegionsInPlayMode = true;
|
||||
}
|
||||
|
||||
if (materials.arraySize == 0) {
|
||||
EditorGUILayout.HelpBox("No materials", MessageType.Error);
|
||||
return;
|
||||
}
|
||||
|
||||
for (int i = 0; i < materials.arraySize; i++) {
|
||||
SerializedProperty prop = materials.GetArrayElementAtIndex(i);
|
||||
Material material = (Material)prop.objectReferenceValue;
|
||||
if (material == null) {
|
||||
EditorGUILayout.HelpBox("Materials cannot be null.", MessageType.Error);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (atlasFile.objectReferenceValue != null) {
|
||||
int baseIndent = EditorGUI.indentLevel;
|
||||
|
||||
List<AtlasRegion> regions = SpineSpriteAtlasAssetInspector.GetRegions(atlasAsset.GetAtlas());
|
||||
int regionsCount = regions.Count;
|
||||
using (new EditorGUILayout.HorizontalScope()) {
|
||||
EditorGUILayout.LabelField("Atlas Regions", EditorStyles.boldLabel);
|
||||
EditorGUILayout.LabelField(string.Format("{0} regions total", regionsCount));
|
||||
}
|
||||
AtlasPage lastPage = null;
|
||||
for (int i = 0; i < regionsCount; i++) {
|
||||
if (lastPage != regions[i].page) {
|
||||
if (lastPage != null) {
|
||||
EditorGUILayout.Separator();
|
||||
EditorGUILayout.Separator();
|
||||
}
|
||||
lastPage = regions[i].page;
|
||||
Material mat = ((Material)lastPage.rendererObject);
|
||||
if (mat != null) {
|
||||
EditorGUI.indentLevel = baseIndent;
|
||||
using (new GUILayout.HorizontalScope())
|
||||
using (new EditorGUI.DisabledGroupScope(true))
|
||||
EditorGUILayout.ObjectField(mat, typeof(Material), false, GUILayout.Width(250));
|
||||
EditorGUI.indentLevel = baseIndent + 1;
|
||||
} else {
|
||||
EditorGUILayout.HelpBox("Page missing material!", MessageType.Warning);
|
||||
}
|
||||
}
|
||||
|
||||
string regionName = regions[i].name;
|
||||
Texture2D icon = SpineEditorUtilities.Icons.image;
|
||||
if (regionName.EndsWith(" ")) {
|
||||
regionName = string.Format("'{0}'", regions[i].name);
|
||||
icon = SpineEditorUtilities.Icons.warning;
|
||||
EditorGUILayout.LabelField(SpineInspectorUtility.TempContent(regionName, icon, "Region name ends with whitespace. This may cause errors. Please check your source image filenames."));
|
||||
} else {
|
||||
EditorGUILayout.LabelField(SpineInspectorUtility.TempContent(regionName, icon));
|
||||
}
|
||||
}
|
||||
EditorGUI.indentLevel = baseIndent;
|
||||
}
|
||||
|
||||
if (serializedObject.ApplyModifiedProperties() || SpineInspectorUtility.UndoRedoPerformed(Event.current))
|
||||
atlasAsset.Clear();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f063dc5ff6881db4a9ee2e059812cba2
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,9 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 0134640f881c8d24d812a6f9af9d0761
|
||||
folderAsset: yes
|
||||
timeCreated: 1563304704
|
||||
licenseType: Free
|
||||
DefaultImporter:
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,214 @@
|
||||
/******************************************************************************
|
||||
* Spine Runtimes License Agreement
|
||||
* Last updated April 5, 2025. Replaces all prior versions.
|
||||
*
|
||||
* Copyright (c) 2013-2026, Esoteric Software LLC
|
||||
*
|
||||
* Integration of the Spine Runtimes into software or otherwise creating
|
||||
* derivative works of the Spine Runtimes is permitted under the terms and
|
||||
* conditions of Section 2 of the Spine Editor License Agreement:
|
||||
* http://esotericsoftware.com/spine-editor-license
|
||||
*
|
||||
* Otherwise, it is permitted to integrate the Spine Runtimes into software
|
||||
* or otherwise create derivative works of the Spine Runtimes (collectively,
|
||||
* "Products"), provided that each user of the Products must obtain their own
|
||||
* Spine Editor license and redistribution of the Products in any form must
|
||||
* include this license and copyright notice.
|
||||
*
|
||||
* THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY
|
||||
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY
|
||||
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES,
|
||||
* BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND
|
||||
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
||||
* THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*****************************************************************************/
|
||||
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Spine.Unity.Editor {
|
||||
|
||||
using Editor = UnityEditor.Editor;
|
||||
using Event = UnityEngine.Event;
|
||||
|
||||
[CustomEditor(typeof(BoneFollowerGraphic)), CanEditMultipleObjects]
|
||||
public class BoneFollowerGraphicInspector : Editor {
|
||||
|
||||
SerializedProperty boneName, skeletonGraphic, followXYPosition, followZPosition, followAttachmentZSpacing,
|
||||
followBoneRotation, followLocalScale, followParentWorldScale, followSkeletonFlip, maintainedAxisOrientation;
|
||||
BoneFollowerGraphic targetBoneFollower;
|
||||
bool needsReset;
|
||||
|
||||
#region Context Menu Item
|
||||
[MenuItem("CONTEXT/SkeletonGraphic/Add BoneFollower GameObject")]
|
||||
static void AddBoneFollowerGameObject (MenuCommand cmd) {
|
||||
SkeletonGraphic skeletonGraphic = cmd.context as SkeletonGraphic;
|
||||
GameObject go = EditorInstantiation.NewGameObject("BoneFollower", true, typeof(RectTransform));
|
||||
Transform t = go.transform;
|
||||
t.SetParent(skeletonGraphic.transform);
|
||||
t.localPosition = Vector3.zero;
|
||||
|
||||
BoneFollowerGraphic f = go.AddComponent<BoneFollowerGraphic>();
|
||||
f.skeletonGraphic = skeletonGraphic;
|
||||
f.SetBone(skeletonGraphic.Skeleton.RootBone.Data.Name);
|
||||
|
||||
EditorGUIUtility.PingObject(t);
|
||||
|
||||
Undo.RegisterCreatedObjectUndo(go, "Add BoneFollowerGraphic");
|
||||
}
|
||||
|
||||
// Validate
|
||||
[MenuItem("CONTEXT/SkeletonGraphic/Add BoneFollower GameObject", true)]
|
||||
static bool ValidateAddBoneFollowerGameObject (MenuCommand cmd) {
|
||||
SkeletonGraphic skeletonGraphic = cmd.context as SkeletonGraphic;
|
||||
return skeletonGraphic.IsValid;
|
||||
}
|
||||
#endregion
|
||||
|
||||
void OnEnable () {
|
||||
skeletonGraphic = serializedObject.FindProperty("skeletonGraphic");
|
||||
boneName = serializedObject.FindProperty("boneName");
|
||||
followBoneRotation = serializedObject.FindProperty("followBoneRotation");
|
||||
followXYPosition = serializedObject.FindProperty("followXYPosition");
|
||||
followZPosition = serializedObject.FindProperty("followZPosition");
|
||||
followAttachmentZSpacing = serializedObject.FindProperty("followAttachmentZSpacing");
|
||||
followLocalScale = serializedObject.FindProperty("followLocalScale");
|
||||
followParentWorldScale = serializedObject.FindProperty("followParentWorldScale");
|
||||
followSkeletonFlip = serializedObject.FindProperty("followSkeletonFlip");
|
||||
maintainedAxisOrientation = serializedObject.FindProperty("maintainedAxisOrientation");
|
||||
|
||||
targetBoneFollower = (BoneFollowerGraphic)target;
|
||||
if (targetBoneFollower.SkeletonGraphic != null)
|
||||
targetBoneFollower.SkeletonGraphic.Initialize(false);
|
||||
|
||||
if (!targetBoneFollower.valid || needsReset) {
|
||||
targetBoneFollower.Initialize();
|
||||
targetBoneFollower.LateUpdate();
|
||||
needsReset = false;
|
||||
SceneView.RepaintAll();
|
||||
}
|
||||
}
|
||||
|
||||
public void OnSceneGUI () {
|
||||
BoneFollowerGraphic tbf = target as BoneFollowerGraphic;
|
||||
SkeletonGraphic skeletonGraphicComponent = tbf.SkeletonGraphic;
|
||||
if (skeletonGraphicComponent == null) return;
|
||||
|
||||
Transform transform = skeletonGraphicComponent.transform;
|
||||
Skeleton skeleton = skeletonGraphicComponent.Skeleton;
|
||||
float positionScale = skeletonGraphicComponent.MeshScale;
|
||||
Vector2 positionOffset = skeletonGraphicComponent.GetScaledPivotOffset();
|
||||
|
||||
if (string.IsNullOrEmpty(boneName.stringValue)) {
|
||||
SpineHandles.DrawBones(transform, skeleton, positionScale, positionOffset);
|
||||
SpineHandles.DrawBoneNames(transform, skeleton, positionScale, positionOffset);
|
||||
Handles.Label(tbf.transform.position, "No bone selected", EditorStyles.helpBox);
|
||||
} else {
|
||||
Bone targetBone = tbf.bone;
|
||||
if (targetBone == null) return;
|
||||
|
||||
SpineHandles.DrawBoneWireframe(transform, targetBone, SpineHandles.TransformContraintColor, positionScale, positionOffset);
|
||||
Handles.Label(targetBone.GetWorldPosition(transform, positionScale, positionOffset),
|
||||
targetBone.Data.Name, SpineHandles.BoneNameStyle);
|
||||
}
|
||||
}
|
||||
|
||||
override public void OnInspectorGUI () {
|
||||
if (serializedObject.isEditingMultipleObjects) {
|
||||
if (needsReset) {
|
||||
needsReset = false;
|
||||
foreach (Object o in targets) {
|
||||
BoneFollower bf = (BoneFollower)o;
|
||||
bf.Initialize();
|
||||
bf.LateUpdate();
|
||||
}
|
||||
SceneView.RepaintAll();
|
||||
}
|
||||
|
||||
EditorGUI.BeginChangeCheck();
|
||||
DrawDefaultInspector();
|
||||
needsReset |= EditorGUI.EndChangeCheck();
|
||||
return;
|
||||
}
|
||||
|
||||
if (needsReset && Event.current.type == EventType.Layout) {
|
||||
targetBoneFollower.Initialize();
|
||||
targetBoneFollower.LateUpdate();
|
||||
needsReset = false;
|
||||
SceneView.RepaintAll();
|
||||
}
|
||||
serializedObject.Update();
|
||||
|
||||
// Find Renderer
|
||||
if (skeletonGraphic.objectReferenceValue == null) {
|
||||
SkeletonGraphic parentRenderer = targetBoneFollower.GetComponentInParent<SkeletonGraphic>();
|
||||
if (parentRenderer != null && parentRenderer.gameObject != targetBoneFollower.gameObject) {
|
||||
skeletonGraphic.objectReferenceValue = parentRenderer;
|
||||
Debug.Log("Inspector automatically assigned BoneFollowerGraphic.SkeletonGraphic");
|
||||
}
|
||||
}
|
||||
|
||||
EditorGUILayout.PropertyField(skeletonGraphic);
|
||||
SkeletonGraphic skeletonGraphicComponent = skeletonGraphic.objectReferenceValue as SkeletonGraphic;
|
||||
if (skeletonGraphicComponent != null) {
|
||||
if (skeletonGraphicComponent.gameObject == targetBoneFollower.gameObject) {
|
||||
skeletonGraphic.objectReferenceValue = null;
|
||||
EditorUtility.DisplayDialog("Invalid assignment.", "BoneFollowerGraphic can only follow a skeleton on a separate GameObject.\n\nCreate a new GameObject for your BoneFollower, or choose a SkeletonGraphic from a different GameObject.", "Ok");
|
||||
}
|
||||
}
|
||||
|
||||
if (!targetBoneFollower.valid) {
|
||||
needsReset = true;
|
||||
}
|
||||
|
||||
if (targetBoneFollower.valid) {
|
||||
EditorGUI.BeginChangeCheck();
|
||||
EditorGUILayout.PropertyField(boneName);
|
||||
needsReset |= EditorGUI.EndChangeCheck();
|
||||
|
||||
EditorGUILayout.PropertyField(followBoneRotation);
|
||||
EditorGUILayout.PropertyField(followXYPosition);
|
||||
EditorGUILayout.PropertyField(followZPosition);
|
||||
if (followZPosition.boolValue == true) {
|
||||
using (new SpineInspectorUtility.IndentScope())
|
||||
EditorGUILayout.PropertyField(followAttachmentZSpacing, new GUIContent("Attachment Z Spacing"));
|
||||
}
|
||||
EditorGUILayout.PropertyField(followLocalScale);
|
||||
EditorGUILayout.PropertyField(followParentWorldScale);
|
||||
EditorGUILayout.PropertyField(followSkeletonFlip);
|
||||
if ((followSkeletonFlip.hasMultipleDifferentValues || followSkeletonFlip.boolValue == false) &&
|
||||
(followBoneRotation.hasMultipleDifferentValues || followBoneRotation.boolValue == true)) {
|
||||
using (new SpineInspectorUtility.IndentScope())
|
||||
EditorGUILayout.PropertyField(maintainedAxisOrientation);
|
||||
}
|
||||
|
||||
//BoneFollowerInspector.RecommendRigidbodyButton(targetBoneFollower);
|
||||
} else {
|
||||
SkeletonGraphic boneFollowerSkeletonGraphic = targetBoneFollower.skeletonGraphic;
|
||||
if (boneFollowerSkeletonGraphic == null) {
|
||||
EditorGUILayout.HelpBox("SkeletonGraphic is unassigned. Please assign a SkeletonRenderer (SkeletonAnimation or SkeletonMecanim).", MessageType.Warning);
|
||||
} else {
|
||||
boneFollowerSkeletonGraphic.Initialize(false);
|
||||
|
||||
if (boneFollowerSkeletonGraphic.skeletonDataAsset == null)
|
||||
EditorGUILayout.HelpBox("Assigned SkeletonGraphic does not have SkeletonData assigned to it.", MessageType.Warning);
|
||||
|
||||
if (!boneFollowerSkeletonGraphic.IsValid)
|
||||
EditorGUILayout.HelpBox("Assigned SkeletonGraphic is invalid. Check target SkeletonGraphic, its SkeletonData asset or the console for other errors.", MessageType.Warning);
|
||||
}
|
||||
}
|
||||
|
||||
Event current = Event.current;
|
||||
bool wasUndo = (current.type == EventType.ValidateCommand && current.commandName == "UndoRedoPerformed");
|
||||
if (wasUndo)
|
||||
targetBoneFollower.Initialize();
|
||||
|
||||
serializedObject.ApplyModifiedProperties();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: da44a8561fd243c43a1f77bda36de0eb
|
||||
timeCreated: 1499279157
|
||||
licenseType: Free
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,235 @@
|
||||
/******************************************************************************
|
||||
* Spine Runtimes License Agreement
|
||||
* Last updated April 5, 2025. Replaces all prior versions.
|
||||
*
|
||||
* Copyright (c) 2013-2026, Esoteric Software LLC
|
||||
*
|
||||
* Integration of the Spine Runtimes into software or otherwise creating
|
||||
* derivative works of the Spine Runtimes is permitted under the terms and
|
||||
* conditions of Section 2 of the Spine Editor License Agreement:
|
||||
* http://esotericsoftware.com/spine-editor-license
|
||||
*
|
||||
* Otherwise, it is permitted to integrate the Spine Runtimes into software
|
||||
* or otherwise create derivative works of the Spine Runtimes (collectively,
|
||||
* "Products"), provided that each user of the Products must obtain their own
|
||||
* Spine Editor license and redistribution of the Products in any form must
|
||||
* include this license and copyright notice.
|
||||
*
|
||||
* THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY
|
||||
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY
|
||||
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES,
|
||||
* BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND
|
||||
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
||||
* THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*****************************************************************************/
|
||||
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Spine.Unity.Editor {
|
||||
|
||||
using Editor = UnityEditor.Editor;
|
||||
using Event = UnityEngine.Event;
|
||||
|
||||
[CustomEditor(typeof(BoneFollower)), CanEditMultipleObjects]
|
||||
public class BoneFollowerInspector : Editor {
|
||||
SerializedProperty boneName, skeletonRenderer, followXYPosition, followZPosition, followAttachmentZSpacing,
|
||||
followBoneRotation, followLocalScale, followParentWorldScale, followSkeletonFlip, maintainedAxisOrientation;
|
||||
BoneFollower targetBoneFollower;
|
||||
bool needsReset;
|
||||
|
||||
#region Context Menu Item
|
||||
[MenuItem("CONTEXT/SkeletonRenderer/Add BoneFollower GameObject")]
|
||||
static void AddBoneFollowerGameObject (MenuCommand cmd) {
|
||||
SkeletonRenderer skeletonRenderer = cmd.context as SkeletonRenderer;
|
||||
GameObject go = EditorInstantiation.NewGameObject("New BoneFollower", true);
|
||||
Transform t = go.transform;
|
||||
t.SetParent(skeletonRenderer.transform);
|
||||
t.localPosition = Vector3.zero;
|
||||
|
||||
BoneFollower f = go.AddComponent<BoneFollower>();
|
||||
f.skeletonRenderer = skeletonRenderer;
|
||||
|
||||
EditorGUIUtility.PingObject(t);
|
||||
|
||||
Undo.RegisterCreatedObjectUndo(go, "Add BoneFollower");
|
||||
}
|
||||
|
||||
// Validate
|
||||
[MenuItem("CONTEXT/SkeletonRenderer/Add BoneFollower GameObject", true)]
|
||||
static bool ValidateAddBoneFollowerGameObject (MenuCommand cmd) {
|
||||
SkeletonRenderer skeletonRenderer = cmd.context as SkeletonRenderer;
|
||||
return skeletonRenderer.IsValid;
|
||||
}
|
||||
|
||||
[MenuItem("CONTEXT/BoneFollower/Rename BoneFollower GameObject")]
|
||||
static void RenameGameObject (MenuCommand cmd) {
|
||||
AutonameGameObject(cmd.context as BoneFollower);
|
||||
}
|
||||
#endregion
|
||||
|
||||
static void AutonameGameObject (BoneFollower boneFollower) {
|
||||
if (boneFollower == null) return;
|
||||
|
||||
string boneName = boneFollower.boneName;
|
||||
boneFollower.gameObject.name = string.IsNullOrEmpty(boneName) ? "BoneFollower" : string.Format("{0} (BoneFollower)", boneName);
|
||||
}
|
||||
|
||||
void OnEnable () {
|
||||
skeletonRenderer = serializedObject.FindProperty("skeletonRenderer");
|
||||
boneName = serializedObject.FindProperty("boneName");
|
||||
followBoneRotation = serializedObject.FindProperty("followBoneRotation");
|
||||
followXYPosition = serializedObject.FindProperty("followXYPosition");
|
||||
followZPosition = serializedObject.FindProperty("followZPosition");
|
||||
followAttachmentZSpacing = serializedObject.FindProperty("followAttachmentZSpacing");
|
||||
followLocalScale = serializedObject.FindProperty("followLocalScale");
|
||||
followParentWorldScale = serializedObject.FindProperty("followParentWorldScale");
|
||||
followSkeletonFlip = serializedObject.FindProperty("followSkeletonFlip");
|
||||
maintainedAxisOrientation = serializedObject.FindProperty("maintainedAxisOrientation");
|
||||
|
||||
targetBoneFollower = (BoneFollower)target;
|
||||
if (targetBoneFollower.SkeletonRenderer != null)
|
||||
targetBoneFollower.SkeletonRenderer.Initialize(false);
|
||||
|
||||
if (!targetBoneFollower.valid || needsReset) {
|
||||
targetBoneFollower.Initialize();
|
||||
targetBoneFollower.LateUpdate();
|
||||
needsReset = false;
|
||||
SceneView.RepaintAll();
|
||||
}
|
||||
}
|
||||
|
||||
public void OnSceneGUI () {
|
||||
BoneFollower tbf = target as BoneFollower;
|
||||
SkeletonRenderer skeletonRendererComponent = tbf.skeletonRenderer;
|
||||
if (skeletonRendererComponent == null) return;
|
||||
|
||||
Transform transform = skeletonRendererComponent.transform;
|
||||
Skeleton skeleton = skeletonRendererComponent.Skeleton;
|
||||
|
||||
if (string.IsNullOrEmpty(boneName.stringValue)) {
|
||||
SpineHandles.DrawBones(transform, skeleton);
|
||||
SpineHandles.DrawBoneNames(transform, skeleton);
|
||||
Handles.Label(tbf.transform.position, "No bone selected", EditorStyles.helpBox);
|
||||
} else {
|
||||
Bone targetBone = tbf.bone;
|
||||
if (targetBone == null) return;
|
||||
SpineHandles.DrawBoneWireframe(transform, targetBone, SpineHandles.TransformContraintColor);
|
||||
Handles.Label(targetBone.GetWorldPosition(transform), targetBone.Data.Name, SpineHandles.BoneNameStyle);
|
||||
}
|
||||
}
|
||||
|
||||
override public void OnInspectorGUI () {
|
||||
if (serializedObject.isEditingMultipleObjects) {
|
||||
if (needsReset) {
|
||||
needsReset = false;
|
||||
foreach (UnityEngine.Object o in targets) {
|
||||
BoneFollower bf = (BoneFollower)o;
|
||||
bf.Initialize();
|
||||
bf.LateUpdate();
|
||||
}
|
||||
SceneView.RepaintAll();
|
||||
}
|
||||
|
||||
EditorGUI.BeginChangeCheck();
|
||||
DrawDefaultInspector();
|
||||
needsReset |= EditorGUI.EndChangeCheck();
|
||||
return;
|
||||
}
|
||||
|
||||
if (needsReset && Event.current.type == EventType.Layout) {
|
||||
targetBoneFollower.Initialize();
|
||||
targetBoneFollower.LateUpdate();
|
||||
needsReset = false;
|
||||
SceneView.RepaintAll();
|
||||
}
|
||||
serializedObject.Update();
|
||||
|
||||
// Find Renderer
|
||||
if (skeletonRenderer.objectReferenceValue == null) {
|
||||
SkeletonRenderer parentRenderer = targetBoneFollower.GetComponentInParent<SkeletonRenderer>();
|
||||
if (parentRenderer != null && parentRenderer.gameObject != targetBoneFollower.gameObject) {
|
||||
skeletonRenderer.objectReferenceValue = parentRenderer;
|
||||
Debug.Log("Inspector automatically assigned BoneFollower.SkeletonRenderer");
|
||||
}
|
||||
}
|
||||
|
||||
EditorGUILayout.PropertyField(skeletonRenderer);
|
||||
SkeletonRenderer skeletonRendererReference = skeletonRenderer.objectReferenceValue as SkeletonRenderer;
|
||||
if (skeletonRendererReference != null) {
|
||||
if (skeletonRendererReference.gameObject == targetBoneFollower.gameObject) {
|
||||
skeletonRenderer.objectReferenceValue = null;
|
||||
EditorUtility.DisplayDialog("Invalid assignment.", "BoneFollower can only follow a skeleton on a separate GameObject.\n\nCreate a new GameObject for your BoneFollower, or choose a SkeletonRenderer from a different GameObject.", "Ok");
|
||||
}
|
||||
}
|
||||
|
||||
if (!targetBoneFollower.valid) {
|
||||
needsReset = true;
|
||||
}
|
||||
|
||||
if (targetBoneFollower.valid) {
|
||||
EditorGUI.BeginChangeCheck();
|
||||
EditorGUILayout.PropertyField(boneName);
|
||||
needsReset |= EditorGUI.EndChangeCheck();
|
||||
|
||||
EditorGUILayout.PropertyField(followBoneRotation);
|
||||
EditorGUILayout.PropertyField(followXYPosition);
|
||||
EditorGUILayout.PropertyField(followZPosition);
|
||||
if (followZPosition.boolValue == true) {
|
||||
using (new SpineInspectorUtility.IndentScope())
|
||||
EditorGUILayout.PropertyField(followAttachmentZSpacing, new GUIContent("Attachment Z Spacing"));
|
||||
}
|
||||
EditorGUILayout.PropertyField(followLocalScale);
|
||||
EditorGUILayout.PropertyField(followParentWorldScale);
|
||||
EditorGUILayout.PropertyField(followSkeletonFlip);
|
||||
if ((followSkeletonFlip.hasMultipleDifferentValues || followSkeletonFlip.boolValue == false) &&
|
||||
(followBoneRotation.hasMultipleDifferentValues || followBoneRotation.boolValue == true)) {
|
||||
using (new SpineInspectorUtility.IndentScope())
|
||||
EditorGUILayout.PropertyField(maintainedAxisOrientation);
|
||||
}
|
||||
|
||||
BoneFollowerInspector.RecommendRigidbodyButton(targetBoneFollower);
|
||||
} else {
|
||||
SkeletonRenderer boneFollowerSkeletonRenderer = targetBoneFollower.skeletonRenderer;
|
||||
if (boneFollowerSkeletonRenderer == null) {
|
||||
EditorGUILayout.HelpBox("SkeletonRenderer is unassigned. Please assign a SkeletonRenderer (SkeletonAnimation or SkeletonMecanim).", MessageType.Warning);
|
||||
} else {
|
||||
boneFollowerSkeletonRenderer.Initialize(false);
|
||||
|
||||
if (boneFollowerSkeletonRenderer.SkeletonDataAsset == null)
|
||||
EditorGUILayout.HelpBox("Assigned SkeletonRenderer does not have SkeletonData assigned to it.", MessageType.Warning);
|
||||
|
||||
if (!boneFollowerSkeletonRenderer.IsValid)
|
||||
EditorGUILayout.HelpBox("Assigned SkeletonRenderer is invalid. Check target SkeletonRenderer, its SkeletonData asset or the console for other errors.", MessageType.Warning);
|
||||
}
|
||||
}
|
||||
|
||||
Event current = Event.current;
|
||||
bool wasUndo = (current.type == EventType.ValidateCommand && current.commandName == "UndoRedoPerformed");
|
||||
if (wasUndo)
|
||||
targetBoneFollower.Initialize();
|
||||
|
||||
serializedObject.ApplyModifiedProperties();
|
||||
}
|
||||
|
||||
internal static void RecommendRigidbodyButton (Component component) {
|
||||
bool hasCollider2D = component.GetComponent<Collider2D>() != null || component.GetComponent<BoundingBoxFollower>() != null;
|
||||
bool hasCollider3D = !hasCollider2D && component.GetComponent<Collider>();
|
||||
bool missingRigidBody = (hasCollider2D && component.GetComponent<Rigidbody2D>() == null) || (hasCollider3D && component.GetComponent<Rigidbody>() == null);
|
||||
if (missingRigidBody) {
|
||||
using (new SpineInspectorUtility.BoxScope()) {
|
||||
EditorGUILayout.HelpBox("Collider detected. Unity recommends adding a Rigidbody to the Transforms of any colliders that are intended to be dynamically repositioned and rotated.", MessageType.Warning);
|
||||
System.Type rbType = hasCollider2D ? typeof(Rigidbody2D) : typeof(Rigidbody);
|
||||
string rbLabel = string.Format("Add {0}", rbType.Name);
|
||||
GUIContent rbContent = SpineInspectorUtility.TempContent(rbLabel, SpineInspectorUtility.UnityIcon(rbType), "Add a rigidbody to this GameObject to be the Physics body parent of the attached collider.");
|
||||
if (SpineInspectorUtility.CenteredButton(rbContent)) component.gameObject.AddComponent(rbType);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c71ca35fd6241cb49a0b0756a664fcf7
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,280 @@
|
||||
/******************************************************************************
|
||||
* Spine Runtimes License Agreement
|
||||
* Last updated April 5, 2025. Replaces all prior versions.
|
||||
*
|
||||
* Copyright (c) 2013-2026, Esoteric Software LLC
|
||||
*
|
||||
* Integration of the Spine Runtimes into software or otherwise creating
|
||||
* derivative works of the Spine Runtimes is permitted under the terms and
|
||||
* conditions of Section 2 of the Spine Editor License Agreement:
|
||||
* http://esotericsoftware.com/spine-editor-license
|
||||
*
|
||||
* Otherwise, it is permitted to integrate the Spine Runtimes into software
|
||||
* or otherwise create derivative works of the Spine Runtimes (collectively,
|
||||
* "Products"), provided that each user of the Products must obtain their own
|
||||
* Spine Editor license and redistribution of the Products in any form must
|
||||
* include this license and copyright notice.
|
||||
*
|
||||
* THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY
|
||||
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY
|
||||
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES,
|
||||
* BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND
|
||||
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
||||
* THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*****************************************************************************/
|
||||
|
||||
#if UNITY_2018_3 || UNITY_2019 || UNITY_2018_3_OR_NEWER
|
||||
#define NEW_PREFAB_SYSTEM
|
||||
#endif
|
||||
|
||||
#if UNITY_2023_1_OR_NEWER
|
||||
#define USE_COLLIDER_COMPOSITE_OPERATION
|
||||
#endif
|
||||
|
||||
using System.Collections.Generic;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Spine.Unity.Editor {
|
||||
using Event = UnityEngine.Event;
|
||||
using Icons = SpineEditorUtilities.Icons;
|
||||
|
||||
[CustomEditor(typeof(BoundingBoxFollowerGraphic))]
|
||||
public class BoundingBoxFollowerGraphicInspector : UnityEditor.Editor {
|
||||
SerializedProperty skeletonGraphic, slotName,
|
||||
isTrigger, usedByEffector, usedByComposite, clearStateOnDisable;
|
||||
BoundingBoxFollowerGraphic follower;
|
||||
bool rebuildRequired = false;
|
||||
bool addBoneFollower = false;
|
||||
bool sceneRepaintRequired = false;
|
||||
bool debugIsExpanded;
|
||||
|
||||
GUIContent addBoneFollowerLabel;
|
||||
GUIContent AddBoneFollowerLabel {
|
||||
get {
|
||||
if (addBoneFollowerLabel == null) addBoneFollowerLabel = new GUIContent("Add Bone Follower", Icons.bone);
|
||||
return addBoneFollowerLabel;
|
||||
}
|
||||
}
|
||||
|
||||
void InitializeEditor () {
|
||||
skeletonGraphic = serializedObject.FindProperty("skeletonGraphic");
|
||||
slotName = serializedObject.FindProperty("slotName");
|
||||
isTrigger = serializedObject.FindProperty("isTrigger");
|
||||
usedByEffector = serializedObject.FindProperty("usedByEffector");
|
||||
usedByComposite = serializedObject.FindProperty("usedByComposite");
|
||||
clearStateOnDisable = serializedObject.FindProperty("clearStateOnDisable");
|
||||
follower = (BoundingBoxFollowerGraphic)target;
|
||||
}
|
||||
|
||||
public override void OnInspectorGUI () {
|
||||
|
||||
#if !NEW_PREFAB_SYSTEM
|
||||
bool isInspectingPrefab = (PrefabUtility.GetPrefabType(target) == PrefabType.Prefab);
|
||||
#else
|
||||
bool isInspectingPrefab = false;
|
||||
#endif
|
||||
|
||||
// Note: when calling InitializeEditor() in OnEnable, it throws exception
|
||||
// "SerializedObjectNotCreatableException: Object at index 0 is null".
|
||||
InitializeEditor();
|
||||
|
||||
// Try to auto-assign SkeletonGraphic field.
|
||||
if (skeletonGraphic.objectReferenceValue == null) {
|
||||
SkeletonGraphic foundSkeletonGraphic = follower.GetComponentInParent<SkeletonGraphic>();
|
||||
if (foundSkeletonGraphic != null)
|
||||
Debug.Log("BoundingBoxFollowerGraphic automatically assigned: " + foundSkeletonGraphic.gameObject.name);
|
||||
else if (Event.current.type == EventType.Repaint)
|
||||
Debug.Log("No Spine GameObject detected. Make sure to set this GameObject as a child of the Spine GameObject; or set BoundingBoxFollowerGraphic's 'Skeleton Graphic' field in the inspector.");
|
||||
|
||||
skeletonGraphic.objectReferenceValue = foundSkeletonGraphic;
|
||||
serializedObject.ApplyModifiedProperties();
|
||||
InitializeEditor();
|
||||
}
|
||||
|
||||
SkeletonGraphic skeletonGraphicValue = skeletonGraphic.objectReferenceValue as SkeletonGraphic;
|
||||
if (skeletonGraphicValue != null && skeletonGraphicValue.gameObject == follower.gameObject) {
|
||||
using (new EditorGUILayout.VerticalScope(EditorStyles.helpBox)) {
|
||||
EditorGUILayout.HelpBox("It's ideal to add BoundingBoxFollowerGraphic to a separate child GameObject of the Spine GameObject.", MessageType.Warning);
|
||||
|
||||
if (GUILayout.Button(new GUIContent("Move BoundingBoxFollowerGraphic to new GameObject", Icons.boundingBox), GUILayout.Height(30f))) {
|
||||
AddBoundingBoxFollowerGraphicChild(skeletonGraphicValue, follower);
|
||||
DestroyImmediate(follower);
|
||||
return;
|
||||
}
|
||||
}
|
||||
EditorGUILayout.Space();
|
||||
}
|
||||
|
||||
EditorGUI.BeginChangeCheck();
|
||||
EditorGUILayout.PropertyField(skeletonGraphic);
|
||||
EditorGUILayout.PropertyField(slotName, new GUIContent("Slot"));
|
||||
if (EditorGUI.EndChangeCheck()) {
|
||||
serializedObject.ApplyModifiedProperties();
|
||||
InitializeEditor();
|
||||
#if !NEW_PREFAB_SYSTEM
|
||||
if (!isInspectingPrefab)
|
||||
rebuildRequired = true;
|
||||
#endif
|
||||
}
|
||||
|
||||
using (new SpineInspectorUtility.LabelWidthScope(150f)) {
|
||||
EditorGUI.BeginChangeCheck();
|
||||
EditorGUILayout.PropertyField(isTrigger);
|
||||
EditorGUILayout.PropertyField(usedByEffector);
|
||||
EditorGUILayout.PropertyField(usedByComposite);
|
||||
bool colliderParamChanged = EditorGUI.EndChangeCheck();
|
||||
|
||||
EditorGUI.BeginChangeCheck();
|
||||
EditorGUILayout.PropertyField(clearStateOnDisable, new GUIContent(clearStateOnDisable.displayName, "Enable this if you are pooling your Spine GameObject"));
|
||||
bool clearStateChanged = EditorGUI.EndChangeCheck();
|
||||
|
||||
if (clearStateChanged || colliderParamChanged) {
|
||||
serializedObject.ApplyModifiedProperties();
|
||||
InitializeEditor();
|
||||
if (colliderParamChanged)
|
||||
foreach (PolygonCollider2D col in follower.colliderTable.Values) {
|
||||
col.isTrigger = isTrigger.boolValue;
|
||||
col.usedByEffector = usedByEffector.boolValue;
|
||||
#if USE_COLLIDER_COMPOSITE_OPERATION
|
||||
col.compositeOperation = usedByComposite.boolValue ?
|
||||
Collider2D.CompositeOperation.Merge : Collider2D.CompositeOperation.None;
|
||||
#else
|
||||
col.usedByComposite = usedByComposite.boolValue;
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (isInspectingPrefab) {
|
||||
follower.colliderTable.Clear();
|
||||
follower.nameTable.Clear();
|
||||
EditorGUILayout.HelpBox("BoundingBoxAttachments cannot be previewed in prefabs.", MessageType.Info);
|
||||
|
||||
// How do you prevent components from being saved into the prefab? No such HideFlag. DontSaveInEditor | DontSaveInBuild does not work. DestroyImmediate does not work.
|
||||
PolygonCollider2D collider = follower.GetComponent<PolygonCollider2D>();
|
||||
if (collider != null) Debug.LogWarning("Found BoundingBoxFollowerGraphic collider components in prefab. These are disposed and regenerated at runtime.");
|
||||
|
||||
} else {
|
||||
using (new SpineInspectorUtility.BoxScope()) {
|
||||
if (debugIsExpanded = EditorGUILayout.Foldout(debugIsExpanded, "Debug Colliders")) {
|
||||
EditorGUI.indentLevel++;
|
||||
EditorGUILayout.LabelField(string.Format("Attachment Names ({0} PolygonCollider2D)", follower.colliderTable.Count));
|
||||
EditorGUI.BeginChangeCheck();
|
||||
foreach (KeyValuePair<BoundingBoxAttachment, string> kp in follower.nameTable) {
|
||||
string attachmentName = kp.Value;
|
||||
PolygonCollider2D collider = follower.colliderTable[kp.Key];
|
||||
bool isPlaceholder = attachmentName != kp.Key.Name;
|
||||
collider.enabled = EditorGUILayout.ToggleLeft(new GUIContent(!isPlaceholder ? attachmentName : string.Format("{0} [{1}]", attachmentName, kp.Key.Name), isPlaceholder ? Icons.skinPlaceholder : Icons.boundingBox), collider.enabled);
|
||||
}
|
||||
sceneRepaintRequired |= EditorGUI.EndChangeCheck();
|
||||
EditorGUI.indentLevel--;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if (follower.Slot == null)
|
||||
follower.Initialize(false);
|
||||
bool hasBoneFollower = follower.GetComponent<BoneFollowerGraphic>() != null;
|
||||
if (!hasBoneFollower) {
|
||||
bool buttonDisabled = follower.Slot == null;
|
||||
using (new EditorGUI.DisabledGroupScope(buttonDisabled)) {
|
||||
addBoneFollower |= SpineInspectorUtility.LargeCenteredButton(AddBoneFollowerLabel, true);
|
||||
EditorGUILayout.Space();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (Event.current.type == EventType.Repaint) {
|
||||
if (addBoneFollower) {
|
||||
BoneFollowerGraphic boneFollower = follower.gameObject.AddComponent<BoneFollowerGraphic>();
|
||||
boneFollower.skeletonGraphic = skeletonGraphicValue;
|
||||
boneFollower.SetBone(follower.Slot.Data.BoneData.Name);
|
||||
addBoneFollower = false;
|
||||
}
|
||||
|
||||
if (sceneRepaintRequired) {
|
||||
SceneView.RepaintAll();
|
||||
sceneRepaintRequired = false;
|
||||
}
|
||||
|
||||
if (rebuildRequired) {
|
||||
follower.Initialize();
|
||||
rebuildRequired = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#region Menus
|
||||
[MenuItem("CONTEXT/SkeletonGraphic/Add BoundingBoxFollowerGraphic GameObject")]
|
||||
static void AddBoundingBoxFollowerGraphicChild (MenuCommand command) {
|
||||
GameObject go = AddBoundingBoxFollowerGraphicChild((SkeletonGraphic)command.context);
|
||||
Undo.RegisterCreatedObjectUndo(go, "Add BoundingBoxFollowerGraphic");
|
||||
}
|
||||
|
||||
[MenuItem("CONTEXT/SkeletonGraphic/Add all BoundingBoxFollowerGraphic GameObjects")]
|
||||
static void AddAllBoundingBoxFollowerGraphicChildren (MenuCommand command) {
|
||||
List<GameObject> objects = AddAllBoundingBoxFollowerGraphicChildren((SkeletonGraphic)command.context);
|
||||
foreach (GameObject go in objects)
|
||||
Undo.RegisterCreatedObjectUndo(go, "Add BoundingBoxFollowerGraphic");
|
||||
}
|
||||
#endregion
|
||||
|
||||
public static GameObject AddBoundingBoxFollowerGraphicChild (SkeletonGraphic skeletonGraphic,
|
||||
BoundingBoxFollowerGraphic original = null, string name = "BoundingBoxFollowerGraphic",
|
||||
string slotName = null) {
|
||||
|
||||
GameObject go = EditorInstantiation.NewGameObject(name, true);
|
||||
go.transform.SetParent(skeletonGraphic.transform, false);
|
||||
go.AddComponent<RectTransform>();
|
||||
BoundingBoxFollowerGraphic newFollower = go.AddComponent<BoundingBoxFollowerGraphic>();
|
||||
|
||||
if (original != null) {
|
||||
newFollower.slotName = original.slotName;
|
||||
newFollower.isTrigger = original.isTrigger;
|
||||
newFollower.usedByEffector = original.usedByEffector;
|
||||
newFollower.usedByComposite = original.usedByComposite;
|
||||
newFollower.clearStateOnDisable = original.clearStateOnDisable;
|
||||
}
|
||||
if (slotName != null)
|
||||
newFollower.slotName = slotName;
|
||||
|
||||
newFollower.skeletonGraphic = skeletonGraphic;
|
||||
newFollower.Initialize();
|
||||
|
||||
Selection.activeGameObject = go;
|
||||
EditorGUIUtility.PingObject(go);
|
||||
return go;
|
||||
}
|
||||
|
||||
public static List<GameObject> AddAllBoundingBoxFollowerGraphicChildren (
|
||||
SkeletonGraphic skeletonGraphic, BoundingBoxFollowerGraphic original = null) {
|
||||
|
||||
List<GameObject> createdGameObjects = new List<GameObject>();
|
||||
foreach (Skin skin in skeletonGraphic.Skeleton.Data.Skins) {
|
||||
ICollection<Skin.SkinEntry> attachments = skin.Attachments;
|
||||
foreach (Skin.SkinEntry entry in attachments) {
|
||||
BoundingBoxAttachment boundingBoxAttachment = entry.Attachment as BoundingBoxAttachment;
|
||||
if (boundingBoxAttachment == null)
|
||||
continue;
|
||||
int slotIndex = entry.SlotIndex;
|
||||
Slot slot = skeletonGraphic.Skeleton.Slots.Items[slotIndex];
|
||||
string slotName = slot.Data.Name;
|
||||
GameObject go = AddBoundingBoxFollowerGraphicChild(skeletonGraphic,
|
||||
original, boundingBoxAttachment.Name, slotName);
|
||||
BoneFollowerGraphic boneFollower = go.AddComponent<BoneFollowerGraphic>();
|
||||
boneFollower.skeletonGraphic = skeletonGraphic;
|
||||
boneFollower.SetBone(slot.Data.BoneData.Name);
|
||||
createdGameObjects.Add(go);
|
||||
}
|
||||
}
|
||||
return createdGameObjects;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 7c4f5b276299bc048ad00f3cd2d1ea09
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,279 @@
|
||||
/******************************************************************************
|
||||
* Spine Runtimes License Agreement
|
||||
* Last updated April 5, 2025. Replaces all prior versions.
|
||||
*
|
||||
* Copyright (c) 2013-2026, Esoteric Software LLC
|
||||
*
|
||||
* Integration of the Spine Runtimes into software or otherwise creating
|
||||
* derivative works of the Spine Runtimes is permitted under the terms and
|
||||
* conditions of Section 2 of the Spine Editor License Agreement:
|
||||
* http://esotericsoftware.com/spine-editor-license
|
||||
*
|
||||
* Otherwise, it is permitted to integrate the Spine Runtimes into software
|
||||
* or otherwise create derivative works of the Spine Runtimes (collectively,
|
||||
* "Products"), provided that each user of the Products must obtain their own
|
||||
* Spine Editor license and redistribution of the Products in any form must
|
||||
* include this license and copyright notice.
|
||||
*
|
||||
* THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY
|
||||
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY
|
||||
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES,
|
||||
* BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND
|
||||
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
||||
* THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*****************************************************************************/
|
||||
|
||||
#if UNITY_2018_3 || UNITY_2019 || UNITY_2018_3_OR_NEWER
|
||||
#define NEW_PREFAB_SYSTEM
|
||||
#endif
|
||||
|
||||
#if UNITY_2023_1_OR_NEWER
|
||||
#define USE_COLLIDER_COMPOSITE_OPERATION
|
||||
#endif
|
||||
|
||||
using System.Collections.Generic;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Spine.Unity.Editor {
|
||||
using Event = UnityEngine.Event;
|
||||
using Icons = SpineEditorUtilities.Icons;
|
||||
|
||||
[CustomEditor(typeof(BoundingBoxFollower))]
|
||||
public class BoundingBoxFollowerInspector : UnityEditor.Editor {
|
||||
SerializedProperty skeletonRenderer, slotName,
|
||||
isTrigger, usedByEffector, usedByComposite, clearStateOnDisable;
|
||||
BoundingBoxFollower follower;
|
||||
bool rebuildRequired = false;
|
||||
bool addBoneFollower = false;
|
||||
bool sceneRepaintRequired = false;
|
||||
bool debugIsExpanded;
|
||||
|
||||
GUIContent addBoneFollowerLabel;
|
||||
GUIContent AddBoneFollowerLabel {
|
||||
get {
|
||||
if (addBoneFollowerLabel == null) addBoneFollowerLabel = new GUIContent("Add Bone Follower", Icons.bone);
|
||||
return addBoneFollowerLabel;
|
||||
}
|
||||
}
|
||||
|
||||
void InitializeEditor () {
|
||||
skeletonRenderer = serializedObject.FindProperty("skeletonRenderer");
|
||||
slotName = serializedObject.FindProperty("slotName");
|
||||
isTrigger = serializedObject.FindProperty("isTrigger");
|
||||
usedByEffector = serializedObject.FindProperty("usedByEffector");
|
||||
usedByComposite = serializedObject.FindProperty("usedByComposite");
|
||||
clearStateOnDisable = serializedObject.FindProperty("clearStateOnDisable");
|
||||
follower = (BoundingBoxFollower)target;
|
||||
}
|
||||
|
||||
public override void OnInspectorGUI () {
|
||||
|
||||
#if !NEW_PREFAB_SYSTEM
|
||||
bool isInspectingPrefab = (PrefabUtility.GetPrefabType(target) == PrefabType.Prefab);
|
||||
#else
|
||||
bool isInspectingPrefab = false;
|
||||
#endif
|
||||
|
||||
// Note: when calling InitializeEditor() in OnEnable, it throws exception
|
||||
// "SerializedObjectNotCreatableException: Object at index 0 is null".
|
||||
InitializeEditor();
|
||||
|
||||
// Try to auto-assign SkeletonRenderer field.
|
||||
if (skeletonRenderer.objectReferenceValue == null) {
|
||||
SkeletonRenderer foundSkeletonRenderer = follower.GetComponentInParent<SkeletonRenderer>();
|
||||
if (foundSkeletonRenderer != null)
|
||||
Debug.Log("BoundingBoxFollower automatically assigned: " + foundSkeletonRenderer.gameObject.name);
|
||||
else if (Event.current.type == EventType.Repaint)
|
||||
Debug.Log("No Spine GameObject detected. Make sure to set this GameObject as a child of the Spine GameObject; or set BoundingBoxFollower's 'Skeleton Renderer' field in the inspector.");
|
||||
|
||||
skeletonRenderer.objectReferenceValue = foundSkeletonRenderer;
|
||||
serializedObject.ApplyModifiedProperties();
|
||||
InitializeEditor();
|
||||
}
|
||||
|
||||
SkeletonRenderer skeletonRendererValue = skeletonRenderer.objectReferenceValue as SkeletonRenderer;
|
||||
if (skeletonRendererValue != null && skeletonRendererValue.gameObject == follower.gameObject) {
|
||||
using (new EditorGUILayout.VerticalScope(EditorStyles.helpBox)) {
|
||||
EditorGUILayout.HelpBox("It's ideal to add BoundingBoxFollower to a separate child GameObject of the Spine GameObject.", MessageType.Warning);
|
||||
|
||||
if (GUILayout.Button(new GUIContent("Move BoundingBoxFollower to new GameObject", Icons.boundingBox), GUILayout.Height(30f))) {
|
||||
AddBoundingBoxFollowerChild(skeletonRendererValue, follower);
|
||||
DestroyImmediate(follower);
|
||||
return;
|
||||
}
|
||||
}
|
||||
EditorGUILayout.Space();
|
||||
}
|
||||
|
||||
EditorGUI.BeginChangeCheck();
|
||||
EditorGUILayout.PropertyField(skeletonRenderer);
|
||||
EditorGUILayout.PropertyField(slotName, new GUIContent("Slot"));
|
||||
if (EditorGUI.EndChangeCheck()) {
|
||||
serializedObject.ApplyModifiedProperties();
|
||||
InitializeEditor();
|
||||
#if !NEW_PREFAB_SYSTEM
|
||||
if (!isInspectingPrefab)
|
||||
rebuildRequired = true;
|
||||
#endif
|
||||
}
|
||||
|
||||
using (new SpineInspectorUtility.LabelWidthScope(150f)) {
|
||||
EditorGUI.BeginChangeCheck();
|
||||
EditorGUILayout.PropertyField(isTrigger);
|
||||
EditorGUILayout.PropertyField(usedByEffector);
|
||||
EditorGUILayout.PropertyField(usedByComposite);
|
||||
bool colliderParamChanged = EditorGUI.EndChangeCheck();
|
||||
|
||||
EditorGUI.BeginChangeCheck();
|
||||
EditorGUILayout.PropertyField(clearStateOnDisable, new GUIContent(clearStateOnDisable.displayName, "Enable this if you are pooling your Spine GameObject"));
|
||||
bool clearStateChanged = EditorGUI.EndChangeCheck();
|
||||
|
||||
if (clearStateChanged || colliderParamChanged) {
|
||||
serializedObject.ApplyModifiedProperties();
|
||||
InitializeEditor();
|
||||
if (colliderParamChanged)
|
||||
foreach (PolygonCollider2D col in follower.colliderTable.Values) {
|
||||
col.isTrigger = isTrigger.boolValue;
|
||||
col.usedByEffector = usedByEffector.boolValue;
|
||||
#if USE_COLLIDER_COMPOSITE_OPERATION
|
||||
col.compositeOperation = usedByComposite.boolValue ?
|
||||
Collider2D.CompositeOperation.Merge : Collider2D.CompositeOperation.None;
|
||||
#else
|
||||
col.usedByComposite = usedByComposite.boolValue;
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (isInspectingPrefab) {
|
||||
follower.colliderTable.Clear();
|
||||
follower.nameTable.Clear();
|
||||
EditorGUILayout.HelpBox("BoundingBoxAttachments cannot be previewed in prefabs.", MessageType.Info);
|
||||
|
||||
// How do you prevent components from being saved into the prefab? No such HideFlag. DontSaveInEditor | DontSaveInBuild does not work. DestroyImmediate does not work.
|
||||
PolygonCollider2D collider = follower.GetComponent<PolygonCollider2D>();
|
||||
if (collider != null) Debug.LogWarning("Found BoundingBoxFollower collider components in prefab. These are disposed and regenerated at runtime.");
|
||||
|
||||
} else {
|
||||
using (new SpineInspectorUtility.BoxScope()) {
|
||||
if (debugIsExpanded = EditorGUILayout.Foldout(debugIsExpanded, "Debug Colliders")) {
|
||||
EditorGUI.indentLevel++;
|
||||
EditorGUILayout.LabelField(string.Format("Attachment Names ({0} PolygonCollider2D)", follower.colliderTable.Count));
|
||||
EditorGUI.BeginChangeCheck();
|
||||
foreach (KeyValuePair<BoundingBoxAttachment, string> pair in follower.nameTable) {
|
||||
string attachmentName = pair.Value;
|
||||
PolygonCollider2D collider = follower.colliderTable[pair.Key];
|
||||
bool isPlaceholder = attachmentName != pair.Key.Name;
|
||||
collider.enabled = EditorGUILayout.ToggleLeft(new GUIContent(!isPlaceholder ? attachmentName : string.Format("{0} [{1}]", attachmentName, pair.Key.Name), isPlaceholder ? Icons.skinPlaceholder : Icons.boundingBox), collider.enabled);
|
||||
}
|
||||
sceneRepaintRequired |= EditorGUI.EndChangeCheck();
|
||||
EditorGUI.indentLevel--;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if (follower.Slot == null)
|
||||
follower.Initialize(false);
|
||||
bool hasBoneFollower = follower.GetComponent<BoneFollower>() != null;
|
||||
if (!hasBoneFollower) {
|
||||
bool buttonDisabled = follower.Slot == null;
|
||||
using (new EditorGUI.DisabledGroupScope(buttonDisabled)) {
|
||||
addBoneFollower |= SpineInspectorUtility.LargeCenteredButton(AddBoneFollowerLabel, true);
|
||||
EditorGUILayout.Space();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (Event.current.type == EventType.Repaint) {
|
||||
if (addBoneFollower) {
|
||||
BoneFollower boneFollower = follower.gameObject.AddComponent<BoneFollower>();
|
||||
boneFollower.skeletonRenderer = skeletonRendererValue;
|
||||
boneFollower.SetBone(follower.Slot.Data.BoneData.Name);
|
||||
addBoneFollower = false;
|
||||
}
|
||||
|
||||
if (sceneRepaintRequired) {
|
||||
SceneView.RepaintAll();
|
||||
sceneRepaintRequired = false;
|
||||
}
|
||||
|
||||
if (rebuildRequired) {
|
||||
follower.Initialize();
|
||||
rebuildRequired = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#region Menus
|
||||
[MenuItem("CONTEXT/SkeletonRenderer/Add BoundingBoxFollower GameObject")]
|
||||
static void AddBoundingBoxFollowerChild (MenuCommand command) {
|
||||
GameObject go = AddBoundingBoxFollowerChild((SkeletonRenderer)command.context);
|
||||
Undo.RegisterCreatedObjectUndo(go, "Add BoundingBoxFollower");
|
||||
}
|
||||
|
||||
[MenuItem("CONTEXT/SkeletonRenderer/Add all BoundingBoxFollower GameObjects")]
|
||||
static void AddAllBoundingBoxFollowerChildren (MenuCommand command) {
|
||||
List<GameObject> objects = AddAllBoundingBoxFollowerChildren((SkeletonRenderer)command.context);
|
||||
foreach (GameObject go in objects)
|
||||
Undo.RegisterCreatedObjectUndo(go, "Add BoundingBoxFollower");
|
||||
}
|
||||
#endregion
|
||||
|
||||
public static GameObject AddBoundingBoxFollowerChild (SkeletonRenderer skeletonRenderer,
|
||||
BoundingBoxFollower original = null, string name = "BoundingBoxFollower",
|
||||
string slotName = null) {
|
||||
|
||||
GameObject go = EditorInstantiation.NewGameObject(name, true);
|
||||
go.transform.SetParent(skeletonRenderer.transform, false);
|
||||
BoundingBoxFollower newFollower = go.AddComponent<BoundingBoxFollower>();
|
||||
|
||||
if (original != null) {
|
||||
newFollower.slotName = original.slotName;
|
||||
newFollower.isTrigger = original.isTrigger;
|
||||
newFollower.usedByEffector = original.usedByEffector;
|
||||
newFollower.usedByComposite = original.usedByComposite;
|
||||
newFollower.clearStateOnDisable = original.clearStateOnDisable;
|
||||
}
|
||||
if (slotName != null)
|
||||
newFollower.slotName = slotName;
|
||||
|
||||
newFollower.skeletonRenderer = skeletonRenderer;
|
||||
newFollower.Initialize();
|
||||
|
||||
Selection.activeGameObject = go;
|
||||
EditorGUIUtility.PingObject(go);
|
||||
return go;
|
||||
}
|
||||
|
||||
public static List<GameObject> AddAllBoundingBoxFollowerChildren (
|
||||
SkeletonRenderer skeletonRenderer, BoundingBoxFollower original = null) {
|
||||
|
||||
List<GameObject> createdGameObjects = new List<GameObject>();
|
||||
foreach (Skin skin in skeletonRenderer.Skeleton.Data.Skins) {
|
||||
ICollection<Skin.SkinEntry> attachments = skin.Attachments;
|
||||
foreach (Skin.SkinEntry entry in attachments) {
|
||||
BoundingBoxAttachment boundingBoxAttachment = entry.Attachment as BoundingBoxAttachment;
|
||||
if (boundingBoxAttachment == null)
|
||||
continue;
|
||||
int slotIndex = entry.SlotIndex;
|
||||
Slot slot = skeletonRenderer.Skeleton.Slots.Items[slotIndex];
|
||||
string slotName = slot.Data.Name;
|
||||
GameObject go = AddBoundingBoxFollowerChild(skeletonRenderer,
|
||||
original, boundingBoxAttachment.Name, slotName);
|
||||
BoneFollower boneFollower = go.AddComponent<BoneFollower>();
|
||||
boneFollower.skeletonRenderer = skeletonRenderer;
|
||||
boneFollower.SetBone(slot.Data.BoneData.Name);
|
||||
createdGameObjects.Add(go);
|
||||
}
|
||||
}
|
||||
return createdGameObjects;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 670a3cefa3853bd48b5da53a424fd542
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
@@ -0,0 +1,537 @@
|
||||
/******************************************************************************
|
||||
* Spine Runtimes License Agreement
|
||||
* Last updated January 1, 2020. Replaces all prior versions.
|
||||
*
|
||||
* Copyright (c) 2013-2020, Esoteric Software LLC
|
||||
*
|
||||
* Integration of the Spine Runtimes into software or otherwise creating
|
||||
* derivative works of the Spine Runtimes is permitted under the terms and
|
||||
* conditions of Section 2 of the Spine Editor License Agreement:
|
||||
* http://esotericsoftware.com/spine-editor-license
|
||||
*
|
||||
* Otherwise, it is permitted to integrate the Spine Runtimes into software
|
||||
* or otherwise create derivative works of the Spine Runtimes (collectively,
|
||||
* "Products"), provided that each user of the Products must obtain their own
|
||||
* Spine Editor license and redistribution of the Products in any form must
|
||||
* include this license and copyright notice.
|
||||
*
|
||||
* THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY
|
||||
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY
|
||||
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES,
|
||||
* BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND
|
||||
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
||||
* THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*****************************************************************************/
|
||||
|
||||
#if UNITY_2018_3 || UNITY_2019 || UNITY_2018_3_OR_NEWER
|
||||
#define NEW_PREFAB_SYSTEM
|
||||
#else
|
||||
#define NO_PREFAB_MESH
|
||||
#endif
|
||||
|
||||
#if UNITY_2018_1_OR_NEWER
|
||||
#define PER_MATERIAL_PROPERTY_BLOCKS
|
||||
#endif
|
||||
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Spine.Unity.Editor {
|
||||
using Event = UnityEngine.Event;
|
||||
using Icons = SpineEditorUtilities.Icons;
|
||||
|
||||
public class ISkeletonRendererInspector : UnityEditor.Editor {
|
||||
public static bool advancedFoldout;
|
||||
protected bool loadingFailed = false;
|
||||
|
||||
const string SeparatorSlotNamesFieldName = "separatorSlotNames";
|
||||
|
||||
protected SerializedProperty skeletonDataAsset, initialSkinName;
|
||||
protected SerializedProperty initialFlipX, initialFlipY;
|
||||
protected SerializedProperty updateWhenInvisible, separatorSlotNames, enableSeparatorSlots;
|
||||
protected SerializedProperty clearStateOnDisable, fixDrawOrder;
|
||||
protected SerializedProperty useClipping, zSpacing, immutableTriangles;
|
||||
protected SerializedProperty threadedMeshGeneration;
|
||||
// Vertex Data parameters
|
||||
protected SerializedProperty tintBlack, canvasGroupCompatible, pmaVertexColors, addNormals, calculateTangents;
|
||||
protected SerializedProperty physicsPositionInheritanceFactor, physicsRotationInheritanceFactor,
|
||||
physicsPositionInheritanceLimit, physicsRotationInheritanceLimit, physicsMovementRelativeTo;
|
||||
|
||||
protected bool isInspectingPrefab;
|
||||
protected bool forceReloadQueued = false;
|
||||
|
||||
private GUIContent SkeletonDataAssetLabel, SkeletonUtilityButtonContent;
|
||||
|
||||
protected readonly GUIContent ClearStateOnDisableLabel = new GUIContent(
|
||||
"Clear State On Disable", "Use this if you are pooling or enabling/disabling your Spine GameObject.");
|
||||
|
||||
protected readonly GUIContent UseClippingLabel = new GUIContent("Use Clipping",
|
||||
"When disabled, clipping attachments are ignored. This may be used to save performance.");
|
||||
protected readonly GUIContent ZSpacingLabel = new GUIContent("Z Spacing",
|
||||
"A value other than 0 adds a space between each rendered attachment to prevent Z Fighting when using shaders" +
|
||||
" that read or write to the depth buffer. Large values may cause unwanted parallax and spaces depending on " +
|
||||
"camera setup.");
|
||||
protected readonly GUIContent ThreadedMeshGenerationLabel = new GUIContent("Use Threading",
|
||||
"When enabled, mesh generation is performed on multiple threads in parallel.");
|
||||
|
||||
protected readonly GUIContent TintBlackLabel = new GUIContent("Tint Black (!)",
|
||||
"Adds black tint vertex data to the mesh as UV2 and UV3. Black tinting requires that the shader interpret " +
|
||||
"UV2 and UV3 as black tint colors for this effect to work. You may then want to use the " +
|
||||
"[Spine/SkeletonGraphic Tint Black] shader.");
|
||||
protected readonly GUIContent CanvasGroupCompatibleLabel = new GUIContent("CanvasGroup Compatible",
|
||||
"Enable when using SkeletonGraphic under a CanvasGroup. " +
|
||||
"When enabled, PMA Vertex Color alpha value is stored at uv2.g instead of color.a to capture " +
|
||||
"CanvasGroup modifying color.a. Also helps to detect correct parameter setting combinations.");
|
||||
protected readonly GUIContent PMAVertexColorsLabel = new GUIContent("PMA Vertex Colors",
|
||||
"Use this if you are using the default Spine/Skeleton shader or any premultiply-alpha shader.");
|
||||
protected readonly GUIContent AddNormalsLabel = new GUIContent("Add Normals",
|
||||
"Use this if your shader requires vertex normals. A more efficient solution for 2D setups is to modify the " +
|
||||
"shader to assume a single normal value for the whole mesh.");
|
||||
protected readonly GUIContent CalculateTangentsLabel = new GUIContent("Solve Tangents",
|
||||
"Calculates the tangents per frame. Use this if you are using lit shaders (usually with normal maps) that " +
|
||||
"require vertex tangents.");
|
||||
|
||||
protected readonly GUIContent ImmutableTrianglesLabel = new GUIContent("Immutable Triangles",
|
||||
"Enable to optimize rendering for skeletons that never change attachment visibility");
|
||||
|
||||
|
||||
private static GUIContent EnableSeparatorSlotsLabel;
|
||||
private GUIContent UpdateWhenInvisibleLabel, FixDrawOrderLabel;
|
||||
|
||||
readonly GUIContent PhysicsPositionInheritanceFactorLabel = new GUIContent("Position",
|
||||
"When set to non-zero, Transform position movement in X and Y direction is applied to skeleton " +
|
||||
"PhysicsConstraints, multiplied by these " +
|
||||
"\nX and Y scale factors to the right. Typical (X,Y) values are " +
|
||||
"\n(1,1) to apply XY movement normally, " +
|
||||
"\n(2,2) to apply movement with double intensity, " +
|
||||
"\n(1,0) to apply only horizontal movement, or" +
|
||||
"\n(0,0) to not apply any Transform position movement at all.");
|
||||
readonly GUIContent PhysicsRotationInheritanceFactorLabel = new GUIContent("Rotation",
|
||||
"When set to non-zero, Transform rotation movement is applied to skeleton PhysicsConstraints, " +
|
||||
"multiplied by this scale factor to the right. Typical values are " +
|
||||
"\n1 to apply movement normally, " +
|
||||
"\n2 to apply movement with double intensity, or " +
|
||||
"\n0 to not apply any Transform rotation movement at all.");
|
||||
readonly GUIContent PhysicsPositionInheritanceLimitLabel = new GUIContent("Limit",
|
||||
"Limits Transform position movement in X and Y direction that is applied to skeleton PhysicsConstraints, " +
|
||||
"after it has been multiplied by Position inheritance above.");
|
||||
readonly GUIContent PhysicsRotationInheritanceLimitLabel = new GUIContent("Limit",
|
||||
"Limits Transform rotation that is applied to skeleton PhysicsConstraints, " +
|
||||
"after it has been multiplied by Rotation inheritance above.");
|
||||
readonly GUIContent PhysicsMovementRelativeToLabel = new GUIContent("Movement relative to",
|
||||
"Reference transform relative to which physics movement will be calculated, or null to use world location.");
|
||||
|
||||
protected SerializedProperty meshSettings;
|
||||
|
||||
const string ReloadButtonString = "Reload";
|
||||
static GUILayoutOption reloadButtonWidth;
|
||||
static GUILayoutOption ReloadButtonWidth { get { return reloadButtonWidth = reloadButtonWidth ?? GUILayout.Width(GUI.skin.label.CalcSize(new GUIContent(ReloadButtonString)).x + 20); } }
|
||||
static GUIStyle ReloadButtonStyle { get { return EditorStyles.miniButton; } }
|
||||
|
||||
protected virtual bool TargetIsValid {
|
||||
get {
|
||||
foreach (var o in targets) {
|
||||
var component = (ISkeletonRenderer)o;
|
||||
if (!component.IsValid)
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
protected virtual void OnEnable () {
|
||||
#if NEW_PREFAB_SYSTEM
|
||||
isInspectingPrefab = false;
|
||||
#else
|
||||
isInspectingPrefab = (PrefabUtility.GetPrefabType(target) == PrefabType.Prefab);
|
||||
#endif
|
||||
SpineEditorUtilities.ConfirmInitialization();
|
||||
loadingFailed = false;
|
||||
|
||||
// Labels
|
||||
SkeletonDataAssetLabel = new GUIContent("SkeletonData Asset", Icons.spine);
|
||||
SkeletonUtilityButtonContent = new GUIContent("Add Skeleton Utility", Icons.skeletonUtility);
|
||||
|
||||
UpdateWhenInvisibleLabel = new GUIContent("Update When Invisible", "Update mode used when the MeshRenderer becomes invisible. Update mode is automatically reset to UpdateMode.FullUpdate when the mesh becomes visible again.");
|
||||
FixDrawOrderLabel = new GUIContent("Fix Draw Order", "Applies only when 3+ submeshes are used (2+ materials with alternating order, e.g. \"A B A\"). If true, GPU instancing will be disabled at all materials and MaterialPropertyBlocks are assigned at each material to prevent aggressive batching of submeshes by e.g. the LWRP renderer, leading to incorrect draw order (e.g. \"A1 B A2\" changed to \"A1A2 B\"). You can disable this parameter when everything is drawn correctly to save the additional performance cost. Note: the GPU instancing setting will remain disabled at affected material assets after exiting play mode, you have to enable it manually if you accidentally enabled this parameter.");
|
||||
|
||||
skeletonDataAsset = serializedObject.FindProperty("skeletonDataAsset");
|
||||
initialSkinName = serializedObject.FindProperty("initialSkinName");
|
||||
initialFlipX = serializedObject.FindProperty("initialFlipX");
|
||||
initialFlipY = serializedObject.FindProperty("initialFlipY");
|
||||
|
||||
clearStateOnDisable = serializedObject.FindProperty("clearStateOnDisable");
|
||||
updateWhenInvisible = serializedObject.FindProperty("updateWhenInvisible");
|
||||
fixDrawOrder = serializedObject.FindProperty("fixDrawOrder");
|
||||
|
||||
meshSettings = serializedObject.FindProperty("meshSettings");
|
||||
meshSettings.isExpanded = SkeletonRendererInspector.advancedFoldout;
|
||||
|
||||
useClipping = meshSettings.FindPropertyRelative("useClipping");
|
||||
zSpacing = meshSettings.FindPropertyRelative("zSpacing");
|
||||
tintBlack = meshSettings.FindPropertyRelative("tintBlack");
|
||||
canvasGroupCompatible = meshSettings.FindPropertyRelative("canvasGroupCompatible");
|
||||
pmaVertexColors = meshSettings.FindPropertyRelative("pmaVertexColors");
|
||||
addNormals = meshSettings.FindPropertyRelative("addNormals");
|
||||
calculateTangents = meshSettings.FindPropertyRelative("calculateTangents");
|
||||
immutableTriangles = meshSettings.FindPropertyRelative("immutableTriangles");
|
||||
|
||||
threadedMeshGeneration = serializedObject.FindProperty("threadedMeshGeneration");
|
||||
separatorSlotNames = serializedObject.FindProperty("separatorSlotNames");
|
||||
separatorSlotNames.isExpanded = true;
|
||||
enableSeparatorSlots = serializedObject.FindProperty("enableSeparatorSlots");
|
||||
|
||||
physicsPositionInheritanceFactor = serializedObject.FindProperty("physicsPositionInheritanceFactor");
|
||||
physicsRotationInheritanceFactor = serializedObject.FindProperty("physicsRotationInheritanceFactor");
|
||||
physicsPositionInheritanceLimit = serializedObject.FindProperty("physicsPositionInheritanceLimit");
|
||||
physicsRotationInheritanceLimit = serializedObject.FindProperty("physicsRotationInheritanceLimit");
|
||||
physicsMovementRelativeTo = serializedObject.FindProperty("physicsMovementRelativeTo");
|
||||
}
|
||||
|
||||
public virtual void OnSceneGUI () {
|
||||
var skeletonRenderer = (ISkeletonRenderer)target;
|
||||
if (loadingFailed)
|
||||
return;
|
||||
|
||||
var skeleton = skeletonRenderer.Skeleton;
|
||||
if (skeleton == null) {
|
||||
loadingFailed = true;
|
||||
return;
|
||||
}
|
||||
var transform = skeletonRenderer.Component.transform;
|
||||
if (skeleton == null) return;
|
||||
|
||||
SpineHandles.DrawBones(transform, skeleton, skeletonRenderer.MeshScale, skeletonRenderer.MeshOffset);
|
||||
}
|
||||
|
||||
override public void OnInspectorGUI () {
|
||||
bool multi = serializedObject.isEditingMultipleObjects;
|
||||
DrawInspectorGUI(multi);
|
||||
serializedObject.ApplyModifiedProperties();
|
||||
}
|
||||
|
||||
protected virtual void InspectorDrawPreparation () { }
|
||||
protected virtual void FirstPropertyFields () { }
|
||||
protected virtual void MaterialWarningsBox () { }
|
||||
protected virtual void AdditionalSeparatorSlotProperties () { }
|
||||
protected virtual void VertexDataProperties () { }
|
||||
protected virtual void AfterAdvancedPropertyFields () { }
|
||||
|
||||
protected virtual void RendererProperties () {
|
||||
using (new SpineInspectorUtility.LabelWidthScope()) {
|
||||
// Optimization options
|
||||
if (updateWhenInvisible != null) EditorGUILayout.PropertyField(updateWhenInvisible, UpdateWhenInvisibleLabel);
|
||||
|
||||
#if PER_MATERIAL_PROPERTY_BLOCKS
|
||||
if (fixDrawOrder != null) EditorGUILayout.PropertyField(fixDrawOrder, FixDrawOrderLabel);
|
||||
#endif
|
||||
if (immutableTriangles != null) EditorGUILayout.PropertyField(immutableTriangles, ImmutableTrianglesLabel);
|
||||
EditorGUILayout.PropertyField(clearStateOnDisable, ClearStateOnDisableLabel);
|
||||
EditorGUILayout.Space();
|
||||
}
|
||||
|
||||
using (new EditorGUILayout.VerticalScope(EditorStyles.helpBox)) {
|
||||
SeparatorSlotProperties(separatorSlotNames, enableSeparatorSlots);
|
||||
AdditionalSeparatorSlotProperties();
|
||||
}
|
||||
|
||||
EditorGUILayout.Space();
|
||||
|
||||
// Render options
|
||||
EditorGUILayout.PropertyField(useClipping, UseClippingLabel);
|
||||
const float MinZSpacing = -0.1f;
|
||||
const float MaxZSpacing = 0f;
|
||||
EditorGUILayout.Slider(zSpacing, MinZSpacing, MaxZSpacing, ZSpacingLabel);
|
||||
}
|
||||
|
||||
protected virtual void PhysicsProperties () {
|
||||
using (new GUILayout.HorizontalScope()) {
|
||||
EditorGUILayout.LabelField(PhysicsPositionInheritanceFactorLabel, GUILayout.Width(EditorGUIUtility.labelWidth));
|
||||
int savedIndentLevel = EditorGUI.indentLevel;
|
||||
EditorGUI.indentLevel = 0;
|
||||
EditorGUILayout.PropertyField(physicsPositionInheritanceFactor, GUIContent.none, GUILayout.MinWidth(60));
|
||||
EditorGUI.indentLevel = savedIndentLevel;
|
||||
}
|
||||
DrawOptionalLimitVector2(physicsPositionInheritanceLimit, PhysicsPositionInheritanceLimitLabel, new Vector2(10f, 10f),
|
||||
EditorGUI.indentLevel + 1);
|
||||
|
||||
EditorGUILayout.PropertyField(physicsRotationInheritanceFactor, PhysicsRotationInheritanceFactorLabel);
|
||||
DrawOptionalLimitFloat(physicsRotationInheritanceLimit, PhysicsRotationInheritanceLimitLabel, 10f,
|
||||
EditorGUI.indentLevel + 1);
|
||||
EditorGUILayout.PropertyField(physicsMovementRelativeTo, PhysicsMovementRelativeToLabel);
|
||||
}
|
||||
|
||||
static readonly GUIContent UnlimitedLabel = new GUIContent("Unlimited");
|
||||
static readonly GUIContent LimitToggleLabel = new GUIContent("",
|
||||
"Enable to set a maximum value. When disabled, no limit is applied.");
|
||||
|
||||
static void DrawOptionalLimitVector2 (SerializedProperty prop, GUIContent label, Vector2 enableDefault, int labelIndentLevel) {
|
||||
using (new GUILayout.HorizontalScope()) {
|
||||
int savedIndentLevel = EditorGUI.indentLevel;
|
||||
EditorGUI.indentLevel = labelIndentLevel;
|
||||
EditorGUILayout.LabelField(label, GUILayout.Width(EditorGUIUtility.labelWidth));
|
||||
EditorGUI.indentLevel = 0;
|
||||
Vector2 currentValue = prop.vector2Value;
|
||||
bool isLimited = !(float.IsPositiveInfinity(currentValue.x) && float.IsPositiveInfinity(currentValue.y));
|
||||
EditorGUI.showMixedValue = prop.hasMultipleDifferentValues;
|
||||
EditorGUI.BeginChangeCheck();
|
||||
bool newIsLimited = EditorGUILayout.Toggle(LimitToggleLabel, isLimited, GUILayout.Width(15));
|
||||
EditorGUI.showMixedValue = false;
|
||||
if (EditorGUI.EndChangeCheck()) {
|
||||
prop.vector2Value = newIsLimited ? enableDefault : Vector2.positiveInfinity;
|
||||
isLimited = newIsLimited;
|
||||
}
|
||||
if (isLimited) {
|
||||
EditorGUILayout.PropertyField(prop, GUIContent.none, GUILayout.MinWidth(60));
|
||||
} else {
|
||||
using (new EditorGUI.DisabledScope(true))
|
||||
EditorGUILayout.LabelField(UnlimitedLabel);
|
||||
}
|
||||
EditorGUI.indentLevel = savedIndentLevel;
|
||||
}
|
||||
}
|
||||
|
||||
static void DrawOptionalLimitFloat (SerializedProperty prop, GUIContent label, float enableDefault, int labelIndentLevel) {
|
||||
using (new GUILayout.HorizontalScope()) {
|
||||
int savedIndentLevel = EditorGUI.indentLevel;
|
||||
EditorGUI.indentLevel = labelIndentLevel;
|
||||
EditorGUILayout.LabelField(label, GUILayout.Width(EditorGUIUtility.labelWidth));
|
||||
EditorGUI.indentLevel = 0;
|
||||
float currentValue = prop.floatValue;
|
||||
bool isLimited = currentValue < float.MaxValue && !float.IsPositiveInfinity(currentValue);
|
||||
EditorGUI.showMixedValue = prop.hasMultipleDifferentValues;
|
||||
EditorGUI.BeginChangeCheck();
|
||||
bool newIsLimited = EditorGUILayout.Toggle(LimitToggleLabel, isLimited, GUILayout.Width(15));
|
||||
EditorGUI.showMixedValue = false;
|
||||
if (EditorGUI.EndChangeCheck()) {
|
||||
prop.floatValue = newIsLimited ? enableDefault : float.MaxValue;
|
||||
isLimited = newIsLimited;
|
||||
}
|
||||
if (isLimited) {
|
||||
EditorGUILayout.PropertyField(prop, GUIContent.none, GUILayout.MinWidth(60));
|
||||
} else {
|
||||
using (new EditorGUI.DisabledScope(true))
|
||||
EditorGUILayout.LabelField(UnlimitedLabel);
|
||||
}
|
||||
EditorGUI.indentLevel = savedIndentLevel;
|
||||
}
|
||||
}
|
||||
|
||||
protected virtual void AdvancedPropertyFields () {
|
||||
EditorGUILayout.Space();
|
||||
EditorGUILayout.LabelField("Renderer Settings", EditorStyles.boldLabel);
|
||||
RendererProperties();
|
||||
EditorGUILayout.Space();
|
||||
|
||||
if (threadedMeshGeneration != null) {
|
||||
EditorGUILayout.LabelField(SpineInspectorUtility.TempContent("Threaded Mesh Generation", SpineEditorUtilities.Icons.subMeshRenderer), EditorStyles.boldLabel);
|
||||
EditorGUILayout.PropertyField(threadedMeshGeneration, ThreadedMeshGenerationLabel);
|
||||
EditorGUILayout.Space();
|
||||
}
|
||||
|
||||
using (new SpineInspectorUtility.LabelWidthScope()) {
|
||||
EditorGUILayout.LabelField(SpineInspectorUtility.TempContent("Vertex Data", SpineInspectorUtility.UnityIcon<MeshFilter>()), EditorStyles.boldLabel);
|
||||
VertexDataProperties();
|
||||
}
|
||||
|
||||
EditorGUILayout.Space();
|
||||
using (new SpineInspectorUtility.LabelWidthScope()) {
|
||||
EditorGUILayout.LabelField(SpineInspectorUtility.TempContent("Physics Inheritance", SpineEditorUtilities.Icons.constraintPhysics), EditorStyles.boldLabel);
|
||||
PhysicsProperties();
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
protected virtual void DrawInspectorGUI (bool multi) {
|
||||
// Initialize.
|
||||
if (Event.current.type == EventType.Layout) {
|
||||
if (forceReloadQueued) {
|
||||
forceReloadQueued = false;
|
||||
foreach (var c in targets) {
|
||||
SpineEditorUtilities.ReloadSkeletonDataAssetAndComponent(c as ISkeletonRenderer);
|
||||
}
|
||||
} else {
|
||||
foreach (var c in targets) {
|
||||
var component = c as ISkeletonRenderer;
|
||||
if (!component.IsValid) {
|
||||
SpineEditorUtilities.ReinitializeComponent(component);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
InspectorDrawPreparation();
|
||||
|
||||
#if NO_PREFAB_MESH
|
||||
if (isInspectingPrefab) {
|
||||
foreach (var c in targets) {
|
||||
var component = c as SkeletonRenderer;
|
||||
if (component != null) {
|
||||
MeshFilter meshFilter = component.GetComponent<MeshFilter>();
|
||||
if (meshFilter != null && meshFilter.sharedMesh != null)
|
||||
meshFilter.sharedMesh = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
bool valid = TargetIsValid;
|
||||
|
||||
// Fields.
|
||||
bool skeletonAssetValid = CommonSkeletonAssetProperties(multi);
|
||||
if (!skeletonAssetValid || !valid)
|
||||
return;
|
||||
|
||||
EditorGUILayout.PropertyField(initialSkinName, SpineInspectorUtility.TempContent("Initial Skin"));
|
||||
|
||||
using (new EditorGUILayout.HorizontalScope()) {
|
||||
SpineInspectorUtility.ToggleLeftLayout(initialFlipX);
|
||||
SpineInspectorUtility.ToggleLeftLayout(initialFlipY);
|
||||
EditorGUILayout.Space();
|
||||
}
|
||||
|
||||
FirstPropertyFields();
|
||||
|
||||
MaterialWarningsBox();
|
||||
|
||||
// More Render Options...
|
||||
using (new SpineInspectorUtility.BoxScope()) {
|
||||
EditorGUILayout.BeginHorizontal(GUILayout.Height(EditorGUIUtility.singleLineHeight + 5));
|
||||
advancedFoldout = EditorGUILayout.Foldout(advancedFoldout, "Advanced");
|
||||
if (advancedFoldout) {
|
||||
EditorGUILayout.Space();
|
||||
if (GUILayout.Button("Debug", EditorStyles.miniButton, GUILayout.Width(65f)))
|
||||
SkeletonDebugWindow.Init();
|
||||
} else {
|
||||
EditorGUILayout.Space();
|
||||
}
|
||||
EditorGUILayout.EndHorizontal();
|
||||
|
||||
if (advancedFoldout) {
|
||||
|
||||
using (new SpineInspectorUtility.IndentScope()) {
|
||||
|
||||
AdvancedPropertyFields();
|
||||
|
||||
EditorGUILayout.Space();
|
||||
|
||||
if (valid && !isInspectingPrefab) {
|
||||
if (multi) {
|
||||
// Support multi-edit SkeletonUtility button.
|
||||
// EditorGUILayout.Space();
|
||||
// bool addSkeletonUtility = GUILayout.Button(buttonContent, GUILayout.Height(30));
|
||||
// foreach (var t in targets) {
|
||||
// var component = t as Component;
|
||||
// if (addSkeletonUtility && component.GetComponent<SkeletonUtility>() == null)
|
||||
// component.gameObject.AddComponent<SkeletonUtility>();
|
||||
// }
|
||||
} else {
|
||||
var component = (Component)target;
|
||||
if (component.GetComponent<SkeletonUtility>() == null) {
|
||||
if (SpineInspectorUtility.CenteredButton(SkeletonUtilityButtonContent, 21, true, 200f))
|
||||
component.gameObject.AddComponent<SkeletonUtility>();
|
||||
}
|
||||
}
|
||||
}
|
||||
EditorGUILayout.Space();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
AfterAdvancedPropertyFields();
|
||||
}
|
||||
|
||||
/// <returns>True when the SkeletonDataAsset is valid, false otherwise.</returns>
|
||||
protected bool CommonSkeletonAssetProperties (bool multi) {
|
||||
if (multi) {
|
||||
using (new EditorGUILayout.HorizontalScope(EditorStyles.helpBox)) {
|
||||
SpineInspectorUtility.PropertyFieldFitLabel(skeletonDataAsset, SkeletonDataAssetLabel);
|
||||
if (GUILayout.Button(ReloadButtonString, ReloadButtonStyle, ReloadButtonWidth))
|
||||
forceReloadQueued = true;
|
||||
}
|
||||
} else {
|
||||
var component = (ISkeletonRenderer)target;
|
||||
|
||||
using (new EditorGUILayout.HorizontalScope(EditorStyles.helpBox)) {
|
||||
SpineInspectorUtility.PropertyFieldFitLabel(skeletonDataAsset, SkeletonDataAssetLabel);
|
||||
if (component.IsValid) {
|
||||
if (GUILayout.Button(ReloadButtonString, ReloadButtonStyle, ReloadButtonWidth))
|
||||
forceReloadQueued = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (component.SkeletonDataAsset == null) {
|
||||
EditorGUILayout.HelpBox("Skeleton Data Asset required", MessageType.Warning);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!SpineEditorUtilities.SkeletonDataAssetIsValid(component.SkeletonDataAsset)) {
|
||||
EditorGUILayout.HelpBox("Skeleton Data Asset error. Please check Skeleton Data Asset.", MessageType.Error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public static void SetSeparatorSlotNames (SkeletonRenderer skeletonRenderer, string[] newSlotNames) {
|
||||
var field = SpineInspectorUtility.GetNonPublicField(typeof(SkeletonRenderer), SeparatorSlotNamesFieldName);
|
||||
field.SetValue(skeletonRenderer, newSlotNames);
|
||||
}
|
||||
|
||||
public static string[] GetSeparatorSlotNames (SkeletonRenderer skeletonRenderer) {
|
||||
var field = SpineInspectorUtility.GetNonPublicField(typeof(SkeletonRenderer), SeparatorSlotNamesFieldName);
|
||||
return field.GetValue(skeletonRenderer) as string[];
|
||||
}
|
||||
|
||||
public static string TerminalSlotWarningString (SerializedProperty separatorSlotNames) {
|
||||
bool multi = separatorSlotNames.serializedObject.isEditingMultipleObjects;
|
||||
bool hasTerminalSlot = false;
|
||||
if (!multi) {
|
||||
var sr = separatorSlotNames.serializedObject.targetObject as ISkeletonComponent;
|
||||
var skeleton = sr.Skeleton;
|
||||
int lastSlot = skeleton.Slots.Count - 1;
|
||||
if (skeleton != null) {
|
||||
for (int i = 0, n = separatorSlotNames.arraySize; i < n; i++) {
|
||||
string slotName = separatorSlotNames.GetArrayElementAtIndex(i).stringValue;
|
||||
SlotData slot = skeleton.Data.FindSlot(slotName);
|
||||
int index = slot != null ? slot.Index : -1;
|
||||
if (index == 0 || index == lastSlot) {
|
||||
hasTerminalSlot = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return hasTerminalSlot ? " (!)" : "";
|
||||
}
|
||||
|
||||
public static void SeparatorSlotProperties (SerializedProperty separatorSlotNames,
|
||||
SerializedProperty enableSeparatorSlots) {
|
||||
|
||||
string terminalSlotWarning = TerminalSlotWarningString(separatorSlotNames);
|
||||
const string SeparatorsDescription = "Stored names of slots where the Skeleton's render will be split into different batches. This is used by separate components that split the render into different MeshRenderers or GameObjects.";
|
||||
if (separatorSlotNames.isExpanded) {
|
||||
EditorGUILayout.PropertyField(separatorSlotNames, SpineInspectorUtility.TempContent(separatorSlotNames.displayName + terminalSlotWarning, Icons.slotRoot, SeparatorsDescription), true);
|
||||
GUILayout.BeginHorizontal();
|
||||
GUILayout.FlexibleSpace();
|
||||
if (GUILayout.Button("+", GUILayout.MaxWidth(28f), GUILayout.MaxHeight(15f))) {
|
||||
separatorSlotNames.arraySize++;
|
||||
}
|
||||
GUILayout.EndHorizontal();
|
||||
|
||||
EditorGUILayout.Space();
|
||||
} else
|
||||
EditorGUILayout.PropertyField(separatorSlotNames, new GUIContent(separatorSlotNames.displayName + string.Format("{0} [{1}]", terminalSlotWarning, separatorSlotNames.arraySize), SeparatorsDescription), true);
|
||||
|
||||
if (EnableSeparatorSlotsLabel == null)
|
||||
EnableSeparatorSlotsLabel = new GUIContent("Enable Separation", "Whether to enable separation at the above separator slots.");
|
||||
|
||||
EditorGUILayout.PropertyField(enableSeparatorSlots, EnableSeparatorSlotsLabel);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 04d6404d1e6803b48974da739a5fefcc
|
||||
timeCreated: 1626445114
|
||||
licenseType: Free
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,187 @@
|
||||
/******************************************************************************
|
||||
* Spine Runtimes License Agreement
|
||||
* Last updated April 5, 2025. Replaces all prior versions.
|
||||
*
|
||||
* Copyright (c) 2013-2026, Esoteric Software LLC
|
||||
*
|
||||
* Integration of the Spine Runtimes into software or otherwise creating
|
||||
* derivative works of the Spine Runtimes is permitted under the terms and
|
||||
* conditions of Section 2 of the Spine Editor License Agreement:
|
||||
* http://esotericsoftware.com/spine-editor-license
|
||||
*
|
||||
* Otherwise, it is permitted to integrate the Spine Runtimes into software
|
||||
* or otherwise create derivative works of the Spine Runtimes (collectively,
|
||||
* "Products"), provided that each user of the Products must obtain their own
|
||||
* Spine Editor license and redistribution of the Products in any form must
|
||||
* include this license and copyright notice.
|
||||
*
|
||||
* THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY
|
||||
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY
|
||||
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES,
|
||||
* BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND
|
||||
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
||||
* THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*****************************************************************************/
|
||||
|
||||
using System.Collections;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Spine.Unity.Editor {
|
||||
|
||||
using Editor = UnityEditor.Editor;
|
||||
using Event = UnityEngine.Event;
|
||||
|
||||
[CustomEditor(typeof(PointFollower)), CanEditMultipleObjects]
|
||||
public class PointFollowerInspector : Editor {
|
||||
SerializedProperty slotName, pointAttachmentName, skeletonRenderer, followZPosition, followBoneRotation, followSkeletonFlip;
|
||||
PointFollower targetPointFollower;
|
||||
bool needsReset;
|
||||
|
||||
#region Context Menu Item
|
||||
[MenuItem("CONTEXT/SkeletonRenderer/Add PointFollower GameObject")]
|
||||
static void AddBoneFollowerGameObject (MenuCommand cmd) {
|
||||
SkeletonRenderer skeletonRenderer = cmd.context as SkeletonRenderer;
|
||||
GameObject go = EditorInstantiation.NewGameObject("PointFollower", true);
|
||||
Transform t = go.transform;
|
||||
t.SetParent(skeletonRenderer.transform);
|
||||
t.localPosition = Vector3.zero;
|
||||
|
||||
PointFollower f = go.AddComponent<PointFollower>();
|
||||
f.skeletonRenderer = skeletonRenderer;
|
||||
|
||||
EditorGUIUtility.PingObject(t);
|
||||
|
||||
Undo.RegisterCreatedObjectUndo(go, "Add PointFollower");
|
||||
}
|
||||
|
||||
// Validate
|
||||
[MenuItem("CONTEXT/SkeletonRenderer/Add PointFollower GameObject", true)]
|
||||
static bool ValidateAddBoneFollowerGameObject (MenuCommand cmd) {
|
||||
SkeletonRenderer skeletonRenderer = cmd.context as SkeletonRenderer;
|
||||
return skeletonRenderer.valid;
|
||||
}
|
||||
#endregion
|
||||
|
||||
void OnEnable () {
|
||||
skeletonRenderer = serializedObject.FindProperty("skeletonRenderer");
|
||||
slotName = serializedObject.FindProperty("slotName");
|
||||
pointAttachmentName = serializedObject.FindProperty("pointAttachmentName");
|
||||
|
||||
targetPointFollower = (PointFollower)target;
|
||||
if (targetPointFollower.skeletonRenderer != null)
|
||||
targetPointFollower.skeletonRenderer.Initialize(false);
|
||||
|
||||
if (!targetPointFollower.IsValid || needsReset) {
|
||||
targetPointFollower.Initialize();
|
||||
targetPointFollower.LateUpdate();
|
||||
needsReset = false;
|
||||
SceneView.RepaintAll();
|
||||
}
|
||||
}
|
||||
|
||||
public void OnSceneGUI () {
|
||||
PointFollower tbf = target as PointFollower;
|
||||
SkeletonRenderer skeletonRendererComponent = tbf.skeletonRenderer;
|
||||
if (skeletonRendererComponent == null)
|
||||
return;
|
||||
|
||||
Skeleton skeleton = skeletonRendererComponent.skeleton;
|
||||
Transform skeletonTransform = skeletonRendererComponent.transform;
|
||||
|
||||
if (string.IsNullOrEmpty(pointAttachmentName.stringValue)) {
|
||||
// Draw all active PointAttachments in the current skin
|
||||
Skin currentSkin = skeleton.Skin;
|
||||
if (currentSkin != skeleton.Data.DefaultSkin) DrawPointsInSkin(skeleton.Data.DefaultSkin, skeleton, skeletonTransform);
|
||||
if (currentSkin != null) DrawPointsInSkin(currentSkin, skeleton, skeletonTransform);
|
||||
} else {
|
||||
Slot slot = skeleton.FindSlot(slotName.stringValue);
|
||||
if (slot != null) {
|
||||
int slotIndex = slot.Data.Index;
|
||||
PointAttachment point = skeleton.GetAttachment(slotIndex, pointAttachmentName.stringValue) as PointAttachment;
|
||||
if (point != null) {
|
||||
DrawPointAttachmentWithLabel(point, slot.Bone, skeletonTransform);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void DrawPointsInSkin (Skin skin, Skeleton skeleton, Transform transform) {
|
||||
foreach (Skin.SkinEntry skinEntry in skin.Attachments) {
|
||||
PointAttachment attachment = skinEntry.Attachment as PointAttachment;
|
||||
if (attachment != null) {
|
||||
Slot slot = skeleton.Slots.Items[skinEntry.SlotIndex];
|
||||
DrawPointAttachmentWithLabel(attachment, slot.Bone, transform);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void DrawPointAttachmentWithLabel (PointAttachment point, Bone bone, Transform transform) {
|
||||
Vector3 labelOffset = new Vector3(0f, -0.2f, 0f);
|
||||
SpineHandles.DrawPointAttachment(bone, point, transform);
|
||||
Handles.Label(labelOffset + point.GetWorldPosition(bone, transform), point.Name, SpineHandles.PointNameStyle);
|
||||
}
|
||||
|
||||
override public void OnInspectorGUI () {
|
||||
if (serializedObject.isEditingMultipleObjects) {
|
||||
if (needsReset) {
|
||||
needsReset = false;
|
||||
foreach (Object o in targets) {
|
||||
BoneFollower bf = (BoneFollower)o;
|
||||
bf.Initialize();
|
||||
bf.LateUpdate();
|
||||
}
|
||||
SceneView.RepaintAll();
|
||||
}
|
||||
|
||||
EditorGUI.BeginChangeCheck();
|
||||
DrawDefaultInspector();
|
||||
needsReset |= EditorGUI.EndChangeCheck();
|
||||
return;
|
||||
}
|
||||
|
||||
if (needsReset && Event.current.type == EventType.Layout) {
|
||||
targetPointFollower.Initialize();
|
||||
targetPointFollower.LateUpdate();
|
||||
needsReset = false;
|
||||
SceneView.RepaintAll();
|
||||
}
|
||||
serializedObject.Update();
|
||||
|
||||
DrawDefaultInspector();
|
||||
|
||||
// Find Renderer
|
||||
if (skeletonRenderer.objectReferenceValue == null) {
|
||||
SkeletonRenderer parentRenderer = targetPointFollower.GetComponentInParent<SkeletonRenderer>();
|
||||
if (parentRenderer != null && parentRenderer.gameObject != targetPointFollower.gameObject) {
|
||||
skeletonRenderer.objectReferenceValue = parentRenderer;
|
||||
Debug.Log("Inspector automatically assigned PointFollower.SkeletonRenderer");
|
||||
}
|
||||
}
|
||||
|
||||
SkeletonRenderer skeletonRendererReference = skeletonRenderer.objectReferenceValue as SkeletonRenderer;
|
||||
if (skeletonRendererReference != null) {
|
||||
if (skeletonRendererReference.gameObject == targetPointFollower.gameObject) {
|
||||
skeletonRenderer.objectReferenceValue = null;
|
||||
EditorUtility.DisplayDialog("Invalid assignment.", "PointFollower can only follow a skeleton on a separate GameObject.\n\nCreate a new GameObject for your PointFollower, or choose a SkeletonRenderer from a different GameObject.", "Ok");
|
||||
}
|
||||
}
|
||||
|
||||
if (!targetPointFollower.IsValid) {
|
||||
needsReset = true;
|
||||
}
|
||||
|
||||
Event current = Event.current;
|
||||
bool wasUndo = (current.type == EventType.ValidateCommand && current.commandName == "UndoRedoPerformed");
|
||||
if (wasUndo)
|
||||
targetPointFollower.Initialize();
|
||||
|
||||
serializedObject.ApplyModifiedProperties();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 7c7e838a8ec295a4e9c53602f690f42f
|
||||
timeCreated: 1518163038
|
||||
licenseType: Free
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,161 @@
|
||||
/******************************************************************************
|
||||
* Spine Runtimes License Agreement
|
||||
* Last updated April 5, 2025. Replaces all prior versions.
|
||||
*
|
||||
* Copyright (c) 2013-2026, Esoteric Software LLC
|
||||
*
|
||||
* Integration of the Spine Runtimes into software or otherwise creating
|
||||
* derivative works of the Spine Runtimes is permitted under the terms and
|
||||
* conditions of Section 2 of the Spine Editor License Agreement:
|
||||
* http://esotericsoftware.com/spine-editor-license
|
||||
*
|
||||
* Otherwise, it is permitted to integrate the Spine Runtimes into software
|
||||
* or otherwise create derivative works of the Spine Runtimes (collectively,
|
||||
* "Products"), provided that each user of the Products must obtain their own
|
||||
* Spine Editor license and redistribution of the Products in any form must
|
||||
* include this license and copyright notice.
|
||||
*
|
||||
* THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY
|
||||
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY
|
||||
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES,
|
||||
* BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND
|
||||
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
||||
* THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*****************************************************************************/
|
||||
|
||||
using Spine;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Spine.Unity.Editor {
|
||||
using Event = UnityEngine.Event;
|
||||
using Icons = SpineEditorUtilities.Icons;
|
||||
|
||||
[CustomEditor(typeof(SkeletonAnimation))]
|
||||
[CanEditMultipleObjects]
|
||||
public class SkeletonAnimationInspector : UnityEditor.Editor {
|
||||
|
||||
protected SerializedProperty updateTiming, animationName, loop, timeScale, unscaledTime, autoReset, threadedAnimation;
|
||||
readonly GUIContent UpdateTimingLabel = new GUIContent("Animation Update",
|
||||
"Whether to update the animation in normal Update (the default), " +
|
||||
"physics step FixedUpdate, or manually via a user call.");
|
||||
readonly GUIContent LoopLabel = new GUIContent("Loop",
|
||||
"Whether or not .AnimationName should loop. This only applies to the initial " +
|
||||
"animation specified in the inspector, or any subsequent Animations played through .AnimationName. " +
|
||||
"Animations set through state.SetAnimation are unaffected.");
|
||||
readonly GUIContent TimeScaleLabel = new GUIContent("Time Scale",
|
||||
"The rate at which animations progress over time. 1 means normal speed. 0.5 means 50% speed.");
|
||||
readonly GUIContent UnscaledTimeLabel = new GUIContent("Unscaled Time",
|
||||
"When enabled, AnimationState uses unscaled game time (Time.unscaledDeltaTime), " +
|
||||
"running animations independent of e.g. game pause (Time.timeScale). " +
|
||||
"Instance SkeletonAnimation.timeScale will still be applied.");
|
||||
readonly GUIContent ThreadedAnimationLabel = new GUIContent("Use Threading",
|
||||
"When enabled, animations are processed on multiple threads in parallel.");
|
||||
|
||||
protected bool TargetIsValid {
|
||||
get {
|
||||
foreach (UnityEngine.Object o in targets) {
|
||||
ISkeletonAnimation component = (ISkeletonAnimation)o;
|
||||
if (!component.IsValid)
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
protected void OnEnable () {
|
||||
animationName = serializedObject.FindProperty("animationName");
|
||||
loop = serializedObject.FindProperty("loop");
|
||||
timeScale = serializedObject.FindProperty("timeScale");
|
||||
unscaledTime = serializedObject.FindProperty("unscaledTime");
|
||||
updateTiming = serializedObject.FindProperty("updateTiming");
|
||||
threadedAnimation = serializedObject.FindProperty("threadedAnimation");
|
||||
}
|
||||
|
||||
override public void OnInspectorGUI () {
|
||||
DrawInspectorGUI();
|
||||
serializedObject.ApplyModifiedProperties();
|
||||
}
|
||||
|
||||
protected virtual void DrawInspectorGUI () {
|
||||
foreach (UnityEngine.Object c in targets) {
|
||||
ISkeletonAnimation component = c as ISkeletonAnimation;
|
||||
if (!component.IsValid) {
|
||||
SpineEditorUtilities.ReinitializeComponent(component);
|
||||
}
|
||||
}
|
||||
|
||||
bool sameData = SpineInspectorUtility.TargetsUseSameData(serializedObject);
|
||||
EditorGUILayout.Space();
|
||||
if (!sameData) {
|
||||
EditorGUILayout.DelayedTextField(animationName);
|
||||
} else {
|
||||
EditorGUILayout.PropertyField(animationName);
|
||||
}
|
||||
|
||||
EditorGUILayout.PropertyField(loop, LoopLabel);
|
||||
EditorGUILayout.PropertyField(timeScale, TimeScaleLabel);
|
||||
foreach (UnityEngine.Object o in targets) {
|
||||
SkeletonAnimation component = o as SkeletonAnimation;
|
||||
component.timeScale = Mathf.Max(component.timeScale, 0);
|
||||
}
|
||||
EditorGUILayout.PropertyField(unscaledTime, UnscaledTimeLabel);
|
||||
EditorGUILayout.PropertyField(updateTiming, UpdateTimingLabel);
|
||||
EditorGUILayout.Space();
|
||||
|
||||
if (threadedAnimation != null) {
|
||||
EditorGUILayout.LabelField(SpineInspectorUtility.TempContent("Threaded Animation", SpineEditorUtilities.Icons.subMeshRenderer), EditorStyles.boldLabel);
|
||||
EditorGUILayout.PropertyField(threadedAnimation, ThreadedAnimationLabel);
|
||||
EditorGUILayout.Space();
|
||||
}
|
||||
|
||||
EditorGUILayout.Space();
|
||||
SkeletonRootMotionParameter();
|
||||
}
|
||||
|
||||
protected void SkeletonRootMotionParameter () {
|
||||
SkeletonRootMotionParameter(targets);
|
||||
}
|
||||
|
||||
public static void SkeletonRootMotionParameter (Object[] targets) {
|
||||
int rootMotionComponentCount = 0;
|
||||
foreach (UnityEngine.Object t in targets) {
|
||||
Component component = t as Component;
|
||||
if (component.GetComponent<SkeletonRootMotion>() != null) {
|
||||
++rootMotionComponentCount;
|
||||
}
|
||||
}
|
||||
bool allHaveRootMotion = rootMotionComponentCount == targets.Length;
|
||||
bool anyHaveRootMotion = rootMotionComponentCount > 0;
|
||||
|
||||
using (new GUILayout.HorizontalScope()) {
|
||||
EditorGUILayout.PrefixLabel("Root Motion");
|
||||
if (!allHaveRootMotion) {
|
||||
if (GUILayout.Button(SpineInspectorUtility.TempContent("Add Component", Icons.constraintTransform), GUILayout.MaxWidth(130), GUILayout.Height(18))) {
|
||||
foreach (UnityEngine.Object t in targets) {
|
||||
Component component = t as Component;
|
||||
if (component.GetComponent<SkeletonRootMotion>() == null) {
|
||||
component.gameObject.AddComponent<SkeletonRootMotion>();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (anyHaveRootMotion) {
|
||||
if (GUILayout.Button(SpineInspectorUtility.TempContent("Remove Component", Icons.constraintTransform), GUILayout.MaxWidth(140), GUILayout.Height(18))) {
|
||||
foreach (UnityEngine.Object t in targets) {
|
||||
Component component = t as Component;
|
||||
SkeletonRootMotion rootMotionComponent = component.GetComponent<SkeletonRootMotion>();
|
||||
if (rootMotionComponent != null) {
|
||||
DestroyImmediate(rootMotionComponent);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 39fbfef61034ca045b5aa80088e1e8a4
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,183 @@
|
||||
/******************************************************************************
|
||||
* Spine Runtimes License Agreement
|
||||
* Last updated April 5, 2025. Replaces all prior versions.
|
||||
*
|
||||
* Copyright (c) 2013-2026, Esoteric Software LLC
|
||||
*
|
||||
* Integration of the Spine Runtimes into software or otherwise creating
|
||||
* derivative works of the Spine Runtimes is permitted under the terms and
|
||||
* conditions of Section 2 of the Spine Editor License Agreement:
|
||||
* http://esotericsoftware.com/spine-editor-license
|
||||
*
|
||||
* Otherwise, it is permitted to integrate the Spine Runtimes into software
|
||||
* or otherwise create derivative works of the Spine Runtimes (collectively,
|
||||
* "Products"), provided that each user of the Products must obtain their own
|
||||
* Spine Editor license and redistribution of the Products in any form must
|
||||
* include this license and copyright notice.
|
||||
*
|
||||
* THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY
|
||||
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY
|
||||
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES,
|
||||
* BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND
|
||||
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
||||
* THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*****************************************************************************/
|
||||
|
||||
using Spine.Unity.Examples;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Spine.Unity.Editor {
|
||||
|
||||
// This script is not intended for use with code. See spine-unity documentation page for additional information.
|
||||
[CustomEditor(typeof(SkeletonGraphicCustomMaterials))]
|
||||
public class SkeletonGraphicCustomMaterialsInspector : UnityEditor.Editor {
|
||||
List<SkeletonGraphicCustomMaterials.SlotMaterialOverride> componentCustomSlotMaterials, _customSlotMaterialsPrev;
|
||||
List<SkeletonGraphicCustomMaterials.AtlasMaterialOverride> componentCustomMaterialOverrides, _customMaterialOverridesPrev;
|
||||
List<SkeletonGraphicCustomMaterials.AtlasTextureOverride> componentCustomTextureOverrides, _customTextureOverridesPrev;
|
||||
SkeletonGraphicCustomMaterials component;
|
||||
|
||||
const BindingFlags PrivateInstance = BindingFlags.Instance | BindingFlags.NonPublic;
|
||||
MethodInfo RemoveCustomMaterialOverrides, RemoveCustomTextureOverrides, RemoveCustomSlotMaterials,
|
||||
SetCustomMaterialOverrides, SetCustomTextureOverrides, SetCustomSlotMaterials;
|
||||
|
||||
#region SkeletonGraphic context menu
|
||||
[MenuItem("CONTEXT/SkeletonGraphic/Add Basic Serialized Custom Materials")]
|
||||
static void AddSkeletonGraphicCustomMaterials (MenuCommand menuCommand) {
|
||||
SkeletonGraphic skeletonGraphic = (SkeletonGraphic)menuCommand.context;
|
||||
SkeletonGraphicCustomMaterials newComponent = skeletonGraphic.gameObject.AddComponent<SkeletonGraphicCustomMaterials>();
|
||||
Undo.RegisterCreatedObjectUndo(newComponent, "Add Basic Serialized Custom Materials");
|
||||
}
|
||||
|
||||
[MenuItem("CONTEXT/SkeletonGraphic/Add Basic Serialized Custom Materials", true)]
|
||||
static bool AddSkeletonGraphicCustomMaterials_Validate (MenuCommand menuCommand) {
|
||||
SkeletonGraphic skeletonGraphic = (SkeletonGraphic)menuCommand.context;
|
||||
return (skeletonGraphic.GetComponent<SkeletonGraphicCustomMaterials>() == null);
|
||||
}
|
||||
#endregion
|
||||
|
||||
void OnEnable () {
|
||||
Type cm = typeof(SkeletonGraphicCustomMaterials);
|
||||
RemoveCustomMaterialOverrides = cm.GetMethod("RemoveCustomMaterialOverrides", PrivateInstance);
|
||||
RemoveCustomTextureOverrides = cm.GetMethod("RemoveCustomTextureOverrides", PrivateInstance);
|
||||
RemoveCustomSlotMaterials = cm.GetMethod("RemoveCustomSlotMaterials", PrivateInstance);
|
||||
SetCustomMaterialOverrides = cm.GetMethod("SetCustomMaterialOverrides", PrivateInstance);
|
||||
SetCustomTextureOverrides = cm.GetMethod("SetCustomTextureOverrides", PrivateInstance);
|
||||
SetCustomSlotMaterials = cm.GetMethod("SetCustomSlotMaterials", PrivateInstance);
|
||||
}
|
||||
|
||||
public override void OnInspectorGUI () {
|
||||
component = (SkeletonGraphicCustomMaterials)target;
|
||||
SkeletonGraphic skeletonGraphic = component.skeletonGraphic;
|
||||
|
||||
// Draw the default inspector
|
||||
DrawDefaultInspector();
|
||||
|
||||
if (serializedObject.isEditingMultipleObjects)
|
||||
return;
|
||||
|
||||
if (componentCustomMaterialOverrides == null) {
|
||||
Type cm = typeof(SkeletonGraphicCustomMaterials);
|
||||
componentCustomMaterialOverrides = cm.GetField("customMaterialOverrides", PrivateInstance).GetValue(component) as List<SkeletonGraphicCustomMaterials.AtlasMaterialOverride>;
|
||||
componentCustomTextureOverrides = cm.GetField("customTextureOverrides", PrivateInstance).GetValue(component) as List<SkeletonGraphicCustomMaterials.AtlasTextureOverride>;
|
||||
componentCustomSlotMaterials = cm.GetField("customSlotMaterials", PrivateInstance).GetValue(component) as List<SkeletonGraphicCustomMaterials.SlotMaterialOverride>;
|
||||
if (componentCustomMaterialOverrides == null || componentCustomTextureOverrides == null ||
|
||||
componentCustomSlotMaterials == null) {
|
||||
Debug.Log("Reflection failed.");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Fill with current values at start
|
||||
if (_customMaterialOverridesPrev == null || _customTextureOverridesPrev == null
|
||||
|| _customSlotMaterialsPrev == null) {
|
||||
_customMaterialOverridesPrev = CopyList(componentCustomMaterialOverrides);
|
||||
_customTextureOverridesPrev = CopyList(componentCustomTextureOverrides);
|
||||
_customSlotMaterialsPrev = CopyList(componentCustomSlotMaterials);
|
||||
}
|
||||
|
||||
// Compare new values with saved. If change is detected:
|
||||
// store new values, restore old values, remove overrides, restore new values, restore overrides.
|
||||
|
||||
// 1. Store new values
|
||||
var customMaterialOverridesNew = CopyList(componentCustomMaterialOverrides);
|
||||
var customTextureOverridesNew = CopyList(componentCustomTextureOverrides);
|
||||
var customSlotMaterialsNew = CopyList(componentCustomSlotMaterials);
|
||||
|
||||
// Detect changes
|
||||
if (!_customMaterialOverridesPrev.SequenceEqual(customMaterialOverridesNew) ||
|
||||
!_customTextureOverridesPrev.SequenceEqual(customTextureOverridesNew) ||
|
||||
!_customSlotMaterialsPrev.SequenceEqual(customSlotMaterialsNew)) {
|
||||
// 2. Restore old values
|
||||
componentCustomMaterialOverrides.Clear();
|
||||
componentCustomTextureOverrides.Clear();
|
||||
componentCustomSlotMaterials.Clear();
|
||||
componentCustomMaterialOverrides.AddRange(_customMaterialOverridesPrev);
|
||||
componentCustomTextureOverrides.AddRange(_customTextureOverridesPrev);
|
||||
componentCustomSlotMaterials.AddRange(_customSlotMaterialsPrev);
|
||||
|
||||
// 3. Remove overrides
|
||||
RemoveCustomMaterials();
|
||||
|
||||
// 4. Restore new values
|
||||
componentCustomMaterialOverrides.Clear();
|
||||
componentCustomTextureOverrides.Clear();
|
||||
componentCustomSlotMaterials.Clear();
|
||||
componentCustomMaterialOverrides.AddRange(customMaterialOverridesNew);
|
||||
componentCustomTextureOverrides.AddRange(customTextureOverridesNew);
|
||||
componentCustomSlotMaterials.AddRange(customSlotMaterialsNew);
|
||||
|
||||
// 5. Restore overrides
|
||||
SetCustomMaterials();
|
||||
|
||||
if (skeletonGraphic != null)
|
||||
skeletonGraphic.LateUpdate();
|
||||
}
|
||||
|
||||
_customMaterialOverridesPrev = CopyList(componentCustomMaterialOverrides);
|
||||
_customTextureOverridesPrev = CopyList(componentCustomTextureOverrides);
|
||||
_customSlotMaterialsPrev = CopyList(componentCustomSlotMaterials);
|
||||
|
||||
if (componentCustomSlotMaterials.Count > 0 && !skeletonGraphic.allowMultipleCanvasRenderers &&
|
||||
componentCustomSlotMaterials.Any(entry => entry.overrideEnabled)) {
|
||||
EditorGUILayout.HelpBox("Please enable 'Advanced - Multiple CanvasRenderers' at the SkeletonGraphic " +
|
||||
"component when using Custom Slot Materials.", MessageType.Warning, true);
|
||||
}
|
||||
|
||||
if (SpineInspectorUtility.LargeCenteredButton(SpineInspectorUtility.TempContent("Clear and Reapply Changes", tooltip: "Removes all non-serialized overrides in the SkeletonGraphic and reapplies the overrides on this component."))) {
|
||||
if (skeletonGraphic != null) {
|
||||
skeletonGraphic.CustomMaterialOverride.Clear();
|
||||
skeletonGraphic.CustomTextureOverride.Clear();
|
||||
skeletonGraphic.CustomSlotMaterials.Clear();
|
||||
RemoveCustomMaterials();
|
||||
SetCustomMaterials();
|
||||
skeletonGraphic.LateUpdate();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void RemoveCustomMaterials () {
|
||||
RemoveCustomMaterialOverrides.Invoke(component, null);
|
||||
RemoveCustomTextureOverrides.Invoke(component, null);
|
||||
RemoveCustomSlotMaterials.Invoke(component, null);
|
||||
}
|
||||
|
||||
void SetCustomMaterials () {
|
||||
SetCustomMaterialOverrides.Invoke(component, null);
|
||||
SetCustomTextureOverrides.Invoke(component, null);
|
||||
SetCustomSlotMaterials.Invoke(component, null);
|
||||
}
|
||||
|
||||
static List<T> CopyList<T> (List<T> list) {
|
||||
return list.GetRange(0, list.Count);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 349bf125947e3aa4bb78690fec69ea17
|
||||
timeCreated: 1588789940
|
||||
licenseType: Pro
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,389 @@
|
||||
/******************************************************************************
|
||||
* Spine Runtimes License Agreement
|
||||
* Last updated April 5, 2025. Replaces all prior versions.
|
||||
*
|
||||
* Copyright (c) 2013-2026, Esoteric Software LLC
|
||||
*
|
||||
* Integration of the Spine Runtimes into software or otherwise creating
|
||||
* derivative works of the Spine Runtimes is permitted under the terms and
|
||||
* conditions of Section 2 of the Spine Editor License Agreement:
|
||||
* http://esotericsoftware.com/spine-editor-license
|
||||
*
|
||||
* Otherwise, it is permitted to integrate the Spine Runtimes into software
|
||||
* or otherwise create derivative works of the Spine Runtimes (collectively,
|
||||
* "Products"), provided that each user of the Products must obtain their own
|
||||
* Spine Editor license and redistribution of the Products in any form must
|
||||
* include this license and copyright notice.
|
||||
*
|
||||
* THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY
|
||||
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY
|
||||
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES,
|
||||
* BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND
|
||||
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
||||
* THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*****************************************************************************/
|
||||
|
||||
#if UNITY_2018_3 || UNITY_2019 || UNITY_2018_3_OR_NEWER
|
||||
#define NEW_PREFAB_SYSTEM
|
||||
#endif
|
||||
|
||||
#if UNITY_2018_2_OR_NEWER
|
||||
#define HAS_CULL_TRANSPARENT_MESH
|
||||
#endif
|
||||
|
||||
#if UNITY_2017_2_OR_NEWER
|
||||
#define NEWPLAYMODECALLBACKS
|
||||
#endif
|
||||
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Spine.Unity.Editor {
|
||||
using Icons = SpineEditorUtilities.Icons;
|
||||
|
||||
[CustomEditor(typeof(SkeletonGraphic))]
|
||||
[CanEditMultipleObjects]
|
||||
|
||||
public class SkeletonGraphicInspector : ISkeletonRendererInspector {
|
||||
|
||||
protected SerializedProperty material, color;
|
||||
protected SerializedProperty additiveMaterial, multiplyMaterial, screenMaterial, forceAdditiveMaterial;
|
||||
protected SerializedProperty freeze;
|
||||
protected SerializedProperty allowMultipleCanvasRenderers,
|
||||
updateSeparatorPartLocation, updateSeparatorPartScale;
|
||||
protected SerializedProperty raycastTarget, maskable;
|
||||
protected SerializedProperty layoutScaleMode, editReferenceRect;
|
||||
|
||||
protected GUIContent allowMultipleCanvasRenderersLabel, updateSeparatorPartLocationLabel,
|
||||
updateSeparatorPartScaleLabel;
|
||||
|
||||
protected SkeletonGraphic thisSkeletonGraphic;
|
||||
|
||||
protected override void OnEnable () {
|
||||
base.OnEnable();
|
||||
|
||||
// Labels
|
||||
allowMultipleCanvasRenderersLabel = new GUIContent("Multiple CanvasRenderers",
|
||||
"When set to true, SkeletonGraphic no longer uses a single CanvasRenderer" +
|
||||
"but automatically creates the required number of child CanvasRenderer" +
|
||||
"GameObjects for each required draw call (submesh).");
|
||||
updateSeparatorPartLocationLabel = new GUIContent("Update Part Location",
|
||||
"Update separator part GameObject location to match the position of the SkeletonGraphic. " +
|
||||
"This can be helpful when re-parenting parts to a different GameObject.");
|
||||
updateSeparatorPartScaleLabel = new GUIContent("Update Part Scale",
|
||||
"Update separator part GameObject scale to match the scale (lossyScale) of the SkeletonGraphic. " +
|
||||
"This can be helpful when re-parenting parts to a different GameObject.");
|
||||
|
||||
// Properties
|
||||
thisSkeletonGraphic = target as SkeletonGraphic;
|
||||
|
||||
// MaskableGraphic
|
||||
material = serializedObject.FindProperty("m_Material");
|
||||
color = serializedObject.FindProperty("m_SkeletonColor");
|
||||
raycastTarget = serializedObject.FindProperty("m_RaycastTarget");
|
||||
maskable = serializedObject.FindProperty("m_Maskable");
|
||||
|
||||
// SkeletonGraphic
|
||||
additiveMaterial = serializedObject.FindProperty("additiveMaterial");
|
||||
multiplyMaterial = serializedObject.FindProperty("multiplyMaterial");
|
||||
screenMaterial = serializedObject.FindProperty("screenMaterial");
|
||||
forceAdditiveMaterial = serializedObject.FindProperty("forceAdditiveMaterial");
|
||||
freeze = serializedObject.FindProperty("freeze");
|
||||
allowMultipleCanvasRenderers = serializedObject.FindProperty("allowMultipleCanvasRenderers");
|
||||
updateSeparatorPartLocation = serializedObject.FindProperty("updateSeparatorPartLocation");
|
||||
updateSeparatorPartScale = serializedObject.FindProperty("updateSeparatorPartScale");
|
||||
layoutScaleMode = serializedObject.FindProperty("layoutScaleMode");
|
||||
editReferenceRect = serializedObject.FindProperty("editReferenceRect");
|
||||
|
||||
#if NEWPLAYMODECALLBACKS
|
||||
EditorApplication.playModeStateChanged += OnPlaymodeChanged;
|
||||
#else
|
||||
EditorApplication.playmodeStateChanged += OnPlaymodeChanged;
|
||||
#endif
|
||||
}
|
||||
|
||||
void OnDisable () {
|
||||
#if NEWPLAYMODECALLBACKS
|
||||
EditorApplication.playModeStateChanged -= OnPlaymodeChanged;
|
||||
#else
|
||||
EditorApplication.playmodeStateChanged -= OnPlaymodeChanged;
|
||||
#endif
|
||||
DisableEditReferenceRectMode();
|
||||
}
|
||||
|
||||
#if NEWPLAYMODECALLBACKS
|
||||
void OnPlaymodeChanged (PlayModeStateChange mode) {
|
||||
#else
|
||||
void OnPlaymodeChanged () {
|
||||
#endif
|
||||
DisableEditReferenceRectMode();
|
||||
}
|
||||
|
||||
void DisableEditReferenceRectMode () {
|
||||
foreach (UnityEngine.Object c in targets) {
|
||||
SkeletonGraphic component = c as SkeletonGraphic;
|
||||
if (component == null) continue;
|
||||
component.EditReferenceRect = false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
protected override void FirstPropertyFields () {
|
||||
using (new SpineInspectorUtility.LabelWidthScope(100)) {
|
||||
using (new EditorGUILayout.HorizontalScope()) {
|
||||
EditorGUILayout.PropertyField(material);
|
||||
if (GUILayout.Button("Detect", EditorStyles.miniButton, GUILayout.Width(67f))) {
|
||||
Undo.RecordObjects(targets, "Detect Material");
|
||||
foreach (UnityEngine.Object target in targets) {
|
||||
SkeletonGraphic skeletonGraphic = target as SkeletonGraphic;
|
||||
if (skeletonGraphic == null) continue;
|
||||
SkeletonGraphicUtility.DetectMaterial(skeletonGraphic);
|
||||
}
|
||||
}
|
||||
}
|
||||
EditorGUILayout.PropertyField(color);
|
||||
}
|
||||
}
|
||||
|
||||
protected override void MaterialWarningsBox () {
|
||||
string errorMessage = null;
|
||||
if (SpineEditorUtilities.Preferences.componentMaterialWarning &&
|
||||
MaterialChecks.IsMaterialSetupProblematic(thisSkeletonGraphic, ref errorMessage)) {
|
||||
EditorGUILayout.HelpBox(errorMessage, MessageType.Error, true);
|
||||
}
|
||||
}
|
||||
|
||||
protected override void VertexDataProperties () {
|
||||
using (new EditorGUILayout.HorizontalScope()) {
|
||||
EditorGUILayout.PropertyField(tintBlack, TintBlackLabel);
|
||||
if (GUILayout.Button("Detect", EditorStyles.miniButton, GUILayout.Width(65f))) {
|
||||
Undo.RecordObjects(targets, "Detect Tint Black");
|
||||
foreach (UnityEngine.Object target in targets) {
|
||||
SkeletonGraphic skeletonGraphic = target as SkeletonGraphic;
|
||||
if (skeletonGraphic == null) continue;
|
||||
SkeletonGraphicUtility.DetectTintBlack(skeletonGraphic);
|
||||
}
|
||||
}
|
||||
}
|
||||
using (new EditorGUILayout.HorizontalScope()) {
|
||||
EditorGUILayout.PropertyField(canvasGroupCompatible, CanvasGroupCompatibleLabel);
|
||||
if (GUILayout.Button("Detect", EditorStyles.miniButton, GUILayout.Width(65f))) {
|
||||
Undo.RecordObjects(targets, "Detect CanvasGroup Compatible");
|
||||
foreach (UnityEngine.Object target in targets) {
|
||||
SkeletonGraphic skeletonGraphic = target as SkeletonGraphic;
|
||||
if (skeletonGraphic == null) continue;
|
||||
SkeletonGraphicUtility.DetectCanvasGroupCompatible(skeletonGraphic);
|
||||
}
|
||||
}
|
||||
}
|
||||
using (new EditorGUILayout.HorizontalScope()) {
|
||||
EditorGUILayout.PropertyField(pmaVertexColors, PMAVertexColorsLabel);
|
||||
if (GUILayout.Button("Detect", EditorStyles.miniButton, GUILayout.Width(65f))) {
|
||||
Undo.RecordObjects(targets, "Detect PMA Vertex Colors");
|
||||
foreach (UnityEngine.Object target in targets) {
|
||||
SkeletonGraphic skeletonGraphic = target as SkeletonGraphic;
|
||||
if (skeletonGraphic == null) continue;
|
||||
SkeletonGraphicUtility.DetectPMAVertexColors(skeletonGraphic);
|
||||
}
|
||||
}
|
||||
}
|
||||
using (new EditorGUILayout.HorizontalScope()) {
|
||||
GUILayout.FlexibleSpace();
|
||||
if (GUILayout.Button("Detect Settings", EditorStyles.miniButton, GUILayout.Width(100f))) {
|
||||
Undo.RecordObjects(targets, "Detect Settings");
|
||||
foreach (UnityEngine.Object target in targets) {
|
||||
SkeletonGraphic skeletonGraphic = target as SkeletonGraphic;
|
||||
if (skeletonGraphic == null) continue;
|
||||
SkeletonGraphicUtility.DetectTintBlack(skeletonGraphic);
|
||||
SkeletonGraphicUtility.DetectCanvasGroupCompatible(skeletonGraphic);
|
||||
SkeletonGraphicUtility.DetectPMAVertexColors(skeletonGraphic);
|
||||
}
|
||||
}
|
||||
if (GUILayout.Button("Detect Material", EditorStyles.miniButton, GUILayout.Width(100f))) {
|
||||
Undo.RecordObjects(targets, "Detect Material");
|
||||
foreach (UnityEngine.Object target in targets) {
|
||||
SkeletonGraphic skeletonGraphic = target as SkeletonGraphic;
|
||||
if (skeletonGraphic == null) continue;
|
||||
SkeletonGraphicUtility.DetectMaterial(skeletonGraphic);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
EditorGUILayout.PropertyField(addNormals, AddNormalsLabel);
|
||||
EditorGUILayout.PropertyField(calculateTangents, CalculateTangentsLabel);
|
||||
EditorGUILayout.PropertyField(immutableTriangles, ImmutableTrianglesLabel);
|
||||
}
|
||||
|
||||
protected override void RendererProperties () {
|
||||
|
||||
bool isSingleRendererOnly = (!allowMultipleCanvasRenderers.hasMultipleDifferentValues && allowMultipleCanvasRenderers.boolValue == false);
|
||||
bool isSeparationEnabledButNotMultipleRenderers =
|
||||
isSingleRendererOnly && (!enableSeparatorSlots.hasMultipleDifferentValues && enableSeparatorSlots.boolValue == true);
|
||||
bool meshRendersIncorrectlyWithSingleRenderer =
|
||||
isSingleRendererOnly && SkeletonHasMultipleSubmeshes();
|
||||
|
||||
if (isSeparationEnabledButNotMultipleRenderers || meshRendersIncorrectlyWithSingleRenderer)
|
||||
advancedFoldout = true;
|
||||
|
||||
base.RendererProperties();
|
||||
|
||||
EditorGUILayout.Space();
|
||||
EditorGUILayout.BeginHorizontal();
|
||||
EditorGUILayout.PropertyField(allowMultipleCanvasRenderers, allowMultipleCanvasRenderersLabel);
|
||||
|
||||
if (GUILayout.Button(new GUIContent("Trim Renderers", "Remove currently unused CanvasRenderer GameObjects. These will be regenerated whenever needed."),
|
||||
EditorStyles.miniButton, GUILayout.Width(100f))) {
|
||||
|
||||
Undo.RecordObjects(targets, "Trim Renderers");
|
||||
foreach (UnityEngine.Object target in targets) {
|
||||
SkeletonGraphic skeletonGraphic = target as SkeletonGraphic;
|
||||
if (skeletonGraphic == null) continue;
|
||||
skeletonGraphic.TrimRenderers();
|
||||
}
|
||||
}
|
||||
EditorGUILayout.EndHorizontal();
|
||||
|
||||
BlendModeMaterials blendModeMaterials = thisSkeletonGraphic.skeletonDataAsset.blendModeMaterials;
|
||||
if (allowMultipleCanvasRenderers.boolValue == true && blendModeMaterials.RequiresBlendModeMaterials) {
|
||||
using (new SpineInspectorUtility.IndentScope()) {
|
||||
EditorGUILayout.BeginHorizontal();
|
||||
EditorGUILayout.LabelField("Blend Mode Materials", EditorStyles.boldLabel);
|
||||
|
||||
if (GUILayout.Button(new GUIContent("Detect", "Auto-Assign Blend Mode Materials according to Vertex Data and Texture settings."),
|
||||
EditorStyles.miniButton, GUILayout.Width(100f))) {
|
||||
|
||||
Undo.RecordObjects(targets, "Detect Blend Mode Materials");
|
||||
foreach (UnityEngine.Object target in targets) {
|
||||
SkeletonGraphic skeletonGraphic = target as SkeletonGraphic;
|
||||
if (skeletonGraphic == null) continue;
|
||||
SkeletonGraphicUtility.DetectBlendModeMaterials(skeletonGraphic);
|
||||
}
|
||||
}
|
||||
EditorGUILayout.EndHorizontal();
|
||||
|
||||
bool usesAdditiveMaterial = blendModeMaterials.applyAdditiveMaterial;
|
||||
bool pmaVertexColors = thisSkeletonGraphic.MeshSettings.pmaVertexColors;
|
||||
bool forceAdditiveEnabled = thisSkeletonGraphic.forceAdditiveMaterial;
|
||||
if (pmaVertexColors) {
|
||||
EditorGUILayout.PropertyField(forceAdditiveMaterial, SpineInspectorUtility.TempContent("Force Additive Material", null, "Still use 'Additive' material regardless of enabled 'PMA Vertex Colors'."));
|
||||
if (forceAdditiveEnabled)
|
||||
EditorGUILayout.PropertyField(additiveMaterial, SpineInspectorUtility.TempContent("Additive Material", null, "SkeletonGraphic Material for 'Additive' blend mode slots. Unused when 'PMA Vertex Colors' is enabled."));
|
||||
else
|
||||
using (new EditorGUI.DisabledGroupScope(true)) {
|
||||
EditorGUILayout.LabelField("Additive Material - Unused with PMA Vertex Colors", EditorStyles.label);
|
||||
}
|
||||
} else if (usesAdditiveMaterial) {
|
||||
EditorGUILayout.PropertyField(additiveMaterial, SpineInspectorUtility.TempContent("Additive Material", null, "SkeletonGraphic Material for 'Additive' blend mode slots. Unused when 'PMA Vertex Colors' is enabled."));
|
||||
} else {
|
||||
using (new EditorGUI.DisabledGroupScope(true)) {
|
||||
EditorGUILayout.LabelField("No Additive Mat - 'Apply Additive Material' disabled at SkeletonDataAsset", EditorStyles.label);
|
||||
}
|
||||
}
|
||||
EditorGUILayout.PropertyField(multiplyMaterial, SpineInspectorUtility.TempContent("Multiply Material", null, "SkeletonGraphic Material for 'Multiply' blend mode slots."));
|
||||
EditorGUILayout.PropertyField(screenMaterial, SpineInspectorUtility.TempContent("Screen Material", null, "SkeletonGraphic Material for 'Screen' blend mode slots."));
|
||||
}
|
||||
}
|
||||
|
||||
// warning box
|
||||
if (isSeparationEnabledButNotMultipleRenderers) {
|
||||
using (new SpineInspectorUtility.BoxScope()) {
|
||||
meshSettings.isExpanded = true;
|
||||
EditorGUILayout.LabelField(SpineInspectorUtility.TempContent("'Multiple Canvas Renderers' must be enabled\nwhen 'Enable Separation' is enabled.", Icons.warning), GUILayout.Height(42), GUILayout.Width(340));
|
||||
}
|
||||
} else if (meshRendersIncorrectlyWithSingleRenderer) {
|
||||
using (new SpineInspectorUtility.BoxScope()) {
|
||||
meshSettings.isExpanded = true;
|
||||
EditorGUILayout.LabelField(SpineInspectorUtility.TempContent("This mesh uses multiple atlas pages or blend modes.\n" +
|
||||
"You need to enable 'Multiple Canvas Renderers'\n" +
|
||||
"for correct rendering. Consider packing\n" +
|
||||
"attachments to a single atlas page if possible.", Icons.warning), GUILayout.Height(60), GUILayout.Width(340));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected override void AfterAdvancedPropertyFields () {
|
||||
|
||||
EditorGUILayout.Space();
|
||||
EditorGUILayout.PropertyField(freeze);
|
||||
EditorGUILayout.Space();
|
||||
EditorGUILayout.LabelField("UI", EditorStyles.boldLabel);
|
||||
EditorGUILayout.PropertyField(raycastTarget);
|
||||
if (maskable != null) EditorGUILayout.PropertyField(maskable);
|
||||
|
||||
EditorGUILayout.PropertyField(layoutScaleMode);
|
||||
|
||||
using (new EditorGUI.DisabledGroupScope(layoutScaleMode.intValue == 0)) {
|
||||
EditorGUILayout.BeginHorizontal(GUILayout.Height(EditorGUIUtility.singleLineHeight + 5));
|
||||
EditorGUILayout.PrefixLabel("Edit Layout Bounds");
|
||||
editReferenceRect.boolValue = GUILayout.Toggle(editReferenceRect.boolValue,
|
||||
EditorGUIUtility.IconContent("EditCollider"), EditorStyles.miniButton, GUILayout.Width(40f));
|
||||
EditorGUILayout.EndHorizontal();
|
||||
}
|
||||
if (layoutScaleMode.intValue == 0) {
|
||||
editReferenceRect.boolValue = false;
|
||||
}
|
||||
|
||||
using (new EditorGUI.DisabledGroupScope(editReferenceRect.boolValue == false && layoutScaleMode.intValue != 0)) {
|
||||
EditorGUILayout.BeginHorizontal(GUILayout.Height(EditorGUIUtility.singleLineHeight + 5));
|
||||
EditorGUILayout.PrefixLabel("Match RectTransform with Mesh");
|
||||
if (GUILayout.Button("Match", EditorStyles.miniButton, GUILayout.Width(65f))) {
|
||||
foreach (UnityEngine.Object target in targets) {
|
||||
SkeletonGraphic skeletonGraphic = target as SkeletonGraphic;
|
||||
if (skeletonGraphic == null) continue;
|
||||
MatchRectTransformWithBounds(skeletonGraphic);
|
||||
}
|
||||
}
|
||||
EditorGUILayout.EndHorizontal();
|
||||
}
|
||||
}
|
||||
|
||||
protected bool SkeletonHasMultipleSubmeshes () {
|
||||
foreach (UnityEngine.Object target in targets) {
|
||||
SkeletonGraphic skeletonGraphic = target as SkeletonGraphic;
|
||||
if (skeletonGraphic == null) continue;
|
||||
if (skeletonGraphic.HasMultipleSubmeshInstructions())
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
protected override void AdditionalSeparatorSlotProperties () {
|
||||
EditorGUILayout.PropertyField(updateSeparatorPartLocation, updateSeparatorPartLocationLabel);
|
||||
EditorGUILayout.PropertyField(updateSeparatorPartScale, updateSeparatorPartScaleLabel);
|
||||
}
|
||||
|
||||
public override void OnSceneGUI () {
|
||||
base.OnSceneGUI();
|
||||
|
||||
SkeletonGraphic skeletonGraphic = (SkeletonGraphic)target;
|
||||
|
||||
if (skeletonGraphic.layoutScaleMode != SkeletonGraphic.LayoutMode.None) {
|
||||
if (skeletonGraphic.EditReferenceRect) {
|
||||
SpineHandles.DrawRectTransformRect(skeletonGraphic, Color.gray);
|
||||
SpineHandles.DrawReferenceRect(skeletonGraphic, Color.green);
|
||||
} else {
|
||||
SpineHandles.DrawReferenceRect(skeletonGraphic, Color.blue);
|
||||
}
|
||||
}
|
||||
SpineHandles.DrawPivotOffsetHandle(skeletonGraphic, Color.green);
|
||||
}
|
||||
|
||||
#region Menus
|
||||
[MenuItem("CONTEXT/SkeletonGraphic/Match RectTransform with Mesh Bounds")]
|
||||
static void MatchRectTransformWithBounds (MenuCommand command) {
|
||||
SkeletonGraphic skeletonGraphic = (SkeletonGraphic)command.context;
|
||||
MatchRectTransformWithBounds(skeletonGraphic);
|
||||
}
|
||||
|
||||
static void MatchRectTransformWithBounds (SkeletonGraphic skeletonGraphic) {
|
||||
if (!skeletonGraphic.MatchRectTransformWithBounds())
|
||||
Debug.Log("Mesh was not previously generated.");
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 0d81cc76b52fcdf499b2db252a317726
|
||||
timeCreated: 1455570945
|
||||
licenseType: Free
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,237 @@
|
||||
/******************************************************************************
|
||||
* Spine Runtimes License Agreement
|
||||
* Last updated April 5, 2025. Replaces all prior versions.
|
||||
*
|
||||
* Copyright (c) 2013-2026, Esoteric Software LLC
|
||||
*
|
||||
* Integration of the Spine Runtimes into software or otherwise creating
|
||||
* derivative works of the Spine Runtimes is permitted under the terms and
|
||||
* conditions of Section 2 of the Spine Editor License Agreement:
|
||||
* http://esotericsoftware.com/spine-editor-license
|
||||
*
|
||||
* Otherwise, it is permitted to integrate the Spine Runtimes into software
|
||||
* or otherwise create derivative works of the Spine Runtimes (collectively,
|
||||
* "Products"), provided that each user of the Products must obtain their own
|
||||
* Spine Editor license and redistribution of the Products in any form must
|
||||
* include this license and copyright notice.
|
||||
*
|
||||
* THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY
|
||||
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY
|
||||
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES,
|
||||
* BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND
|
||||
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
||||
* THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*****************************************************************************/
|
||||
|
||||
// Contributed by: Mitch Thompson
|
||||
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Spine.Unity.Editor {
|
||||
using Event = UnityEngine.Event;
|
||||
|
||||
[CustomEditor(typeof(SkeletonMecanim))]
|
||||
[CanEditMultipleObjects]
|
||||
public class SkeletonMecanimInspector : UnityEditor.Editor {
|
||||
public static bool mecanimSettingsFoldout;
|
||||
public static bool enableScenePreview;
|
||||
|
||||
protected SerializedProperty updateTiming, autoReset, useCustomMixMode, layerMixModes, threadedAnimation;
|
||||
|
||||
readonly GUIContent UpdateTimingLabel = new GUIContent("Animation Update",
|
||||
"Whether to update the animation in normal Update (the default), " +
|
||||
"physics step FixedUpdate, or manually via a user call.");
|
||||
readonly GUIContent AutoResetLabel = new GUIContent("Auto Reset",
|
||||
"When set to true, the skeleton state is mixed out to setup-" +
|
||||
"pose when an animation finishes, according to the " +
|
||||
"animation's keyed items.");
|
||||
readonly GUIContent UseCustomMixModeLabel = new GUIContent("Custom MixMode",
|
||||
"When disabled, the recommended MixMode is used according to the layer blend mode. " +
|
||||
"Enable to specify a custom MixMode for each Mecanim layer.");
|
||||
readonly GUIContent ThreadedAnimationLabel = new GUIContent("Use Threading",
|
||||
"When enabled, animations are processed on multiple threads in parallel.");
|
||||
|
||||
protected virtual void OnEnable () {
|
||||
SerializedProperty mecanimTranslator = serializedObject.FindProperty("translator");
|
||||
|
||||
autoReset = mecanimTranslator.FindPropertyRelative("autoReset");
|
||||
useCustomMixMode = mecanimTranslator.FindPropertyRelative("useCustomMixMode");
|
||||
layerMixModes = mecanimTranslator.FindPropertyRelative("layerMixModes");
|
||||
|
||||
updateTiming = serializedObject.FindProperty("updateTiming");
|
||||
threadedAnimation = serializedObject.FindProperty("threadedAnimation");
|
||||
}
|
||||
|
||||
override public void OnInspectorGUI () {
|
||||
DrawInspectorGUI();
|
||||
serializedObject.ApplyModifiedProperties();
|
||||
}
|
||||
|
||||
protected virtual void DrawInspectorGUI () {
|
||||
foreach (UnityEngine.Object c in targets) {
|
||||
ISkeletonAnimation component = c as ISkeletonAnimation;
|
||||
if (!component.IsValid) {
|
||||
SpineEditorUtilities.ReinitializeComponent(component);
|
||||
}
|
||||
}
|
||||
|
||||
AddRootMotionComponentIfEnabled();
|
||||
|
||||
EditorGUILayout.PropertyField(updateTiming, UpdateTimingLabel);
|
||||
EditorGUILayout.Space();
|
||||
|
||||
if (threadedAnimation != null) {
|
||||
EditorGUILayout.LabelField(SpineInspectorUtility.TempContent("Threaded Animation", SpineEditorUtilities.Icons.subMeshRenderer), EditorStyles.boldLabel);
|
||||
EditorGUILayout.PropertyField(threadedAnimation, ThreadedAnimationLabel);
|
||||
EditorGUILayout.Space();
|
||||
}
|
||||
|
||||
using (new SpineInspectorUtility.BoxScope()) {
|
||||
mecanimSettingsFoldout = EditorGUILayout.Foldout(mecanimSettingsFoldout, "Mecanim Translator");
|
||||
if (mecanimSettingsFoldout) {
|
||||
EditorGUILayout.PropertyField(autoReset, AutoResetLabel);
|
||||
|
||||
EditorGUILayout.PropertyField(useCustomMixMode, UseCustomMixModeLabel);
|
||||
|
||||
if (useCustomMixMode.hasMultipleDifferentValues || useCustomMixMode.boolValue == true) {
|
||||
DrawLayerSettings();
|
||||
EditorGUILayout.Space();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
EditorGUI.BeginChangeCheck();
|
||||
enableScenePreview = EditorGUILayout.Toggle(new GUIContent("Scene Preview",
|
||||
"Preview the Animation Clip selected in the Animation window. Lock this SkeletonMecanim Inspector " +
|
||||
"window, open the Animation window and select the Animation Clip. Then in the Animation window " +
|
||||
"scrub through the timeline."),
|
||||
enableScenePreview, GUILayout.MaxWidth(150f));
|
||||
bool wasScenePreviewChanged = EditorGUI.EndChangeCheck();
|
||||
if (enableScenePreview)
|
||||
HandleAnimationPreview();
|
||||
else if (wasScenePreviewChanged) // just disabled, back to setup pose
|
||||
PreviewAnimationInScene(null, 0.0f);
|
||||
}
|
||||
|
||||
protected void AddRootMotionComponentIfEnabled () {
|
||||
foreach (UnityEngine.Object t in targets) {
|
||||
Component component = t as Component;
|
||||
Animator animator = component.GetComponent<Animator>();
|
||||
if (animator != null && animator.applyRootMotion) {
|
||||
if (component.GetComponent<SkeletonMecanimRootMotion>() == null) {
|
||||
component.gameObject.AddComponent<SkeletonMecanimRootMotion>();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected void HandleAnimationPreview () {
|
||||
UnityEngine.Object animationWindow = AnimationWindowPreview.GetOpenAnimationWindow();
|
||||
|
||||
AnimationClip selectedClip = null;
|
||||
if (animationWindow != null) {
|
||||
selectedClip = AnimationWindowPreview.GetAnimationClip(animationWindow);
|
||||
}
|
||||
|
||||
if (selectedClip != null) {
|
||||
float time = AnimationWindowPreview.GetAnimationTime(animationWindow);
|
||||
PreviewAnimationInScene(selectedClip, time);
|
||||
} else // back to setup pose
|
||||
PreviewAnimationInScene(null, 0.0f);
|
||||
}
|
||||
|
||||
protected void PreviewAnimationInScene (AnimationClip clip, float time) {
|
||||
foreach (UnityEngine.Object c in targets) {
|
||||
SkeletonRenderer skeletonRenderer = ((SkeletonMecanim)c).Renderer as SkeletonRenderer;
|
||||
if (skeletonRenderer == null) continue;
|
||||
Skeleton skeleton = skeletonRenderer.Skeleton;
|
||||
SkeletonData skeletonData = skeleton.Data;
|
||||
|
||||
skeleton.SetupPose();
|
||||
if (clip != null) {
|
||||
Spine.Animation animation = skeletonData.FindAnimation(clip.name);
|
||||
animation.Apply(skeleton, 0, time, false, null, 1.0f, true, false, false, false);
|
||||
}
|
||||
skeletonRenderer.LateUpdate();
|
||||
}
|
||||
SceneView.RepaintAll();
|
||||
}
|
||||
|
||||
protected void DrawLayerSettings () {
|
||||
string[] layerNames = GetLayerNames();
|
||||
float widthLayerColumn = 140;
|
||||
float widthMixColumn = 84;
|
||||
|
||||
using (new GUILayout.HorizontalScope()) {
|
||||
Rect rect = GUILayoutUtility.GetRect(EditorGUIUtility.currentViewWidth, EditorGUIUtility.singleLineHeight);
|
||||
rect.width = widthLayerColumn;
|
||||
EditorGUI.LabelField(rect, SpineInspectorUtility.TempContent("Mecanim Layer"), EditorStyles.boldLabel);
|
||||
|
||||
int savedIndent = EditorGUI.indentLevel;
|
||||
EditorGUI.indentLevel = 0;
|
||||
|
||||
rect.position += new Vector2(rect.width, 0);
|
||||
rect.width = widthMixColumn;
|
||||
EditorGUI.LabelField(rect, SpineInspectorUtility.TempContent("Mix Mode"), EditorStyles.boldLabel);
|
||||
|
||||
EditorGUI.indentLevel = savedIndent;
|
||||
}
|
||||
|
||||
using (new SpineInspectorUtility.IndentScope()) {
|
||||
int layerCount = layerMixModes.arraySize;
|
||||
for (int i = 0; i < layerCount; ++i) {
|
||||
using (new GUILayout.HorizontalScope()) {
|
||||
string layerName = i < layerNames.Length ? layerNames[i] : ("Layer " + i);
|
||||
|
||||
Rect rect = GUILayoutUtility.GetRect(EditorGUIUtility.currentViewWidth, EditorGUIUtility.singleLineHeight);
|
||||
rect.width = widthLayerColumn;
|
||||
EditorGUI.PrefixLabel(rect, SpineInspectorUtility.TempContent(layerName));
|
||||
|
||||
int savedIndent = EditorGUI.indentLevel;
|
||||
EditorGUI.indentLevel = 0;
|
||||
|
||||
SerializedProperty mixMode = layerMixModes.GetArrayElementAtIndex(i);
|
||||
rect.position += new Vector2(rect.width, 0);
|
||||
rect.width = widthMixColumn;
|
||||
EditorGUI.PropertyField(rect, mixMode, GUIContent.none);
|
||||
|
||||
EditorGUI.indentLevel = savedIndent;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected string[] GetLayerNames () {
|
||||
int maxLayerCount = 0;
|
||||
int maxIndex = 0;
|
||||
for (int i = 0; i < targets.Length; ++i) {
|
||||
SkeletonMecanim skeletonMecanim = ((SkeletonMecanim)targets[i]);
|
||||
|
||||
Animator animator = skeletonMecanim.Translator.Animator;
|
||||
if (!Application.isPlaying) {
|
||||
if (animator != null && animator.isInitialized &&
|
||||
animator.isActiveAndEnabled && animator.runtimeAnimatorController != null) {
|
||||
// Note: Rebind is required to prevent warning "Animator is not playing an AnimatorController"
|
||||
// when saving and perhaps also with prefabs.
|
||||
animator.Rebind();
|
||||
}
|
||||
}
|
||||
|
||||
int count = skeletonMecanim.Translator.MecanimLayerCount;
|
||||
if (count > maxLayerCount) {
|
||||
maxLayerCount = count;
|
||||
maxIndex = i;
|
||||
}
|
||||
}
|
||||
if (maxLayerCount == 0)
|
||||
return new string[0];
|
||||
SkeletonMecanim skeletonMecanimMaxLayers = ((SkeletonMecanim)targets[maxIndex]);
|
||||
return skeletonMecanimMaxLayers.Translator.MecanimLayerNames;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 6a9ca5213a3a4614c9a9f2e60909bc33
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,81 @@
|
||||
/******************************************************************************
|
||||
* Spine Runtimes License Agreement
|
||||
* Last updated April 5, 2025. Replaces all prior versions.
|
||||
*
|
||||
* Copyright (c) 2013-2026, Esoteric Software LLC
|
||||
*
|
||||
* Integration of the Spine Runtimes into software or otherwise creating
|
||||
* derivative works of the Spine Runtimes is permitted under the terms and
|
||||
* conditions of Section 2 of the Spine Editor License Agreement:
|
||||
* http://esotericsoftware.com/spine-editor-license
|
||||
*
|
||||
* Otherwise, it is permitted to integrate the Spine Runtimes into software
|
||||
* or otherwise create derivative works of the Spine Runtimes (collectively,
|
||||
* "Products"), provided that each user of the Products must obtain their own
|
||||
* Spine Editor license and redistribution of the Products in any form must
|
||||
* include this license and copyright notice.
|
||||
*
|
||||
* THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY
|
||||
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY
|
||||
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES,
|
||||
* BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND
|
||||
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
||||
* THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*****************************************************************************/
|
||||
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Spine.Unity.Editor {
|
||||
[CustomEditor(typeof(SkeletonMecanimRootMotion))]
|
||||
[CanEditMultipleObjects]
|
||||
public class SkeletonMecanimRootMotionInspector : SkeletonRootMotionBaseInspector {
|
||||
protected SerializedProperty mecanimLayerFlags;
|
||||
|
||||
protected GUIContent mecanimLayersLabel;
|
||||
|
||||
protected override void OnEnable () {
|
||||
base.OnEnable();
|
||||
mecanimLayerFlags = serializedObject.FindProperty("mecanimLayerFlags");
|
||||
|
||||
mecanimLayersLabel = new UnityEngine.GUIContent("Mecanim Layers", "Mecanim layers to apply root motion at. Defaults to the first Mecanim layer.");
|
||||
}
|
||||
|
||||
override public void OnInspectorGUI () {
|
||||
|
||||
base.MainPropertyFields();
|
||||
MecanimLayerMaskPropertyField();
|
||||
|
||||
base.OptionalPropertyFields();
|
||||
serializedObject.ApplyModifiedProperties();
|
||||
}
|
||||
|
||||
protected string[] GetLayerNames () {
|
||||
int maxLayerCount = 0;
|
||||
int maxIndex = 0;
|
||||
for (int i = 0; i < targets.Length; ++i) {
|
||||
SkeletonMecanim skeletonMecanim = ((SkeletonMecanimRootMotion)targets[i]).SkeletonMecanim;
|
||||
int count = skeletonMecanim.Translator.MecanimLayerCount;
|
||||
if (count > maxLayerCount) {
|
||||
maxLayerCount = count;
|
||||
maxIndex = i;
|
||||
}
|
||||
}
|
||||
if (maxLayerCount == 0)
|
||||
return new string[0];
|
||||
SkeletonMecanim skeletonMecanimMaxLayers = ((SkeletonMecanimRootMotion)targets[maxIndex]).SkeletonMecanim;
|
||||
return skeletonMecanimMaxLayers.Translator.MecanimLayerNames;
|
||||
}
|
||||
|
||||
protected void MecanimLayerMaskPropertyField () {
|
||||
string[] layerNames = GetLayerNames();
|
||||
if (layerNames.Length > 0)
|
||||
mecanimLayerFlags.intValue = EditorGUILayout.MaskField(
|
||||
mecanimLayersLabel, mecanimLayerFlags.intValue, layerNames);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 4613924c50d66cf458f0db803776dd2f
|
||||
timeCreated: 1593175106
|
||||
licenseType: Pro
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,161 @@
|
||||
/******************************************************************************
|
||||
* Spine Runtimes License Agreement
|
||||
* Last updated April 5, 2025. Replaces all prior versions.
|
||||
*
|
||||
* Copyright (c) 2013-2026, Esoteric Software LLC
|
||||
*
|
||||
* Integration of the Spine Runtimes into software or otherwise creating
|
||||
* derivative works of the Spine Runtimes is permitted under the terms and
|
||||
* conditions of Section 2 of the Spine Editor License Agreement:
|
||||
* http://esotericsoftware.com/spine-editor-license
|
||||
*
|
||||
* Otherwise, it is permitted to integrate the Spine Runtimes into software
|
||||
* or otherwise create derivative works of the Spine Runtimes (collectively,
|
||||
* "Products"), provided that each user of the Products must obtain their own
|
||||
* Spine Editor license and redistribution of the Products in any form must
|
||||
* include this license and copyright notice.
|
||||
*
|
||||
* THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY
|
||||
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY
|
||||
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES,
|
||||
* BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND
|
||||
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
||||
* THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*****************************************************************************/
|
||||
|
||||
// Contributed by: Lost Polygon
|
||||
|
||||
using Spine.Unity.Examples;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Spine.Unity.Editor {
|
||||
|
||||
// This script is not intended for use with code. See the readme.txt file in SkeletonRendererCustomMaterials folder to learn more.
|
||||
[CustomEditor(typeof(SkeletonRendererCustomMaterials))]
|
||||
public class SkeletonRendererCustomMaterialsInspector : UnityEditor.Editor {
|
||||
List<SkeletonRendererCustomMaterials.AtlasMaterialOverride> componentCustomMaterialOverrides, _customMaterialOverridesPrev;
|
||||
List<SkeletonRendererCustomMaterials.SlotMaterialOverride> componentCustomSlotMaterials, _customSlotMaterialsPrev;
|
||||
SkeletonRendererCustomMaterials component;
|
||||
|
||||
const BindingFlags PrivateInstance = BindingFlags.Instance | BindingFlags.NonPublic;
|
||||
MethodInfo RemoveCustomMaterialOverrides, RemoveCustomSlotMaterials, SetCustomMaterialOverrides, SetCustomSlotMaterials;
|
||||
|
||||
#region SkeletonRenderer context menu
|
||||
[MenuItem("CONTEXT/SkeletonRenderer/Add Basic Serialized Custom Materials")]
|
||||
static void AddSkeletonRendererCustomMaterials (MenuCommand menuCommand) {
|
||||
SkeletonRenderer skeletonRenderer = (SkeletonRenderer)menuCommand.context;
|
||||
SkeletonRendererCustomMaterials newComponent = skeletonRenderer.gameObject.AddComponent<SkeletonRendererCustomMaterials>();
|
||||
Undo.RegisterCreatedObjectUndo(newComponent, "Add Basic Serialized Custom Materials");
|
||||
}
|
||||
|
||||
[MenuItem("CONTEXT/SkeletonRenderer/Add Basic Serialized Custom Materials", true)]
|
||||
static bool AddSkeletonRendererCustomMaterials_Validate (MenuCommand menuCommand) {
|
||||
SkeletonRenderer skeletonRenderer = (SkeletonRenderer)menuCommand.context;
|
||||
return (skeletonRenderer.GetComponent<SkeletonRendererCustomMaterials>() == null);
|
||||
}
|
||||
#endregion
|
||||
|
||||
void OnEnable () {
|
||||
Type cm = typeof(SkeletonRendererCustomMaterials);
|
||||
RemoveCustomMaterialOverrides = cm.GetMethod("RemoveCustomMaterialOverrides", PrivateInstance);
|
||||
RemoveCustomSlotMaterials = cm.GetMethod("RemoveCustomSlotMaterials", PrivateInstance);
|
||||
SetCustomMaterialOverrides = cm.GetMethod("SetCustomMaterialOverrides", PrivateInstance);
|
||||
SetCustomSlotMaterials = cm.GetMethod("SetCustomSlotMaterials", PrivateInstance);
|
||||
}
|
||||
|
||||
public override void OnInspectorGUI () {
|
||||
component = (SkeletonRendererCustomMaterials)target;
|
||||
SkeletonRenderer skeletonRenderer = component.skeletonRenderer;
|
||||
|
||||
// Draw the default inspector
|
||||
DrawDefaultInspector();
|
||||
|
||||
if (serializedObject.isEditingMultipleObjects)
|
||||
return;
|
||||
|
||||
if (componentCustomMaterialOverrides == null) {
|
||||
Type cm = typeof(SkeletonRendererCustomMaterials);
|
||||
componentCustomMaterialOverrides = cm.GetField("customMaterialOverrides", PrivateInstance).GetValue(component) as List<SkeletonRendererCustomMaterials.AtlasMaterialOverride>;
|
||||
componentCustomSlotMaterials = cm.GetField("customSlotMaterials", PrivateInstance).GetValue(component) as List<SkeletonRendererCustomMaterials.SlotMaterialOverride>;
|
||||
if (componentCustomMaterialOverrides == null || componentCustomSlotMaterials == null) {
|
||||
Debug.Log("Reflection failed.");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Fill with current values at start
|
||||
if (_customMaterialOverridesPrev == null || _customSlotMaterialsPrev == null) {
|
||||
_customMaterialOverridesPrev = CopyList(componentCustomMaterialOverrides);
|
||||
_customSlotMaterialsPrev = CopyList(componentCustomSlotMaterials);
|
||||
}
|
||||
|
||||
// Compare new values with saved. If change is detected:
|
||||
// store new values, restore old values, remove overrides, restore new values, restore overrides.
|
||||
|
||||
// 1. Store new values
|
||||
var customMaterialOverridesNew = CopyList(componentCustomMaterialOverrides);
|
||||
var customSlotMaterialsNew = CopyList(componentCustomSlotMaterials);
|
||||
|
||||
// Detect changes
|
||||
if (!_customMaterialOverridesPrev.SequenceEqual(customMaterialOverridesNew) ||
|
||||
!_customSlotMaterialsPrev.SequenceEqual(customSlotMaterialsNew)) {
|
||||
// 2. Restore old values
|
||||
componentCustomMaterialOverrides.Clear();
|
||||
componentCustomSlotMaterials.Clear();
|
||||
componentCustomMaterialOverrides.AddRange(_customMaterialOverridesPrev);
|
||||
componentCustomSlotMaterials.AddRange(_customSlotMaterialsPrev);
|
||||
|
||||
// 3. Remove overrides
|
||||
RemoveCustomMaterials();
|
||||
|
||||
// 4. Restore new values
|
||||
componentCustomMaterialOverrides.Clear();
|
||||
componentCustomSlotMaterials.Clear();
|
||||
componentCustomMaterialOverrides.AddRange(customMaterialOverridesNew);
|
||||
componentCustomSlotMaterials.AddRange(customSlotMaterialsNew);
|
||||
|
||||
// 5. Restore overrides
|
||||
SetCustomMaterials();
|
||||
|
||||
if (skeletonRenderer != null)
|
||||
skeletonRenderer.LateUpdate();
|
||||
}
|
||||
|
||||
_customMaterialOverridesPrev = CopyList(componentCustomMaterialOverrides);
|
||||
_customSlotMaterialsPrev = CopyList(componentCustomSlotMaterials);
|
||||
|
||||
if (SpineInspectorUtility.LargeCenteredButton(SpineInspectorUtility.TempContent("Clear and Reapply Changes", tooltip: "Removes all non-serialized overrides in the SkeletonRenderer and reapplies the overrides on this component."))) {
|
||||
if (skeletonRenderer != null) {
|
||||
skeletonRenderer.CustomMaterialOverride.Clear();
|
||||
skeletonRenderer.CustomSlotMaterials.Clear();
|
||||
RemoveCustomMaterials();
|
||||
SetCustomMaterials();
|
||||
skeletonRenderer.LateUpdate();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void RemoveCustomMaterials () {
|
||||
RemoveCustomMaterialOverrides.Invoke(component, null);
|
||||
RemoveCustomSlotMaterials.Invoke(component, null);
|
||||
}
|
||||
|
||||
void SetCustomMaterials () {
|
||||
SetCustomMaterialOverrides.Invoke(component, null);
|
||||
SetCustomSlotMaterials.Invoke(component, null);
|
||||
}
|
||||
|
||||
static List<T> CopyList<T> (List<T> list) {
|
||||
return list.GetRange(0, list.Count);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e70f7f2a241d6d34aafd6a4a52a368d0
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,129 @@
|
||||
/******************************************************************************
|
||||
* Spine Runtimes License Agreement
|
||||
* Last updated April 5, 2025. Replaces all prior versions.
|
||||
*
|
||||
* Copyright (c) 2013-2026, Esoteric Software LLC
|
||||
*
|
||||
* Integration of the Spine Runtimes into software or otherwise creating
|
||||
* derivative works of the Spine Runtimes is permitted under the terms and
|
||||
* conditions of Section 2 of the Spine Editor License Agreement:
|
||||
* http://esotericsoftware.com/spine-editor-license
|
||||
*
|
||||
* Otherwise, it is permitted to integrate the Spine Runtimes into software
|
||||
* or otherwise create derivative works of the Spine Runtimes (collectively,
|
||||
* "Products"), provided that each user of the Products must obtain their own
|
||||
* Spine Editor license and redistribution of the Products in any form must
|
||||
* include this license and copyright notice.
|
||||
*
|
||||
* THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY
|
||||
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY
|
||||
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES,
|
||||
* BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND
|
||||
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
||||
* THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*****************************************************************************/
|
||||
|
||||
#if UNITY_2018_3 || UNITY_2019 || UNITY_2018_3_OR_NEWER
|
||||
#define NEW_PREFAB_SYSTEM
|
||||
#else
|
||||
#define NO_PREFAB_MESH
|
||||
#endif
|
||||
|
||||
#if UNITY_2018_1_OR_NEWER
|
||||
#define PER_MATERIAL_PROPERTY_BLOCKS
|
||||
#endif
|
||||
|
||||
#if UNITY_2017_1_OR_NEWER
|
||||
#define BUILT_IN_SPRITE_MASK_COMPONENT
|
||||
#endif
|
||||
|
||||
#if UNITY_2020_2_OR_NEWER
|
||||
#define HAS_ON_POSTPROCESS_PREFAB
|
||||
#endif
|
||||
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Spine.Unity.Editor {
|
||||
[CustomEditor(typeof(SkeletonRenderer))]
|
||||
[CanEditMultipleObjects]
|
||||
public class SkeletonRendererInspector : ISkeletonRendererInspector {
|
||||
|
||||
protected SerializedProperty singleSubmesh;
|
||||
protected SerializedProperty fixPrefabOverrideViaMeshFilter;
|
||||
protected SerializedProperty maskInteraction;
|
||||
protected SpineInspectorUtility.SerializedSortingProperties sortingProperties;
|
||||
|
||||
|
||||
protected GUIContent SingleSubmeshLabel;
|
||||
protected GUIContent FixPrefabOverrideViaMeshFilterLabel;
|
||||
protected GUIContent MaskInteractionLabel;
|
||||
|
||||
const string ReloadButtonString = "Reload";
|
||||
static GUILayoutOption reloadButtonWidth;
|
||||
static GUILayoutOption ReloadButtonWidth { get { return reloadButtonWidth = reloadButtonWidth ?? GUILayout.Width(GUI.skin.label.CalcSize(new GUIContent(ReloadButtonString)).x + 20); } }
|
||||
static GUIStyle ReloadButtonStyle { get { return EditorStyles.miniButton; } }
|
||||
|
||||
|
||||
protected override void OnEnable () {
|
||||
base.OnEnable();
|
||||
|
||||
// Labels
|
||||
SingleSubmeshLabel = new GUIContent("Use Single Submesh", "Simplifies submesh generation by assuming you are only using one Material and need only one submesh. This is will disable multiple materials, render separation, and custom slot materials.");
|
||||
FixPrefabOverrideViaMeshFilterLabel = new GUIContent("Fix Prefab Overr. MeshFilter", "Fixes the prefab always being marked as changed (sets the MeshFilter's hide flags to DontSaveInEditor), but at the cost of references to the MeshFilter by other components being lost. For global settings see Edit - Preferences - Spine.");
|
||||
MaskInteractionLabel = new GUIContent("Mask Interaction", "SkeletonRenderer's interaction with a Sprite Mask.");
|
||||
|
||||
// Properties
|
||||
singleSubmesh = serializedObject.FindProperty("singleSubmesh");
|
||||
fixPrefabOverrideViaMeshFilter = serializedObject.FindProperty("fixPrefabOverrideViaMeshFilter");
|
||||
maskInteraction = serializedObject.FindProperty("maskInteraction");
|
||||
|
||||
SerializedObject renderersSerializedObject = SpineInspectorUtility.GetRenderersSerializedObject(serializedObject); // Allows proper multi-edit behavior.
|
||||
sortingProperties = new SpineInspectorUtility.SerializedSortingProperties(renderersSerializedObject);
|
||||
}
|
||||
|
||||
protected override void InspectorDrawPreparation () {
|
||||
#if BUILT_IN_SPRITE_MASK_COMPONENT
|
||||
foreach (UnityEngine.Object t in targets)
|
||||
SpineMaskUtilities.EditorSetupSpriteMaskMaterials((SkeletonRenderer)t);
|
||||
#endif
|
||||
}
|
||||
|
||||
protected override void FirstPropertyFields () {
|
||||
EditorGUILayout.Space();
|
||||
|
||||
SpineInspectorUtility.SortingPropertyFields(sortingProperties, applyModifiedProperties: true);
|
||||
if (maskInteraction != null) EditorGUILayout.PropertyField(maskInteraction, MaskInteractionLabel);
|
||||
}
|
||||
|
||||
protected override void VertexDataProperties () {
|
||||
EditorGUILayout.PropertyField(pmaVertexColors, PMAVertexColorsLabel);
|
||||
EditorGUILayout.PropertyField(tintBlack, TintBlackLabel);
|
||||
EditorGUILayout.PropertyField(addNormals, AddNormalsLabel);
|
||||
EditorGUILayout.PropertyField(calculateTangents, CalculateTangentsLabel);
|
||||
}
|
||||
|
||||
protected override void RendererProperties () {
|
||||
base.RendererProperties();
|
||||
|
||||
if (singleSubmesh != null) EditorGUILayout.PropertyField(singleSubmesh, SingleSubmeshLabel);
|
||||
|
||||
#if HAS_ON_POSTPROCESS_PREFAB
|
||||
if (fixPrefabOverrideViaMeshFilter != null) EditorGUILayout.PropertyField(fixPrefabOverrideViaMeshFilter, FixPrefabOverrideViaMeshFilterLabel);
|
||||
EditorGUILayout.Space();
|
||||
#endif
|
||||
}
|
||||
|
||||
protected override void MaterialWarningsBox () {
|
||||
string errorMessage = null;
|
||||
if (SpineEditorUtilities.Preferences.componentMaterialWarning &&
|
||||
MaterialChecks.IsMaterialSetupProblematic((SkeletonRenderer)this.target, ref errorMessage)) {
|
||||
EditorGUILayout.HelpBox(errorMessage, MessageType.Error, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d0fc5db9788bce4418ad3252d43faa8a
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,148 @@
|
||||
/******************************************************************************
|
||||
* Spine Runtimes License Agreement
|
||||
* Last updated April 5, 2025. Replaces all prior versions.
|
||||
*
|
||||
* Copyright (c) 2013-2026, Esoteric Software LLC
|
||||
*
|
||||
* Integration of the Spine Runtimes into software or otherwise creating
|
||||
* derivative works of the Spine Runtimes is permitted under the terms and
|
||||
* conditions of Section 2 of the Spine Editor License Agreement:
|
||||
* http://esotericsoftware.com/spine-editor-license
|
||||
*
|
||||
* Otherwise, it is permitted to integrate the Spine Runtimes into software
|
||||
* or otherwise create derivative works of the Spine Runtimes (collectively,
|
||||
* "Products"), provided that each user of the Products must obtain their own
|
||||
* Spine Editor license and redistribution of the Products in any form must
|
||||
* include this license and copyright notice.
|
||||
*
|
||||
* THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY
|
||||
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY
|
||||
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES,
|
||||
* BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND
|
||||
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
||||
* THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*****************************************************************************/
|
||||
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Spine.Unity.Editor {
|
||||
[CustomEditor(typeof(SkeletonRootMotionBase))]
|
||||
[CanEditMultipleObjects]
|
||||
public class SkeletonRootMotionBaseInspector : UnityEditor.Editor {
|
||||
protected SerializedProperty rootMotionBoneName;
|
||||
protected SerializedProperty transformPositionX;
|
||||
protected SerializedProperty transformPositionY;
|
||||
protected SerializedProperty transformRotation;
|
||||
protected SerializedProperty rootMotionScaleX;
|
||||
protected SerializedProperty rootMotionScaleY;
|
||||
protected SerializedProperty rootMotionScaleRotation;
|
||||
protected SerializedProperty rootMotionTranslateXPerY;
|
||||
protected SerializedProperty rootMotionTranslateYPerX;
|
||||
protected SerializedProperty rigidBody2D;
|
||||
protected SerializedProperty applyRigidbody2DGravity;
|
||||
protected SerializedProperty rigidBody;
|
||||
|
||||
protected GUIContent rootMotionBoneNameLabel;
|
||||
protected GUIContent transformPositionXLabel;
|
||||
protected GUIContent transformPositionYLabel;
|
||||
protected GUIContent transformRotationLabel;
|
||||
protected GUIContent rootMotionScaleXLabel;
|
||||
protected GUIContent rootMotionScaleYLabel;
|
||||
protected GUIContent rootMotionScaleRotationLabel;
|
||||
protected GUIContent rootMotionTranslateXPerYLabel;
|
||||
protected GUIContent rootMotionTranslateYPerXLabel;
|
||||
protected GUIContent rigidBody2DLabel;
|
||||
protected GUIContent applyRigidbody2DGravityLabel;
|
||||
protected GUIContent rigidBodyLabel;
|
||||
|
||||
protected virtual void OnEnable () {
|
||||
|
||||
rootMotionBoneName = serializedObject.FindProperty("rootMotionBoneName");
|
||||
transformPositionX = serializedObject.FindProperty("transformPositionX");
|
||||
transformPositionY = serializedObject.FindProperty("transformPositionY");
|
||||
transformRotation = serializedObject.FindProperty("transformRotation");
|
||||
rootMotionScaleX = serializedObject.FindProperty("rootMotionScaleX");
|
||||
rootMotionScaleY = serializedObject.FindProperty("rootMotionScaleY");
|
||||
rootMotionScaleRotation = serializedObject.FindProperty("rootMotionScaleRotation");
|
||||
rootMotionTranslateXPerY = serializedObject.FindProperty("rootMotionTranslateXPerY");
|
||||
rootMotionTranslateYPerX = serializedObject.FindProperty("rootMotionTranslateYPerX");
|
||||
rigidBody2D = serializedObject.FindProperty("rigidBody2D");
|
||||
applyRigidbody2DGravity = serializedObject.FindProperty("applyRigidbody2DGravity");
|
||||
rigidBody = serializedObject.FindProperty("rigidBody");
|
||||
|
||||
rootMotionBoneNameLabel = new UnityEngine.GUIContent("Root Motion Bone", "The bone to take the motion from.");
|
||||
transformPositionXLabel = new UnityEngine.GUIContent("X", "Root transform position (X)");
|
||||
transformPositionYLabel = new UnityEngine.GUIContent("Y", "Use the Y-movement of the bone.");
|
||||
transformRotationLabel = new UnityEngine.GUIContent("Rotation", "Use the rotation of the bone.");
|
||||
rootMotionScaleXLabel = new UnityEngine.GUIContent("Root Motion Scale (X)", "Scale applied to the horizontal root motion delta. Can be used for delta compensation to e.g. stretch a jump to the desired distance.");
|
||||
rootMotionScaleYLabel = new UnityEngine.GUIContent("Root Motion Scale (Y)", "Scale applied to the vertical root motion delta. Can be used for delta compensation to e.g. stretch a jump to the desired distance.");
|
||||
rootMotionScaleRotationLabel = new UnityEngine.GUIContent("Root Motion Scale (Rotation)", "Scale applied to the rotational root motion delta. Can be used for delta compensation to e.g. adjust an angled jump landing to the desired platform angle.");
|
||||
rootMotionTranslateXPerYLabel = new UnityEngine.GUIContent("Root Motion Translate (X)", "Added X translation per root motion Y delta. Can be used for delta compensation when scaling is not enough, to e.g. offset a horizontal jump to a vertically different goal.");
|
||||
rootMotionTranslateYPerXLabel = new UnityEngine.GUIContent("Root Motion Translate (Y)", "Added Y translation per root motion X delta. Can be used for delta compensation when scaling is not enough, to e.g. offset a horizontal jump to a vertically different goal.");
|
||||
rigidBody2DLabel = new UnityEngine.GUIContent("Rigidbody2D",
|
||||
"Optional Rigidbody2D: Assign a Rigidbody2D here if you want " +
|
||||
" to apply the root motion to the rigidbody instead of the Transform." +
|
||||
"\n\n" +
|
||||
"Note that animation and physics updates are not always in sync." +
|
||||
"Some jitter may result at certain framerates.");
|
||||
applyRigidbody2DGravityLabel = new UnityEngine.GUIContent("Apply Gravity",
|
||||
"Apply Rigidbody2D Gravity");
|
||||
rigidBodyLabel = new UnityEngine.GUIContent("Rigidbody",
|
||||
"Optional Rigidbody: Assign a Rigidbody here if you want " +
|
||||
" to apply the root motion to the rigidbody instead of the Transform." +
|
||||
"\n\n" +
|
||||
"Note that animation and physics updates are not always in sync." +
|
||||
"Some jitter may result at certain framerates.");
|
||||
}
|
||||
|
||||
public override void OnInspectorGUI () {
|
||||
MainPropertyFields();
|
||||
OptionalPropertyFields();
|
||||
serializedObject.ApplyModifiedProperties();
|
||||
}
|
||||
|
||||
protected virtual void MainPropertyFields () {
|
||||
EditorGUILayout.PropertyField(rootMotionBoneName, rootMotionBoneNameLabel);
|
||||
EditorGUILayout.PropertyField(transformPositionX, transformPositionXLabel);
|
||||
EditorGUILayout.PropertyField(transformPositionY, transformPositionYLabel);
|
||||
EditorGUILayout.PropertyField(transformRotation, transformRotationLabel);
|
||||
|
||||
EditorGUILayout.PropertyField(rootMotionScaleX, rootMotionScaleXLabel);
|
||||
EditorGUILayout.PropertyField(rootMotionScaleY, rootMotionScaleYLabel);
|
||||
EditorGUILayout.PropertyField(rootMotionScaleRotation, rootMotionScaleRotationLabel);
|
||||
|
||||
EditorGUILayout.PropertyField(rootMotionTranslateXPerY, rootMotionTranslateXPerYLabel);
|
||||
EditorGUILayout.PropertyField(rootMotionTranslateYPerX, rootMotionTranslateYPerXLabel);
|
||||
}
|
||||
|
||||
protected virtual void OptionalPropertyFields () {
|
||||
EditorGUILayout.PropertyField(rigidBody2D, rigidBody2DLabel);
|
||||
|
||||
if (rigidBody2D.objectReferenceValue != null || rigidBody2D.hasMultipleDifferentValues) {
|
||||
using (new SpineInspectorUtility.IndentScope())
|
||||
EditorGUILayout.PropertyField(applyRigidbody2DGravity, applyRigidbody2DGravityLabel);
|
||||
}
|
||||
|
||||
EditorGUILayout.PropertyField(rigidBody, rigidBodyLabel);
|
||||
DisplayWarnings();
|
||||
}
|
||||
|
||||
protected void DisplayWarnings () {
|
||||
bool usesRigidbodyPhysics = rigidBody.objectReferenceValue != null || rigidBody2D.objectReferenceValue != null;
|
||||
if (usesRigidbodyPhysics) {
|
||||
SkeletonRootMotionBase rootMotionComponent = (SkeletonRootMotionBase)serializedObject.targetObject;
|
||||
ISkeletonAnimation skeletonComponent = rootMotionComponent ? rootMotionComponent.TargetSkeletonAnimationComponent : null;
|
||||
if (skeletonComponent != null && skeletonComponent.UpdateTiming == UpdateTiming.InUpdate) {
|
||||
string warningMessage = "Skeleton component uses 'Advanced - Animation Update' mode 'In Update'.\n" +
|
||||
"When using a Rigidbody, 'In FixedUpdate' is recommended instead.";
|
||||
EditorGUILayout.HelpBox(warningMessage, MessageType.Warning, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f2cba83baf6afdf44a996e40017c6325
|
||||
timeCreated: 1593175106
|
||||
licenseType: Pro
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,79 @@
|
||||
/******************************************************************************
|
||||
* Spine Runtimes License Agreement
|
||||
* Last updated April 5, 2025. Replaces all prior versions.
|
||||
*
|
||||
* Copyright (c) 2013-2026, Esoteric Software LLC
|
||||
*
|
||||
* Integration of the Spine Runtimes into software or otherwise creating
|
||||
* derivative works of the Spine Runtimes is permitted under the terms and
|
||||
* conditions of Section 2 of the Spine Editor License Agreement:
|
||||
* http://esotericsoftware.com/spine-editor-license
|
||||
*
|
||||
* Otherwise, it is permitted to integrate the Spine Runtimes into software
|
||||
* or otherwise create derivative works of the Spine Runtimes (collectively,
|
||||
* "Products"), provided that each user of the Products must obtain their own
|
||||
* Spine Editor license and redistribution of the Products in any form must
|
||||
* include this license and copyright notice.
|
||||
*
|
||||
* THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY
|
||||
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY
|
||||
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES,
|
||||
* BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND
|
||||
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
||||
* THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*****************************************************************************/
|
||||
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Spine.Unity.Editor {
|
||||
[CustomEditor(typeof(SkeletonRootMotion))]
|
||||
[CanEditMultipleObjects]
|
||||
public class SkeletonRootMotionInspector : SkeletonRootMotionBaseInspector {
|
||||
protected SerializedProperty animationTrackFlags;
|
||||
protected GUIContent animationTrackFlagsLabel;
|
||||
|
||||
string[] TrackNames;
|
||||
|
||||
protected override void OnEnable () {
|
||||
base.OnEnable();
|
||||
|
||||
animationTrackFlags = serializedObject.FindProperty("animationTrackFlags");
|
||||
animationTrackFlagsLabel = new UnityEngine.GUIContent("Animation Tracks",
|
||||
"Animation tracks to apply root motion at. Defaults to the first" +
|
||||
" animation track (index 0).");
|
||||
}
|
||||
|
||||
override public void OnInspectorGUI () {
|
||||
|
||||
base.MainPropertyFields();
|
||||
AnimationTracksPropertyField();
|
||||
|
||||
base.OptionalPropertyFields();
|
||||
serializedObject.ApplyModifiedProperties();
|
||||
}
|
||||
|
||||
protected void AnimationTracksPropertyField () {
|
||||
|
||||
if (TrackNames == null) {
|
||||
InitTrackNames();
|
||||
|
||||
}
|
||||
|
||||
animationTrackFlags.intValue = EditorGUILayout.MaskField(
|
||||
animationTrackFlagsLabel, animationTrackFlags.intValue, TrackNames);
|
||||
}
|
||||
|
||||
protected void InitTrackNames () {
|
||||
int numEntries = 32;
|
||||
TrackNames = new string[numEntries];
|
||||
for (int i = 0; i < numEntries; ++i) {
|
||||
TrackNames[i] = string.Format("Track {0}", i);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e4836100aed984c4a9af11d39c63cb6b
|
||||
timeCreated: 1593183609
|
||||
licenseType: Pro
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,43 @@
|
||||
/******************************************************************************
|
||||
* Spine Runtimes License Agreement
|
||||
* Last updated April 5, 2025. Replaces all prior versions.
|
||||
*
|
||||
* Copyright (c) 2013-2026, Esoteric Software LLC
|
||||
*
|
||||
* Integration of the Spine Runtimes into software or otherwise creating
|
||||
* derivative works of the Spine Runtimes is permitted under the terms and
|
||||
* conditions of Section 2 of the Spine Editor License Agreement:
|
||||
* http://esotericsoftware.com/spine-editor-license
|
||||
*
|
||||
* Otherwise, it is permitted to integrate the Spine Runtimes into software
|
||||
* or otherwise create derivative works of the Spine Runtimes (collectively,
|
||||
* "Products"), provided that each user of the Products must obtain their own
|
||||
* Spine Editor license and redistribution of the Products in any form must
|
||||
* include this license and copyright notice.
|
||||
*
|
||||
* THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY
|
||||
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY
|
||||
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES,
|
||||
* BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND
|
||||
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
||||
* THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*****************************************************************************/
|
||||
|
||||
using UnityEditor;
|
||||
|
||||
namespace Spine.Unity.Editor {
|
||||
using Icons = SpineEditorUtilities.Icons;
|
||||
|
||||
[CustomEditor(typeof(SkeletonSubmeshGraphic))]
|
||||
[CanEditMultipleObjects]
|
||||
public class SkeletonGraphicSubmeshInspector : UnityEditor.Editor {
|
||||
|
||||
public override void OnInspectorGUI () {
|
||||
EditorGUILayout.HelpBox("This component is manged by the parent SkeletonGraphic component.", MessageType.None);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 1bc35530b2335ef4da1dafa6214b6ccd
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,606 @@
|
||||
/******************************************************************************
|
||||
* Spine Runtimes License Agreement
|
||||
* Last updated April 5, 2025. Replaces all prior versions.
|
||||
*
|
||||
* Copyright (c) 2013-2026, Esoteric Software LLC
|
||||
*
|
||||
* Integration of the Spine Runtimes into software or otherwise creating
|
||||
* derivative works of the Spine Runtimes is permitted under the terms and
|
||||
* conditions of Section 2 of the Spine Editor License Agreement:
|
||||
* http://esotericsoftware.com/spine-editor-license
|
||||
*
|
||||
* Otherwise, it is permitted to integrate the Spine Runtimes into software
|
||||
* or otherwise create derivative works of the Spine Runtimes (collectively,
|
||||
* "Products"), provided that each user of the Products must obtain their own
|
||||
* Spine Editor license and redistribution of the Products in any form must
|
||||
* include this license and copyright notice.
|
||||
*
|
||||
* THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY
|
||||
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY
|
||||
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES,
|
||||
* BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND
|
||||
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
||||
* THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*****************************************************************************/
|
||||
|
||||
// Contributed by: Mitch Thompson
|
||||
|
||||
#if UNITY_2019_2_OR_NEWER
|
||||
#define HINGE_JOINT_NEW_BEHAVIOUR
|
||||
#endif
|
||||
|
||||
#if UNITY_6000_0_OR_NEWER
|
||||
#define USE_RIGIDBODY_BODY_TYPE
|
||||
#endif
|
||||
|
||||
using Spine;
|
||||
using System.Collections.Generic;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Spine.Unity.Editor {
|
||||
using Icons = SpineEditorUtilities.Icons;
|
||||
|
||||
[CustomEditor(typeof(SkeletonUtilityBone)), CanEditMultipleObjects]
|
||||
public class SkeletonUtilityBoneInspector : UnityEditor.Editor {
|
||||
SerializedProperty mode, boneName, zPosition, position, rotation, scale, overrideAlpha, hierarchy, parentReference;
|
||||
GUIContent hierarchyLabel;
|
||||
|
||||
//multi selected flags
|
||||
bool containsFollows, containsOverrides, multiObject;
|
||||
|
||||
//single selected helpers
|
||||
SkeletonUtilityBone utilityBone;
|
||||
SkeletonUtility skeletonUtility;
|
||||
bool canCreateHingeChain = false;
|
||||
|
||||
Dictionary<Slot, List<BoundingBoxAttachment>> boundingBoxTable = new Dictionary<Slot, List<BoundingBoxAttachment>>();
|
||||
|
||||
void OnEnable () {
|
||||
mode = this.serializedObject.FindProperty("boneMode");
|
||||
boneName = this.serializedObject.FindProperty("boneName");
|
||||
zPosition = this.serializedObject.FindProperty("zPosition");
|
||||
position = this.serializedObject.FindProperty("position");
|
||||
rotation = this.serializedObject.FindProperty("rotation");
|
||||
scale = this.serializedObject.FindProperty("scale");
|
||||
overrideAlpha = this.serializedObject.FindProperty("overrideAlpha");
|
||||
hierarchy = this.serializedObject.FindProperty("hierarchy");
|
||||
hierarchyLabel = new GUIContent("Skeleton Utility Parent");
|
||||
parentReference = this.serializedObject.FindProperty("parentReference");
|
||||
|
||||
utilityBone = (SkeletonUtilityBone)target;
|
||||
skeletonUtility = utilityBone.hierarchy;
|
||||
EvaluateFlags();
|
||||
|
||||
if (!utilityBone.valid && skeletonUtility != null) {
|
||||
if (skeletonUtility.skeletonRenderer != null)
|
||||
skeletonUtility.skeletonRenderer.Initialize(false);
|
||||
if (skeletonUtility.skeletonGraphic != null)
|
||||
skeletonUtility.skeletonGraphic.Initialize(false);
|
||||
}
|
||||
|
||||
canCreateHingeChain = CanCreateHingeChain();
|
||||
boundingBoxTable.Clear();
|
||||
|
||||
if (multiObject) return;
|
||||
if (utilityBone.bone == null) return;
|
||||
|
||||
Skeleton skeleton = skeletonUtility.SkeletonComponent.Skeleton;
|
||||
int slotCount = skeleton.Slots.Count;
|
||||
Skin skin = skeleton.Skin;
|
||||
if (skeleton.Skin == null)
|
||||
skin = skeleton.Data.DefaultSkin;
|
||||
|
||||
for (int i = 0; i < slotCount; i++) {
|
||||
Slot slot = skeletonUtility.Skeleton.Slots.Items[i];
|
||||
if (slot.Bone == utilityBone.bone) {
|
||||
List<Skin.SkinEntry> slotAttachments = new List<Skin.SkinEntry>();
|
||||
int slotIndex = skeleton.Data.FindSlot(slot.Data.Name).Index;
|
||||
skin.GetAttachments(slotIndex, slotAttachments);
|
||||
|
||||
List<BoundingBoxAttachment> boundingBoxes = new List<BoundingBoxAttachment>();
|
||||
foreach (Skin.SkinEntry entry in slotAttachments) {
|
||||
BoundingBoxAttachment boundingBoxAttachment = entry.Attachment as BoundingBoxAttachment;
|
||||
if (boundingBoxAttachment != null)
|
||||
boundingBoxes.Add(boundingBoxAttachment);
|
||||
}
|
||||
|
||||
if (boundingBoxes.Count > 0)
|
||||
boundingBoxTable.Add(slot, boundingBoxes);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void EvaluateFlags () {
|
||||
if (Selection.objects.Length == 1) {
|
||||
containsFollows = utilityBone.mode == SkeletonUtilityBone.Mode.Follow;
|
||||
containsOverrides = utilityBone.mode == SkeletonUtilityBone.Mode.Override;
|
||||
} else {
|
||||
int boneCount = 0;
|
||||
foreach (Object o in Selection.objects) {
|
||||
GameObject go = o as GameObject;
|
||||
if (go != null) {
|
||||
SkeletonUtilityBone sub = go.GetComponent<SkeletonUtilityBone>();
|
||||
if (sub != null) {
|
||||
boneCount++;
|
||||
containsFollows |= (sub.mode == SkeletonUtilityBone.Mode.Follow);
|
||||
containsOverrides |= (sub.mode == SkeletonUtilityBone.Mode.Override);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
multiObject |= (boneCount > 1);
|
||||
}
|
||||
}
|
||||
|
||||
public override void OnInspectorGUI () {
|
||||
serializedObject.Update();
|
||||
|
||||
EditorGUI.BeginChangeCheck();
|
||||
EditorGUILayout.PropertyField(mode);
|
||||
if (EditorGUI.EndChangeCheck()) {
|
||||
containsOverrides = mode.enumValueIndex == 1;
|
||||
containsFollows = mode.enumValueIndex == 0;
|
||||
if (skeletonUtility != null)
|
||||
skeletonUtility.OnUtilityBoneChanged();
|
||||
}
|
||||
|
||||
using (new EditorGUI.DisabledGroupScope(multiObject)) {
|
||||
string str = boneName.stringValue;
|
||||
if (str == "")
|
||||
str = "<None>";
|
||||
if (multiObject)
|
||||
str = "<Multiple>";
|
||||
|
||||
using (new GUILayout.HorizontalScope()) {
|
||||
EditorGUILayout.PrefixLabel("Bone");
|
||||
if (GUILayout.Button(str, EditorStyles.popup)) {
|
||||
BoneSelectorContextMenu(str, ((SkeletonUtilityBone)target).hierarchy.Skeleton.Bones, "<None>", TargetBoneSelected);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool isOverrideMode = mode.enumValueIndex == 1;
|
||||
using (new EditorGUI.DisabledGroupScope(isOverrideMode))
|
||||
EditorGUILayout.PropertyField(zPosition);
|
||||
EditorGUILayout.PropertyField(position, new GUIContent("XY Position"));
|
||||
EditorGUILayout.PropertyField(rotation);
|
||||
EditorGUILayout.PropertyField(scale);
|
||||
|
||||
using (new EditorGUI.DisabledGroupScope(containsFollows)) {
|
||||
EditorGUILayout.PropertyField(overrideAlpha);
|
||||
EditorGUILayout.PropertyField(parentReference);
|
||||
EditorGUILayout.PropertyField(hierarchy, hierarchyLabel);
|
||||
}
|
||||
|
||||
EditorGUILayout.Space();
|
||||
|
||||
using (new GUILayout.HorizontalScope()) {
|
||||
EditorGUILayout.Space();
|
||||
using (new EditorGUI.DisabledGroupScope(multiObject || !utilityBone.valid || utilityBone.bone == null || utilityBone.bone.Children.Count == 0)) {
|
||||
if (GUILayout.Button(SpineInspectorUtility.TempContent("Add Child Bone", Icons.bone), GUILayout.MinWidth(120), GUILayout.Height(24)))
|
||||
BoneSelectorContextMenu("", utilityBone.bone.Children, "<Recursively>", SpawnChildBoneSelected);
|
||||
}
|
||||
using (new EditorGUI.DisabledGroupScope(multiObject || !utilityBone.valid || utilityBone.bone == null || containsOverrides)) {
|
||||
if (GUILayout.Button(SpineInspectorUtility.TempContent("Add Override", Icons.poseBones), GUILayout.MinWidth(120), GUILayout.Height(24)))
|
||||
SpawnOverride();
|
||||
}
|
||||
EditorGUILayout.Space();
|
||||
}
|
||||
EditorGUILayout.Space();
|
||||
using (new GUILayout.HorizontalScope()) {
|
||||
EditorGUILayout.Space();
|
||||
using (new EditorGUI.DisabledGroupScope(multiObject || !utilityBone.valid || !canCreateHingeChain)) {
|
||||
if (GUILayout.Button(SpineInspectorUtility.TempContent("Create 3D Hinge Chain", Icons.hingeChain), GUILayout.MinWidth(120), GUILayout.Height(24)))
|
||||
CreateHingeChain();
|
||||
if (GUILayout.Button(SpineInspectorUtility.TempContent("Create 2D Hinge Chain", Icons.hingeChain), GUILayout.MinWidth(120), GUILayout.Height(24)))
|
||||
CreateHingeChain2D();
|
||||
}
|
||||
EditorGUILayout.Space();
|
||||
}
|
||||
|
||||
using (new EditorGUI.DisabledGroupScope(multiObject || boundingBoxTable.Count == 0)) {
|
||||
EditorGUILayout.LabelField(SpineInspectorUtility.TempContent("Bounding Boxes", Icons.boundingBox), EditorStyles.boldLabel);
|
||||
|
||||
foreach (KeyValuePair<Slot, List<BoundingBoxAttachment>> entry in boundingBoxTable) {
|
||||
Slot slot = entry.Key;
|
||||
List<BoundingBoxAttachment> boundingBoxes = entry.Value;
|
||||
|
||||
EditorGUI.indentLevel++;
|
||||
EditorGUILayout.LabelField(slot.Data.Name);
|
||||
EditorGUI.indentLevel++;
|
||||
{
|
||||
Skeleton skeleton = skeletonUtility.SkeletonComponent.Skeleton;
|
||||
foreach (BoundingBoxAttachment box in boundingBoxes) {
|
||||
using (new GUILayout.HorizontalScope()) {
|
||||
GUILayout.Space(30);
|
||||
string buttonLabel = box.IsWeighted() ? box.Name + " (!)" : box.Name;
|
||||
if (GUILayout.Button(buttonLabel, GUILayout.Width(200))) {
|
||||
skeleton.UpdateWorldTransform(Physics.Update);
|
||||
Transform bbTransform = utilityBone.transform.Find("[BoundingBox]" + box.Name); // Use FindChild in older versions of Unity.
|
||||
if (bbTransform != null) {
|
||||
PolygonCollider2D originalCollider = bbTransform.GetComponent<PolygonCollider2D>();
|
||||
if (originalCollider != null)
|
||||
SkeletonUtility.SetColliderPointsLocal(originalCollider, skeleton, slot, box);
|
||||
else
|
||||
SkeletonUtility.AddBoundingBoxAsComponent(box, skeleton, slot, bbTransform.gameObject);
|
||||
} else {
|
||||
PolygonCollider2D newPolygonCollider = SkeletonUtility.AddBoundingBoxGameObject(null, box, skeleton, slot, utilityBone.transform);
|
||||
bbTransform = newPolygonCollider.transform;
|
||||
}
|
||||
EditorGUIUtility.PingObject(bbTransform);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
EditorGUI.indentLevel--;
|
||||
EditorGUI.indentLevel--;
|
||||
}
|
||||
}
|
||||
|
||||
BoneFollowerInspector.RecommendRigidbodyButton(utilityBone);
|
||||
|
||||
serializedObject.ApplyModifiedProperties();
|
||||
}
|
||||
|
||||
static void BoneSelectorContextMenu (string current, ExposedList<Bone> bones, string topValue, GenericMenu.MenuFunction2 callback) {
|
||||
GenericMenu menu = new GenericMenu();
|
||||
|
||||
if (topValue != "")
|
||||
menu.AddItem(new GUIContent(topValue), current == topValue, callback, null);
|
||||
|
||||
for (int i = 0; i < bones.Count; i++)
|
||||
menu.AddItem(new GUIContent(bones.Items[i].Data.Name), bones.Items[i].Data.Name == current, callback, bones.Items[i]);
|
||||
|
||||
menu.ShowAsContext();
|
||||
}
|
||||
|
||||
void TargetBoneSelected (object obj) {
|
||||
if (obj == null) {
|
||||
boneName.stringValue = "";
|
||||
serializedObject.ApplyModifiedProperties();
|
||||
} else {
|
||||
Bone bone = (Bone)obj;
|
||||
boneName.stringValue = bone.Data.Name;
|
||||
serializedObject.ApplyModifiedProperties();
|
||||
utilityBone.Reset();
|
||||
}
|
||||
}
|
||||
|
||||
void SpawnChildBoneSelected (object obj) {
|
||||
if (obj == null) {
|
||||
// Add recursively
|
||||
foreach (Bone bone in utilityBone.bone.Children) {
|
||||
GameObject go = skeletonUtility.SpawnBoneRecursively(bone, utilityBone.transform, utilityBone.mode, utilityBone.position, utilityBone.rotation, utilityBone.scale);
|
||||
SkeletonUtilityBone[] newUtilityBones = go.GetComponentsInChildren<SkeletonUtilityBone>();
|
||||
foreach (SkeletonUtilityBone utilBone in newUtilityBones)
|
||||
SkeletonUtilityInspector.AttachIcon(utilBone);
|
||||
}
|
||||
} else {
|
||||
Bone bone = (Bone)obj;
|
||||
GameObject go = skeletonUtility.SpawnBone(bone, utilityBone.transform, utilityBone.mode, utilityBone.position, utilityBone.rotation, utilityBone.scale);
|
||||
SkeletonUtilityInspector.AttachIcon(go.GetComponent<SkeletonUtilityBone>());
|
||||
Selection.activeGameObject = go;
|
||||
EditorGUIUtility.PingObject(go);
|
||||
}
|
||||
}
|
||||
|
||||
void SpawnOverride () {
|
||||
GameObject go = skeletonUtility.SpawnBone(utilityBone.bone, utilityBone.transform.parent, SkeletonUtilityBone.Mode.Override, utilityBone.position, utilityBone.rotation, utilityBone.scale);
|
||||
go.name = go.name + " [Override]";
|
||||
SkeletonUtilityInspector.AttachIcon(go.GetComponent<SkeletonUtilityBone>());
|
||||
Selection.activeGameObject = go;
|
||||
EditorGUIUtility.PingObject(go);
|
||||
}
|
||||
|
||||
bool CanCreateHingeChain () {
|
||||
if (utilityBone == null)
|
||||
return false;
|
||||
if (utilityBone.GetComponent<Rigidbody>() != null || utilityBone.GetComponent<Rigidbody2D>() != null)
|
||||
return false;
|
||||
if (utilityBone.bone != null && utilityBone.bone.Children.Count == 0)
|
||||
return false;
|
||||
|
||||
Rigidbody[] rigidbodies = utilityBone.GetComponentsInChildren<Rigidbody>();
|
||||
Rigidbody2D[] rigidbodies2D = utilityBone.GetComponentsInChildren<Rigidbody2D>();
|
||||
return rigidbodies.Length <= 0 && rigidbodies2D.Length <= 0;
|
||||
}
|
||||
|
||||
void CreateHingeChain2D () {
|
||||
SkeletonUtilityBone kinematicParentUtilityBone = utilityBone.transform.parent.GetComponent<SkeletonUtilityBone>();
|
||||
if (kinematicParentUtilityBone == null) {
|
||||
UnityEditor.EditorUtility.DisplayDialog("No parent SkeletonUtilityBone found!", "Please select the first physically moving chain node, having a parent GameObject with a SkeletonUtilityBone component attached.", "OK");
|
||||
return;
|
||||
}
|
||||
|
||||
int undoGroup = Undo.GetCurrentGroup();
|
||||
Undo.SetCurrentGroupName("Create 2D Hinge Chain");
|
||||
|
||||
float mass = 10;
|
||||
const float rotationLimit = 20.0f;
|
||||
|
||||
SetSkeletonUtilityToFlipByRotation();
|
||||
|
||||
Undo.RecordObject(kinematicParentUtilityBone, "Create 2D Hinge Chain");
|
||||
kinematicParentUtilityBone.mode = SkeletonUtilityBone.Mode.Follow;
|
||||
kinematicParentUtilityBone.position = kinematicParentUtilityBone.rotation = kinematicParentUtilityBone.scale = kinematicParentUtilityBone.zPosition = true;
|
||||
|
||||
GameObject commonParentObject = new GameObject(skeletonUtility.name + " HingeChain Parent " + utilityBone.name);
|
||||
Undo.RegisterCreatedObjectUndo(commonParentObject, "Create 2D Hinge Chain");
|
||||
ActivateBasedOnFlipDirection commonParentActivateOnFlip = commonParentObject.AddComponent<ActivateBasedOnFlipDirection>();
|
||||
commonParentActivateOnFlip.skeletonRenderer = skeletonUtility.skeletonRenderer;
|
||||
commonParentActivateOnFlip.skeletonGraphic = skeletonUtility.skeletonGraphic;
|
||||
|
||||
// HingeChain Parent
|
||||
// Needs to be on top hierarchy level (not attached to the moving skeleton at least) for physics to apply proper momentum.
|
||||
GameObject normalChainParentObject = new GameObject("HingeChain");
|
||||
normalChainParentObject.transform.SetParent(commonParentObject.transform);
|
||||
commonParentActivateOnFlip.activeOnNormalX = normalChainParentObject;
|
||||
|
||||
//FollowSkeletonUtilityRootRotation followRotationComponent = normalChainParentObject.AddComponent<FollowSkeletonUtilityRootRotation>();
|
||||
//followRotationComponent.reference = skeletonUtility.boneRoot;
|
||||
|
||||
// Follower Kinematic Rigidbody
|
||||
GameObject rootFollowerKinematic = new GameObject(kinematicParentUtilityBone.name + " Follower");
|
||||
rootFollowerKinematic.transform.parent = normalChainParentObject.transform;
|
||||
Rigidbody2D followerRigidbody = rootFollowerKinematic.AddComponent<Rigidbody2D>();
|
||||
followerRigidbody.mass = mass;
|
||||
#if USE_RIGIDBODY_BODY_TYPE
|
||||
followerRigidbody.bodyType = RigidbodyType2D.Kinematic;
|
||||
#else
|
||||
followerRigidbody.isKinematic = true;
|
||||
#endif
|
||||
rootFollowerKinematic.AddComponent<FollowLocationRigidbody2D>().reference = kinematicParentUtilityBone.transform;
|
||||
rootFollowerKinematic.transform.position = kinematicParentUtilityBone.transform.position;
|
||||
rootFollowerKinematic.transform.rotation = kinematicParentUtilityBone.transform.rotation;
|
||||
|
||||
CreateHingeChain2D(utilityBone, mass, rotationLimit, normalChainParentObject.transform,
|
||||
rootFollowerKinematic.transform, kinematicParentUtilityBone.transform);
|
||||
|
||||
Duplicate2DHierarchyForFlippedChains(normalChainParentObject, commonParentActivateOnFlip, skeletonUtility.transform, rotationLimit);
|
||||
Undo.CollapseUndoOperations(undoGroup);
|
||||
UnityEditor.Selection.activeGameObject = commonParentObject;
|
||||
}
|
||||
|
||||
void CreateHingeChain2D (SkeletonUtilityBone bone, float mass, float rotationLimit, Transform groupObject,
|
||||
Transform jointParent, Transform utilityParent) {
|
||||
|
||||
mass *= 0.75f;
|
||||
Undo.RecordObject(bone, "Create 2D Hinge Chain");
|
||||
bone.parentReference = utilityParent;
|
||||
// Note: we need a flat hierarchy of all Joint objects in Unity.
|
||||
Undo.SetTransformParent(bone.transform, groupObject, "Create 2D Hinge Chain");
|
||||
AttachRigidbodyAndCollider2D(bone);
|
||||
bone.mode = SkeletonUtilityBone.Mode.Override;
|
||||
bone.scale = bone.position = bone.zPosition = false;
|
||||
|
||||
HingeJoint2D joint = Undo.AddComponent<HingeJoint2D>(bone.gameObject);
|
||||
joint.connectedBody = jointParent.GetComponent<Rigidbody2D>();
|
||||
joint.useLimits = true;
|
||||
ApplyJoint2DAngleLimits(joint, rotationLimit, jointParent, bone.transform);
|
||||
bone.GetComponent<Rigidbody2D>().mass = mass;
|
||||
|
||||
Transform parent = bone.transform;
|
||||
List<SkeletonUtilityBone> children = new List<SkeletonUtilityBone>();
|
||||
int utilityChildCount = 0;
|
||||
for (int i = 0; i < parent.childCount; ++i) {
|
||||
var childUtilityBone = parent.GetChild(i).GetComponent<SkeletonUtilityBone>();
|
||||
if (childUtilityBone != null)
|
||||
children.Add(childUtilityBone);
|
||||
}
|
||||
mass /= Mathf.Max(1.0f, utilityChildCount);
|
||||
|
||||
for (int i = 0; i < children.Count; ++i) {
|
||||
SkeletonUtilityBone childBone = children[i];
|
||||
if (childBone == null) continue;
|
||||
CreateHingeChain2D(childBone, mass, rotationLimit, groupObject, parent, parent);
|
||||
}
|
||||
}
|
||||
|
||||
void ApplyJoint2DAngleLimits (HingeJoint2D joint, float rotationLimit, Transform parentBone, Transform bone) {
|
||||
#if HINGE_JOINT_NEW_BEHAVIOUR
|
||||
float referenceAngle = (parentBone.eulerAngles.z - bone.eulerAngles.z + 360f) % 360f;
|
||||
float minAngle = referenceAngle - rotationLimit;
|
||||
float maxAngle = referenceAngle + rotationLimit;
|
||||
if (maxAngle > 270f) {
|
||||
minAngle -= 360f;
|
||||
maxAngle -= 360f;
|
||||
}
|
||||
if (minAngle < -90f) {
|
||||
minAngle += 360f;
|
||||
maxAngle += 360f;
|
||||
}
|
||||
#else
|
||||
float minAngle = -rotationLimit;
|
||||
float maxAngle = rotationLimit;
|
||||
#endif
|
||||
joint.limits = new JointAngleLimits2D {
|
||||
min = minAngle,
|
||||
max = maxAngle
|
||||
};
|
||||
}
|
||||
|
||||
void Duplicate2DHierarchyForFlippedChains (GameObject normalChainParentObject, ActivateBasedOnFlipDirection commonParentActivateOnFlip,
|
||||
Transform skeletonUtilityRoot, float rotationLimit) {
|
||||
|
||||
GameObject mirroredChain = GameObject.Instantiate(normalChainParentObject, normalChainParentObject.transform.position,
|
||||
normalChainParentObject.transform.rotation, commonParentActivateOnFlip.transform);
|
||||
mirroredChain.name = normalChainParentObject.name + " FlippedX";
|
||||
|
||||
commonParentActivateOnFlip.activeOnFlippedX = mirroredChain;
|
||||
|
||||
FollowLocationRigidbody2D followerKinematicObject = mirroredChain.GetComponentInChildren<FollowLocationRigidbody2D>();
|
||||
followerKinematicObject.followFlippedX = true;
|
||||
FlipBone2DHorizontal(followerKinematicObject.transform, skeletonUtilityRoot);
|
||||
|
||||
HingeJoint2D[] childBoneJoints = mirroredChain.GetComponentsInChildren<HingeJoint2D>();
|
||||
Transform prevRotatedChild = null;
|
||||
Transform parentTransformForAngles = followerKinematicObject.transform;
|
||||
for (int i = 0; i < childBoneJoints.Length; ++i) {
|
||||
HingeJoint2D joint = childBoneJoints[i];
|
||||
FlipBone2DHorizontal(joint.transform, skeletonUtilityRoot);
|
||||
ApplyJoint2DAngleLimits(joint, rotationLimit, parentTransformForAngles, joint.transform);
|
||||
|
||||
GameObject rotatedChild = GameObject.Instantiate(joint.gameObject, joint.transform, true);
|
||||
rotatedChild.name = joint.name + " rotated";
|
||||
Vector3 rotationEulerAngles = rotatedChild.transform.localEulerAngles;
|
||||
rotationEulerAngles.x = 180;
|
||||
rotatedChild.transform.localEulerAngles = rotationEulerAngles;
|
||||
DestroyImmediate(rotatedChild.GetComponent<HingeJoint2D>());
|
||||
DestroyImmediate(rotatedChild.GetComponent<BoxCollider2D>());
|
||||
DestroyImmediate(rotatedChild.GetComponent<Rigidbody2D>());
|
||||
|
||||
DestroyImmediate(joint.gameObject.GetComponent<SkeletonUtilityBone>());
|
||||
|
||||
if (i > 0) {
|
||||
SkeletonUtilityBone utilityBone = rotatedChild.GetComponent<SkeletonUtilityBone>();
|
||||
utilityBone.parentReference = prevRotatedChild;
|
||||
}
|
||||
prevRotatedChild = rotatedChild.transform;
|
||||
parentTransformForAngles = joint.transform;
|
||||
}
|
||||
|
||||
mirroredChain.SetActive(false);
|
||||
}
|
||||
|
||||
void FlipBone2DHorizontal (Transform bone, Transform mirrorPosition) {
|
||||
Vector3 position = bone.position;
|
||||
position.x = 2 * mirrorPosition.position.x - position.x; // = mirrorPosition + (mirrorPosition - bone.position)
|
||||
bone.position = position;
|
||||
|
||||
Vector3 boneZ = bone.forward;
|
||||
Vector3 boneX = bone.right;
|
||||
boneX.x *= -1;
|
||||
|
||||
bone.rotation = Quaternion.LookRotation(boneZ, Vector3.Cross(boneZ, boneX));
|
||||
}
|
||||
|
||||
void CreateHingeChain () {
|
||||
SkeletonUtilityBone kinematicParentUtilityBone = utilityBone.transform.parent.GetComponent<SkeletonUtilityBone>();
|
||||
if (kinematicParentUtilityBone == null) {
|
||||
UnityEditor.EditorUtility.DisplayDialog("No parent SkeletonUtilityBone found!", "Please select the first physically moving chain node, having a parent GameObject with a SkeletonUtilityBone component attached.", "OK");
|
||||
return;
|
||||
}
|
||||
|
||||
int undoGroup = Undo.GetCurrentGroup();
|
||||
Undo.SetCurrentGroupName("Create 3D Hinge Chain");
|
||||
|
||||
float mass = 10;
|
||||
const float rotationLimit = 20.0f;
|
||||
|
||||
SetSkeletonUtilityToFlipByRotation();
|
||||
|
||||
Undo.RecordObject(kinematicParentUtilityBone, "Create 3D Hinge Chain");
|
||||
kinematicParentUtilityBone.mode = SkeletonUtilityBone.Mode.Follow;
|
||||
kinematicParentUtilityBone.position = kinematicParentUtilityBone.rotation = kinematicParentUtilityBone.scale = kinematicParentUtilityBone.zPosition = true;
|
||||
|
||||
// HingeChain Parent
|
||||
// Needs to be on top hierarchy level (not attached to the moving skeleton at least) for physics to apply proper momentum.
|
||||
GameObject chainParentObject = new GameObject(skeletonUtility.name + " HingeChain Parent " + utilityBone.name);
|
||||
Undo.RegisterCreatedObjectUndo(chainParentObject, "Create 3D Hinge Chain");
|
||||
FollowSkeletonUtilityRootRotation followRotationComponent = chainParentObject.AddComponent<FollowSkeletonUtilityRootRotation>();
|
||||
followRotationComponent.reference = skeletonUtility.boneRoot;
|
||||
|
||||
// Follower Kinematic Rigidbody
|
||||
GameObject rootFollowerKinematic = new GameObject(kinematicParentUtilityBone.name + " Follower");
|
||||
rootFollowerKinematic.transform.parent = chainParentObject.transform;
|
||||
Rigidbody followerRigidbody = rootFollowerKinematic.AddComponent<Rigidbody>();
|
||||
followerRigidbody.mass = mass;
|
||||
followerRigidbody.isKinematic = true;
|
||||
rootFollowerKinematic.AddComponent<FollowLocationRigidbody>().reference = kinematicParentUtilityBone.transform;
|
||||
rootFollowerKinematic.transform.position = kinematicParentUtilityBone.transform.position;
|
||||
rootFollowerKinematic.transform.rotation = kinematicParentUtilityBone.transform.rotation;
|
||||
|
||||
CreateHingeChain(utilityBone, mass, rotationLimit, chainParentObject.transform, rootFollowerKinematic.transform);
|
||||
|
||||
Undo.CollapseUndoOperations(undoGroup);
|
||||
UnityEditor.Selection.activeGameObject = chainParentObject;
|
||||
}
|
||||
|
||||
void CreateHingeChain (SkeletonUtilityBone bone, float mass, float rotationLimit, Transform groupObject,
|
||||
Transform jointParent) {
|
||||
|
||||
mass *= 0.75f;
|
||||
|
||||
Undo.RecordObject(bone, "Create 3D Hinge Chain");
|
||||
bone.parentReference = jointParent;
|
||||
// Note: we need a flat hierarchy of all Joint objects in Unity.
|
||||
Undo.SetTransformParent(bone.transform, groupObject.transform, "Create 3D Hinge Chain");
|
||||
AttachRigidbodyAndCollider(bone);
|
||||
bone.mode = SkeletonUtilityBone.Mode.Override;
|
||||
|
||||
HingeJoint joint = Undo.AddComponent<HingeJoint>(bone.gameObject);
|
||||
joint.axis = Vector3.forward;
|
||||
joint.connectedBody = jointParent.GetComponent<Rigidbody>();
|
||||
joint.useLimits = true;
|
||||
joint.limits = new JointLimits {
|
||||
min = -rotationLimit,
|
||||
max = rotationLimit
|
||||
};
|
||||
bone.GetComponent<Rigidbody>().mass = mass;
|
||||
|
||||
Transform parent = bone.transform;
|
||||
List<SkeletonUtilityBone> children = new List<SkeletonUtilityBone>();
|
||||
int utilityChildCount = 0;
|
||||
for (int i = 0; i < parent.childCount; ++i) {
|
||||
var childUtilityBone = parent.GetChild(i).GetComponent<SkeletonUtilityBone>();
|
||||
if (childUtilityBone != null)
|
||||
children.Add(childUtilityBone);
|
||||
}
|
||||
mass /= Mathf.Max(1.0f, utilityChildCount);
|
||||
|
||||
for (int i = 0; i < children.Count; ++i) {
|
||||
SkeletonUtilityBone childBone = children[i];
|
||||
if (childBone == null) continue;
|
||||
CreateHingeChain(childBone, mass, rotationLimit, groupObject, parent);
|
||||
}
|
||||
}
|
||||
|
||||
void SetSkeletonUtilityToFlipByRotation () {
|
||||
if (!skeletonUtility.flipBy180DegreeRotation) {
|
||||
Undo.RecordObject(skeletonUtility, "Create Hinge Chain");
|
||||
skeletonUtility.flipBy180DegreeRotation = true;
|
||||
Debug.Log("Set SkeletonUtility " + skeletonUtility.name + " to flip by rotation instead of negative scale (required).", skeletonUtility);
|
||||
}
|
||||
}
|
||||
|
||||
static void AttachRigidbodyAndCollider (SkeletonUtilityBone utilBone, bool enableCollider = false) {
|
||||
if (utilBone.GetComponent<Collider>() == null) {
|
||||
if (utilBone.bone.Data.Length == 0) {
|
||||
SphereCollider sphere = Undo.AddComponent<SphereCollider>(utilBone.gameObject);
|
||||
sphere.radius = 0.1f;
|
||||
sphere.enabled = enableCollider;
|
||||
} else {
|
||||
float length = utilBone.bone.Data.Length;
|
||||
BoxCollider box = Undo.AddComponent<BoxCollider>(utilBone.gameObject);
|
||||
box.size = new Vector3(length, length / 3f, 0.2f);
|
||||
box.center = new Vector3(length / 2f, 0, 0);
|
||||
box.enabled = enableCollider;
|
||||
}
|
||||
}
|
||||
Undo.AddComponent<Rigidbody>(utilBone.gameObject);
|
||||
}
|
||||
|
||||
static void AttachRigidbodyAndCollider2D (SkeletonUtilityBone utilBone, bool enableCollider = false) {
|
||||
if (utilBone.GetComponent<Collider2D>() == null) {
|
||||
if (utilBone.bone.Data.Length == 0) {
|
||||
CircleCollider2D sphere = Undo.AddComponent<CircleCollider2D>(utilBone.gameObject);
|
||||
sphere.radius = 0.1f;
|
||||
sphere.enabled = enableCollider;
|
||||
} else {
|
||||
float length = utilBone.bone.Data.Length;
|
||||
BoxCollider2D box = Undo.AddComponent<BoxCollider2D>(utilBone.gameObject);
|
||||
box.size = new Vector3(length, length / 3f, 0.2f);
|
||||
box.offset = new Vector3(length / 2f, 0, 0);
|
||||
box.enabled = enableCollider;
|
||||
}
|
||||
}
|
||||
Undo.AddComponent<Rigidbody2D>(utilBone.gameObject);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b3ae20b4bcc31f645afd6f5b64f82473
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,197 @@
|
||||
/******************************************************************************
|
||||
* Spine Runtimes License Agreement
|
||||
* Last updated April 5, 2025. Replaces all prior versions.
|
||||
*
|
||||
* Copyright (c) 2013-2026, Esoteric Software LLC
|
||||
*
|
||||
* Integration of the Spine Runtimes into software or otherwise creating
|
||||
* derivative works of the Spine Runtimes is permitted under the terms and
|
||||
* conditions of Section 2 of the Spine Editor License Agreement:
|
||||
* http://esotericsoftware.com/spine-editor-license
|
||||
*
|
||||
* Otherwise, it is permitted to integrate the Spine Runtimes into software
|
||||
* or otherwise create derivative works of the Spine Runtimes (collectively,
|
||||
* "Products"), provided that each user of the Products must obtain their own
|
||||
* Spine Editor license and redistribution of the Products in any form must
|
||||
* include this license and copyright notice.
|
||||
*
|
||||
* THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY
|
||||
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY
|
||||
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES,
|
||||
* BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND
|
||||
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
||||
* THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*****************************************************************************/
|
||||
|
||||
#if UNITY_2018_3 || UNITY_2019 || UNITY_2018_3_OR_NEWER
|
||||
#define NEW_PREFAB_SYSTEM
|
||||
#endif
|
||||
|
||||
#if UNITY_2021_2_OR_NEWER
|
||||
#define PUBLIC_SET_ICON_FOR_OBJECT
|
||||
#endif
|
||||
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Spine.Unity.Editor {
|
||||
using Icons = SpineEditorUtilities.Icons;
|
||||
|
||||
[CustomEditor(typeof(SkeletonUtility))]
|
||||
public class SkeletonUtilityInspector : UnityEditor.Editor {
|
||||
|
||||
SkeletonUtility skeletonUtility;
|
||||
Skeleton skeleton;
|
||||
SkeletonRenderer skeletonRenderer;
|
||||
SkeletonGraphic skeletonGraphic;
|
||||
|
||||
#if !NEW_PREFAB_SYSTEM
|
||||
bool isPrefab;
|
||||
#endif
|
||||
|
||||
readonly GUIContent SpawnHierarchyButtonLabel = new GUIContent("Spawn Hierarchy", Icons.skeleton);
|
||||
|
||||
void OnEnable () {
|
||||
skeletonUtility = (SkeletonUtility)target;
|
||||
skeletonRenderer = skeletonUtility.skeletonRenderer;
|
||||
skeletonGraphic = skeletonUtility.skeletonGraphic;
|
||||
skeleton = skeletonUtility.Skeleton;
|
||||
|
||||
if (skeleton == null) {
|
||||
if (skeletonRenderer != null) {
|
||||
skeletonRenderer.Initialize(false);
|
||||
skeletonRenderer.LateUpdate();
|
||||
} else if (skeletonGraphic != null) {
|
||||
skeletonGraphic.Initialize(false);
|
||||
skeletonGraphic.LateUpdate();
|
||||
}
|
||||
skeleton = skeletonUtility.Skeleton;
|
||||
}
|
||||
|
||||
if ((skeletonRenderer != null && !skeletonRenderer.valid) ||
|
||||
(skeletonGraphic != null && !skeletonGraphic.IsValid)) return;
|
||||
|
||||
#if !NEW_PREFAB_SYSTEM
|
||||
isPrefab |= PrefabUtility.GetPrefabType(this.target) == PrefabType.Prefab;
|
||||
#endif
|
||||
}
|
||||
|
||||
public override void OnInspectorGUI () {
|
||||
|
||||
#if !NEW_PREFAB_SYSTEM
|
||||
if (isPrefab) {
|
||||
GUILayout.Label(new GUIContent("Cannot edit Prefabs", Icons.warning));
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
|
||||
serializedObject.Update();
|
||||
|
||||
if ((skeletonRenderer != null && !skeletonRenderer.valid) ||
|
||||
(skeletonGraphic != null && !skeletonGraphic.IsValid)) {
|
||||
GUILayout.Label(new GUIContent("Spine Component invalid. Check SkeletonData asset.", Icons.warning));
|
||||
return;
|
||||
}
|
||||
|
||||
EditorGUILayout.PropertyField(serializedObject.FindProperty("boneRoot"), SpineInspectorUtility.TempContent("Skeleton Root"));
|
||||
EditorGUILayout.PropertyField(serializedObject.FindProperty("flipBy180DegreeRotation"), SpineInspectorUtility.TempContent("Flip by Rotation", null,
|
||||
"If true, Skeleton.ScaleX and Skeleton.ScaleY are followed " +
|
||||
"by 180 degree rotation. If false, negative Transform scale is used. " +
|
||||
"Note that using negative scale is consistent with previous behaviour (hence the default), " +
|
||||
"however causes serious problems with rigidbodies and physics. Therefore, it is recommended to " +
|
||||
"enable this parameter where possible. When creating hinge chains for a chain of skeleton bones " +
|
||||
"via SkeletonUtilityBone, it is mandatory to have this parameter enabled."));
|
||||
|
||||
bool hasRootBone = skeletonUtility.boneRoot != null;
|
||||
|
||||
if (!hasRootBone)
|
||||
EditorGUILayout.HelpBox("No hierarchy found. Use Spawn Hierarchy to generate GameObjects for bones.", MessageType.Info);
|
||||
|
||||
using (new EditorGUI.DisabledGroupScope(hasRootBone)) {
|
||||
if (SpineInspectorUtility.LargeCenteredButton(SpawnHierarchyButtonLabel))
|
||||
SpawnHierarchyContextMenu();
|
||||
}
|
||||
|
||||
if (hasRootBone) {
|
||||
if (SpineInspectorUtility.CenteredButton(new GUIContent("Remove Hierarchy"))) {
|
||||
Undo.RegisterCompleteObjectUndo(skeletonUtility, "Remove Hierarchy");
|
||||
Undo.DestroyObjectImmediate(skeletonUtility.boneRoot.gameObject);
|
||||
skeletonUtility.boneRoot = null;
|
||||
}
|
||||
}
|
||||
|
||||
serializedObject.ApplyModifiedProperties();
|
||||
}
|
||||
|
||||
void SpawnHierarchyContextMenu () {
|
||||
GenericMenu menu = new GenericMenu();
|
||||
|
||||
menu.AddItem(new GUIContent("Follow all bones"), false, SpawnFollowHierarchy);
|
||||
menu.AddItem(new GUIContent("Follow (Root Only)"), false, SpawnFollowHierarchyRootOnly);
|
||||
menu.AddSeparator("");
|
||||
menu.AddItem(new GUIContent("Override all bones"), false, SpawnOverrideHierarchy);
|
||||
menu.AddItem(new GUIContent("Override (Root Only)"), false, SpawnOverrideHierarchyRootOnly);
|
||||
|
||||
menu.ShowAsContext();
|
||||
}
|
||||
|
||||
public static void AttachIcon (SkeletonUtilityBone boneComponent) {
|
||||
Skeleton skeleton = boneComponent.hierarchy.Skeleton;
|
||||
Texture2D icon = boneComponent.bone.Data.Length == 0 ? Icons.nullBone : Icons.boneNib;
|
||||
|
||||
var ikConstraints = skeleton.Constraints.OfType<IkConstraint>();
|
||||
foreach (IkConstraint c in ikConstraints)
|
||||
if (c.Target == boneComponent.bone) {
|
||||
icon = Icons.constraintNib;
|
||||
break;
|
||||
}
|
||||
#if PUBLIC_SET_ICON_FOR_OBJECT
|
||||
EditorGUIUtility.SetIconForObject(boneComponent.gameObject, icon);
|
||||
#else
|
||||
typeof(EditorGUIUtility).InvokeMember("SetIconForObject", BindingFlags.InvokeMethod | BindingFlags.Static | BindingFlags.NonPublic, null, null, new object[2] {
|
||||
boneComponent.gameObject,
|
||||
icon
|
||||
});
|
||||
#endif
|
||||
}
|
||||
|
||||
static void AttachIconsToChildren (Transform root) {
|
||||
if (root != null) {
|
||||
SkeletonUtilityBone[] utilityBones = root.GetComponentsInChildren<SkeletonUtilityBone>();
|
||||
foreach (SkeletonUtilityBone utilBone in utilityBones)
|
||||
AttachIcon(utilBone);
|
||||
}
|
||||
}
|
||||
|
||||
void SpawnFollowHierarchy () {
|
||||
Undo.RegisterCompleteObjectUndo(skeletonUtility, "Spawn Hierarchy");
|
||||
Selection.activeGameObject = skeletonUtility.SpawnHierarchy(SkeletonUtilityBone.Mode.Follow, true, true, true);
|
||||
AttachIconsToChildren(skeletonUtility.boneRoot);
|
||||
}
|
||||
|
||||
void SpawnFollowHierarchyRootOnly () {
|
||||
Undo.RegisterCompleteObjectUndo(skeletonUtility, "Spawn Root");
|
||||
Selection.activeGameObject = skeletonUtility.SpawnRoot(SkeletonUtilityBone.Mode.Follow, true, true, true);
|
||||
AttachIconsToChildren(skeletonUtility.boneRoot);
|
||||
}
|
||||
|
||||
void SpawnOverrideHierarchy () {
|
||||
Undo.RegisterCompleteObjectUndo(skeletonUtility, "Spawn Hierarchy");
|
||||
Selection.activeGameObject = skeletonUtility.SpawnHierarchy(SkeletonUtilityBone.Mode.Override, true, true, true);
|
||||
AttachIconsToChildren(skeletonUtility.boneRoot);
|
||||
}
|
||||
|
||||
void SpawnOverrideHierarchyRootOnly () {
|
||||
Undo.RegisterCompleteObjectUndo(skeletonUtility, "Spawn Root");
|
||||
Selection.activeGameObject = skeletonUtility.SpawnRoot(SkeletonUtilityBone.Mode.Override, true, true, true);
|
||||
AttachIconsToChildren(skeletonUtility.boneRoot);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a5b90df955eb8c2429ac67c8b2de6c5c
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,5 @@
|
||||
fileFormatVersion: 2
|
||||
guid: bfaea6b7e7f52bc46b8d1c3cb5e9eaa1
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
userData:
|
||||
|
After Width: | Height: | Size: 529 B |
@@ -0,0 +1,92 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 3fc714a0dc1cf6b4b959e073fff2844e
|
||||
timeCreated: 1508165143
|
||||
licenseType: Free
|
||||
TextureImporter:
|
||||
fileIDToRecycleName: {}
|
||||
serializedVersion: 4
|
||||
mipmaps:
|
||||
mipMapMode: 0
|
||||
enableMipMap: 0
|
||||
sRGBTexture: 0
|
||||
linearTexture: 0
|
||||
fadeOut: 0
|
||||
borderMipMap: 0
|
||||
mipMapFadeDistanceStart: 1
|
||||
mipMapFadeDistanceEnd: 3
|
||||
bumpmap:
|
||||
convertToNormalMap: 0
|
||||
externalNormalMap: 0
|
||||
heightScale: 0.25
|
||||
normalMapFilter: 0
|
||||
isReadable: 0
|
||||
grayScaleToAlpha: 0
|
||||
generateCubemap: 6
|
||||
cubemapConvolution: 0
|
||||
seamlessCubemap: 0
|
||||
textureFormat: 1
|
||||
maxTextureSize: 2048
|
||||
textureSettings:
|
||||
filterMode: -1
|
||||
aniso: 1
|
||||
mipBias: -1
|
||||
wrapMode: 1
|
||||
nPOTScale: 0
|
||||
lightmap: 0
|
||||
compressionQuality: 50
|
||||
spriteMode: 1
|
||||
spriteExtrude: 1
|
||||
spriteMeshType: 1
|
||||
alignment: 0
|
||||
spritePivot: {x: 0.5, y: 0.5}
|
||||
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
|
||||
spritePixelsToUnits: 100
|
||||
alphaUsage: 1
|
||||
alphaIsTransparency: 1
|
||||
spriteTessellationDetail: -1
|
||||
textureType: 2
|
||||
textureShape: 1
|
||||
maxTextureSizeSet: 0
|
||||
compressionQualitySet: 0
|
||||
textureFormatSet: 0
|
||||
platformSettings:
|
||||
- buildTarget: DefaultTexturePlatform
|
||||
maxTextureSize: 1024
|
||||
textureFormat: -1
|
||||
textureCompression: 0
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
- buildTarget: Standalone
|
||||
maxTextureSize: 1024
|
||||
textureFormat: -1
|
||||
textureCompression: 0
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
- buildTarget: Android
|
||||
maxTextureSize: 1024
|
||||
textureFormat: -1
|
||||
textureCompression: 0
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
- buildTarget: WebGL
|
||||
maxTextureSize: 1024
|
||||
textureFormat: -1
|
||||
textureCompression: 0
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
spriteSheet:
|
||||
serializedVersion: 2
|
||||
sprites: []
|
||||
outline: []
|
||||
spritePackingTag:
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
After Width: | Height: | Size: 507 B |
@@ -0,0 +1,46 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 68defdbc95b30a74a9ad396bfc9a2277
|
||||
TextureImporter:
|
||||
serializedVersion: 2
|
||||
mipmaps:
|
||||
mipMapMode: 0
|
||||
enableMipMap: 0
|
||||
linearTexture: 1
|
||||
correctGamma: 0
|
||||
fadeOut: 0
|
||||
borderMipMap: 0
|
||||
mipMapFadeDistanceStart: 1
|
||||
mipMapFadeDistanceEnd: 3
|
||||
bumpmap:
|
||||
convertToNormalMap: 0
|
||||
externalNormalMap: 0
|
||||
heightScale: .25
|
||||
normalMapFilter: 0
|
||||
isReadable: 0
|
||||
grayScaleToAlpha: 0
|
||||
generateCubemap: 0
|
||||
seamlessCubemap: 0
|
||||
textureFormat: -3
|
||||
maxTextureSize: 1024
|
||||
textureSettings:
|
||||
filterMode: -1
|
||||
aniso: 1
|
||||
mipBias: -1
|
||||
wrapMode: 1
|
||||
nPOTScale: 0
|
||||
lightmap: 0
|
||||
compressionQuality: 50
|
||||
spriteMode: 0
|
||||
spriteExtrude: 1
|
||||
spriteMeshType: 1
|
||||
alignment: 0
|
||||
spritePivot: {x: .5, y: .5}
|
||||
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
|
||||
spritePixelsToUnits: 100
|
||||
alphaIsTransparency: 1
|
||||
textureType: 2
|
||||
buildTargetSettings: []
|
||||
spriteSheet:
|
||||
sprites: []
|
||||
spritePackingTag:
|
||||
userData:
|
||||
|
After Width: | Height: | Size: 581 B |
@@ -0,0 +1,53 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 52b12ec801461494185a4d3dc66f3d1d
|
||||
TextureImporter:
|
||||
fileIDToRecycleName: {}
|
||||
serializedVersion: 2
|
||||
mipmaps:
|
||||
mipMapMode: 0
|
||||
enableMipMap: 0
|
||||
linearTexture: 1
|
||||
correctGamma: 0
|
||||
fadeOut: 0
|
||||
borderMipMap: 0
|
||||
mipMapFadeDistanceStart: 1
|
||||
mipMapFadeDistanceEnd: 3
|
||||
bumpmap:
|
||||
convertToNormalMap: 0
|
||||
externalNormalMap: 0
|
||||
heightScale: .25
|
||||
normalMapFilter: 0
|
||||
isReadable: 0
|
||||
grayScaleToAlpha: 0
|
||||
generateCubemap: 0
|
||||
cubemapConvolution: 0
|
||||
cubemapConvolutionSteps: 8
|
||||
cubemapConvolutionExponent: 1.5
|
||||
seamlessCubemap: 0
|
||||
textureFormat: -3
|
||||
maxTextureSize: 1024
|
||||
textureSettings:
|
||||
filterMode: -1
|
||||
aniso: 1
|
||||
mipBias: -1
|
||||
wrapMode: 1
|
||||
nPOTScale: 0
|
||||
lightmap: 0
|
||||
rGBM: 0
|
||||
compressionQuality: 50
|
||||
spriteMode: 0
|
||||
spriteExtrude: 1
|
||||
spriteMeshType: 1
|
||||
alignment: 0
|
||||
spritePivot: {x: .5, y: .5}
|
||||
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
|
||||
spritePixelsToUnits: 100
|
||||
alphaIsTransparency: 1
|
||||
textureType: 2
|
||||
buildTargetSettings: []
|
||||
spriteSheet:
|
||||
sprites: []
|
||||
spritePackingTag:
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
After Width: | Height: | Size: 552 B |
@@ -0,0 +1,53 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 3d1be4ea889f3a14b864352fe49a1bde
|
||||
TextureImporter:
|
||||
fileIDToRecycleName: {}
|
||||
serializedVersion: 2
|
||||
mipmaps:
|
||||
mipMapMode: 0
|
||||
enableMipMap: 0
|
||||
linearTexture: 1
|
||||
correctGamma: 0
|
||||
fadeOut: 0
|
||||
borderMipMap: 0
|
||||
mipMapFadeDistanceStart: 1
|
||||
mipMapFadeDistanceEnd: 3
|
||||
bumpmap:
|
||||
convertToNormalMap: 0
|
||||
externalNormalMap: 0
|
||||
heightScale: .25
|
||||
normalMapFilter: 0
|
||||
isReadable: 0
|
||||
grayScaleToAlpha: 0
|
||||
generateCubemap: 0
|
||||
cubemapConvolution: 0
|
||||
cubemapConvolutionSteps: 8
|
||||
cubemapConvolutionExponent: 1.5
|
||||
seamlessCubemap: 0
|
||||
textureFormat: -3
|
||||
maxTextureSize: 1024
|
||||
textureSettings:
|
||||
filterMode: -1
|
||||
aniso: 1
|
||||
mipBias: -1
|
||||
wrapMode: 1
|
||||
nPOTScale: 0
|
||||
lightmap: 0
|
||||
rGBM: 0
|
||||
compressionQuality: 50
|
||||
spriteMode: 0
|
||||
spriteExtrude: 1
|
||||
spriteMeshType: 1
|
||||
alignment: 0
|
||||
spritePivot: {x: .5, y: .5}
|
||||
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
|
||||
spritePixelsToUnits: 100
|
||||
alphaIsTransparency: 1
|
||||
textureType: 2
|
||||
buildTargetSettings: []
|
||||
spriteSheet:
|
||||
sprites: []
|
||||
spritePackingTag:
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
After Width: | Height: | Size: 289 B |
@@ -0,0 +1,92 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 04ae56b3698d3e844844cfcef2f009e7
|
||||
timeCreated: 1494928093
|
||||
licenseType: Free
|
||||
TextureImporter:
|
||||
fileIDToRecycleName: {}
|
||||
serializedVersion: 4
|
||||
mipmaps:
|
||||
mipMapMode: 0
|
||||
enableMipMap: 0
|
||||
sRGBTexture: 0
|
||||
linearTexture: 0
|
||||
fadeOut: 0
|
||||
borderMipMap: 0
|
||||
mipMapFadeDistanceStart: 1
|
||||
mipMapFadeDistanceEnd: 3
|
||||
bumpmap:
|
||||
convertToNormalMap: 0
|
||||
externalNormalMap: 0
|
||||
heightScale: 0.25
|
||||
normalMapFilter: 0
|
||||
isReadable: 0
|
||||
grayScaleToAlpha: 0
|
||||
generateCubemap: 6
|
||||
cubemapConvolution: 0
|
||||
seamlessCubemap: 0
|
||||
textureFormat: 1
|
||||
maxTextureSize: 2048
|
||||
textureSettings:
|
||||
filterMode: -1
|
||||
aniso: -1
|
||||
mipBias: -1
|
||||
wrapMode: 1
|
||||
nPOTScale: 0
|
||||
lightmap: 0
|
||||
compressionQuality: 50
|
||||
spriteMode: 1
|
||||
spriteExtrude: 1
|
||||
spriteMeshType: 1
|
||||
alignment: 0
|
||||
spritePivot: {x: 0.5, y: 0.5}
|
||||
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
|
||||
spritePixelsToUnits: 100
|
||||
alphaUsage: 1
|
||||
alphaIsTransparency: 1
|
||||
spriteTessellationDetail: -1
|
||||
textureType: 2
|
||||
textureShape: 1
|
||||
maxTextureSizeSet: 0
|
||||
compressionQualitySet: 0
|
||||
textureFormatSet: 0
|
||||
platformSettings:
|
||||
- buildTarget: DefaultTexturePlatform
|
||||
maxTextureSize: 2048
|
||||
textureFormat: -1
|
||||
textureCompression: 1
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
- buildTarget: Standalone
|
||||
maxTextureSize: 2048
|
||||
textureFormat: -1
|
||||
textureCompression: 1
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
- buildTarget: Android
|
||||
maxTextureSize: 2048
|
||||
textureFormat: -1
|
||||
textureCompression: 1
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
- buildTarget: WebGL
|
||||
maxTextureSize: 2048
|
||||
textureFormat: -1
|
||||
textureCompression: 1
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
spriteSheet:
|
||||
serializedVersion: 2
|
||||
sprites: []
|
||||
outline: []
|
||||
spritePackingTag:
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
After Width: | Height: | Size: 386 B |
@@ -0,0 +1,53 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 8322793223a533a4ca8be6f430256dfc
|
||||
TextureImporter:
|
||||
fileIDToRecycleName: {}
|
||||
serializedVersion: 2
|
||||
mipmaps:
|
||||
mipMapMode: 0
|
||||
enableMipMap: 0
|
||||
linearTexture: 1
|
||||
correctGamma: 0
|
||||
fadeOut: 0
|
||||
borderMipMap: 0
|
||||
mipMapFadeDistanceStart: 1
|
||||
mipMapFadeDistanceEnd: 3
|
||||
bumpmap:
|
||||
convertToNormalMap: 0
|
||||
externalNormalMap: 0
|
||||
heightScale: .25
|
||||
normalMapFilter: 0
|
||||
isReadable: 0
|
||||
grayScaleToAlpha: 0
|
||||
generateCubemap: 0
|
||||
cubemapConvolution: 0
|
||||
cubemapConvolutionSteps: 8
|
||||
cubemapConvolutionExponent: 1.5
|
||||
seamlessCubemap: 0
|
||||
textureFormat: -3
|
||||
maxTextureSize: 1024
|
||||
textureSettings:
|
||||
filterMode: -1
|
||||
aniso: 1
|
||||
mipBias: -1
|
||||
wrapMode: 1
|
||||
nPOTScale: 0
|
||||
lightmap: 0
|
||||
rGBM: 0
|
||||
compressionQuality: 50
|
||||
spriteMode: 0
|
||||
spriteExtrude: 1
|
||||
spriteMeshType: 1
|
||||
alignment: 0
|
||||
spritePivot: {x: .5, y: .5}
|
||||
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
|
||||
spritePixelsToUnits: 100
|
||||
alphaIsTransparency: 1
|
||||
textureType: 2
|
||||
buildTargetSettings: []
|
||||
spriteSheet:
|
||||
sprites: []
|
||||
spritePackingTag:
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
After Width: | Height: | Size: 411 B |
@@ -0,0 +1,53 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 97a43f11e00735147a9dc3dff6d68191
|
||||
TextureImporter:
|
||||
fileIDToRecycleName: {}
|
||||
serializedVersion: 2
|
||||
mipmaps:
|
||||
mipMapMode: 0
|
||||
enableMipMap: 0
|
||||
linearTexture: 1
|
||||
correctGamma: 0
|
||||
fadeOut: 0
|
||||
borderMipMap: 0
|
||||
mipMapFadeDistanceStart: 1
|
||||
mipMapFadeDistanceEnd: 3
|
||||
bumpmap:
|
||||
convertToNormalMap: 0
|
||||
externalNormalMap: 0
|
||||
heightScale: .25
|
||||
normalMapFilter: 0
|
||||
isReadable: 0
|
||||
grayScaleToAlpha: 0
|
||||
generateCubemap: 0
|
||||
cubemapConvolution: 0
|
||||
cubemapConvolutionSteps: 8
|
||||
cubemapConvolutionExponent: 1.5
|
||||
seamlessCubemap: 0
|
||||
textureFormat: -3
|
||||
maxTextureSize: 1024
|
||||
textureSettings:
|
||||
filterMode: -1
|
||||
aniso: 1
|
||||
mipBias: -1
|
||||
wrapMode: 1
|
||||
nPOTScale: 0
|
||||
lightmap: 0
|
||||
rGBM: 0
|
||||
compressionQuality: 50
|
||||
spriteMode: 0
|
||||
spriteExtrude: 1
|
||||
spriteMeshType: 1
|
||||
alignment: 0
|
||||
spritePivot: {x: .5, y: .5}
|
||||
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
|
||||
spritePixelsToUnits: 100
|
||||
alphaIsTransparency: 1
|
||||
textureType: 2
|
||||
buildTargetSettings: []
|
||||
spriteSheet:
|
||||
sprites: []
|
||||
spritePackingTag:
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
After Width: | Height: | Size: 225 B |
@@ -0,0 +1,47 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 955aed20030d0504b8a9c6934a5cb47a
|
||||
TextureImporter:
|
||||
fileIDToRecycleName: {}
|
||||
serializedVersion: 2
|
||||
mipmaps:
|
||||
mipMapMode: 0
|
||||
enableMipMap: 0
|
||||
linearTexture: 1
|
||||
correctGamma: 0
|
||||
fadeOut: 0
|
||||
borderMipMap: 0
|
||||
mipMapFadeDistanceStart: 1
|
||||
mipMapFadeDistanceEnd: 3
|
||||
bumpmap:
|
||||
convertToNormalMap: 0
|
||||
externalNormalMap: 0
|
||||
heightScale: .25
|
||||
normalMapFilter: 0
|
||||
isReadable: 0
|
||||
grayScaleToAlpha: 0
|
||||
generateCubemap: 0
|
||||
seamlessCubemap: 0
|
||||
textureFormat: -3
|
||||
maxTextureSize: 1024
|
||||
textureSettings:
|
||||
filterMode: -1
|
||||
aniso: 1
|
||||
mipBias: -1
|
||||
wrapMode: 1
|
||||
nPOTScale: 0
|
||||
lightmap: 0
|
||||
compressionQuality: 50
|
||||
spriteMode: 0
|
||||
spriteExtrude: 1
|
||||
spriteMeshType: 1
|
||||
alignment: 0
|
||||
spritePivot: {x: .5, y: .5}
|
||||
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
|
||||
spritePixelsToUnits: 100
|
||||
alphaIsTransparency: 1
|
||||
textureType: 2
|
||||
buildTargetSettings: []
|
||||
spriteSheet:
|
||||
sprites: []
|
||||
spritePackingTag:
|
||||
userData:
|
||||
|
After Width: | Height: | Size: 482 B |
@@ -0,0 +1,92 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f5fff1b5caee03642ab77c9984b4bb6a
|
||||
timeCreated: 1497479335
|
||||
licenseType: Free
|
||||
TextureImporter:
|
||||
fileIDToRecycleName: {}
|
||||
serializedVersion: 4
|
||||
mipmaps:
|
||||
mipMapMode: 0
|
||||
enableMipMap: 0
|
||||
sRGBTexture: 0
|
||||
linearTexture: 0
|
||||
fadeOut: 0
|
||||
borderMipMap: 0
|
||||
mipMapFadeDistanceStart: 1
|
||||
mipMapFadeDistanceEnd: 3
|
||||
bumpmap:
|
||||
convertToNormalMap: 0
|
||||
externalNormalMap: 0
|
||||
heightScale: 0.25
|
||||
normalMapFilter: 0
|
||||
isReadable: 0
|
||||
grayScaleToAlpha: 0
|
||||
generateCubemap: 6
|
||||
cubemapConvolution: 0
|
||||
seamlessCubemap: 0
|
||||
textureFormat: 1
|
||||
maxTextureSize: 2048
|
||||
textureSettings:
|
||||
filterMode: -1
|
||||
aniso: 1
|
||||
mipBias: -1
|
||||
wrapMode: 1
|
||||
nPOTScale: 0
|
||||
lightmap: 0
|
||||
compressionQuality: 50
|
||||
spriteMode: 1
|
||||
spriteExtrude: 1
|
||||
spriteMeshType: 1
|
||||
alignment: 0
|
||||
spritePivot: {x: 0.5, y: 0.5}
|
||||
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
|
||||
spritePixelsToUnits: 100
|
||||
alphaUsage: 1
|
||||
alphaIsTransparency: 1
|
||||
spriteTessellationDetail: -1
|
||||
textureType: 2
|
||||
textureShape: 1
|
||||
maxTextureSizeSet: 0
|
||||
compressionQualitySet: 0
|
||||
textureFormatSet: 0
|
||||
platformSettings:
|
||||
- buildTarget: DefaultTexturePlatform
|
||||
maxTextureSize: 2048
|
||||
textureFormat: -1
|
||||
textureCompression: 1
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
- buildTarget: Standalone
|
||||
maxTextureSize: 2048
|
||||
textureFormat: -1
|
||||
textureCompression: 1
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
- buildTarget: Android
|
||||
maxTextureSize: 2048
|
||||
textureFormat: -1
|
||||
textureCompression: 1
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
- buildTarget: WebGL
|
||||
maxTextureSize: 2048
|
||||
textureFormat: -1
|
||||
textureCompression: 1
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
spriteSheet:
|
||||
serializedVersion: 2
|
||||
sprites: []
|
||||
outline: []
|
||||
spritePackingTag:
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
After Width: | Height: | Size: 540 B |
@@ -0,0 +1,92 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 02822eb69e09dd947b434ab81e3d938f
|
||||
timeCreated: 1494878353
|
||||
licenseType: Free
|
||||
TextureImporter:
|
||||
fileIDToRecycleName: {}
|
||||
serializedVersion: 4
|
||||
mipmaps:
|
||||
mipMapMode: 0
|
||||
enableMipMap: 0
|
||||
sRGBTexture: 0
|
||||
linearTexture: 0
|
||||
fadeOut: 0
|
||||
borderMipMap: 0
|
||||
mipMapFadeDistanceStart: 1
|
||||
mipMapFadeDistanceEnd: 3
|
||||
bumpmap:
|
||||
convertToNormalMap: 0
|
||||
externalNormalMap: 0
|
||||
heightScale: 0.25
|
||||
normalMapFilter: 0
|
||||
isReadable: 0
|
||||
grayScaleToAlpha: 0
|
||||
generateCubemap: 6
|
||||
cubemapConvolution: 0
|
||||
seamlessCubemap: 0
|
||||
textureFormat: 1
|
||||
maxTextureSize: 2048
|
||||
textureSettings:
|
||||
filterMode: -1
|
||||
aniso: -1
|
||||
mipBias: -1
|
||||
wrapMode: 1
|
||||
nPOTScale: 0
|
||||
lightmap: 0
|
||||
compressionQuality: 50
|
||||
spriteMode: 1
|
||||
spriteExtrude: 1
|
||||
spriteMeshType: 1
|
||||
alignment: 0
|
||||
spritePivot: {x: 0.5, y: 0.5}
|
||||
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
|
||||
spritePixelsToUnits: 100
|
||||
alphaUsage: 1
|
||||
alphaIsTransparency: 1
|
||||
spriteTessellationDetail: -1
|
||||
textureType: 2
|
||||
textureShape: 1
|
||||
maxTextureSizeSet: 0
|
||||
compressionQualitySet: 0
|
||||
textureFormatSet: 0
|
||||
platformSettings:
|
||||
- buildTarget: DefaultTexturePlatform
|
||||
maxTextureSize: 2048
|
||||
textureFormat: -1
|
||||
textureCompression: 1
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
- buildTarget: Standalone
|
||||
maxTextureSize: 2048
|
||||
textureFormat: -1
|
||||
textureCompression: 1
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
- buildTarget: Android
|
||||
maxTextureSize: 2048
|
||||
textureFormat: -1
|
||||
textureCompression: 1
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
- buildTarget: WebGL
|
||||
maxTextureSize: 2048
|
||||
textureFormat: -1
|
||||
textureCompression: 1
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
spriteSheet:
|
||||
serializedVersion: 2
|
||||
sprites: []
|
||||
outline: []
|
||||
spritePackingTag:
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
After Width: | Height: | Size: 752 B |
@@ -0,0 +1,53 @@
|
||||
fileFormatVersion: 2
|
||||
guid: de1a4f5ad4bdf1a4ea072c4d59ba87d8
|
||||
TextureImporter:
|
||||
fileIDToRecycleName: {}
|
||||
serializedVersion: 2
|
||||
mipmaps:
|
||||
mipMapMode: 0
|
||||
enableMipMap: 0
|
||||
linearTexture: 1
|
||||
correctGamma: 0
|
||||
fadeOut: 0
|
||||
borderMipMap: 0
|
||||
mipMapFadeDistanceStart: 1
|
||||
mipMapFadeDistanceEnd: 3
|
||||
bumpmap:
|
||||
convertToNormalMap: 0
|
||||
externalNormalMap: 0
|
||||
heightScale: .25
|
||||
normalMapFilter: 0
|
||||
isReadable: 0
|
||||
grayScaleToAlpha: 0
|
||||
generateCubemap: 0
|
||||
cubemapConvolution: 0
|
||||
cubemapConvolutionSteps: 8
|
||||
cubemapConvolutionExponent: 1.5
|
||||
seamlessCubemap: 0
|
||||
textureFormat: -3
|
||||
maxTextureSize: 1024
|
||||
textureSettings:
|
||||
filterMode: -1
|
||||
aniso: 1
|
||||
mipBias: -1
|
||||
wrapMode: 1
|
||||
nPOTScale: 0
|
||||
lightmap: 0
|
||||
rGBM: 0
|
||||
compressionQuality: 50
|
||||
spriteMode: 0
|
||||
spriteExtrude: 1
|
||||
spriteMeshType: 1
|
||||
alignment: 0
|
||||
spritePivot: {x: .5, y: .5}
|
||||
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
|
||||
spritePixelsToUnits: 100
|
||||
alphaIsTransparency: 1
|
||||
textureType: 2
|
||||
buildTargetSettings: []
|
||||
spriteSheet:
|
||||
sprites: []
|
||||
spritePackingTag:
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
After Width: | Height: | Size: 502 B |
@@ -0,0 +1,92 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c1aae98dd56b14c4b8c25360000b7e9e
|
||||
timeCreated: 1494878353
|
||||
licenseType: Free
|
||||
TextureImporter:
|
||||
fileIDToRecycleName: {}
|
||||
serializedVersion: 4
|
||||
mipmaps:
|
||||
mipMapMode: 0
|
||||
enableMipMap: 0
|
||||
sRGBTexture: 0
|
||||
linearTexture: 0
|
||||
fadeOut: 0
|
||||
borderMipMap: 0
|
||||
mipMapFadeDistanceStart: 1
|
||||
mipMapFadeDistanceEnd: 3
|
||||
bumpmap:
|
||||
convertToNormalMap: 0
|
||||
externalNormalMap: 0
|
||||
heightScale: 0.25
|
||||
normalMapFilter: 0
|
||||
isReadable: 0
|
||||
grayScaleToAlpha: 0
|
||||
generateCubemap: 6
|
||||
cubemapConvolution: 0
|
||||
seamlessCubemap: 0
|
||||
textureFormat: 1
|
||||
maxTextureSize: 2048
|
||||
textureSettings:
|
||||
filterMode: -1
|
||||
aniso: -1
|
||||
mipBias: -1
|
||||
wrapMode: 1
|
||||
nPOTScale: 0
|
||||
lightmap: 0
|
||||
compressionQuality: 50
|
||||
spriteMode: 1
|
||||
spriteExtrude: 1
|
||||
spriteMeshType: 1
|
||||
alignment: 0
|
||||
spritePivot: {x: 0.5, y: 0.5}
|
||||
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
|
||||
spritePixelsToUnits: 100
|
||||
alphaUsage: 1
|
||||
alphaIsTransparency: 1
|
||||
spriteTessellationDetail: -1
|
||||
textureType: 2
|
||||
textureShape: 1
|
||||
maxTextureSizeSet: 0
|
||||
compressionQualitySet: 0
|
||||
textureFormatSet: 0
|
||||
platformSettings:
|
||||
- buildTarget: DefaultTexturePlatform
|
||||
maxTextureSize: 2048
|
||||
textureFormat: -1
|
||||
textureCompression: 1
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
- buildTarget: Standalone
|
||||
maxTextureSize: 2048
|
||||
textureFormat: -1
|
||||
textureCompression: 1
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
- buildTarget: Android
|
||||
maxTextureSize: 2048
|
||||
textureFormat: -1
|
||||
textureCompression: 1
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
- buildTarget: WebGL
|
||||
maxTextureSize: 2048
|
||||
textureFormat: -1
|
||||
textureCompression: 1
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
spriteSheet:
|
||||
serializedVersion: 2
|
||||
sprites: []
|
||||
outline: []
|
||||
spritePackingTag:
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
After Width: | Height: | Size: 3.5 KiB |
@@ -0,0 +1,124 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 10e534174824cb04e8a7ec21825f2827
|
||||
TextureImporter:
|
||||
internalIDToNameTable: []
|
||||
externalObjects: {}
|
||||
serializedVersion: 12
|
||||
mipmaps:
|
||||
mipMapMode: 0
|
||||
enableMipMap: 0
|
||||
sRGBTexture: 1
|
||||
linearTexture: 0
|
||||
fadeOut: 0
|
||||
borderMipMap: 0
|
||||
mipMapsPreserveCoverage: 0
|
||||
alphaTestReferenceValue: 0.5
|
||||
mipMapFadeDistanceStart: 1
|
||||
mipMapFadeDistanceEnd: 3
|
||||
bumpmap:
|
||||
convertToNormalMap: 0
|
||||
externalNormalMap: 0
|
||||
heightScale: 0.25
|
||||
normalMapFilter: 0
|
||||
flipGreenChannel: 0
|
||||
isReadable: 0
|
||||
streamingMipmaps: 0
|
||||
streamingMipmapsPriority: 0
|
||||
vTOnly: 0
|
||||
ignoreMasterTextureLimit: 0
|
||||
grayScaleToAlpha: 0
|
||||
generateCubemap: 6
|
||||
cubemapConvolution: 0
|
||||
seamlessCubemap: 0
|
||||
textureFormat: 1
|
||||
maxTextureSize: 2048
|
||||
textureSettings:
|
||||
serializedVersion: 2
|
||||
filterMode: 1
|
||||
aniso: 1
|
||||
mipBias: 0
|
||||
wrapU: 1
|
||||
wrapV: 1
|
||||
wrapW: 1
|
||||
nPOTScale: 0
|
||||
lightmap: 0
|
||||
compressionQuality: 50
|
||||
spriteMode: 1
|
||||
spriteExtrude: 1
|
||||
spriteMeshType: 1
|
||||
alignment: 0
|
||||
spritePivot: {x: 0.5, y: 0.5}
|
||||
spritePixelsToUnits: 100
|
||||
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
|
||||
spriteGenerateFallbackPhysicsShape: 1
|
||||
alphaUsage: 1
|
||||
alphaIsTransparency: 1
|
||||
spriteTessellationDetail: -1
|
||||
textureType: 2
|
||||
textureShape: 1
|
||||
singleChannelComponent: 0
|
||||
flipbookRows: 1
|
||||
flipbookColumns: 1
|
||||
maxTextureSizeSet: 0
|
||||
compressionQualitySet: 0
|
||||
textureFormatSet: 0
|
||||
ignorePngGamma: 0
|
||||
applyGammaDecoding: 0
|
||||
swizzle: 50462976
|
||||
cookieLightType: 0
|
||||
platformSettings:
|
||||
- serializedVersion: 3
|
||||
buildTarget: DefaultTexturePlatform
|
||||
maxTextureSize: 2048
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 1
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
- serializedVersion: 3
|
||||
buildTarget: Standalone
|
||||
maxTextureSize: 2048
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 1
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
- serializedVersion: 3
|
||||
buildTarget: Server
|
||||
maxTextureSize: 2048
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 1
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
spriteSheet:
|
||||
serializedVersion: 2
|
||||
sprites: []
|
||||
outline: []
|
||||
physicsShape: []
|
||||
bones: []
|
||||
spriteID: 5e97eb03825dee720800000000000000
|
||||
internalID: 0
|
||||
vertices: []
|
||||
indices:
|
||||
edges: []
|
||||
weights: []
|
||||
secondaryTextures: []
|
||||
nameFileIdTable: {}
|
||||
spritePackingTag:
|
||||
pSDRemoveMatte: 0
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
After Width: | Height: | Size: 471 B |
@@ -0,0 +1,92 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 4709175437c21f64bab9b061f98a49fc
|
||||
timeCreated: 1494878353
|
||||
licenseType: Free
|
||||
TextureImporter:
|
||||
fileIDToRecycleName: {}
|
||||
serializedVersion: 4
|
||||
mipmaps:
|
||||
mipMapMode: 0
|
||||
enableMipMap: 0
|
||||
sRGBTexture: 0
|
||||
linearTexture: 0
|
||||
fadeOut: 0
|
||||
borderMipMap: 0
|
||||
mipMapFadeDistanceStart: 1
|
||||
mipMapFadeDistanceEnd: 3
|
||||
bumpmap:
|
||||
convertToNormalMap: 0
|
||||
externalNormalMap: 0
|
||||
heightScale: 0.25
|
||||
normalMapFilter: 0
|
||||
isReadable: 0
|
||||
grayScaleToAlpha: 0
|
||||
generateCubemap: 6
|
||||
cubemapConvolution: 0
|
||||
seamlessCubemap: 0
|
||||
textureFormat: 1
|
||||
maxTextureSize: 2048
|
||||
textureSettings:
|
||||
filterMode: -1
|
||||
aniso: -1
|
||||
mipBias: -1
|
||||
wrapMode: 1
|
||||
nPOTScale: 0
|
||||
lightmap: 0
|
||||
compressionQuality: 50
|
||||
spriteMode: 1
|
||||
spriteExtrude: 1
|
||||
spriteMeshType: 1
|
||||
alignment: 0
|
||||
spritePivot: {x: 0.5, y: 0.5}
|
||||
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
|
||||
spritePixelsToUnits: 100
|
||||
alphaUsage: 1
|
||||
alphaIsTransparency: 1
|
||||
spriteTessellationDetail: -1
|
||||
textureType: 2
|
||||
textureShape: 1
|
||||
maxTextureSizeSet: 0
|
||||
compressionQualitySet: 0
|
||||
textureFormatSet: 0
|
||||
platformSettings:
|
||||
- buildTarget: DefaultTexturePlatform
|
||||
maxTextureSize: 2048
|
||||
textureFormat: -1
|
||||
textureCompression: 1
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
- buildTarget: Standalone
|
||||
maxTextureSize: 2048
|
||||
textureFormat: -1
|
||||
textureCompression: 1
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
- buildTarget: Android
|
||||
maxTextureSize: 2048
|
||||
textureFormat: -1
|
||||
textureCompression: 1
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
- buildTarget: WebGL
|
||||
maxTextureSize: 2048
|
||||
textureFormat: -1
|
||||
textureCompression: 1
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
spriteSheet:
|
||||
serializedVersion: 2
|
||||
sprites: []
|
||||
outline: []
|
||||
spritePackingTag:
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
After Width: | Height: | Size: 587 B |
@@ -0,0 +1,92 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ed0736a1eb519ef42b4892d1db2426b3
|
||||
timeCreated: 1494878353
|
||||
licenseType: Free
|
||||
TextureImporter:
|
||||
fileIDToRecycleName: {}
|
||||
serializedVersion: 4
|
||||
mipmaps:
|
||||
mipMapMode: 0
|
||||
enableMipMap: 0
|
||||
sRGBTexture: 0
|
||||
linearTexture: 0
|
||||
fadeOut: 0
|
||||
borderMipMap: 0
|
||||
mipMapFadeDistanceStart: 1
|
||||
mipMapFadeDistanceEnd: 3
|
||||
bumpmap:
|
||||
convertToNormalMap: 0
|
||||
externalNormalMap: 0
|
||||
heightScale: 0.25
|
||||
normalMapFilter: 0
|
||||
isReadable: 0
|
||||
grayScaleToAlpha: 0
|
||||
generateCubemap: 6
|
||||
cubemapConvolution: 0
|
||||
seamlessCubemap: 0
|
||||
textureFormat: 1
|
||||
maxTextureSize: 2048
|
||||
textureSettings:
|
||||
filterMode: -1
|
||||
aniso: -1
|
||||
mipBias: -1
|
||||
wrapMode: 1
|
||||
nPOTScale: 0
|
||||
lightmap: 0
|
||||
compressionQuality: 50
|
||||
spriteMode: 1
|
||||
spriteExtrude: 1
|
||||
spriteMeshType: 1
|
||||
alignment: 0
|
||||
spritePivot: {x: 0.5, y: 0.5}
|
||||
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
|
||||
spritePixelsToUnits: 100
|
||||
alphaUsage: 1
|
||||
alphaIsTransparency: 1
|
||||
spriteTessellationDetail: -1
|
||||
textureType: 2
|
||||
textureShape: 1
|
||||
maxTextureSizeSet: 0
|
||||
compressionQualitySet: 0
|
||||
textureFormatSet: 0
|
||||
platformSettings:
|
||||
- buildTarget: DefaultTexturePlatform
|
||||
maxTextureSize: 2048
|
||||
textureFormat: -1
|
||||
textureCompression: 1
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
- buildTarget: Standalone
|
||||
maxTextureSize: 2048
|
||||
textureFormat: -1
|
||||
textureCompression: 1
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
- buildTarget: Android
|
||||
maxTextureSize: 2048
|
||||
textureFormat: -1
|
||||
textureCompression: 1
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
- buildTarget: WebGL
|
||||
maxTextureSize: 2048
|
||||
textureFormat: -1
|
||||
textureCompression: 1
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
spriteSheet:
|
||||
serializedVersion: 2
|
||||
sprites: []
|
||||
outline: []
|
||||
spritePackingTag:
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
After Width: | Height: | Size: 359 B |
@@ -0,0 +1,53 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d226a80acc775714aa78b85e16a00e9b
|
||||
TextureImporter:
|
||||
fileIDToRecycleName: {}
|
||||
serializedVersion: 2
|
||||
mipmaps:
|
||||
mipMapMode: 0
|
||||
enableMipMap: 0
|
||||
linearTexture: 1
|
||||
correctGamma: 0
|
||||
fadeOut: 0
|
||||
borderMipMap: 0
|
||||
mipMapFadeDistanceStart: 1
|
||||
mipMapFadeDistanceEnd: 3
|
||||
bumpmap:
|
||||
convertToNormalMap: 0
|
||||
externalNormalMap: 0
|
||||
heightScale: .25
|
||||
normalMapFilter: 0
|
||||
isReadable: 0
|
||||
grayScaleToAlpha: 0
|
||||
generateCubemap: 0
|
||||
cubemapConvolution: 0
|
||||
cubemapConvolutionSteps: 8
|
||||
cubemapConvolutionExponent: 1.5
|
||||
seamlessCubemap: 0
|
||||
textureFormat: -3
|
||||
maxTextureSize: 1024
|
||||
textureSettings:
|
||||
filterMode: -1
|
||||
aniso: 1
|
||||
mipBias: -1
|
||||
wrapMode: 1
|
||||
nPOTScale: 0
|
||||
lightmap: 0
|
||||
rGBM: 0
|
||||
compressionQuality: 50
|
||||
spriteMode: 0
|
||||
spriteExtrude: 1
|
||||
spriteMeshType: 1
|
||||
alignment: 0
|
||||
spritePivot: {x: .5, y: .5}
|
||||
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
|
||||
spritePixelsToUnits: 100
|
||||
alphaIsTransparency: 1
|
||||
textureType: 2
|
||||
buildTargetSettings: []
|
||||
spriteSheet:
|
||||
sprites: []
|
||||
spritePackingTag:
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
After Width: | Height: | Size: 596 B |
@@ -0,0 +1,47 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 2c2c6d283dcf3654baf40001c982891c
|
||||
TextureImporter:
|
||||
fileIDToRecycleName: {}
|
||||
serializedVersion: 2
|
||||
mipmaps:
|
||||
mipMapMode: 0
|
||||
enableMipMap: 0
|
||||
linearTexture: 1
|
||||
correctGamma: 0
|
||||
fadeOut: 0
|
||||
borderMipMap: 0
|
||||
mipMapFadeDistanceStart: 1
|
||||
mipMapFadeDistanceEnd: 3
|
||||
bumpmap:
|
||||
convertToNormalMap: 0
|
||||
externalNormalMap: 0
|
||||
heightScale: .25
|
||||
normalMapFilter: 0
|
||||
isReadable: 0
|
||||
grayScaleToAlpha: 0
|
||||
generateCubemap: 0
|
||||
seamlessCubemap: 0
|
||||
textureFormat: -3
|
||||
maxTextureSize: 1024
|
||||
textureSettings:
|
||||
filterMode: -1
|
||||
aniso: 1
|
||||
mipBias: -1
|
||||
wrapMode: 1
|
||||
nPOTScale: 0
|
||||
lightmap: 0
|
||||
compressionQuality: 50
|
||||
spriteMode: 0
|
||||
spriteExtrude: 1
|
||||
spriteMeshType: 1
|
||||
alignment: 0
|
||||
spritePivot: {x: .5, y: .5}
|
||||
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
|
||||
spritePixelsToUnits: 100
|
||||
alphaIsTransparency: 1
|
||||
textureType: 2
|
||||
buildTargetSettings: []
|
||||
spriteSheet:
|
||||
sprites: []
|
||||
spritePackingTag:
|
||||
userData:
|
||||
|
After Width: | Height: | Size: 387 B |