| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441 |
- /******************************************************************************
- * Spine Runtimes License Agreement
- * Last updated July 28, 2023. Replaces all prior versions.
- *
- * Copyright (c) 2013-2024, 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 Unity.Collections;
- using UnityEngine;
- using UnityEngine.UIElements;
- using UIVertex = UnityEngine.UIElements.Vertex;
- namespace Spine.Unity {
- public class BoundsFromAnimationAttribute : PropertyAttribute {
- public readonly string animationField;
- public readonly string dataField;
- public readonly string skinField;
- public BoundsFromAnimationAttribute (string animationField, string skinField, string dataField = "skeletonDataAsset") {
- this.animationField = animationField;
- this.skinField = skinField;
- this.dataField = dataField;
- }
- }
- [UxmlElement]
- public partial class SpineVisualElement : VisualElement {
- [UxmlAttribute]
- public SkeletonDataAsset SkeletonDataAsset {
- get { return skeletonDataAsset; }
- set {
- if (skeletonDataAsset == value) return;
- skeletonDataAsset = value;
- #if UNITY_EDITOR
- if (!Application.isPlaying)
- Initialize(true);
- #endif
- }
- }
- public SkeletonDataAsset skeletonDataAsset;
- [SpineAnimation(dataField: "SkeletonDataAsset", avoidGenericMenu: true)]
- [UxmlAttribute]
- public string StartingAnimation {
- get { return startingAnimation; }
- set {
- if (startingAnimation == value) return;
- startingAnimation = value;
- #if UNITY_EDITOR
- if (!Application.isPlaying)
- Initialize(true);
- #endif
- }
- }
- public string startingAnimation = "";
- [SpineSkin(dataField: "SkeletonDataAsset", defaultAsEmptyString: true, avoidGenericMenu: true)]
- [UxmlAttribute]
- public string InitialSkinName {
- get { return initialSkinName; }
- set {
- if (initialSkinName == value) return;
- initialSkinName = value;
- #if UNITY_EDITOR
- if (!Application.isPlaying)
- Initialize(true);
- #endif
- }
- }
- public string initialSkinName;
- [UxmlAttribute] public bool startingLoop { get; set; } = true;
- [UxmlAttribute] public float timeScale { get; set; } = 1.0f;
- [SpineAnimation(dataField: "SkeletonDataAsset", avoidGenericMenu: true)]
- [UxmlAttribute]
- public string BoundsAnimation {
- get { return boundsAnimation; }
- set {
- boundsAnimation = value;
- #if UNITY_EDITOR
- if (!Application.isPlaying) {
- if (!this.IsValid)
- Initialize(true);
- else {
- UpdateAnimation();
- }
- }
- #endif
- }
- }
- public string boundsAnimation = "";
- [UxmlAttribute]
- [BoundsFromAnimation(animationField: "BoundsAnimation",
- skinField: "InitialSkinName", dataField: "SkeletonDataAsset")]
- public Bounds ReferenceBounds {
- get { return referenceMeshBounds; }
- set {
- if (referenceMeshBounds == value) return;
- #if UNITY_EDITOR
- if (!Application.isPlaying && (value.size.x == 0 || value.size.y == 0)) return;
- #endif
- referenceMeshBounds = value;
- if (!this.IsValid) return;
- AdjustOffsetScaleToMeshBounds(rendererElement);
- }
- }
- public Bounds referenceMeshBounds;
- public AnimationState AnimationState {
- get {
- Initialize(false);
- return state;
- }
- }
- [UxmlAttribute]
- public bool freeze { get; set; }
- [UxmlAttribute]
- public bool unscaledTime { get; set; }
- /// <summary>Update mode to optionally limit updates to e.g. only apply animations but not update the mesh.</summary>
- public UpdateMode UpdateMode { get { return updateMode; } set { updateMode = value; } }
- protected UpdateMode updateMode = UpdateMode.FullUpdate;
- protected AnimationState state = null;
- protected Skeleton skeleton = null;
- protected SkeletonRendererInstruction currentInstructions = new();// to match existing code better
- protected Spine.Unity.MeshGeneratorUIElements meshGenerator = new MeshGeneratorUIElements();
- protected VisualElement rendererElement;
- IVisualElementScheduledItem scheduledItem;
- protected float scale = 100;
- protected float offsetX, offsetY;
- bool IsValid { get { return skeleton != null; } }
- public SpineVisualElement () {
- RegisterCallback<AttachToPanelEvent>(OnAttachedCallback);
- RegisterCallback<DetachFromPanelEvent>(OnDetatchedCallback);
- rendererElement = new VisualElement();
- rendererElement.generateVisualContent += GenerateVisualContents;
- rendererElement.pickingMode = PickingMode.Ignore;
- rendererElement.style.position = Position.Absolute;
- rendererElement.style.top = 0;
- rendererElement.style.left = 0;
- rendererElement.style.bottom = 0;
- rendererElement.style.right = 0;
- Add(rendererElement);
- rendererElement.RegisterCallback<GeometryChangedEvent>(OnGeometryChanged);
- }
- void OnGeometryChanged (GeometryChangedEvent evt) {
- if (!this.IsValid) return;
- if (referenceMeshBounds.size.x == 0 || referenceMeshBounds.size.y == 0) {
- AdjustReferenceMeshBounds();
- }
- AdjustOffsetScaleToMeshBounds(rendererElement);
- }
- void OnAttachedCallback (AttachToPanelEvent evt) {
- Initialize(false);
- }
- void OnDetatchedCallback (DetachFromPanelEvent evt) {
- ClearElement();
- }
- public void ClearElement () {
- skeleton = null;
- DisposeUISubmeshes();
- }
- public virtual void Update () {
- #if UNITY_EDITOR
- if (!Application.isPlaying) {
- Update(0f);
- return;
- }
- #endif
- if (freeze) return;
- Update(unscaledTime ? Time.unscaledDeltaTime : Time.deltaTime);
- rendererElement.MarkDirtyRepaint();
- }
- public virtual void Update (float deltaTime) {
- if (!this.IsValid) return;
- if (updateMode < UpdateMode.OnlyAnimationStatus)
- return;
- UpdateAnimationStatus(deltaTime);
- if (updateMode == UpdateMode.OnlyAnimationStatus)
- return;
- ApplyAnimation();
- }
- protected void UpdateAnimationStatus (float deltaTime) {
- deltaTime *= timeScale;
- state.Update(deltaTime);
- skeleton.Update(deltaTime);
- }
- protected void ApplyAnimation () {
- if (updateMode != UpdateMode.OnlyEventTimelines)
- state.Apply(skeleton);
- else
- state.ApplyEventTimelinesOnly(skeleton);
- skeleton.UpdateWorldTransform(Skeleton.Physics.Update);
- }
- void Initialize (bool overwrite) {
- if (this.IsValid && !overwrite) return;
- if (this.SkeletonDataAsset == null) return;
- var skeletonData = this.SkeletonDataAsset.GetSkeletonData(false);
- if (skeletonData == null) return;
- if (SkeletonDataAsset.atlasAssets.Length <= 0 || SkeletonDataAsset.atlasAssets[0].MaterialCount <= 0) return;
- this.state = new Spine.AnimationState(SkeletonDataAsset.GetAnimationStateData());
- if (state == null) {
- Clear();
- return;
- }
- this.skeleton = new Skeleton(skeletonData) {
- ScaleX = 1,
- ScaleY = -1
- };
- // Set the initial Skin and Animation
- if (!string.IsNullOrEmpty(initialSkinName))
- skeleton.SetSkin(initialSkinName);
- string displayedAnimation = Application.isPlaying ? startingAnimation : boundsAnimation;
- if (!string.IsNullOrEmpty(displayedAnimation)) {
- var animationObject = skeletonData.FindAnimation(displayedAnimation);
- if (animationObject != null) {
- state.SetAnimation(0, animationObject, startingLoop);
- }
- }
- if (referenceMeshBounds.size.x == 0 || referenceMeshBounds.size.y == 0) {
- AdjustReferenceMeshBounds();
- AdjustOffsetScaleToMeshBounds(rendererElement);
- }
- if (scheduledItem == null)
- scheduledItem = schedule.Execute(Update).Every(1);
- if (!Application.isPlaying)
- Update(0.0f);
- rendererElement.MarkDirtyRepaint();
- }
- protected void UpdateAnimation () {
- this.state.ClearTracks();
- skeleton.SetToSetupPose();
- string displayedAnimation = Application.isPlaying ? startingAnimation : boundsAnimation;
- if (!string.IsNullOrEmpty(displayedAnimation)) {
- var animationObject = SkeletonDataAsset.GetSkeletonData(false).FindAnimation(displayedAnimation);
- if (animationObject != null) {
- state.SetAnimation(0, animationObject, startingLoop);
- }
- }
- if (referenceMeshBounds.size.x == 0 || referenceMeshBounds.size.y == 0) {
- AdjustReferenceMeshBounds();
- AdjustOffsetScaleToMeshBounds(rendererElement);
- }
- Update(0.0f);
- rendererElement.MarkDirtyRepaint();
- }
- protected class UISubmesh {
- public NativeArray<UIVertex>? vertices = null;
- public NativeArray<ushort>? indices = null;
- public NativeSlice<UIVertex> verticesSlice;
- public NativeSlice<ushort> indicesSlice;
- }
- protected readonly ExposedList<UISubmesh> uiSubmeshes = new ExposedList<UISubmesh>();
- protected void GenerateVisualContents (MeshGenerationContext context) {
- if (!this.IsValid) return;
- MeshGeneratorUIElements.GenerateSkeletonRendererInstruction(currentInstructions, skeleton, null,
- null,
- false,
- false);
- int submeshCount = currentInstructions.submeshInstructions.Count;
- PrepareUISubmeshCount(submeshCount);
- // Generate meshes.
- for (int i = 0; i < submeshCount; i++) {
- var submeshInstructionItem = currentInstructions.submeshInstructions.Items[i];
- UISubmesh uiSubmesh = uiSubmeshes.Items[i];
- meshGenerator.Begin();
- meshGenerator.AddSubmesh(submeshInstructionItem);
- // clipping is done, vertex counts are final.
- PrepareUISubmesh(uiSubmesh, meshGenerator.VertexCount, meshGenerator.SubmeshIndexCount(0));
- meshGenerator.FillVertexData(ref uiSubmesh.verticesSlice);
- meshGenerator.FillTrianglesSingleSubmesh(ref uiSubmesh.indicesSlice);
- var submeshMaterial = submeshInstructionItem.material;
- Texture usedTexture = submeshMaterial.mainTexture;
- FillContext(context, uiSubmesh, usedTexture);
- }
- }
- protected void PrepareUISubmeshCount (int targetCount) {
- int oldCount = uiSubmeshes.Count;
- uiSubmeshes.EnsureCapacity(targetCount);
- for (int i = oldCount; i < targetCount; ++i) {
- uiSubmeshes.Add(new UISubmesh());
- }
- }
- protected void PrepareUISubmesh (UISubmesh uiSubmesh, int vertexCount, int indexCount) {
- bool shallReallocateVertices = uiSubmesh.vertices == null || uiSubmesh.vertices.Value.Length < vertexCount;
- if (shallReallocateVertices) {
- int allocationCount = vertexCount;
- if (uiSubmesh.vertices != null) {
- allocationCount = Math.Max(vertexCount, 2 * uiSubmesh.vertices.Value.Length);
- uiSubmesh.vertices.Value.Dispose();
- }
- uiSubmesh.vertices = new NativeArray<UIVertex>(allocationCount, Allocator.Persistent, NativeArrayOptions.ClearMemory);
- }
- if (shallReallocateVertices || uiSubmesh.verticesSlice.Length != vertexCount) {
- uiSubmesh.verticesSlice = new NativeSlice<UIVertex>(uiSubmesh.vertices.Value, 0, vertexCount);
- }
- bool shallReallocateIndices = uiSubmesh.indices == null || uiSubmesh.indices.Value.Length < indexCount;
- if (shallReallocateIndices) {
- int allocationCount = indexCount;
- if (uiSubmesh.indices != null) {
- allocationCount = Math.Max(indexCount, uiSubmesh.indices.Value.Length * 2);
- uiSubmesh.indices.Value.Dispose();
- }
- uiSubmesh.indices = new NativeArray<ushort>(allocationCount, Allocator.Persistent, NativeArrayOptions.ClearMemory);
- }
- if (shallReallocateIndices || uiSubmesh.indicesSlice.Length != indexCount) {
- uiSubmesh.indicesSlice = new NativeSlice<ushort>(uiSubmesh.indices.Value, 0, indexCount);
- }
- }
- protected void DisposeUISubmeshes () {
- for (int i = 0, count = uiSubmeshes.Count; i < count; ++i) {
- UISubmesh uiSubmesh = uiSubmeshes.Items[i];
- if (uiSubmesh.vertices != null) uiSubmesh.vertices.Value.Dispose();
- if (uiSubmesh.indices != null) uiSubmesh.indices.Value.Dispose();
- }
- uiSubmeshes.Clear();
- }
- void FillContext (MeshGenerationContext context, UISubmesh submesh, Texture texture) {
- MeshWriteData meshWriteData = context.Allocate(submesh.verticesSlice.Length, submesh.indicesSlice.Length, texture);
- meshWriteData.SetAllVertices(submesh.verticesSlice);
- meshWriteData.SetAllIndices(submesh.indicesSlice);
- }
- public void AdjustReferenceMeshBounds () {
- if (skeleton == null)
- return;
- // Need one update to obtain valid mesh bounds
- Update(0.0f);
- MeshGeneratorUIElements.GenerateSkeletonRendererInstruction(currentInstructions, skeleton,
- null, null, false, false);
- int submeshCount = currentInstructions.submeshInstructions.Count;
- meshGenerator.Begin();
- for (int i = 0; i < submeshCount; i++) {
- var submeshInstructionItem = currentInstructions.submeshInstructions.Items[i];
- meshGenerator.AddSubmesh(submeshInstructionItem);
- }
- Bounds meshBounds = meshGenerator.GetMeshBounds();
- if (meshBounds.extents.x == 0 || meshBounds.extents.y == 0) {
- ReferenceBounds = new Bounds(Vector3.zero, Vector3.one * 2f);
- } else {
- ReferenceBounds = meshBounds;
- }
- }
- void AdjustOffsetScaleToMeshBounds (VisualElement visualElement) {
- Rect targetRect = visualElement.layout;
- if (float.IsNaN(targetRect.width)) return;
- float xScale = targetRect.width / referenceMeshBounds.size.x;
- float yScale = targetRect.height / referenceMeshBounds.size.y;
- this.scale = Math.Min(xScale, yScale);
- float targetOffsetX = targetRect.width / 2;
- float targetOffsetY = targetRect.height / 2;
- this.offsetX = targetOffsetX - referenceMeshBounds.center.x * this.scale;
- this.offsetY = targetOffsetY - referenceMeshBounds.center.y * this.scale;
- visualElement.style.translate = new StyleTranslate(new Translate(offsetX, offsetY, 0));
- visualElement.style.transformOrigin = new TransformOrigin(0, 0, 0);
- visualElement.style.scale = new Scale(new Vector3(scale, scale, 1));
- }
- }
- }
|