mirror of
https://github.com/EsotericSoftware/spine-runtimes.git
synced 2026-03-26 22:49:01 +08:00
[unity] Added RenderCombinedMesh sample component for combined outlines with SkeletonRenderSeparator or multiple atlas pages.
This commit is contained in:
parent
30311f2cd8
commit
ed75bffc6f
@ -105,6 +105,12 @@
|
|||||||
* Added `SkeletonGraphic.MeshScale` property to allow access to calculated mesh scale. `MeshScale` is based on (1) Canvas pixels per unit, and (2) `RectTransform` bounds when using `Layout Scale Mode` other than `None` at `SkeletonGraphic` which scales the skeleton mesh to fit the parent `RectTransform` bounds accordingly.
|
* Added `SkeletonGraphic.MeshScale` property to allow access to calculated mesh scale. `MeshScale` is based on (1) Canvas pixels per unit, and (2) `RectTransform` bounds when using `Layout Scale Mode` other than `None` at `SkeletonGraphic` which scales the skeleton mesh to fit the parent `RectTransform` bounds accordingly.
|
||||||
* Added `updateSeparatorPartScale` property to `SkeletonGraphic` to let render separator parts follow the scale (lossy scale) of the `SkeletonGraphic` GameObject. Defaults to `false` to maintain existing behaviour.
|
* Added `updateSeparatorPartScale` property to `SkeletonGraphic` to let render separator parts follow the scale (lossy scale) of the `SkeletonGraphic` GameObject. Defaults to `false` to maintain existing behaviour.
|
||||||
* Added experimental `EditorSkeletonPlayer` component to allow Editor playback of the initial animation set at `SkeletonAnimation` or `SkeletonGraphic` components. Add this component to your skeleton GameObject to enable the in-editor animation preview. Allows configurations for continuous playback when selected, deselected, and alternative single-frame preview by setting `Fixed Track Time` to any value other than 0. Limitations: At skeletons with variable material count the Inspector preview may be too unresponsive. It is then recommended to disable the `EditorSkeletonPlayer` component (at the top of the Inspector) to make it responsive again, then you can disable `Play When Selected` and re-enable the component to preview playback only when deselected.
|
* Added experimental `EditorSkeletonPlayer` component to allow Editor playback of the initial animation set at `SkeletonAnimation` or `SkeletonGraphic` components. Add this component to your skeleton GameObject to enable the in-editor animation preview. Allows configurations for continuous playback when selected, deselected, and alternative single-frame preview by setting `Fixed Track Time` to any value other than 0. Limitations: At skeletons with variable material count the Inspector preview may be too unresponsive. It is then recommended to disable the `EditorSkeletonPlayer` component (at the top of the Inspector) to make it responsive again, then you can disable `Play When Selected` and re-enable the component to preview playback only when deselected.
|
||||||
|
* Added example component `RenderCombinedMesh` to render a combined mesh of multiple meshes or submeshes. This is required by `OutlineOnly` shaders to render a combined outline when using `SkeletonRenderSeparator` or multiple atlas pages which would normally lead to outlines around individual parts. To add a combined outline to your SkeletenRenderer:
|
||||||
|
1) Add a child GameObject and move it a bit back (e.g. position Z = 0.01).
|
||||||
|
2) Add a `RenderCombinedMesh` component, provided in the `Spine Examples/Scripts/Sample Components` directory.
|
||||||
|
3) Copy the original material, add *_Outline* to its name and set the shader to your outline-only shader like `Universal Render Pipeline/Spine/Outline/Skeleton-OutlineOnly` or `Spine/Outline/OutlineOnly-ZWrite`.
|
||||||
|
4) Assign this *_Outline* material at the new child GameObject's `MeshRenderer` component.
|
||||||
|
If you are using `SkeletonRenderSeparator` and need to enable and disable the `SkeletonRenderSeparator` component at runtime, you can increase the `RenderCombinedMesh` `Reference Renderers` array by one and assign the `SkeletonRenderer` itself at the last entry after the parts renderers. Disabled `MeshRenderer` components will be skipped when combining the final mesh, so the combined mesh is automatically filled from the desired active renderers.
|
||||||
|
|
||||||
* **Breaking changes**
|
* **Breaking changes**
|
||||||
* Made `SkeletonGraphic.unscaledTime` parameter protected, use the new property `UnscaledTime` instead.
|
* Made `SkeletonGraphic.unscaledTime` parameter protected, use the new property `UnscaledTime` instead.
|
||||||
|
|||||||
@ -0,0 +1,253 @@
|
|||||||
|
/******************************************************************************
|
||||||
|
* Spine Runtimes License Agreement
|
||||||
|
* Last updated July 28, 2023. Replaces all prior versions.
|
||||||
|
*
|
||||||
|
* Copyright (c) 2013-2023, Esoteric Software LLC
|
||||||
|
*
|
||||||
|
* Integration of the Spine Runtimes into software or otherwise creating
|
||||||
|
* derivative works of the Spine Runtimes is permitted under the terms and
|
||||||
|
* conditions of Section 2 of the Spine Editor License Agreement:
|
||||||
|
* http://esotericsoftware.com/spine-editor-license
|
||||||
|
*
|
||||||
|
* Otherwise, it is permitted to integrate the Spine Runtimes into software or
|
||||||
|
* otherwise create derivative works of the Spine Runtimes (collectively,
|
||||||
|
* "Products"), provided that each user of the Products must obtain their own
|
||||||
|
* Spine Editor license and redistribution of the Products in any form must
|
||||||
|
* include this license and copyright notice.
|
||||||
|
*
|
||||||
|
* THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY
|
||||||
|
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||||
|
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||||
|
* DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY
|
||||||
|
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||||
|
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES,
|
||||||
|
* BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND
|
||||||
|
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THE
|
||||||
|
* SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
*****************************************************************************/
|
||||||
|
|
||||||
|
#if UNITY_2018_3 || UNITY_2019 || UNITY_2018_3_OR_NEWER
|
||||||
|
#define NEW_PREFAB_SYSTEM
|
||||||
|
#endif
|
||||||
|
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
namespace Spine.Unity.Examples {
|
||||||
|
|
||||||
|
#if NEW_PREFAB_SYSTEM
|
||||||
|
[ExecuteAlways]
|
||||||
|
#else
|
||||||
|
[ExecuteInEditMode]
|
||||||
|
#endif
|
||||||
|
public class RenderCombinedMesh : MonoBehaviour {
|
||||||
|
public SkeletonRenderer skeletonRenderer;
|
||||||
|
public SkeletonRenderSeparator renderSeparator;
|
||||||
|
public MeshRenderer[] referenceRenderers;
|
||||||
|
|
||||||
|
bool updateViaSkeletonCallback = false;
|
||||||
|
MeshFilter[] referenceMeshFilters;
|
||||||
|
MeshRenderer ownRenderer;
|
||||||
|
MeshFilter ownMeshFilter;
|
||||||
|
|
||||||
|
protected DoubleBuffered<Mesh> doubleBufferedMesh;
|
||||||
|
protected ExposedList<Vector3> positionBuffer;
|
||||||
|
protected ExposedList<Color32> colorBuffer;
|
||||||
|
protected ExposedList<Vector2> uvBuffer;
|
||||||
|
protected ExposedList<int> indexBuffer;
|
||||||
|
|
||||||
|
#if UNITY_EDITOR
|
||||||
|
private void Reset () {
|
||||||
|
if (skeletonRenderer == null)
|
||||||
|
skeletonRenderer = this.GetComponentInParent<SkeletonRenderer>();
|
||||||
|
GatherRenderers();
|
||||||
|
|
||||||
|
Awake();
|
||||||
|
if (referenceRenderers.Length > 0)
|
||||||
|
ownRenderer.sharedMaterial = referenceRenderers[0].sharedMaterial;
|
||||||
|
|
||||||
|
LateUpdate();
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
protected void GatherRenderers () {
|
||||||
|
referenceRenderers = this.GetComponentsInChildren<MeshRenderer>();
|
||||||
|
if (referenceRenderers.Length == 0 ||
|
||||||
|
(referenceRenderers.Length == 1 && referenceRenderers[0].gameObject == this.gameObject)) {
|
||||||
|
Transform parent = this.transform.parent;
|
||||||
|
if (parent)
|
||||||
|
referenceRenderers = parent.GetComponentsInChildren<MeshRenderer>();
|
||||||
|
}
|
||||||
|
referenceRenderers = referenceRenderers.Where(
|
||||||
|
(val, idx) => val.gameObject != this.gameObject && val.enabled).ToArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Awake () {
|
||||||
|
if (skeletonRenderer == null)
|
||||||
|
skeletonRenderer = this.GetComponentInParent<SkeletonRenderer>();
|
||||||
|
if (referenceRenderers == null || referenceRenderers.Length == 0) {
|
||||||
|
GatherRenderers();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (renderSeparator == null) {
|
||||||
|
if (skeletonRenderer)
|
||||||
|
renderSeparator = skeletonRenderer.GetComponent<SkeletonRenderSeparator>();
|
||||||
|
else
|
||||||
|
renderSeparator = this.GetComponentInParent<SkeletonRenderSeparator>();
|
||||||
|
}
|
||||||
|
|
||||||
|
int count = referenceRenderers.Length;
|
||||||
|
referenceMeshFilters = new MeshFilter[count];
|
||||||
|
for (int i = 0; i < count; ++i) {
|
||||||
|
referenceMeshFilters[i] = referenceRenderers[i].GetComponent<MeshFilter>();
|
||||||
|
}
|
||||||
|
|
||||||
|
ownRenderer = this.GetComponent<MeshRenderer>();
|
||||||
|
if (ownRenderer == null)
|
||||||
|
ownRenderer = this.gameObject.AddComponent<MeshRenderer>();
|
||||||
|
ownMeshFilter = this.GetComponent<MeshFilter>();
|
||||||
|
if (ownMeshFilter == null)
|
||||||
|
ownMeshFilter = this.gameObject.AddComponent<MeshFilter>();
|
||||||
|
}
|
||||||
|
|
||||||
|
void OnEnable () {
|
||||||
|
#if UNITY_EDITOR
|
||||||
|
if (Application.isPlaying)
|
||||||
|
Awake();
|
||||||
|
#endif
|
||||||
|
if (skeletonRenderer) {
|
||||||
|
skeletonRenderer.OnMeshAndMaterialsUpdated -= UpdateOnCallback;
|
||||||
|
skeletonRenderer.OnMeshAndMaterialsUpdated += UpdateOnCallback;
|
||||||
|
updateViaSkeletonCallback = true;
|
||||||
|
}
|
||||||
|
if (renderSeparator) {
|
||||||
|
renderSeparator.OnMeshAndMaterialsUpdated -= UpdateOnCallback;
|
||||||
|
renderSeparator.OnMeshAndMaterialsUpdated += UpdateOnCallback;
|
||||||
|
updateViaSkeletonCallback = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void OnDisable () {
|
||||||
|
if (skeletonRenderer)
|
||||||
|
skeletonRenderer.OnMeshAndMaterialsUpdated -= UpdateOnCallback;
|
||||||
|
if (renderSeparator)
|
||||||
|
renderSeparator.OnMeshAndMaterialsUpdated -= UpdateOnCallback;
|
||||||
|
}
|
||||||
|
|
||||||
|
void OnDestroy () {
|
||||||
|
for (int i = 0; i < 2; ++i) {
|
||||||
|
Mesh mesh = doubleBufferedMesh.GetNext();
|
||||||
|
#if UNITY_EDITOR
|
||||||
|
if (Application.isEditor && !Application.isPlaying)
|
||||||
|
UnityEngine.Object.DestroyImmediate(mesh);
|
||||||
|
else
|
||||||
|
UnityEngine.Object.Destroy(mesh);
|
||||||
|
#else
|
||||||
|
UnityEngine.Object.Destroy(mesh);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void LateUpdate () {
|
||||||
|
#if UNITY_EDITOR
|
||||||
|
if (!Application.isPlaying) {
|
||||||
|
UpdateMesh();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
if (updateViaSkeletonCallback)
|
||||||
|
return;
|
||||||
|
UpdateMesh();
|
||||||
|
}
|
||||||
|
|
||||||
|
void UpdateOnCallback (SkeletonRenderer r) {
|
||||||
|
UpdateMesh();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void EnsureBufferSizes (int combinedVertexCount, int combinedIndexCount) {
|
||||||
|
if (positionBuffer == null) {
|
||||||
|
positionBuffer = new ExposedList<Vector3>(combinedVertexCount);
|
||||||
|
uvBuffer = new ExposedList<Vector2>(combinedVertexCount);
|
||||||
|
colorBuffer = new ExposedList<Color32>(combinedVertexCount);
|
||||||
|
indexBuffer = new ExposedList<int>(combinedIndexCount);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (positionBuffer.Count < combinedVertexCount) {
|
||||||
|
positionBuffer.Resize(combinedVertexCount);
|
||||||
|
uvBuffer.Resize(combinedVertexCount);
|
||||||
|
colorBuffer.Resize(combinedVertexCount);
|
||||||
|
}
|
||||||
|
if (indexBuffer.Count < combinedIndexCount) {
|
||||||
|
indexBuffer.Resize(combinedIndexCount);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void InitMesh () {
|
||||||
|
if (doubleBufferedMesh == null) {
|
||||||
|
doubleBufferedMesh = new DoubleBuffered<Mesh>();
|
||||||
|
for (int i = 0; i < 2; ++i) {
|
||||||
|
Mesh combinedMesh = doubleBufferedMesh.GetNext();
|
||||||
|
combinedMesh.MarkDynamic();
|
||||||
|
combinedMesh.name = "RenderCombinedMesh" + i;
|
||||||
|
combinedMesh.subMeshCount = 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void UpdateMesh () {
|
||||||
|
InitMesh();
|
||||||
|
int combinedVertexCount = 0;
|
||||||
|
int combinedIndexCount = 0;
|
||||||
|
GetCombinedMeshInfo(ref combinedVertexCount, ref combinedIndexCount);
|
||||||
|
|
||||||
|
EnsureBufferSizes(combinedVertexCount, combinedIndexCount);
|
||||||
|
|
||||||
|
int combinedV = 0;
|
||||||
|
int combinedI = 0;
|
||||||
|
for (int r = 0, rendererCount = referenceMeshFilters.Length; r < rendererCount; ++r) {
|
||||||
|
MeshFilter meshFilter = referenceMeshFilters[r];
|
||||||
|
Mesh mesh = meshFilter.sharedMesh;
|
||||||
|
if (mesh == null) continue;
|
||||||
|
|
||||||
|
int vertexCount = mesh.vertexCount;
|
||||||
|
Vector3[] positions = mesh.vertices;
|
||||||
|
Vector2[] uvs = mesh.uv;
|
||||||
|
Color32[] colors = mesh.colors32;
|
||||||
|
|
||||||
|
System.Array.Copy(positions, 0, this.positionBuffer.Items, combinedV, vertexCount);
|
||||||
|
System.Array.Copy(uvs, 0, this.uvBuffer.Items, combinedV, vertexCount);
|
||||||
|
System.Array.Copy(colors, 0, this.colorBuffer.Items, combinedV, vertexCount);
|
||||||
|
combinedV += vertexCount;
|
||||||
|
|
||||||
|
for (int s = 0, submeshCount = mesh.subMeshCount; s < submeshCount; ++s) {
|
||||||
|
int submeshIndexCount = (int)mesh.GetIndexCount(s);
|
||||||
|
int[] submeshIndices = mesh.GetIndices(s);
|
||||||
|
System.Array.Copy(submeshIndices, 0, this.indexBuffer.Items, combinedI, submeshIndexCount);
|
||||||
|
combinedI += submeshIndexCount;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Mesh combinedMesh = doubleBufferedMesh.GetNext();
|
||||||
|
combinedMesh.SetVertices(this.positionBuffer.Items, 0, this.positionBuffer.Count);
|
||||||
|
combinedMesh.SetUVs(0, this.uvBuffer.Items, 0, this.uvBuffer.Count);
|
||||||
|
combinedMesh.SetColors(this.colorBuffer.Items, 0, this.colorBuffer.Count);
|
||||||
|
combinedMesh.SetTriangles(this.indexBuffer.Items, 0, this.indexBuffer.Count, 0);
|
||||||
|
ownMeshFilter.sharedMesh = combinedMesh;
|
||||||
|
}
|
||||||
|
|
||||||
|
void GetCombinedMeshInfo (ref int vertexCount, ref int indexCount) {
|
||||||
|
for (int r = 0, rendererCount = referenceMeshFilters.Length; r < rendererCount; ++r) {
|
||||||
|
MeshFilter meshFilter = referenceMeshFilters[r];
|
||||||
|
Mesh mesh = meshFilter.sharedMesh;
|
||||||
|
if (mesh == null) continue;
|
||||||
|
|
||||||
|
vertexCount += mesh.vertexCount;
|
||||||
|
for (int s = 0, submeshCount = mesh.subMeshCount; s < submeshCount; ++s) {
|
||||||
|
indexCount += (int)mesh.GetIndexCount(s);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,11 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 05709c69e8e14304b9781652ad05daef
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
@ -2,7 +2,7 @@
|
|||||||
"name": "com.esotericsoftware.spine.spine-unity-examples",
|
"name": "com.esotericsoftware.spine.spine-unity-examples",
|
||||||
"displayName": "spine-unity Runtime Examples",
|
"displayName": "spine-unity Runtime Examples",
|
||||||
"description": "This plugin provides example scenes and scripts for the spine-unity runtime.",
|
"description": "This plugin provides example scenes and scripts for the spine-unity runtime.",
|
||||||
"version": "4.1.13",
|
"version": "4.1.21",
|
||||||
"unity": "2018.3",
|
"unity": "2018.3",
|
||||||
"author": {
|
"author": {
|
||||||
"name": "Esoteric Software",
|
"name": "Esoteric Software",
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user