From 0820bd70242ca347fd37449be37efa0ac6f1479b Mon Sep 17 00:00:00 2001 From: Harald Csaszar Date: Thu, 7 Apr 2022 16:34:57 +0200 Subject: [PATCH 1/7] [unity] Fixed incorrect reloading behaviour (especially of deform timeline) at Domain Reload. Closes #2066. --- .../Editor/Utility/DataReloadHandler.cs | 40 +++++++------------ 1 file changed, 15 insertions(+), 25 deletions(-) diff --git a/spine-unity/Assets/Spine/Editor/spine-unity/Editor/Utility/DataReloadHandler.cs b/spine-unity/Assets/Spine/Editor/spine-unity/Editor/Utility/DataReloadHandler.cs index 6e802ada9..206520587 100644 --- a/spine-unity/Assets/Spine/Editor/spine-unity/Editor/Utility/DataReloadHandler.cs +++ b/spine-unity/Assets/Spine/Editor/spine-unity/Editor/Utility/DataReloadHandler.cs @@ -80,36 +80,25 @@ namespace Spine.Unity.Editor { // Here we save the skeletonGraphic.skeletonDataAsset asset path in order // to restore it later. var activeSkeletonGraphics = GameObject.FindObjectsOfType(); - foreach (var sg in activeSkeletonGraphics) { - var skeletonDataAsset = sg.skeletonDataAsset; + foreach (var skeletonGraphic in activeSkeletonGraphics) { + var skeletonDataAsset = skeletonGraphic.skeletonDataAsset; if (skeletonDataAsset != null) { var assetPath = AssetDatabase.GetAssetPath(skeletonDataAsset); - var sgID = sg.GetInstanceID(); + var sgID = skeletonGraphic.GetInstanceID(); savedSkeletonDataAssetAtSKeletonGraphicID[sgID] = assetPath; skeletonDataAssetsToReload.Add(skeletonDataAsset); } } - foreach (var sda in skeletonDataAssetsToReload) { - sda.Clear(); - sda.GetSkeletonData(true); + foreach (var skeletonDataAsset in skeletonDataAssetsToReload) { + skeletonDataAsset.Clear(); + skeletonDataAsset.GetSkeletonData(true); } - foreach (var sr in activeSkeletonRenderers) { - var meshRenderer = sr.GetComponent(); - var sharedMaterials = meshRenderer.sharedMaterials; - foreach (var m in sharedMaterials) { - if (m == null) { - sr.Initialize(true); - break; - } - } - } - - foreach (var sg in activeSkeletonGraphics) { - if (sg.mainTexture == null) - sg.Initialize(true); - } + foreach (var skeletonRenderer in activeSkeletonRenderers) + skeletonRenderer.Initialize(true); + foreach (var skeletonGraphic in activeSkeletonGraphics) + skeletonGraphic.Initialize(true); } public static void ReloadSceneSkeletonComponents (SkeletonDataAsset skeletonDataAsset) { @@ -119,13 +108,14 @@ namespace Spine.Unity.Editor { if (EditorApplication.isPlayingOrWillChangePlaymode) return; var activeSkeletonRenderers = GameObject.FindObjectsOfType(); - foreach (var sr in activeSkeletonRenderers) { - if (sr.isActiveAndEnabled && sr.skeletonDataAsset == skeletonDataAsset) sr.Initialize(true); + foreach (var renderer in activeSkeletonRenderers) { + if (renderer.isActiveAndEnabled && renderer.skeletonDataAsset == skeletonDataAsset) renderer.Initialize(true); } var activeSkeletonGraphics = GameObject.FindObjectsOfType(); - foreach (var sg in activeSkeletonGraphics) { - if (sg.isActiveAndEnabled && sg.skeletonDataAsset == skeletonDataAsset) sg.Initialize(true); + foreach (var graphic in activeSkeletonGraphics) { + if (graphic.isActiveAndEnabled && graphic.skeletonDataAsset == skeletonDataAsset) + graphic.Initialize(true); } } From e4372e5329f9fe66b7b97e939d2fa800b04ce99a Mon Sep 17 00:00:00 2001 From: Mario Zechner Date: Thu, 7 Apr 2022 20:18:04 +0200 Subject: [PATCH 2/7] [ts] Enable TS strict mode. Closes #2058 --- spine-ts/spine-canvas/src/AssetManager.ts | 4 +- spine-ts/spine-canvas/src/CanvasTexture.ts | 2 +- spine-ts/spine-canvas/src/SkeletonRenderer.ts | 9 +- spine-ts/spine-core/src/Animation.ts | 56 +-- spine-ts/spine-core/src/AnimationState.ts | 104 +++-- spine-ts/spine-core/src/AnimationStateData.ts | 2 +- spine-ts/spine-core/src/AssetManagerBase.ts | 32 +- .../spine-core/src/AtlasAttachmentLoader.ts | 11 +- spine-ts/spine-core/src/Bone.ts | 8 +- spine-ts/spine-core/src/BoneData.ts | 6 +- spine-ts/spine-core/src/Event.ts | 4 +- spine-ts/spine-core/src/EventData.ts | 6 +- spine-ts/spine-core/src/IkConstraint.ts | 19 +- spine-ts/spine-core/src/IkConstraintData.ts | 7 +- spine-ts/spine-core/src/PathConstraint.ts | 21 +- spine-ts/spine-core/src/PathConstraintData.ts | 13 +- spine-ts/spine-core/src/Skeleton.ts | 46 +- spine-ts/spine-core/src/SkeletonBinary.ts | 74 ++-- spine-ts/spine-core/src/SkeletonBounds.ts | 2 +- spine-ts/spine-core/src/SkeletonClipping.ts | 10 +- spine-ts/spine-core/src/SkeletonData.ts | 12 +- spine-ts/spine-core/src/SkeletonJson.ts | 100 +++-- spine-ts/spine-core/src/Skin.ts | 8 +- spine-ts/spine-core/src/Slot.ts | 18 +- spine-ts/spine-core/src/SlotData.ts | 10 +- spine-ts/spine-core/src/TextureAtlas.ts | 104 ++--- .../spine-core/src/TransformConstraint.ts | 17 +- .../spine-core/src/TransformConstraintData.ts | 7 +- spine-ts/spine-core/src/Utils.ts | 4 +- .../spine-core/src/attachments/Attachment.ts | 7 +- .../src/attachments/AttachmentLoader.ts | 4 +- .../src/attachments/ClippingAttachment.ts | 2 +- .../src/attachments/HasTextureRegion.ts | 4 +- .../src/attachments/MeshAttachment.ts | 28 +- .../src/attachments/PathAttachment.ts | 2 +- .../src/attachments/RegionAttachment.ts | 13 +- spine-ts/spine-player/src/Player.ts | 404 +++++++++--------- spine-ts/spine-threejs/src/AssetManager.ts | 4 +- spine-ts/spine-threejs/src/MeshBatcher.ts | 12 +- spine-ts/spine-threejs/src/SkeletonMesh.ts | 25 +- spine-ts/spine-threejs/src/ThreeJsTexture.ts | 3 +- spine-ts/spine-webgl/example/index.html | 2 +- spine-ts/spine-webgl/src/AssetManager.ts | 2 +- spine-ts/spine-webgl/src/GLTexture.ts | 2 +- spine-ts/spine-webgl/src/Input.ts | 7 +- spine-ts/spine-webgl/src/LoadingScreen.ts | 10 +- spine-ts/spine-webgl/src/Matrix4.ts | 14 +- spine-ts/spine-webgl/src/Mesh.ts | 4 +- spine-ts/spine-webgl/src/PolygonBatcher.ts | 11 +- spine-ts/spine-webgl/src/SceneRenderer.ts | 36 +- spine-ts/spine-webgl/src/Shader.ts | 14 +- spine-ts/spine-webgl/src/ShapeRenderer.ts | 61 +-- .../spine-webgl/src/SkeletonDebugRenderer.ts | 2 +- spine-ts/spine-webgl/src/SkeletonRenderer.ts | 18 +- spine-ts/spine-webgl/src/SpineCanvas.ts | 16 +- spine-ts/spine-webgl/src/WebGL.ts | 32 +- spine-ts/tsconfig.base.json | 5 +- 57 files changed, 802 insertions(+), 658 deletions(-) diff --git a/spine-ts/spine-canvas/src/AssetManager.ts b/spine-ts/spine-canvas/src/AssetManager.ts index a5ea16af3..b7c42b28b 100644 --- a/spine-ts/spine-canvas/src/AssetManager.ts +++ b/spine-ts/spine-canvas/src/AssetManager.ts @@ -31,7 +31,7 @@ import { AssetManagerBase, Downloader } from "@esotericsoftware/spine-core" import { CanvasTexture } from "./CanvasTexture"; export class AssetManager extends AssetManagerBase { - constructor (pathPrefix: string = "", downloader: Downloader = null) { - super((image: HTMLImageElement) => { return new CanvasTexture(image); }, pathPrefix, downloader); + constructor (pathPrefix: string = "", downloader: Downloader = new Downloader()) { + super((image: HTMLImageElement | ImageBitmap) => { return new CanvasTexture(image); }, pathPrefix, downloader); } } diff --git a/spine-ts/spine-canvas/src/CanvasTexture.ts b/spine-ts/spine-canvas/src/CanvasTexture.ts index ad21025b0..510c45ccb 100644 --- a/spine-ts/spine-canvas/src/CanvasTexture.ts +++ b/spine-ts/spine-canvas/src/CanvasTexture.ts @@ -30,7 +30,7 @@ import { Texture, TextureFilter, TextureWrap } from "@esotericsoftware/spine-core"; export class CanvasTexture extends Texture { - constructor (image: HTMLImageElement) { + constructor (image: HTMLImageElement | ImageBitmap) { super(image); } diff --git a/spine-ts/spine-canvas/src/SkeletonRenderer.ts b/spine-ts/spine-canvas/src/SkeletonRenderer.ts index b2b6d82c9..67340c5bd 100644 --- a/spine-ts/spine-canvas/src/SkeletonRenderer.ts +++ b/spine-ts/spine-canvas/src/SkeletonRenderer.ts @@ -85,7 +85,7 @@ export class SkeletonRenderer { let w = region.width, h = region.height; ctx.translate(w / 2, h / 2); - if (attachment.region.degrees == 90) { + if (attachment.region!.degrees == 90) { let t = w; w = h; h = t; @@ -107,9 +107,9 @@ export class SkeletonRenderer { let skeletonColor = skeleton.color; let drawOrder = skeleton.drawOrder; - let blendMode: BlendMode = null; + let blendMode: BlendMode | null = null; let vertices: ArrayLike = this.vertices; - let triangles: Array = null; + let triangles: Array | null = null; for (let i = 0, n = drawOrder.length; i < n; i++) { let slot = drawOrder[i]; @@ -127,7 +127,8 @@ export class SkeletonRenderer { let mesh = attachment; vertices = this.computeMeshVertices(slot, mesh, false); triangles = mesh.triangles; - texture = (mesh.region.renderObject).page.texture.getImage() as HTMLImageElement; + let region = (mesh.region!.renderObject); + texture = region.page.texture!.getImage() as HTMLImageElement; } else continue; diff --git a/spine-ts/spine-core/src/Animation.ts b/spine-ts/spine-core/src/Animation.ts index 510d92671..01d1d66c7 100644 --- a/spine-ts/spine-core/src/Animation.ts +++ b/spine-ts/spine-core/src/Animation.ts @@ -42,8 +42,8 @@ import { SequenceMode, SequenceModeValues } from "./attachments/Sequence"; export class Animation { /** The animation's name, which is unique across all animations in the skeleton. */ name: string; - timelines: Array = null; - timelineIds: StringSet = null; + timelines: Array = []; + timelineIds: StringSet = new StringSet(); /** The duration of the animation in seconds, which is the highest time of all keys in the timeline. */ duration: number; @@ -58,7 +58,7 @@ export class Animation { setTimelines (timelines: Array) { if (!timelines) throw new Error("timelines cannot be null."); this.timelines = timelines; - this.timelineIds = new StringSet(); + this.timelineIds.clear(); for (var i = 0; i < timelines.length; i++) this.timelineIds.addAll(timelines[i].getPropertyIds()); } @@ -155,8 +155,8 @@ const Property = { /** The interface for all timelines. */ export abstract class Timeline { - propertyIds: string[] = null; - frames: NumberArrayLike = null; + propertyIds: string[]; + frames: NumberArrayLike; constructor (frameCount: number, propertyIds: string[]) { this.propertyIds = propertyIds; @@ -179,7 +179,7 @@ export abstract class Timeline { return this.frames[this.frames.length - this.getFrameEntries()]; } - abstract apply (skeleton: Skeleton, lastTime: number, time: number, events: Array, alpha: number, blend: MixBlend, direction: MixDirection): void; + abstract apply (skeleton: Skeleton, lastTime: number, time: number, events: Array | null, alpha: number, blend: MixBlend, direction: MixDirection): void; static search1 (frames: NumberArrayLike, time: number) { let n = frames.length; @@ -208,7 +208,7 @@ export interface SlotTimeline { /** The base class for timelines that use interpolation between key frame values. */ export abstract class CurveTimeline extends Timeline { - protected curves: NumberArrayLike = null; // type, x, y, ... + protected curves: NumberArrayLike; // type, x, y, ... constructor (frameCount: number, bezierCount: number, propertyIds: string[]) { super(frameCount, propertyIds); @@ -369,7 +369,7 @@ export class RotateTimeline extends CurveTimeline1 implements BoneTimeline { this.boneIndex = boneIndex; } - apply (skeleton: Skeleton, lastTime: number, time: number, events: Array, alpha: number, blend: MixBlend, direction: MixDirection) { + apply (skeleton: Skeleton, lastTime: number, time: number, events: Array | null, alpha: number, blend: MixBlend, direction: MixDirection) { let bone = skeleton.bones[this.boneIndex]; if (!bone.active) return; @@ -1176,9 +1176,9 @@ export class RGBA2Timeline extends CurveTimeline implements SlotTimeline { if (!slot.bone.active) return; let frames = this.frames; - let light = slot.color, dark = slot.darkColor; + let light = slot.color, dark = slot.darkColor!; if (time < frames[0]) { - let setupLight = slot.data.color, setupDark = slot.data.darkColor; + let setupLight = slot.data.color, setupDark = slot.data.darkColor!; switch (blend) { case MixBlend.setup: light.setFromColor(setupLight); @@ -1245,7 +1245,7 @@ export class RGBA2Timeline extends CurveTimeline implements SlotTimeline { } else { if (blend == MixBlend.setup) { light.setFromColor(slot.data.color); - let setupDark = slot.data.darkColor; + let setupDark = slot.data.darkColor!; dark.r = setupDark.r; dark.g = setupDark.g; dark.b = setupDark.b; @@ -1291,9 +1291,9 @@ export class RGB2Timeline extends CurveTimeline implements SlotTimeline { if (!slot.bone.active) return; let frames = this.frames; - let light = slot.color, dark = slot.darkColor; + let light = slot.color, dark = slot.darkColor!; if (time < frames[0]) { - let setupLight = slot.data.color, setupDark = slot.data.darkColor; + let setupLight = slot.data.color, setupDark = slot.data.darkColor!; switch (blend) { case MixBlend.setup: light.r = setupLight.r; @@ -1360,7 +1360,7 @@ export class RGB2Timeline extends CurveTimeline implements SlotTimeline { dark.b = b2; } else { if (blend == MixBlend.setup) { - let setupLight = slot.data.color, setupDark = slot.data.darkColor; + let setupLight = slot.data.color, setupDark = slot.data.darkColor!; light.r = setupLight.r; light.g = setupLight.g; light.b = setupLight.b; @@ -1383,7 +1383,7 @@ export class AttachmentTimeline extends Timeline implements SlotTimeline { slotIndex = 0; /** The attachment name for each key frame. May contain null values to clear the attachment. */ - attachmentNames: Array; + attachmentNames: Array; constructor (frameCount: number, slotIndex: number) { super(frameCount, [ @@ -1398,7 +1398,7 @@ export class AttachmentTimeline extends Timeline implements SlotTimeline { } /** Sets the time in seconds and the attachment name for the specified key frame. */ - setFrame (frame: number, time: number, attachmentName: string) { + setFrame (frame: number, time: number, attachmentName: string | null) { this.frames[frame] = time; this.attachmentNames[frame] = attachmentName; } @@ -1420,7 +1420,7 @@ export class AttachmentTimeline extends Timeline implements SlotTimeline { this.setAttachment(skeleton, slot, this.attachmentNames[Timeline.search1(this.frames, time)]); } - setAttachment (skeleton: Skeleton, slot: Slot, attachmentName: string) { + setAttachment (skeleton: Skeleton, slot: Slot, attachmentName: string | null) { slot.setAttachment(!attachmentName ? null : skeleton.getAttachment(this.slotIndex, attachmentName)); } } @@ -1430,10 +1430,10 @@ export class DeformTimeline extends CurveTimeline implements SlotTimeline { slotIndex = 0; /** The attachment that will be deformed. */ - attachment: VertexAttachment = null; + attachment: VertexAttachment; /** The vertices for each key frame. */ - vertices: Array = null; + vertices: Array; constructor (frameCount: number, bezierCount: number, slotIndex: number, attachment: VertexAttachment) { super(frameCount, bezierCount, [ @@ -1508,7 +1508,8 @@ export class DeformTimeline extends CurveTimeline implements SlotTimeline { apply (skeleton: Skeleton, lastTime: number, time: number, firedEvents: Array, alpha: number, blend: MixBlend, direction: MixDirection) { let slot: Slot = skeleton.slots[this.slotIndex]; if (!slot.bone.active) return; - let slotAttachment: Attachment = slot.getAttachment(); + let slotAttachment: Attachment | null = slot.getAttachment(); + if (!slotAttachment) return; if (!(slotAttachment instanceof VertexAttachment) || (slotAttachment).timelineAttahment != this.attachment) return; let deform: Array = slot.deform; @@ -1685,7 +1686,7 @@ export class EventTimeline extends Timeline { static propertyIds = ["" + Property.event]; /** The event for each key frame. */ - events: Array = null; + events: Array; constructor (frameCount: number) { super(frameCount, EventTimeline.propertyIds); @@ -1738,11 +1739,11 @@ export class DrawOrderTimeline extends Timeline { static propertyIds = ["" + Property.drawOrder]; /** The draw order for each key frame. See {@link #setFrame(int, float, int[])}. */ - drawOrders: Array> = null; + drawOrders: Array | null>; constructor (frameCount: number) { super(frameCount, DrawOrderTimeline.propertyIds); - this.drawOrders = new Array>(frameCount); + this.drawOrders = new Array | null>(frameCount); } getFrameCount () { @@ -1752,7 +1753,7 @@ export class DrawOrderTimeline extends Timeline { /** Sets the time in seconds and the draw order for the specified key frame. * @param drawOrder For each slot in {@link Skeleton#slots}, the index of the new draw order. May be null to use setup pose * draw order. */ - setFrame (frame: number, time: number, drawOrder: Array) { + setFrame (frame: number, time: number, drawOrder: Array | null) { this.frames[frame] = time; this.drawOrders[frame] = drawOrder; } @@ -1768,7 +1769,8 @@ export class DrawOrderTimeline extends Timeline { return; } - let drawOrderToSetupIndex = this.drawOrders[Timeline.search1(this.frames, time)]; + let idx = Timeline.search1(this.frames, time); + let drawOrderToSetupIndex = this.drawOrders[idx]; if (!drawOrderToSetupIndex) Utils.arrayCopy(skeleton.slots, 0, skeleton.drawOrder, 0, skeleton.slots.length); else { @@ -2157,7 +2159,7 @@ export class SequenceTimeline extends Timeline implements SlotTimeline { constructor (frameCount: number, slotIndex: number, attachment: HasTextureRegion) { super(frameCount, [ - Property.sequence + "|" + slotIndex + "|" + attachment.sequence.id + Property.sequence + "|" + slotIndex + "|" + attachment.sequence!.id ]); this.slotIndex = slotIndex; this.attachment = attachment; @@ -2207,7 +2209,7 @@ export class SequenceTimeline extends Timeline implements SlotTimeline { let modeAndIndex = frames[i + SequenceTimeline.MODE]; let delay = frames[i + SequenceTimeline.DELAY]; - let index = modeAndIndex >> 4, count = this.attachment.sequence.regions.length; + let index = modeAndIndex >> 4, count = this.attachment.sequence!.regions.length; let mode = SequenceModeValues[modeAndIndex & 0xf]; if (mode != SequenceMode.hold) { index += (((time - before) / delay + 0.00001) | 0); diff --git a/spine-ts/spine-core/src/AnimationState.ts b/spine-ts/spine-core/src/AnimationState.ts index 4e01a3b43..1435960ee 100644 --- a/spine-ts/spine-core/src/AnimationState.ts +++ b/spine-ts/spine-core/src/AnimationState.ts @@ -40,16 +40,16 @@ import { Event } from "./Event"; * * See [Applying Animations](http://esotericsoftware.com/spine-applying-animations/) in the Spine Runtimes Guide. */ export class AnimationState { + static _emptyAnimation = new Animation("", [], 0); private static emptyAnimation (): Animation { - if (!_emptyAnimation) _emptyAnimation = new Animation("", [], 0); - return _emptyAnimation; + return AnimationState._emptyAnimation; } /** The AnimationStateData to look up mix durations. */ - data: AnimationStateData = null; + data: AnimationStateData; /** The list of tracks that currently have animations, which may contain null entries. */ - tracks = new Array(); + tracks = new Array(); /** Multiplier for the delta time when the animation state is updated, causing time for all animations and mixes to play slower * or faster. Defaults to 1. @@ -113,7 +113,7 @@ export class AnimationState { } if (current.mixingFrom && this.updateMixingFrom(current, delta)) { // End mixing from entries once all have completed. - let from = current.mixingFrom; + let from: TrackEntry | null = current.mixingFrom; current.mixingFrom = null; if (from) from.mixingTo = null; while (from) { @@ -181,12 +181,12 @@ export class AnimationState { // Apply current entry. let animationLast = current.animationLast, animationTime = current.getAnimationTime(), applyTime = animationTime; - let applyEvents = events; + let applyEvents: Event[] | null = events; if (current.reverse) { - applyTime = current.animation.duration - applyTime; + applyTime = current.animation!.duration - applyTime; applyEvents = null; } - let timelines = current.animation.timelines; + let timelines = current.animation!.timelines; let timelineCount = timelines.length; if ((i == 0 && mix == 1) || blend == MixBlend.add) { for (let ii = 0; ii < timelineCount; ii++) { @@ -246,7 +246,7 @@ export class AnimationState { } applyMixingFrom (to: TrackEntry, skeleton: Skeleton, blend: MixBlend) { - let from = to.mixingFrom; + let from = to.mixingFrom!; if (from.mixingFrom) this.applyMixingFrom(from, skeleton, blend); let mix = 0; @@ -260,13 +260,13 @@ export class AnimationState { } let attachments = mix < from.attachmentThreshold, drawOrder = mix < from.drawOrderThreshold; - let timelines = from.animation.timelines; + let timelines = from.animation!.timelines; let timelineCount = timelines.length; let alphaHold = from.alpha * to.interruptAlpha, alphaMix = alphaHold * (1 - mix); let animationLast = from.animationLast, animationTime = from.getAnimationTime(), applyTime = animationTime; let events = null; if (from.reverse) - applyTime = from.animation.duration - applyTime; + applyTime = from.animation!.duration - applyTime; else if (mix < from.eventThreshold) events = this.events; @@ -349,7 +349,7 @@ export class AnimationState { if (slot.attachmentState <= this.unkeyedState) slot.attachmentState = this.unkeyedState + SETUP; } - setAttachment (skeleton: Skeleton, slot: Slot, attachmentName: string, attachments: boolean) { + setAttachment (skeleton: Skeleton, slot: Slot, attachmentName: string | null, attachments: boolean) { slot.setAttachment(!attachmentName ? null : skeleton.getAttachment(slot.data.index, attachmentName)); if (attachments) slot.attachmentState = this.unkeyedState + CURRENT; } @@ -645,7 +645,7 @@ export class AnimationState { } /** @param last May be null. */ - trackEntry (trackIndex: number, animation: Animation, loop: boolean, last: TrackEntry) { + trackEntry (trackIndex: number, animation: Animation, loop: boolean, last: TrackEntry | null) { let entry = this.trackEntryPool.obtain(); entry.reset(); entry.trackIndex = trackIndex; @@ -674,7 +674,7 @@ export class AnimationState { entry.alpha = 1; entry.mixTime = 0; - entry.mixDuration = !last ? 0 : this.data.getMix(last.animation, animation); + entry.mixDuration = !last ? 0 : this.data.getMix(last.animation!, animation); entry.interruptAlpha = 1; entry.totalAlpha = 0; entry.mixBlend = MixBlend.replace; @@ -710,8 +710,8 @@ export class AnimationState { computeHold (entry: TrackEntry) { let to = entry.mixingTo; - let timelines = entry.animation.timelines; - let timelinesCount = entry.animation.timelines.length; + let timelines = entry.animation!.timelines; + let timelinesCount = entry.animation!.timelines.length; let timelineMode = entry.timelineMode; timelineMode.length = timelinesCount; let timelineHoldMix = entry.timelineHoldMix; @@ -731,11 +731,11 @@ export class AnimationState { if (!propertyIDs.addAll(ids)) timelineMode[i] = SUBSEQUENT; else if (!to || timeline instanceof AttachmentTimeline || timeline instanceof DrawOrderTimeline - || timeline instanceof EventTimeline || !to.animation.hasTimeline(ids)) { + || timeline instanceof EventTimeline || !to.animation!.hasTimeline(ids)) { timelineMode[i] = FIRST; } else { - for (let next = to.mixingTo; next; next = next.mixingTo) { - if (next.animation.hasTimeline(ids)) continue; + for (let next = to.mixingTo; next; next = next!.mixingTo) { + if (next.animation!.hasTimeline(ids)) continue; if (entry.mixDuration > 0) { timelineMode[i] = HOLD_MIX; timelineHoldMix[i] = next; @@ -784,26 +784,26 @@ export class AnimationState { * References to a track entry must not be kept after the {@link AnimationStateListener#dispose()} event occurs. */ export class TrackEntry { /** The animation to apply for this track entry. */ - animation: Animation = null; + animation: Animation | null = null; - previous: TrackEntry = null; + previous: TrackEntry | null = null; /** The animation queued to start after this animation, or null. `next` makes up a linked list. */ - next: TrackEntry = null; + next: TrackEntry | null = null; /** The track entry for the previous animation when mixing from the previous animation to this animation, or null if no * mixing is currently occuring. When mixing from multiple animations, `mixingFrom` makes up a linked list. */ - mixingFrom: TrackEntry = null; + mixingFrom: TrackEntry | null = null; /** The track entry for the next animation when mixing from this animation to the next animation, or null if no mixing is * currently occuring. When mixing to multiple animations, `mixingTo` makes up a linked list. */ - mixingTo: TrackEntry = null; + mixingTo: TrackEntry | null = null; /** The listener for events generated by this track entry, or null. * * A track entry returned from {@link AnimationState#setAnimation()} is already the current animation * for the track, so the track entry listener {@link AnimationStateListener#start()} will not be called. */ - listener: AnimationStateListener = null; + listener: AnimationStateListener | null = null; /** The index of the track where this track entry is either current or queued. * @@ -999,7 +999,7 @@ export class TrackEntry { export class EventQueue { objects: Array = []; drainDisabled = false; - animState: AnimationState = null; + animState: AnimationState; constructor (animState: AnimationState) { this.animState = animState; @@ -1051,35 +1051,47 @@ export class EventQueue { switch (type) { case EventType.start: if (entry.listener && entry.listener.start) entry.listener.start(entry); - for (let ii = 0; ii < listeners.length; ii++) - if (listeners[ii].start) listeners[ii].start(entry); + for (let ii = 0; ii < listeners.length; ii++) { + let listener = listeners[ii]; + if (listener.start) listener.start(entry); + } break; case EventType.interrupt: if (entry.listener && entry.listener.interrupt) entry.listener.interrupt(entry); - for (let ii = 0; ii < listeners.length; ii++) - if (listeners[ii].interrupt) listeners[ii].interrupt(entry); + for (let ii = 0; ii < listeners.length; ii++) { + let listener = listeners[ii]; + if (listener.interrupt) listener.interrupt(entry); + } break; case EventType.end: if (entry.listener && entry.listener.end) entry.listener.end(entry); - for (let ii = 0; ii < listeners.length; ii++) - if (listeners[ii].end) listeners[ii].end(entry); + for (let ii = 0; ii < listeners.length; ii++) { + let listener = listeners[ii]; + if (listener.end) listener.end(entry); + } // Fall through. case EventType.dispose: if (entry.listener && entry.listener.dispose) entry.listener.dispose(entry); - for (let ii = 0; ii < listeners.length; ii++) - if (listeners[ii].dispose) listeners[ii].dispose(entry); + for (let ii = 0; ii < listeners.length; ii++) { + let listener = listeners[ii]; + if (listener.dispose) listener.dispose(entry); + } this.animState.trackEntryPool.free(entry); break; case EventType.complete: if (entry.listener && entry.listener.complete) entry.listener.complete(entry); - for (let ii = 0; ii < listeners.length; ii++) - if (listeners[ii].complete) listeners[ii].complete(entry); + for (let ii = 0; ii < listeners.length; ii++) { + let listener = listeners[ii]; + if (listener.complete) listener.complete(entry); + } break; case EventType.event: let event = objects[i++ + 2] as Event; if (entry.listener && entry.listener.event) entry.listener.event(entry, event); - for (let ii = 0; ii < listeners.length; ii++) - if (listeners[ii].event) listeners[ii].event(entry, event); + for (let ii = 0; ii < listeners.length; ii++) { + let listener = listeners[ii]; + if (listener.event) listener.event(entry, event); + } break; } } @@ -1104,24 +1116,24 @@ export enum EventType { * {@link AnimationState#addListener()}. */ export interface AnimationStateListener { /** Invoked when this entry has been set as the current entry. */ - start?(entry: TrackEntry): void; + start?: (entry: TrackEntry) => void; /** Invoked when another entry has replaced this entry as the current entry. This entry may continue being applied for * mixing. */ - interrupt?(entry: TrackEntry): void; + interrupt?: (entry: TrackEntry) => void; /** Invoked when this entry is no longer the current entry and will never be applied again. */ - end?(entry: TrackEntry): void; + end?: (entry: TrackEntry) => void; /** Invoked when this entry will be disposed. This may occur without the entry ever being set as the current entry. * References to the entry should not be kept after dispose is called, as it may be destroyed or reused. */ - dispose?(entry: TrackEntry): void; + dispose?: (entry: TrackEntry) => void; /** Invoked every time this entry's animation completes a loop. */ - complete?(entry: TrackEntry): void; + complete?: (entry: TrackEntry) => void; /** Invoked when this entry's animation triggers an event. */ - event?(entry: TrackEntry, event: Event): void; + event?: (entry: TrackEntry, event: Event) => void; } export abstract class AnimationStateAdapter implements AnimationStateListener { @@ -1180,6 +1192,4 @@ export const HOLD_FIRST = 3; export const HOLD_MIX = 4; export const SETUP = 1; -export const CURRENT = 2; - -let _emptyAnimation: Animation = null; +export const CURRENT = 2; \ No newline at end of file diff --git a/spine-ts/spine-core/src/AnimationStateData.ts b/spine-ts/spine-core/src/AnimationStateData.ts index f6d63deea..41a60b747 100644 --- a/spine-ts/spine-core/src/AnimationStateData.ts +++ b/spine-ts/spine-core/src/AnimationStateData.ts @@ -35,7 +35,7 @@ import { StringMap } from "./Utils"; /** Stores mix (crossfade) durations to be applied when {@link AnimationState} animations are changed. */ export class AnimationStateData { /** The SkeletonData to look up animations when they are specified by name. */ - skeletonData: SkeletonData = null; + skeletonData: SkeletonData; animationToMixTime: StringMap = {}; diff --git a/spine-ts/spine-core/src/AssetManagerBase.ts b/spine-ts/spine-core/src/AssetManagerBase.ts index 678371461..64eeb3aba 100644 --- a/spine-ts/spine-core/src/AssetManagerBase.ts +++ b/spine-ts/spine-core/src/AssetManagerBase.ts @@ -32,7 +32,7 @@ import { TextureAtlas } from "./TextureAtlas"; import { Disposable, StringMap } from "./Utils"; export class AssetManagerBase implements Disposable { - private pathPrefix: string = null; + private pathPrefix: string = ""; private textureLoader: (image: HTMLImageElement | ImageBitmap) => Texture; private downloader: Downloader; private assets: StringMap = {}; @@ -40,10 +40,10 @@ export class AssetManagerBase implements Disposable { private toLoad = 0; private loaded = 0; - constructor (textureLoader: (image: HTMLImageElement | ImageBitmap) => Texture, pathPrefix: string = "", downloader: Downloader = null) { + constructor (textureLoader: (image: HTMLImageElement | ImageBitmap) => Texture, pathPrefix: string = "", downloader: Downloader = new Downloader()) { this.textureLoader = textureLoader; this.pathPrefix = pathPrefix; - this.downloader = downloader || new Downloader(); + this.downloader = downloader; } private start (path: string): string { @@ -85,8 +85,8 @@ export class AssetManagerBase implements Disposable { } loadBinary (path: string, - success: (path: string, binary: Uint8Array) => void = null, - error: (path: string, message: string) => void = null) { + success: (path: string, binary: Uint8Array) => void = () => { }, + error: (path: string, message: string) => void = () => { }) { path = this.start(path); this.downloader.downloadBinary(path, (data: Uint8Array): void => { @@ -97,8 +97,8 @@ export class AssetManagerBase implements Disposable { } loadText (path: string, - success: (path: string, text: string) => void = null, - error: (path: string, message: string) => void = null) { + success: (path: string, text: string) => void = () => { }, + error: (path: string, message: string) => void = () => { }) { path = this.start(path); this.downloader.downloadText(path, (data: string): void => { @@ -109,8 +109,8 @@ export class AssetManagerBase implements Disposable { } loadJson (path: string, - success: (path: string, object: object) => void = null, - error: (path: string, message: string) => void = null) { + success: (path: string, object: object) => void = () => { }, + error: (path: string, message: string) => void = () => { }) { path = this.start(path); this.downloader.downloadJson(path, (data: object): void => { @@ -121,8 +121,8 @@ export class AssetManagerBase implements Disposable { } loadTexture (path: string, - success: (path: string, texture: Texture) => void = null, - error: (path: string, message: string) => void = null) { + success: (path: string, texture: Texture) => void = () => { }, + error: (path: string, message: string) => void = () => { }) { path = this.start(path); let isBrowser = !!(typeof window !== 'undefined' && typeof navigator !== 'undefined' && window.document); @@ -152,9 +152,9 @@ export class AssetManagerBase implements Disposable { } loadTextureAtlas (path: string, - success: (path: string, atlas: TextureAtlas) => void = null, - error: (path: string, message: string) => void = null, - fileAlias: { [keyword: string]: string } = null + success: (path: string, atlas: TextureAtlas) => void = () => { }, + error: (path: string, message: string) => void = () => { }, + fileAlias?: { [keyword: string]: string } ) { let index = path.lastIndexOf("/"); let parent = index >= 0 ? path.substring(0, index + 1) : ""; @@ -165,7 +165,7 @@ export class AssetManagerBase implements Disposable { let atlas = new TextureAtlas(atlasText); let toLoad = atlas.pages.length, abort = false; for (let page of atlas.pages) { - this.loadTexture(fileAlias == null ? parent + page.name : fileAlias[page.name], + this.loadTexture(!fileAlias ? parent + page.name : fileAlias[page.name!], (imagePath: string, texture: Texture) => { if (!abort) { page.setTexture(texture); @@ -179,7 +179,7 @@ export class AssetManagerBase implements Disposable { ); } } catch (e) { - this.error(error, path, `Couldn't parse texture atlas ${path}: ${e.message}`); + this.error(error, path, `Couldn't parse texture atlas ${path}: ${(e as any).message}`); } }, (status: number, responseText: string): void => { this.error(error, path, `Couldn't load texture atlas ${path}: status ${status}, ${responseText}`); diff --git a/spine-ts/spine-core/src/AtlasAttachmentLoader.ts b/spine-ts/spine-core/src/AtlasAttachmentLoader.ts index bb5b1a995..a5a044108 100644 --- a/spine-ts/spine-core/src/AtlasAttachmentLoader.ts +++ b/spine-ts/spine-core/src/AtlasAttachmentLoader.ts @@ -43,7 +43,7 @@ import { Sequence } from "./attachments/Sequence" * See [Loading skeleton data](http://esotericsoftware.com/spine-loading-skeleton-data#JSON-and-binary-data) in the * Spine Runtimes Guide. */ export class AtlasAttachmentLoader implements AttachmentLoader { - atlas: TextureAtlas = null; + atlas: TextureAtlas; constructor (atlas: TextureAtlas) { this.atlas = atlas; @@ -53,14 +53,15 @@ export class AtlasAttachmentLoader implements AttachmentLoader { let regions = sequence.regions; for (let i = 0, n = regions.length; i < n; i++) { let path = sequence.getPath(basePath, i); - regions[i] = this.atlas.findRegion(path); + let region = this.atlas.findRegion(path); + if (region == null) throw new Error("Region not found in atlas: " + path + " (sequence: " + name + ")"); + regions[i] = region; regions[i].renderObject = regions[i]; - if (regions[i] == null) throw new Error("Region not found in atlas: " + path + " (sequence: " + name + ")"); } } newRegionAttachment (skin: Skin, name: string, path: string, sequence: Sequence): RegionAttachment { - let attachment = new RegionAttachment(name); + let attachment = new RegionAttachment(name, path); if (sequence != null) { this.loadSequence(name, path, sequence); } else { @@ -73,7 +74,7 @@ export class AtlasAttachmentLoader implements AttachmentLoader { } newMeshAttachment (skin: Skin, name: string, path: string, sequence: Sequence): MeshAttachment { - let attachment = new MeshAttachment(name); + let attachment = new MeshAttachment(name, path); if (sequence != null) { this.loadSequence(name, path, sequence); } else { diff --git a/spine-ts/spine-core/src/Bone.ts b/spine-ts/spine-core/src/Bone.ts index 4d288a07f..05d821b74 100644 --- a/spine-ts/spine-core/src/Bone.ts +++ b/spine-ts/spine-core/src/Bone.ts @@ -39,13 +39,13 @@ import { MathUtils, Vector2 } from "./Utils"; * constraint or application code modifies the world transform after it was computed from the local transform. */ export class Bone implements Updatable { /** The bone's setup pose data. */ - data: BoneData = null; + data: BoneData; /** The skeleton this bone belongs to. */ - skeleton: Skeleton = null; + skeleton: Skeleton; /** The parent bone, or null if this is the root bone. */ - parent: Bone = null; + parent: Bone | null = null; /** The immediate children of this bone. */ children = new Array(); @@ -114,7 +114,7 @@ export class Bone implements Updatable { active = false; /** @param parent May be null. */ - constructor (data: BoneData, skeleton: Skeleton, parent: Bone) { + constructor (data: BoneData, skeleton: Skeleton, parent: Bone | null) { if (!data) throw new Error("data cannot be null."); if (!skeleton) throw new Error("skeleton cannot be null."); this.data = data; diff --git a/spine-ts/spine-core/src/BoneData.ts b/spine-ts/spine-core/src/BoneData.ts index c28caacb8..0e9fa142f 100644 --- a/spine-ts/spine-core/src/BoneData.ts +++ b/spine-ts/spine-core/src/BoneData.ts @@ -35,10 +35,10 @@ export class BoneData { index: number = 0; /** The name of the bone, which is unique across all bones in the skeleton. */ - name: string = null; + name: string; /** @returns May be null. */ - parent: BoneData = null; + parent: BoneData | null = null; /** The bone's length. */ length: number = 0; @@ -76,7 +76,7 @@ export class BoneData { * rendered at runtime. */ color = new Color(); - constructor (index: number, name: string, parent: BoneData) { + constructor (index: number, name: string, parent: BoneData | null) { if (index < 0) throw new Error("index must be >= 0."); if (!name) throw new Error("name cannot be null."); this.index = index; diff --git a/spine-ts/spine-core/src/Event.ts b/spine-ts/spine-core/src/Event.ts index 2967d96d3..50e59ade9 100644 --- a/spine-ts/spine-core/src/Event.ts +++ b/spine-ts/spine-core/src/Event.ts @@ -35,10 +35,10 @@ import { EventData } from "./EventData"; * AnimationStateListener {@link AnimationStateListener#event()}, and * [Events](http://esotericsoftware.com/spine-events) in the Spine User Guide. */ export class Event { - data: EventData = null; + data: EventData; intValue: number = 0; floatValue: number = 0; - stringValue: string = null; + stringValue: string | null = null; time: number = 0; volume: number = 0; balance: number = 0; diff --git a/spine-ts/spine-core/src/EventData.ts b/spine-ts/spine-core/src/EventData.ts index 8a4d6898c..3337468ea 100644 --- a/spine-ts/spine-core/src/EventData.ts +++ b/spine-ts/spine-core/src/EventData.ts @@ -31,11 +31,11 @@ * * See [Events](http://esotericsoftware.com/spine-events) in the Spine User Guide. */ export class EventData { - name: string = null; + name: string; intValue: number = 0; floatValue: number = 0; - stringValue: string = null; - audioPath: string = null; + stringValue: string | null = null; + audioPath: string | null = null; volume: number = 0; balance: number = 0; diff --git a/spine-ts/spine-core/src/IkConstraint.ts b/spine-ts/spine-core/src/IkConstraint.ts index 55b32d771..c17cfe0fe 100644 --- a/spine-ts/spine-core/src/IkConstraint.ts +++ b/spine-ts/spine-core/src/IkConstraint.ts @@ -40,13 +40,13 @@ import { MathUtils } from "./Utils"; * See [IK constraints](http://esotericsoftware.com/spine-ik-constraints) in the Spine User Guide. */ export class IkConstraint implements Updatable { /** The IK constraint's setup pose data. */ - data: IkConstraintData = null; + data: IkConstraintData; /** The bones that will be modified by this IK constraint. */ - bones: Array = null; + bones: Array; /** The bone that is the IK target. */ - target: Bone = null; + target: Bone; /** Controls the bend direction of the IK bones, either 1 or -1. */ bendDirection = 0; @@ -76,9 +76,14 @@ export class IkConstraint implements Updatable { this.stretch = data.stretch; this.bones = new Array(); - for (let i = 0; i < data.bones.length; i++) - this.bones.push(skeleton.findBone(data.bones[i].name)); - this.target = skeleton.findBone(data.target.name); + for (let i = 0; i < data.bones.length; i++) { + let bone = skeleton.findBone(data.bones[i].name); + if (!bone) throw new Error(`Couldn't find bone ${data.bones[i].name}`); + this.bones.push(bone); + } + let target = skeleton.findBone(data.target.name); + if (!target) throw new Error(`Couldn't find bone ${data.target.name}`); + this.target = target; } isActive () { @@ -102,6 +107,7 @@ export class IkConstraint implements Updatable { /** Applies 1 bone IK. The target is specified in the world coordinate system. */ apply1 (bone: Bone, targetX: number, targetY: number, compress: boolean, stretch: boolean, uniform: boolean, alpha: number) { let p = bone.parent; + if (!p) throw new Error("IK bone must have parent."); let pa = p.a, pb = p.b, pc = p.c, pd = p.d; let rotationIK = -bone.ashearX - bone.arotation, tx = 0, ty = 0; @@ -183,6 +189,7 @@ export class IkConstraint implements Updatable { cwy = c * cx + d * cy + parent.worldY; } let pp = parent.parent; + if (!pp) throw new Error("IK parent must itself have a parent."); a = pp.a; b = pp.b; c = pp.c; diff --git a/spine-ts/spine-core/src/IkConstraintData.ts b/spine-ts/spine-core/src/IkConstraintData.ts index 35c4a5685..3271750ad 100644 --- a/spine-ts/spine-core/src/IkConstraintData.ts +++ b/spine-ts/spine-core/src/IkConstraintData.ts @@ -39,7 +39,12 @@ export class IkConstraintData extends ConstraintData { bones = new Array(); /** The bone that is the IK target. */ - target: BoneData = null; + private _target: BoneData | null = null; + public set target (boneData: BoneData) { this._target = boneData; } + public get target () { + if (!this._target) throw new Error("BoneData not set.") + else return this._target; + } /** Controls the bend direction of the IK bones, either 1 or -1. */ bendDirection = 1; diff --git a/spine-ts/spine-core/src/PathConstraint.ts b/spine-ts/spine-core/src/PathConstraint.ts index 063b3bbce..197527c12 100644 --- a/spine-ts/spine-core/src/PathConstraint.ts +++ b/spine-ts/spine-core/src/PathConstraint.ts @@ -45,13 +45,13 @@ export class PathConstraint implements Updatable { static epsilon = 0.00001; /** The path constraint's setup pose data. */ - data: PathConstraintData = null; + data: PathConstraintData; /** The bones that will be modified by this path constraint. */ - bones: Array = null; + bones: Array; /** The slot whose path attachment will be used to constrained the bones. */ - target: Slot = null; + target: Slot; /** The position along the path. */ position = 0; @@ -76,9 +76,14 @@ export class PathConstraint implements Updatable { if (!skeleton) throw new Error("skeleton cannot be null."); this.data = data; this.bones = new Array(); - for (let i = 0, n = data.bones.length; i < n; i++) - this.bones.push(skeleton.findBone(data.bones[i].name)); - this.target = skeleton.findSlot(data.target.name); + for (let i = 0, n = data.bones.length; i < n; i++) { + let bone = skeleton.findBone(data.bones[i].name); + if (!bone) throw new Error(`Couldn't find bone ${data.bones[i].name}.`); + this.bones.push(bone); + } + let target = skeleton.findSlot(data.target.name); + if (!target) throw new Error(`Couldn't find target bone ${data.target.name}`); + this.target = target; this.position = data.position; this.spacing = data.spacing; this.mixRotate = data.mixRotate; @@ -102,7 +107,7 @@ export class PathConstraint implements Updatable { let bones = this.bones; let boneCount = bones.length, spacesCount = tangents ? boneCount : boneCount + 1; - let spaces = Utils.setArraySize(this.spaces, spacesCount), lengths: Array = scale ? this.lengths = Utils.setArraySize(this.lengths, boneCount) : null; + let spaces = Utils.setArraySize(this.spaces, spacesCount), lengths: Array = scale ? this.lengths = Utils.setArraySize(this.lengths, boneCount) : []; let spacing = this.spacing; switch (data.spacingMode) { @@ -222,7 +227,7 @@ export class PathConstraint implements Updatable { computeWorldPositions (path: PathAttachment, spacesCount: number, tangents: boolean) { let target = this.target; let position = this.position; - let spaces = this.spaces, out = Utils.setArraySize(this.positions, spacesCount * 3 + 2), world: Array = null; + let spaces = this.spaces, out = Utils.setArraySize(this.positions, spacesCount * 3 + 2), world: Array = this.world; let closed = path.closed; let verticesLength = path.worldVerticesLength, curveCount = verticesLength / 6, prevCurve = PathConstraint.NONE; diff --git a/spine-ts/spine-core/src/PathConstraintData.ts b/spine-ts/spine-core/src/PathConstraintData.ts index b0049e366..b7c63c1a7 100644 --- a/spine-ts/spine-core/src/PathConstraintData.ts +++ b/spine-ts/spine-core/src/PathConstraintData.ts @@ -41,16 +41,21 @@ export class PathConstraintData extends ConstraintData { bones = new Array(); /** The slot whose path attachment will be used to constrained the bones. */ - target: SlotData = null; + private _target: SlotData | null = null; + public set target (slotData: SlotData) { this._target = slotData; } + public get target () { + if (!this._target) throw new Error("SlotData not set.") + else return this._target; + } /** The mode for positioning the first bone on the path. */ - positionMode: PositionMode = null; + positionMode: PositionMode = PositionMode.Fixed; /** The mode for positioning the bones after the first bone on the path. */ - spacingMode: SpacingMode = null; + spacingMode: SpacingMode = SpacingMode.Fixed; /** The mode for adjusting the rotation of the bones. */ - rotateMode: RotateMode = null; + rotateMode: RotateMode = RotateMode.Chain; /** An offset added to the constrained bone rotation. */ offsetRotation: number = 0; diff --git a/spine-ts/spine-core/src/Skeleton.ts b/spine-ts/spine-core/src/Skeleton.ts index af91b2c9c..4c43b2f7d 100644 --- a/spine-ts/spine-core/src/Skeleton.ts +++ b/spine-ts/spine-core/src/Skeleton.ts @@ -46,34 +46,34 @@ import { Color, Utils, MathUtils, Vector2, NumberArrayLike } from "./Utils"; * See [Instance objects](http://esotericsoftware.com/spine-runtime-architecture#Instance-objects) in the Spine Runtimes Guide. */ export class Skeleton { /** The skeleton's setup pose data. */ - data: SkeletonData = null; + data: SkeletonData; /** The skeleton's bones, sorted parent first. The root bone is always the first bone. */ - bones: Array = null; + bones: Array; /** The skeleton's slots. */ - slots: Array = null; + slots: Array; /** The skeleton's slots in the order they should be drawn. The returned array may be modified to change the draw order. */ - drawOrder: Array = null; + drawOrder: Array; /** The skeleton's IK constraints. */ - ikConstraints: Array = null; + ikConstraints: Array; /** The skeleton's transform constraints. */ - transformConstraints: Array = null; + transformConstraints: Array; /** The skeleton's path constraints. */ - pathConstraints: Array = null; + pathConstraints: Array; /** The list of bones and constraints, sorted in the order they should be updated, as computed by {@link #updateCache()}. */ _updateCache = new Array(); /** The skeleton's current skin. May be null. */ - skin: Skin = null; + skin: Skin | null = null; /** The color to tint all the skeleton's attachments. */ - color: Color = null; + color: Color; /** Scales the entire skeleton on the X axis. This affects all bones, even if the bone's transform mode disallows scale * inheritance. */ @@ -155,7 +155,7 @@ export class Skeleton { if (this.skin) { let skinBones = this.skin.bones; for (let i = 0, n = this.skin.bones.length; i < n; i++) { - let bone = this.bones[skinBones[i].index]; + let bone: Bone | null = this.bones[skinBones[i].index]; do { bone.sorted = false; bone.active = true; @@ -201,7 +201,7 @@ export class Skeleton { } sortIkConstraint (constraint: IkConstraint) { - constraint.active = constraint.target.isActive() && (!constraint.data.skinRequired || (this.skin && Utils.contains(this.skin.constraints, constraint.data, true))); + constraint.active = constraint.target.isActive() && (!constraint.data.skinRequired || (this.skin && Utils.contains(this.skin.constraints, constraint.data, true)))!; if (!constraint.active) return; let target = constraint.target; @@ -226,7 +226,7 @@ export class Skeleton { } sortPathConstraint (constraint: PathConstraint) { - constraint.active = constraint.target.bone.isActive() && (!constraint.data.skinRequired || (this.skin && Utils.contains(this.skin.constraints, constraint.data, true))); + constraint.active = constraint.target.bone.isActive() && (!constraint.data.skinRequired || (this.skin && Utils.contains(this.skin.constraints, constraint.data, true)))!; if (!constraint.active) return; let slot = constraint.target; @@ -255,7 +255,7 @@ export class Skeleton { } sortTransformConstraint (constraint: TransformConstraint) { - constraint.active = constraint.target.isActive() && (!constraint.data.skinRequired || (this.skin && Utils.contains(this.skin.constraints, constraint.data, true))); + constraint.active = constraint.target.isActive() && (!constraint.data.skinRequired || (this.skin && Utils.contains(this.skin.constraints, constraint.data, true)))!; if (!constraint.active) return; this.sortBone(constraint.target); @@ -265,7 +265,7 @@ export class Skeleton { if (constraint.data.local) { for (let i = 0; i < boneCount; i++) { let child = constrained[i]; - this.sortBone(child.parent); + this.sortBone(child.parent!); this.sortBone(child); } } else { @@ -307,6 +307,7 @@ export class Skeleton { } sortBone (bone: Bone) { + if (!bone) return; if (bone.sorted) return; let parent = bone.parent; if (parent) this.sortBone(parent); @@ -348,6 +349,7 @@ export class Skeleton { updateWorldTransformWith (parent: Bone) { // Apply the parent bone transform to the root bone. The root bone always inherits scale, rotation and reflection. let rootBone = this.getRootBone(); + if (!rootBone) throw new Error("Root bone must not be null."); let pa = parent.a, pb = parent.b, pc = parent.c, pd = parent.d; rootBone.worldX = pa * this.x + pb * this.y + parent.worldX; rootBone.worldY = pc * this.x + pd * this.y + parent.worldY; @@ -484,7 +486,7 @@ export class Skeleton { let slot = slots[i]; let name = slot.data.attachmentName; if (name) { - let attachment: Attachment = newSkin.getAttachment(i, name); + let attachment = newSkin.getAttachment(i, name); if (attachment) slot.setAttachment(attachment); } } @@ -500,8 +502,10 @@ export class Skeleton { * * See {@link #getAttachment()}. * @returns May be null. */ - getAttachmentByName (slotName: string, attachmentName: string): Attachment { - return this.getAttachment(this.data.findSlot(slotName).index, attachmentName); + getAttachmentByName (slotName: string, attachmentName: string): Attachment | null { + let slot = this.data.findSlot(slotName); + if (!slot) throw new Error(`Can't find slot with name ${slotName}`); + return this.getAttachment(slot.index, attachmentName); } /** Finds an attachment by looking in the {@link #skin} and {@link SkeletonData#defaultSkin} using the slot index and @@ -509,10 +513,10 @@ export class Skeleton { * * See [Runtime skins](http://esotericsoftware.com/spine-runtime-skins) in the Spine Runtimes Guide. * @returns May be null. */ - getAttachment (slotIndex: number, attachmentName: string): Attachment { + getAttachment (slotIndex: number, attachmentName: string): Attachment | null { if (!attachmentName) throw new Error("attachmentName cannot be null."); if (this.skin) { - let attachment: Attachment = this.skin.getAttachment(slotIndex, attachmentName); + let attachment = this.skin.getAttachment(slotIndex, attachmentName); if (attachment) return attachment; } if (this.data.defaultSkin) return this.data.defaultSkin.getAttachment(slotIndex, attachmentName); @@ -528,7 +532,7 @@ export class Skeleton { for (let i = 0, n = slots.length; i < n; i++) { let slot = slots[i]; if (slot.data.name == slotName) { - let attachment: Attachment = null; + let attachment: Attachment | null = null; if (attachmentName) { attachment = this.getAttachment(i, attachmentName); if (!attachment) throw new Error("Attachment not found: " + attachmentName + ", for slot: " + slotName); @@ -602,7 +606,7 @@ export class Skeleton { let slot = drawOrder[i]; if (!slot.bone.active) continue; let verticesLength = 0; - let vertices: NumberArrayLike = null; + let vertices: NumberArrayLike | null = null; let attachment = slot.getAttachment(); if (attachment instanceof RegionAttachment) { verticesLength = 8; diff --git a/spine-ts/spine-core/src/SkeletonBinary.ts b/spine-ts/spine-core/src/SkeletonBinary.ts index 9596a80db..6c73ae20f 100644 --- a/spine-ts/spine-core/src/SkeletonBinary.ts +++ b/spine-ts/spine-core/src/SkeletonBinary.ts @@ -56,7 +56,7 @@ export class SkeletonBinary { * See [Scaling](http://esotericsoftware.com/spine-loading-skeleton-data#Scaling) in the Spine Runtimes Guide. */ scale = 1; - attachmentLoader: AttachmentLoader = null; + attachmentLoader: AttachmentLoader; private linkedMeshes = new Array(); constructor (attachmentLoader: AttachmentLoader) { @@ -91,13 +91,17 @@ export class SkeletonBinary { let n = 0; // Strings. n = input.readInt(true) - for (let i = 0; i < n; i++) - input.strings.push(input.readString()); + for (let i = 0; i < n; i++) { + let str = input.readString(); + if (!str) throw new Error("String in string table must not be null."); + input.strings.push(str); + } // Bones. n = input.readInt(true) for (let i = 0; i < n; i++) { let name = input.readString(); + if (!name) throw new Error("Bone name must not be null."); let parent = i == 0 ? null : skeletonData.bones[input.readInt(true)]; let data = new BoneData(i, name, parent); data.rotation = input.readFloat(); @@ -118,6 +122,7 @@ export class SkeletonBinary { n = input.readInt(true); for (let i = 0; i < n; i++) { let slotName = input.readString(); + if (!slotName) throw new Error("Slot name must not be null."); let boneData = skeletonData.bones[input.readInt(true)]; let data = new SlotData(i, slotName, boneData); Color.rgba8888ToColor(data.color, input.readInt32()); @@ -133,7 +138,9 @@ export class SkeletonBinary { // IK constraints. n = input.readInt(true); for (let i = 0, nn; i < n; i++) { - let data = new IkConstraintData(input.readString()); + let name = input.readString(); + if (!name) throw new Error("IK constraint data name must not be null."); + let data = new IkConstraintData(name); data.order = input.readInt(true); data.skinRequired = input.readBoolean(); nn = input.readInt(true); @@ -152,7 +159,9 @@ export class SkeletonBinary { // Transform constraints. n = input.readInt(true); for (let i = 0, nn; i < n; i++) { - let data = new TransformConstraintData(input.readString()); + let name = input.readString(); + if (!name) throw new Error("Transform constraint data name must not be null."); + let data = new TransformConstraintData(name); data.order = input.readInt(true); data.skinRequired = input.readBoolean(); nn = input.readInt(true); @@ -179,7 +188,9 @@ export class SkeletonBinary { // Path constraints. n = input.readInt(true); for (let i = 0, nn; i < n; i++) { - let data = new PathConstraintData(input.readString()); + let name = input.readString(); + if (!name) throw new Error("Path constraint data name must not be null."); + let data = new PathConstraintData(name); data.order = input.readInt(true); data.skinRequired = input.readBoolean(); nn = input.readInt(true); @@ -211,8 +222,11 @@ export class SkeletonBinary { { let i = skeletonData.skins.length; Utils.setArraySize(skeletonData.skins, n = i + input.readInt(true)); - for (; i < n; i++) - skeletonData.skins[i] = this.readSkin(input, skeletonData, false, nonessential); + for (; i < n; i++) { + let skin = this.readSkin(input, skeletonData, false, nonessential); + if (!skin) throw new Error("readSkin() should not have returned null."); + skeletonData.skins[i] = skin; + } } // Linked meshes. @@ -220,7 +234,10 @@ export class SkeletonBinary { for (let i = 0; i < n; i++) { let linkedMesh = this.linkedMeshes[i]; let skin = !linkedMesh.skin ? skeletonData.defaultSkin : skeletonData.findSkin(linkedMesh.skin); + if (!skin) throw new Error("Not skin found for linked mesh."); + if (!linkedMesh.parent) throw new Error("Linked mesh parent must not be null"); let parent = skin.getAttachment(linkedMesh.slotIndex, linkedMesh.parent); + if (!parent) throw new Error(`Parent mesh not found: ${linkedMesh.parent}`); linkedMesh.mesh.timelineAttahment = linkedMesh.inheritTimeline ? parent as VertexAttachment : linkedMesh.mesh; linkedMesh.mesh.setParentMesh(parent as MeshAttachment); if (linkedMesh.mesh.region != null) linkedMesh.mesh.updateRegion(); @@ -230,7 +247,9 @@ export class SkeletonBinary { // Events. n = input.readInt(true); for (let i = 0; i < n; i++) { - let data = new EventData(input.readStringRef()); + let eventName = input.readStringRef(); + if (!eventName) throw new Error + let data = new EventData(eventName); data.intValue = input.readInt(false); data.floatValue = input.readFloat(); data.stringValue = input.readString(); @@ -244,12 +263,15 @@ export class SkeletonBinary { // Animations. n = input.readInt(true); - for (let i = 0; i < n; i++) - skeletonData.animations.push(this.readAnimation(input, input.readString(), skeletonData)); + for (let i = 0; i < n; i++) { + let animationName = input.readString(); + if (!animationName) throw new Error("Animatio name must not be null."); + skeletonData.animations.push(this.readAnimation(input, animationName, skeletonData)); + } return skeletonData; } - private readSkin (input: BinaryInput, skeletonData: SkeletonData, defaultSkin: boolean, nonessential: boolean): Skin { + private readSkin (input: BinaryInput, skeletonData: SkeletonData, defaultSkin: boolean, nonessential: boolean): Skin | null { let skin = null; let slotCount = 0; @@ -258,7 +280,9 @@ export class SkeletonBinary { if (slotCount == 0) return null; skin = new Skin("default"); } else { - skin = new Skin(input.readStringRef()); + let skinName = input.readStringRef(); + if (!skinName) throw new Error("Skin name must not be null."); + skin = new Skin(skinName); skin.bones.length = input.readInt(true); for (let i = 0, n = skin.bones.length; i < n; i++) skin.bones[i] = skeletonData.bones[input.readInt(true)]; @@ -277,6 +301,7 @@ export class SkeletonBinary { let slotIndex = input.readInt(true); for (let ii = 0, nn = input.readInt(true); ii < nn; ii++) { let name = input.readStringRef(); + if (!name) throw new Error("Attachment name must not be null"); let attachment = this.readAttachment(input, skeletonData, skin, slotIndex, name, nonessential); if (attachment) skin.setAttachment(slotIndex, name, attachment); } @@ -284,7 +309,7 @@ export class SkeletonBinary { return skin; } - private readAttachment (input: BinaryInput, skeletonData: SkeletonData, skin: Skin, slotIndex: number, attachmentName: string, nonessential: boolean): Attachment { + private readAttachment (input: BinaryInput, skeletonData: SkeletonData, skin: Skin, slotIndex: number, attachmentName: string, nonessential: boolean): Attachment | null { let scale = this.scale; let name = input.readStringRef(); @@ -327,7 +352,7 @@ export class SkeletonBinary { let box = this.attachmentLoader.newBoundingBoxAttachment(skin, name); if (!box) return null; box.worldVerticesLength = vertexCount << 1; - box.vertices = vertices.vertices; + box.vertices = vertices.vertices!; box.bones = vertices.bones; if (nonessential) Color.rgba8888ToColor(box.color, color); return box; @@ -341,7 +366,7 @@ export class SkeletonBinary { let vertices = this.readVertices(input, vertexCount); let hullLength = input.readInt(true); let sequence = this.readSequence(input); - let edges = null; + let edges: number[] = []; let width = 0, height = 0; if (nonessential) { edges = this.readShortArray(input); @@ -355,7 +380,7 @@ export class SkeletonBinary { mesh.path = path; Color.rgba8888ToColor(mesh.color, color); mesh.bones = vertices.bones; - mesh.vertices = vertices.vertices; + mesh.vertices = vertices.vertices!; mesh.worldVerticesLength = vertexCount << 1; mesh.triangles = triangles; mesh.regionUVs = uvs; @@ -410,7 +435,7 @@ export class SkeletonBinary { path.closed = closed; path.constantSpeed = constantSpeed; path.worldVerticesLength = vertexCount << 1; - path.vertices = vertices.vertices; + path.vertices = vertices.vertices!; path.bones = vertices.bones; path.lengths = lengths; if (nonessential) Color.rgba8888ToColor(path.color, color); @@ -440,7 +465,7 @@ export class SkeletonBinary { if (!clip) return null; clip.endSlot = skeletonData.slots[endSlotIndex]; clip.worldVerticesLength = vertexCount << 1; - clip.vertices = vertices.vertices; + clip.vertices = vertices.vertices!; clip.bones = vertices.bones; if (nonessential) Color.rgba8888ToColor(clip.color, color); return clip; @@ -866,6 +891,7 @@ export class SkeletonBinary { let slotIndex = input.readInt(true); for (let iii = 0, nnn = input.readInt(true); iii < nnn; iii++) { let attachmentName = input.readStringRef(); + if (!attachmentName) throw new Error("attachmentName must not be null."); let attachment = skin.getAttachment(slotIndex, attachmentName); let timelineType = input.readByte(); let frameCount = input.readInt(true); @@ -1041,12 +1067,12 @@ export class BinaryInput { return optimizePositive ? result : ((result >>> 1) ^ -(result & 1)); } - readStringRef (): string { + readStringRef (): string | null { let index = this.readInt(true); return index == 0 ? null : this.strings[index - 1]; } - readString (): string { + readString (): string | null { let byteCount = this.readInt(true); switch (byteCount) { case 0: @@ -1089,12 +1115,12 @@ export class BinaryInput { } class LinkedMesh { - parent: string; skin: string; + parent: string | null; skin: string | null; slotIndex: number; mesh: MeshAttachment; inheritTimeline: boolean; - constructor (mesh: MeshAttachment, skin: string, slotIndex: number, parent: string, inheritDeform: boolean) { + constructor (mesh: MeshAttachment, skin: string | null, slotIndex: number, parent: string | null, inheritDeform: boolean) { this.mesh = mesh; this.skin = skin; this.slotIndex = slotIndex; @@ -1104,7 +1130,7 @@ class LinkedMesh { } class Vertices { - constructor (public bones: Array = null, public vertices: Array | Float32Array = null) { } + constructor (public bones: Array | null = null, public vertices: Array | Float32Array | null = null) { } } enum AttachmentType { Region, BoundingBox, Mesh, LinkedMesh, Path, Point, Clipping } diff --git a/spine-ts/spine-core/src/SkeletonBounds.ts b/spine-ts/spine-core/src/SkeletonBounds.ts index bbaf07ce3..0d387ab74 100644 --- a/spine-ts/spine-core/src/SkeletonBounds.ts +++ b/spine-ts/spine-core/src/SkeletonBounds.ts @@ -153,7 +153,7 @@ export class SkeletonBounds { /** Returns the first bounding box attachment that contains the point, or null. When doing many checks, it is usually more * efficient to only call this method if {@link #aabbContainsPoint(float, float)} returns true. */ - containsPoint (x: number, y: number): BoundingBoxAttachment { + containsPoint (x: number, y: number): BoundingBoxAttachment | null { let polygons = this.polygons; for (let i = 0, n = polygons.length; i < n; i++) if (this.containsPointPolygon(polygons[i], x, y)) return this.boundingBoxes[i]; diff --git a/spine-ts/spine-core/src/SkeletonClipping.ts b/spine-ts/spine-core/src/SkeletonClipping.ts index ec5a30ee5..88c94d636 100644 --- a/spine-ts/spine-core/src/SkeletonClipping.ts +++ b/spine-ts/spine-core/src/SkeletonClipping.ts @@ -40,8 +40,8 @@ export class SkeletonClipping { clippedTriangles = new Array(); private scratch = new Array(); - private clipAttachment: ClippingAttachment; - private clippingPolygons: Array>; + private clipAttachment: ClippingAttachment | null = null; + private clippingPolygons: Array> | null = null; clipStart (slot: Slot, clip: ClippingAttachment): number { if (this.clipAttachment) return 0; @@ -85,8 +85,8 @@ export class SkeletonClipping { let clipOutput = this.clipOutput, clippedVertices = this.clippedVertices; let clippedTriangles = this.clippedTriangles; - let polygons = this.clippingPolygons; - let polygonsCount = this.clippingPolygons.length; + let polygons = this.clippingPolygons!; + let polygonsCount = polygons.length; let vertexSize = twoColor ? 12 : 8; let index = 0; @@ -234,7 +234,7 @@ export class SkeletonClipping { let clipped = false; // Avoid copy at the end. - let input: Array = null; + let input: Array; if (clippingArea.length % 4 >= 2) { input = output; output = this.scratch; diff --git a/spine-ts/spine-core/src/SkeletonData.ts b/spine-ts/spine-core/src/SkeletonData.ts index c543cf3b5..dea832089 100644 --- a/spine-ts/spine-core/src/SkeletonData.ts +++ b/spine-ts/spine-core/src/SkeletonData.ts @@ -43,7 +43,7 @@ import { TransformConstraintData } from "./TransformConstraintData"; export class SkeletonData { /** The skeleton's name, which by default is the name of the skeleton data file, if possible. May be null. */ - name: string = null; + name: string | null = null; /** The skeleton's bones, sorted parent first. The root bone is always the first bone. */ bones = new Array(); // Ordered parents first. @@ -56,7 +56,7 @@ export class SkeletonData { * * See {@link Skeleton#getAttachmentByName()}. * May be null. */ - defaultSkin: Skin = null; + defaultSkin: Skin | null = null; /** The skeleton's events. */ events = new Array(); @@ -86,20 +86,20 @@ export class SkeletonData { height: number = 0; /** The Spine version used to export the skeleton data, or null. */ - version: string = null; + version: string | null = null; /** The skeleton data hash. This value will change if any of the skeleton data has changed. May be null. */ - hash: string = null; + hash: string | null = null; // Nonessential /** The dopesheet FPS in Spine. Available only when nonessential data was exported. */ fps = 0; /** The path to the images directory as defined in Spine. Available only when nonessential data was exported. May be null. */ - imagesPath: string = null; + imagesPath: string | null = null; /** The path to the audio directory as defined in Spine. Available only when nonessential data was exported. May be null. */ - audioPath: string = null; + audioPath: string | null = null; /** Finds a bone by comparing each bone's name. It is more efficient to cache the results of this method than to call it * multiple times. diff --git a/spine-ts/spine-core/src/SkeletonJson.ts b/spine-ts/spine-core/src/SkeletonJson.ts index 000599404..ef3983617 100644 --- a/spine-ts/spine-core/src/SkeletonJson.ts +++ b/spine-ts/spine-core/src/SkeletonJson.ts @@ -51,7 +51,7 @@ import { HasTextureRegion } from "./attachments/HasTextureRegion"; * [JSON and binary data](http://esotericsoftware.com/spine-loading-skeleton-data#JSON-and-binary-data) in the Spine * Runtimes Guide. */ export class SkeletonJson { - attachmentLoader: AttachmentLoader = null; + attachmentLoader: AttachmentLoader; /** Scales bone positions, image sizes, and translations as they are loaded. This allows different size images to be used at * runtime than were used in Spine. @@ -87,7 +87,7 @@ export class SkeletonJson { for (let i = 0; i < root.bones.length; i++) { let boneMap = root.bones[i]; - let parent: BoneData = null; + let parent: BoneData | null = null; let parentName: string = getValue(boneMap, "parent", null); if (parentName) parent = skeletonData.findBone(parentName); let data = new BoneData(skeletonData.bones.length, boneMap.name, parent); @@ -114,6 +114,7 @@ export class SkeletonJson { for (let i = 0; i < root.slots.length; i++) { let slotMap = root.slots[i]; let boneData = skeletonData.findBone(slotMap.bone); + if (!boneData) throw new Error(`Couldn't find bone ${slotMap.bone} for slot ${slotMap.name}`); let data = new SlotData(skeletonData.slots.length, slotMap.name, boneData); let color: string = getValue(slotMap, "color", null); @@ -136,10 +137,15 @@ export class SkeletonJson { data.order = getValue(constraintMap, "order", 0); data.skinRequired = getValue(constraintMap, "skin", false); - for (let ii = 0; ii < constraintMap.bones.length; ii++) - data.bones.push(skeletonData.findBone(constraintMap.bones[ii])); + for (let ii = 0; ii < constraintMap.bones.length; ii++) { + let bone = skeletonData.findBone(constraintMap.bones[ii]); + if (!bone) throw new Error(`Couldn't find bone ${constraintMap.bones[ii]} for IK constraint ${constraintMap.name}.`); + data.bones.push(bone); + } - data.target = skeletonData.findBone(constraintMap.target); + let target = skeletonData.findBone(constraintMap.target);; + if (!target) throw new Error(`Couldn't find target bone ${constraintMap.target} for IK constraint ${constraintMap.name}.`); + data.target = target; data.mix = getValue(constraintMap, "mix", 1); data.softness = getValue(constraintMap, "softness", 0) * scale; @@ -160,11 +166,17 @@ export class SkeletonJson { data.order = getValue(constraintMap, "order", 0); data.skinRequired = getValue(constraintMap, "skin", false); - for (let ii = 0; ii < constraintMap.bones.length; ii++) - data.bones.push(skeletonData.findBone(constraintMap.bones[ii])); + for (let ii = 0; ii < constraintMap.bones.length; ii++) { + let boneName = constraintMap.bones[ii]; + let bone = skeletonData.findBone(boneName); + if (!bone) throw new Error(`Couldn't find bone ${boneName} for transform constraint ${constraintMap.name}.`); + data.bones.push(bone); + } let targetName: string = constraintMap.target; - data.target = skeletonData.findBone(targetName); + let target = skeletonData.findBone(targetName); + if (!target) throw new Error(`Couldn't find target bone ${targetName} for transform constraint ${constraintMap.name}.`); + data.target = target; data.local = getValue(constraintMap, "local", false); data.relative = getValue(constraintMap, "relative", false); @@ -194,11 +206,17 @@ export class SkeletonJson { data.order = getValue(constraintMap, "order", 0); data.skinRequired = getValue(constraintMap, "skin", false); - for (let ii = 0; ii < constraintMap.bones.length; ii++) - data.bones.push(skeletonData.findBone(constraintMap.bones[ii])); + for (let ii = 0; ii < constraintMap.bones.length; ii++) { + let boneName = constraintMap.bones[ii]; + let bone = skeletonData.findBone(boneName); + if (!bone) throw new Error(`Couldn't find bone ${boneName} for path constraint ${constraintMap.name}.`); + data.bones.push(bone); + } let targetName: string = constraintMap.target; - data.target = skeletonData.findSlot(targetName); + let target = skeletonData.findSlot(targetName); + if (!target) throw new Error(`Couldn't find target slot ${targetName} for path constraint ${constraintMap.name}.`); + data.target = target; data.positionMode = Utils.enumValue(PositionMode, getValue(constraintMap, "positionMode", "Percent")); data.spacingMode = Utils.enumValue(SpacingMode, getValue(constraintMap, "spacingMode", "Length")); @@ -223,27 +241,44 @@ export class SkeletonJson { let skin = new Skin(skinMap.name); if (skinMap.bones) { - for (let ii = 0; ii < skinMap.bones.length; ii++) - skin.bones.push(skeletonData.findBone(skinMap.bones[ii])); + for (let ii = 0; ii < skinMap.bones.length; ii++) { + let boneName = skinMap.bones[ii]; + let bone = skeletonData.findBone(boneName); + if (!bone) throw new Error(`Couldn't find bone ${boneName} for skin ${skinMap.name}.`); + skin.bones.push(bone); + } } if (skinMap.ik) { - for (let ii = 0; ii < skinMap.ik.length; ii++) - skin.constraints.push(skeletonData.findIkConstraint(skinMap.ik[ii])); + for (let ii = 0; ii < skinMap.ik.length; ii++) { + let constraintName = skinMap.ik[ii]; + let constraint = skeletonData.findIkConstraint(constraintName); + if (!constraint) throw new Error(`Couldn't find IK constraint ${constraintName} for skin ${skinMap.name}.`); + skin.constraints.push(constraint); + } } if (skinMap.transform) { - for (let ii = 0; ii < skinMap.transform.length; ii++) - skin.constraints.push(skeletonData.findTransformConstraint(skinMap.transform[ii])); + for (let ii = 0; ii < skinMap.transform.length; ii++) { + let constraintName = skinMap.transform[ii]; + let constraint = skeletonData.findTransformConstraint(constraintName); + if (!constraint) throw new Error(`Couldn't find transform constraint ${constraintName} for skin ${skinMap.name}.`); + skin.constraints.push(constraint); + } } if (skinMap.path) { - for (let ii = 0; ii < skinMap.path.length; ii++) - skin.constraints.push(skeletonData.findPathConstraint(skinMap.path[ii])); + for (let ii = 0; ii < skinMap.path.length; ii++) { + let constraintName = skinMap.path[ii]; + let constraint = skeletonData.findPathConstraint(constraintName); + if (!constraint) throw new Error(`Couldn't find path constraint ${constraintName} for skin ${skinMap.name}.`); + skin.constraints.push(constraint); + } } for (let slotName in skinMap.attachments) { let slot = skeletonData.findSlot(slotName); + if (!slot) throw new Error(`Couldn't find slot ${slotName} for skin ${skinMap.name}.`); let slotMap = skinMap.attachments[slotName]; for (let entryName in slotMap) { let attachment = this.readAttachment(slotMap[entryName], skin, slot.index, entryName, skeletonData); @@ -259,7 +294,9 @@ export class SkeletonJson { for (let i = 0, n = this.linkedMeshes.length; i < n; i++) { let linkedMesh = this.linkedMeshes[i]; let skin = !linkedMesh.skin ? skeletonData.defaultSkin : skeletonData.findSkin(linkedMesh.skin); + if (!skin) throw new Error(`Skin not found: ${linkedMesh.skin}`); let parent = skin.getAttachment(linkedMesh.slotIndex, linkedMesh.parent); + if (!parent) throw new Error(`Parent mesh not found: ${linkedMesh.parent}`); linkedMesh.mesh.timelineAttahment = linkedMesh.inheritTimeline ? parent : linkedMesh.mesh; linkedMesh.mesh.setParentMesh(parent); if (linkedMesh.mesh.region != null) linkedMesh.mesh.updateRegion(); @@ -294,7 +331,7 @@ export class SkeletonJson { return skeletonData; } - readAttachment (map: any, skin: Skin, slotIndex: number, name: string, skeletonData: SkeletonData): Attachment { + readAttachment (map: any, skin: Skin, slotIndex: number, name: string, skeletonData: SkeletonData): Attachment | null { let scale = this.scale; name = getValue(map, "name", name); @@ -452,7 +489,9 @@ export class SkeletonJson { if (map.slots) { for (let slotName in map.slots) { let slotMap = map.slots[slotName]; - let slotIndex = skeletonData.findSlot(slotName).index; + let slot = skeletonData.findSlot(slotName); + if (!slot) throw new Error("Slot not found: " + slotName); + let slotIndex = slot.index; for (let timelineName in slotMap) { let timelineMap = slotMap[timelineName]; if (!timelineMap) continue; @@ -603,7 +642,9 @@ export class SkeletonJson { if (map.bones) { for (let boneName in map.bones) { let boneMap = map.bones[boneName]; - let boneIndex = skeletonData.findBone(boneName).index; + let bone = skeletonData.findBone(boneName); + if (!bone) throw new Error("Bone not found: " + boneName); + let boneIndex = bone.index; for (let timelineName in boneMap) { let timelineMap = boneMap[timelineName]; let frames = timelineMap.length; @@ -651,6 +692,7 @@ export class SkeletonJson { if (!keyMap) continue; let constraint = skeletonData.findIkConstraint(constraintName); + if (!constraint) throw new Error("IK Constraint not found: " + constraintName); let constraintIndex = skeletonData.ikConstraints.indexOf(constraint); let timeline = new IkConstraintTimeline(constraintMap.length, constraintMap.length << 1, constraintIndex); @@ -692,6 +734,7 @@ export class SkeletonJson { if (!keyMap) continue; let constraint = skeletonData.findTransformConstraint(constraintName); + if (!constraint) throw new Error("Transform constraint not found: " + constraintName); let constraintIndex = skeletonData.transformConstraints.indexOf(constraint); let timeline = new TransformConstraintTimeline(timelineMap.length, timelineMap.length * 6, constraintIndex); @@ -746,6 +789,7 @@ export class SkeletonJson { for (let constraintName in map.path) { let constraintMap = map.path[constraintName]; let constraint = skeletonData.findPathConstraint(constraintName); + if (!constraint) throw new Error("Path constraint not found: " + constraintName); let constraintIndex = skeletonData.pathConstraints.indexOf(constraint); for (let timelineName in constraintMap) { let timelineMap = constraintMap[timelineName]; @@ -799,9 +843,12 @@ export class SkeletonJson { for (let attachmentsName in map.attachments) { let attachmentsMap = map.attachments[attachmentsName]; let skin = skeletonData.findSkin(attachmentsName); + if (!skin) throw new Error("Skin not found: " + attachmentsName); for (let slotMapName in attachmentsMap) { let slotMap = attachmentsMap[slotMapName]; - let slotIndex = skeletonData.findSlot(slotMapName).index; + let slot = skeletonData.findSlot(slotMapName); + if (!slot) throw new Error("Slot not found: " + slotMapName); + let slotIndex = slot.index; for (let attachmentMapName in slotMap) { let attachmentMap = slotMap[attachmentMapName]; let attachment = skin.getAttachment(slotIndex, attachmentMapName); @@ -877,7 +924,7 @@ export class SkeletonJson { let frame = 0; for (let i = 0; i < map.drawOrder.length; i++, frame++) { let drawOrderMap = map.drawOrder[i]; - let drawOrder: Array = null; + let drawOrder: Array | null = null; let offsets = getValue(drawOrderMap, "offsets", null); if (offsets) { drawOrder = Utils.newArray(slotCount, -1); @@ -885,7 +932,9 @@ export class SkeletonJson { let originalIndex = 0, unchangedIndex = 0; for (let ii = 0; ii < offsets.length; ii++) { let offsetMap = offsets[ii]; - let slotIndex = skeletonData.findSlot(offsetMap.slot).index; + let slot = skeletonData.findSlot(offsetMap.slot); + if (!slot) throw new Error("Slot not found: " + slot); + let slotIndex = slot.index; // Collect unchanged items. while (originalIndex != slotIndex) unchanged[unchangedIndex++] = originalIndex++; @@ -911,6 +960,7 @@ export class SkeletonJson { for (let i = 0; i < map.events.length; i++, frame++) { let eventMap = map.events[i]; let eventData = skeletonData.findEvent(eventMap.name); + if (!eventData) throw new Error("Event not found: " + eventMap.name); let event = new Event(Utils.toSinglePrecision(getValue(eventMap, "time", 0)), eventData); event.intValue = getValue(eventMap, "int", eventData.intValue); event.floatValue = getValue(eventMap, "float", eventData.floatValue); diff --git a/spine-ts/spine-core/src/Skin.ts b/spine-ts/spine-core/src/Skin.ts index fcbcef7ef..1712a50bf 100644 --- a/spine-ts/spine-core/src/Skin.ts +++ b/spine-ts/spine-core/src/Skin.ts @@ -36,7 +36,7 @@ import { StringMap } from "./Utils"; /** Stores an entry in the skin consisting of the slot index, name, and attachment **/ export class SkinEntry { - constructor (public slotIndex: number = 0, public name: string = null, public attachment: Attachment = null) { } + constructor (public slotIndex: number = 0, public name: string, public attachment: Attachment) { } } /** Stores attachments by slot index and attachment name. @@ -45,7 +45,7 @@ export class SkinEntry { * [Runtime skins](http://esotericsoftware.com/spine-runtime-skins) in the Spine Runtimes Guide. */ export class Skin { /** The skin's name, which is unique across all skins in the skeleton. */ - name: string = null; + name: string; attachments = new Array>(); bones = Array(); @@ -140,7 +140,7 @@ export class Skin { } /** Returns the attachment for the specified slot index and name, or null. */ - getAttachment (slotIndex: number, name: string): Attachment { + getAttachment (slotIndex: number, name: string): Attachment | null { let dictionary = this.attachments[slotIndex]; return dictionary ? dictionary[name] : null; } @@ -148,7 +148,7 @@ export class Skin { /** Removes the attachment in the skin for the specified slot index and name, if any. */ removeAttachment (slotIndex: number, name: string) { let dictionary = this.attachments[slotIndex]; - if (dictionary) dictionary[name] = null; + if (dictionary) delete dictionary[name]; } /** Returns all attachments in this skin. */ diff --git a/spine-ts/spine-core/src/Slot.ts b/spine-ts/spine-core/src/Slot.ts index 755fbd87a..b625fb35a 100644 --- a/spine-ts/spine-core/src/Slot.ts +++ b/spine-ts/spine-core/src/Slot.ts @@ -38,26 +38,26 @@ import { Color } from "./Utils"; * across multiple skeletons. */ export class Slot { /** The slot's setup pose data. */ - data: SlotData = null; + data: SlotData; /** The bone this slot belongs to. */ - bone: Bone = null; + bone: Bone; /** The color used to tint the slot's attachment. If {@link #getDarkColor()} is set, this is used as the light color for two * color tinting. */ - color: Color = null; + color: Color; /** The dark color used to tint the slot's attachment for two color tinting, or null if two color tinting is not used. The dark * color's alpha is not used. */ - darkColor: Color = null; + darkColor: Color | null = null; - attachment: Attachment = null; + attachment: Attachment | null = null; attachmentState: number = 0; /** The index of the texture region to display when the slot's attachment has a {@link Sequence}. -1 represents the * {@link Sequence#getSetupIndex()}. */ - sequenceIndex: number; + sequenceIndex: number = -1; /** Values to deform the slot's attachment. For an unweighted mesh, the entries are local positions for each vertex. For a * weighted mesh, the entries are an offset for each vertex which will be added to the mesh's local vertex positions. @@ -81,14 +81,14 @@ export class Slot { } /** The current attachment for the slot, or null if the slot has no attachment. */ - getAttachment (): Attachment { + getAttachment (): Attachment | null { return this.attachment; } /** Sets the slot's attachment and, if the attachment changed, resets {@link #sequenceIndex} and clears the {@link #deform}. * The deform is not cleared if the old attachment has the same {@link VertexAttachment#getTimelineAttachment()} as the * specified attachment. */ - setAttachment (attachment: Attachment) { + setAttachment (attachment: Attachment | null) { if (this.attachment == attachment) return; if (!(attachment instanceof VertexAttachment) || !(this.attachment instanceof VertexAttachment) || (attachment).timelineAttahment != (this.attachment).timelineAttahment) { @@ -101,7 +101,7 @@ export class Slot { /** Sets this slot to the setup pose. */ setToSetupPose () { this.color.setFromColor(this.data.color); - if (this.darkColor) this.darkColor.setFromColor(this.data.darkColor); + if (this.darkColor) this.darkColor.setFromColor(this.data.darkColor!); if (!this.data.attachmentName) this.attachment = null; else { diff --git a/spine-ts/spine-core/src/SlotData.ts b/spine-ts/spine-core/src/SlotData.ts index 44cfc5e86..da088ef5e 100644 --- a/spine-ts/spine-core/src/SlotData.ts +++ b/spine-ts/spine-core/src/SlotData.ts @@ -36,10 +36,10 @@ export class SlotData { index: number = 0; /** The name of the slot, which is unique across all slots in the skeleton. */ - name: string = null; + name: string; /** The bone this slot belongs to. */ - boneData: BoneData = null; + boneData: BoneData; /** The color used to tint the slot's attachment. If {@link #getDarkColor()} is set, this is used as the light color for two * color tinting. */ @@ -47,13 +47,13 @@ export class SlotData { /** The dark color used to tint the slot's attachment for two color tinting, or null if two color tinting is not used. The dark * color's alpha is not used. */ - darkColor: Color = null; + darkColor: Color | null = null; /** The name of the attachment that is visible for this slot in the setup pose, or null if no attachment is visible. */ - attachmentName: string = null; + attachmentName: string | null = null; /** The blend mode for drawing the slot's attachment. */ - blendMode: BlendMode = null; + blendMode: BlendMode = BlendMode.Normal; constructor (index: number, name: string, boneData: BoneData) { if (index < 0) throw new Error("index must be >= 0."); diff --git a/spine-ts/spine-core/src/TextureAtlas.ts b/spine-ts/spine-core/src/TextureAtlas.ts index 56098e49f..4bcf50c8b 100644 --- a/spine-ts/spine-core/src/TextureAtlas.ts +++ b/spine-ts/spine-core/src/TextureAtlas.ts @@ -38,66 +38,64 @@ export class TextureAtlas implements Disposable { constructor (atlasText: string) { let reader = new TextureAtlasReader(atlasText); let entry = new Array(4); - let page: TextureAtlasPage = null; - let region: TextureAtlasRegion = null; - let pageFields: StringMap = {}; - pageFields["size"] = () => { - page.width = parseInt(entry[1]); - page.height = parseInt(entry[2]); + let pageFields: StringMap<(page: TextureAtlasPage) => void> = {}; + pageFields["size"] = (page: TextureAtlasPage) => { + page!.width = parseInt(entry[1]); + page!.height = parseInt(entry[2]); }; pageFields["format"] = () => { // page.format = Format[tuple[0]]; we don't need format in WebGL }; - pageFields["filter"] = () => { - page.minFilter = Utils.enumValue(TextureFilter, entry[1]); - page.magFilter = Utils.enumValue(TextureFilter, entry[2]); + pageFields["filter"] = (page: TextureAtlasPage) => { + page!.minFilter = Utils.enumValue(TextureFilter, entry[1]); + page!.magFilter = Utils.enumValue(TextureFilter, entry[2]); }; - pageFields["repeat"] = () => { - if (entry[1].indexOf('x') != -1) page.uWrap = TextureWrap.Repeat; - if (entry[1].indexOf('y') != -1) page.vWrap = TextureWrap.Repeat; + pageFields["repeat"] = (page: TextureAtlasPage) => { + if (entry[1].indexOf('x') != -1) page!.uWrap = TextureWrap.Repeat; + if (entry[1].indexOf('y') != -1) page!.vWrap = TextureWrap.Repeat; }; - pageFields["pma"] = () => { - page.pma = entry[1] == "true"; + pageFields["pma"] = (page: TextureAtlasPage) => { + page!.pma = entry[1] == "true"; }; - var regionFields: StringMap = {}; - regionFields["xy"] = () => { // Deprecated, use bounds. + var regionFields: StringMap<(region: TextureAtlasRegion) => void> = {}; + regionFields["xy"] = (region: TextureAtlasRegion) => { // Deprecated, use bounds. region.x = parseInt(entry[1]); region.y = parseInt(entry[2]); }; - regionFields["size"] = () => { // Deprecated, use bounds. + regionFields["size"] = (region: TextureAtlasRegion) => { // Deprecated, use bounds. region.width = parseInt(entry[1]); region.height = parseInt(entry[2]); }; - regionFields["bounds"] = () => { + regionFields["bounds"] = (region: TextureAtlasRegion) => { region.x = parseInt(entry[1]); region.y = parseInt(entry[2]); region.width = parseInt(entry[3]); region.height = parseInt(entry[4]); }; - regionFields["offset"] = () => { // Deprecated, use offsets. + regionFields["offset"] = (region: TextureAtlasRegion) => { // Deprecated, use offsets. region.offsetX = parseInt(entry[1]); region.offsetY = parseInt(entry[2]); }; - regionFields["orig"] = () => { // Deprecated, use offsets. + regionFields["orig"] = (region: TextureAtlasRegion) => { // Deprecated, use offsets. region.originalWidth = parseInt(entry[1]); region.originalHeight = parseInt(entry[2]); }; - regionFields["offsets"] = () => { + regionFields["offsets"] = (region: TextureAtlasRegion) => { region.offsetX = parseInt(entry[1]); region.offsetY = parseInt(entry[2]); region.originalWidth = parseInt(entry[3]); region.originalHeight = parseInt(entry[4]); }; - regionFields["rotate"] = () => { + regionFields["rotate"] = (region: TextureAtlasRegion) => { let value = entry[1]; if (value == "true") region.degrees = 90; else if (value != "false") region.degrees = parseInt(value); }; - regionFields["index"] = () => { + regionFields["index"] = (region: TextureAtlasRegion) => { region.index = parseInt(entry[1]); }; @@ -113,38 +111,34 @@ export class TextureAtlas implements Disposable { } // Page and region entries. - let names: string[] = null; - let values: number[][] = null; + let page: TextureAtlasPage | null = null; + let names: string[] | null = null; + let values: number[][] | null = null; while (true) { if (line === null) break; if (line.trim().length == 0) { page = null; line = reader.readLine(); } else if (!page) { - page = new TextureAtlasPage(); - page.name = line.trim(); + page = new TextureAtlasPage(line.trim()); while (true) { if (reader.readEntry(entry, line = reader.readLine()) == 0) break; - let field: Function = pageFields[entry[0]]; - if (field) field(); + let field = pageFields[entry[0]]; + if (field) field(page); } this.pages.push(page); } else { - region = new TextureAtlasRegion(); + let region = new TextureAtlasRegion(page, line); - region.page = page; - region.name = line; while (true) { let count = reader.readEntry(entry, line = reader.readLine()); if (count == 0) break; - let field: Function = regionFields[entry[0]]; + let field = regionFields[entry[0]]; if (field) - field(); + field(region); else { - if (!names) { - names = []; - values = []; - } + if (!names) names = []; + if (!values) values = []; names.push(entry[0]); let entryValues: number[] = []; for (let i = 0; i < count; i++) @@ -156,7 +150,7 @@ export class TextureAtlas implements Disposable { region.originalWidth = region.width; region.originalHeight = region.height; } - if (names && names.length > 0) { + if (names && names.length > 0 && values && values.length > 0) { region.names = names; region.values = values; names = null; @@ -176,7 +170,7 @@ export class TextureAtlas implements Disposable { } } - findRegion (name: string): TextureAtlasRegion { + findRegion (name: string): TextureAtlasRegion | null { for (let i = 0; i < this.regions.length; i++) { if (this.regions[i].name == name) { return this.regions[i]; @@ -192,26 +186,26 @@ export class TextureAtlas implements Disposable { dispose () { for (let i = 0; i < this.pages.length; i++) { - this.pages[i].texture.dispose(); + this.pages[i].texture?.dispose(); } } } class TextureAtlasReader { - lines: Array = null; + lines: Array; index: number = 0; constructor (text: string) { this.lines = text.split(/\r\n|\r|\n/); } - readLine (): string { + readLine (): string | null { if (this.index >= this.lines.length) return null; return this.lines[this.index++]; } - readEntry (entry: string[], line: string): number { + readEntry (entry: string[], line: string | null): number { if (!line) return 0; line = line.trim(); if (line.length == 0) return 0; @@ -233,16 +227,20 @@ class TextureAtlasReader { } export class TextureAtlasPage { - name: string = null; + name: string; minFilter: TextureFilter = TextureFilter.Nearest; magFilter: TextureFilter = TextureFilter.Nearest; uWrap: TextureWrap = TextureWrap.ClampToEdge; vWrap: TextureWrap = TextureWrap.ClampToEdge; - texture: Texture = null; + texture: Texture | null = null; width: number = 0; height: number = 0; pma: boolean = false; + constructor (name: string) { + this.name = name; + } + setTexture (texture: Texture) { this.texture = texture; texture.setFilters(this.minFilter, this.magFilter); @@ -251,8 +249,8 @@ export class TextureAtlasPage { } export class TextureAtlasRegion extends TextureRegion { - page: TextureAtlasPage = null; - name: string = null; + page: TextureAtlasPage; + name: string; x: number = 0; y: number = 0; offsetX: number = 0; @@ -261,6 +259,12 @@ export class TextureAtlasRegion extends TextureRegion { originalHeight: number = 0; index: number = 0; degrees: number = 0; - names: string[] = null; - values: number[][] = null; + names: string[] | null = null; + values: number[][] | null = null; + + constructor (page: TextureAtlasPage, name: string) { + super(); + this.page = page; + this.name = name; + } } diff --git a/spine-ts/spine-core/src/TransformConstraint.ts b/spine-ts/spine-core/src/TransformConstraint.ts index 6c15398fe..797db5c3d 100644 --- a/spine-ts/spine-core/src/TransformConstraint.ts +++ b/spine-ts/spine-core/src/TransformConstraint.ts @@ -41,13 +41,13 @@ import { Vector2, MathUtils } from "./Utils"; export class TransformConstraint implements Updatable { /** The transform constraint's setup pose data. */ - data: TransformConstraintData = null; + data: TransformConstraintData; /** The bones that will be modified by this transform constraint. */ - bones: Array = null; + bones: Array; /** The target bone whose world transform will be copied to the constrained bones. */ - target: Bone = null; + target: Bone; mixRotate = 0; mixX = 0; mixY = 0; mixScaleX = 0; mixScaleY = 0; mixShearY = 0; @@ -65,9 +65,14 @@ export class TransformConstraint implements Updatable { this.mixScaleY = data.mixScaleY; this.mixShearY = data.mixShearY; this.bones = new Array(); - for (let i = 0; i < data.bones.length; i++) - this.bones.push(skeleton.findBone(data.bones[i].name)); - this.target = skeleton.findBone(data.target.name); + for (let i = 0; i < data.bones.length; i++) { + let bone = skeleton.findBone(data.bones[i].name); + if (!bone) throw new Error(`Couldn't find bone ${data.bones[i].name}.`); + this.bones.push(bone); + } + let target = skeleton.findBone(data.target.name); + if (!target) throw new Error(`Couldn't find target bone ${data.target.name}.`); + this.target = target; } isActive () { diff --git a/spine-ts/spine-core/src/TransformConstraintData.ts b/spine-ts/spine-core/src/TransformConstraintData.ts index 6646f83ae..d28e45822 100644 --- a/spine-ts/spine-core/src/TransformConstraintData.ts +++ b/spine-ts/spine-core/src/TransformConstraintData.ts @@ -39,7 +39,12 @@ export class TransformConstraintData extends ConstraintData { bones = new Array(); /** The target bone whose world transform will be copied to the constrained bones. */ - target: BoneData = null; + private _target: BoneData | null = null; + public set target (boneData: BoneData) { this._target = boneData; } + public get target () { + if (!this._target) throw new Error("BoneData not set.") + else return this._target; + } mixRotate = 0; mixX = 0; diff --git a/spine-ts/spine-core/src/Utils.ts b/spine-ts/spine-core/src/Utils.ts index 4404e810a..e03902e4b 100644 --- a/spine-ts/spine-core/src/Utils.ts +++ b/spine-ts/spine-core/src/Utils.ts @@ -35,7 +35,7 @@ export interface StringMap { } export class IntSet { - array = new Array(); + array = new Array(); add (value: number): boolean { let contains = this.contains(value); @@ -354,7 +354,7 @@ export class Pool { } obtain () { - return this.items.length > 0 ? this.items.pop() : this.instantiator(); + return this.items.length > 0 ? this.items.pop()! : this.instantiator(); } free (item: T) { diff --git a/spine-ts/spine-core/src/attachments/Attachment.ts b/spine-ts/spine-core/src/attachments/Attachment.ts index 8ba50495a..becf5fe8e 100644 --- a/spine-ts/spine-core/src/attachments/Attachment.ts +++ b/spine-ts/spine-core/src/attachments/Attachment.ts @@ -53,12 +53,12 @@ export abstract class VertexAttachment extends Attachment { /** The bones which affect the {@link #getVertices()}. The array entries are, for each vertex, the number of bones affecting * the vertex followed by that many bone indices, which is the index of the bone in {@link Skeleton#bones}. Will be null * if this attachment has no weights. */ - bones: Array = null; + bones: Array | null = null; /** The vertex positions in the bone's coordinate system. For a non-weighted attachment, the values are `x,y` * entries for each vertex. For a weighted attachment, the values are `x,y,weight` entries for each bone affecting * each vertex. */ - vertices: NumberArrayLike = null; + vertices: NumberArrayLike = []; /** The maximum number of world vertex values that can be output by * {@link #computeWorldVertices()} using the `count` parameter. */ @@ -152,8 +152,7 @@ export abstract class VertexAttachment extends Attachment { if (this.vertices) { attachment.vertices = Utils.newFloatArray(this.vertices.length); Utils.arrayCopy(this.vertices, 0, attachment.vertices, 0, this.vertices.length); - } else - attachment.vertices = null; + } attachment.worldVerticesLength = this.worldVerticesLength; attachment.timelineAttahment = this.timelineAttahment; diff --git a/spine-ts/spine-core/src/attachments/AttachmentLoader.ts b/spine-ts/spine-core/src/attachments/AttachmentLoader.ts index 8166d807f..b33e704c7 100644 --- a/spine-ts/spine-core/src/attachments/AttachmentLoader.ts +++ b/spine-ts/spine-core/src/attachments/AttachmentLoader.ts @@ -42,10 +42,10 @@ import { Sequence } from "./Sequence"; * Runtimes Guide. */ export interface AttachmentLoader { /** @return May be null to not load an attachment. */ - newRegionAttachment (skin: Skin, name: string, path: string, sequence: Sequence): RegionAttachment; + newRegionAttachment (skin: Skin, name: string, path: string, sequence: Sequence | null): RegionAttachment; /** @return May be null to not load an attachment. */ - newMeshAttachment (skin: Skin, name: string, path: string, sequence: Sequence): MeshAttachment; + newMeshAttachment (skin: Skin, name: string, path: string, sequence: Sequence | null): MeshAttachment; /** @return May be null to not load an attachment. */ newBoundingBoxAttachment (skin: Skin, name: string): BoundingBoxAttachment; diff --git a/spine-ts/spine-core/src/attachments/ClippingAttachment.ts b/spine-ts/spine-core/src/attachments/ClippingAttachment.ts index 8e0ecfc70..f60ae0186 100644 --- a/spine-ts/spine-core/src/attachments/ClippingAttachment.ts +++ b/spine-ts/spine-core/src/attachments/ClippingAttachment.ts @@ -35,7 +35,7 @@ import { VertexAttachment, Attachment } from "./Attachment"; export class ClippingAttachment extends VertexAttachment { /** Clipping is performed between the clipping polygon's slot and the end slot. Returns null if clipping is done until the end of * the skeleton's rendering. */ - endSlot: SlotData = null; + endSlot: SlotData | null = null; // Nonessential. /** The color of the clipping polygon as it was in Spine. Available only when nonessential data was exported. Clipping polygons diff --git a/spine-ts/spine-core/src/attachments/HasTextureRegion.ts b/spine-ts/spine-core/src/attachments/HasTextureRegion.ts index b8ca7d42a..f117db703 100644 --- a/spine-ts/spine-core/src/attachments/HasTextureRegion.ts +++ b/spine-ts/spine-core/src/attachments/HasTextureRegion.ts @@ -37,7 +37,7 @@ export interface HasTextureRegion { /** The region used to draw the attachment. After setting the region or if the region's properties are changed, * {@link #updateRegion()} must be called. */ - region: TextureRegion; + region: TextureRegion | null; /** Updates any values the attachment calculates using the {@link #getRegion()}. Must be called after setting the * {@link #getRegion()} or if the region's properties are changed. */ @@ -46,5 +46,5 @@ export interface HasTextureRegion { /** The color to tint the attachment. */ color: Color; - sequence: Sequence; + sequence: Sequence | null; } \ No newline at end of file diff --git a/spine-ts/spine-core/src/attachments/MeshAttachment.ts b/spine-ts/spine-core/src/attachments/MeshAttachment.ts index 7567cabc1..0c737f016 100644 --- a/spine-ts/spine-core/src/attachments/MeshAttachment.ts +++ b/spine-ts/spine-core/src/attachments/MeshAttachment.ts @@ -40,21 +40,21 @@ import { Slot } from "../Slot"; * * See [Mesh attachments](http://esotericsoftware.com/spine-meshes) in the Spine User Guide. */ export class MeshAttachment extends VertexAttachment implements HasTextureRegion { - region: TextureRegion = null; + region: TextureRegion | null = null; /** The name of the texture region for this attachment. */ - path: string = null; + path: string; /** The UV pair for each vertex, normalized within the texture region. */ - regionUVs: NumberArrayLike = null; + regionUVs: NumberArrayLike = []; /** The UV pair for each vertex, normalized within the entire texture. * * See {@link #updateUVs}. */ - uvs: NumberArrayLike = null; + uvs: NumberArrayLike = []; /** Triplets of vertex indices which describe the mesh's triangulation. */ - triangles: Array = null; + triangles: Array = []; /** The color to tint the mesh. */ color = new Color(1, 1, 1, 1); @@ -70,28 +70,30 @@ export class MeshAttachment extends VertexAttachment implements HasTextureRegion /** Vertex index pairs describing edges for controling triangulation. Mesh triangles will never cross edges. Only available if * nonessential data was exported. Triangulation is not performed at runtime. */ - edges: Array = null; + edges: Array = []; - private parentMesh: MeshAttachment = null; + private parentMesh: MeshAttachment | null = null; - sequence: Sequence = null; + sequence: Sequence | null = null; tempColor = new Color(0, 0, 0, 0); - constructor (name: string) { + constructor (name: string, path: string) { super(name); + this.path = path; } /** Calculates {@link #uvs} using the {@link #regionUVs} and region. Must be called if the region, the region's properties, or * the {@link #regionUVs} are changed. */ updateRegion () { + if (!this.region) throw new Error("Region not set."); let regionUVs = this.regionUVs; if (!this.uvs || this.uvs.length != regionUVs.length) this.uvs = Utils.newFloatArray(regionUVs.length); let uvs = this.uvs; let n = this.uvs.length; let u = this.region.u, v = this.region.v, width = 0, height = 0; if (this.region instanceof TextureAtlasRegion) { - let region = this.region, image = region.page.texture.getImage(); + let region = this.region, image = region.page!.texture!.getImage(); let textureWidth = image.width, textureHeight = image.height; switch (region.degrees) { case 90: @@ -167,9 +169,8 @@ export class MeshAttachment extends VertexAttachment implements HasTextureRegion copy (): Attachment { if (this.parentMesh) return this.newLinkedMesh(); - let copy = new MeshAttachment(this.name); + let copy = new MeshAttachment(this.name, this.path); copy.region = this.region; - copy.path = this.path; copy.color.setFromColor(this.color); this.copyTo(copy); @@ -201,9 +202,8 @@ export class MeshAttachment extends VertexAttachment implements HasTextureRegion /** Returns a new mesh with the {@link #parentMesh} set to this mesh's parent mesh, if any, else to this mesh. **/ newLinkedMesh (): MeshAttachment { - let copy = new MeshAttachment(this.name); + let copy = new MeshAttachment(this.name, this.path); copy.region = this.region; - copy.path = this.path; copy.color.setFromColor(this.color); copy.timelineAttahment = this.timelineAttahment; copy.setParentMesh(this.parentMesh ? this.parentMesh : this); diff --git a/spine-ts/spine-core/src/attachments/PathAttachment.ts b/spine-ts/spine-core/src/attachments/PathAttachment.ts index cd1912d9c..7d4ba31f3 100644 --- a/spine-ts/spine-core/src/attachments/PathAttachment.ts +++ b/spine-ts/spine-core/src/attachments/PathAttachment.ts @@ -36,7 +36,7 @@ import { VertexAttachment, Attachment } from "./Attachment"; export class PathAttachment extends VertexAttachment { /** The lengths along the path in the setup pose from the start of the path to the end of each Bezier curve. */ - lengths: Array = null; + lengths: Array = []; /** If true, the start and end knots are connected. */ closed = false; diff --git a/spine-ts/spine-core/src/attachments/RegionAttachment.ts b/spine-ts/spine-core/src/attachments/RegionAttachment.ts index 90d401c5b..598bcfda9 100644 --- a/spine-ts/spine-core/src/attachments/RegionAttachment.ts +++ b/spine-ts/spine-core/src/attachments/RegionAttachment.ts @@ -64,11 +64,11 @@ export class RegionAttachment extends Attachment implements HasTextureRegion { color = new Color(1, 1, 1, 1); /** The name of the texture region for this attachment. */ - path: string = null; + path: string; private rendererObject: any = null; - region: TextureRegion = null; - sequence: Sequence = null; + region: TextureRegion | null = null; + sequence: Sequence | null = null; /** For each of the 4 vertices, a pair of x,y values that is the local position of the vertex. * @@ -79,12 +79,14 @@ export class RegionAttachment extends Attachment implements HasTextureRegion { tempColor = new Color(1, 1, 1, 1); - constructor (name: string) { + constructor (name: string, path: string) { super(name); + this.path = path; } /** Calculates the {@link #offset} using the region settings. Must be called after changing region settings. */ updateRegion (): void { + if (!this.region) throw new Error("Region not set."); let region = this.region; let regionScaleX = this.width / this.region.originalWidth * this.scaleX; let regionScaleY = this.height / this.region.originalHeight * this.scaleY; @@ -179,10 +181,9 @@ export class RegionAttachment extends Attachment implements HasTextureRegion { } copy (): Attachment { - let copy = new RegionAttachment(this.name); + let copy = new RegionAttachment(this.name, this.path); copy.region = this.region; copy.rendererObject = this.rendererObject; - copy.path = this.path; copy.x = this.x; copy.y = this.y; copy.scaleX = this.scaleX; diff --git a/spine-ts/spine-player/src/Player.ts b/spine-ts/spine-player/src/Player.ts index 2bca2c350..43e0dbb5e 100644 --- a/spine-ts/spine-player/src/Player.ts +++ b/spine-ts/spine-player/src/Player.ts @@ -31,49 +31,49 @@ import { Animation, AnimationState, AnimationStateData, AtlasAttachmentLoader, B import { AssetManager, GLTexture, Input, LoadingScreen, ManagedWebGLRenderingContext, ResizeMode, SceneRenderer, Vector3 } from "@esotericsoftware/spine-webgl" export interface SpinePlayerConfig { - /* The URL of the skeleton JSON file (.json). */ - jsonUrl: string + /* The URL of the skeleton JSON file (.json). Undefined if binaryUrl is given. */ + jsonUrl?: string /* Optional: The name of a field in the JSON that holds the skeleton data. Default: none */ - jsonField: string + jsonField?: string - /* The URL of the skeleton binary file (.skel). */ - binaryUrl: string + /* The URL of the skeleton binary file (.skel). Undefined if jsonUrl is given. */ + binaryUrl?: string /* The URL of the skeleton atlas file (.atlas). Atlas page images are automatically resolved. */ - atlasUrl: string + atlasUrl?: string /* Raw data URIs, mapping a path to base64 encoded raw data. When player's asset manager resolves the jsonUrl, binaryUrl, atlasUrl, or the image paths referenced in the atlas, it will first look for that path in the raw data URIs. This allows embedding assets directly in HTML/JS. Default: none */ - rawDataURIs: StringMap + rawDataURIs?: StringMap /* Optional: The name of the animation to be played. Default: empty animation */ - animation: string + animation?: string /* Optional: List of animation names from which the user can choose. Default: all animations */ - animations: string[] + animations?: string[] /* Optional: The default mix time used to switch between two animations. Default: 0.25 */ - defaultMix: number + defaultMix?: number /* Optional: The name of the skin to be set. Default: the default skin */ - skin: string + skin?: string /* Optional: List of skin names from which the user can choose. Default: all skins */ - skins: string[] + skins?: string[] /* Optional: Whether the skeleton's atlas images use premultiplied alpha. Default: true */ - premultipliedAlpha: boolean + premultipliedAlpha?: boolean /* Optional: Whether to show the player controls. When false, no external CSS file is needed. Default: true */ - showControls: boolean + showControls?: boolean /* Optional: Whether to show the loading animation. Default: true */ - showLoading: boolean + showLoading?: boolean /* Optional: Which debugging visualizations are shown. Default: none */ - debug: { + debug?: { bones: boolean regions: boolean meshes: boolean @@ -86,81 +86,81 @@ export interface SpinePlayerConfig { /* Optional: The position and size of the viewport in the skeleton's world coordinates. Default: the bounding box that fits the current animation, 10% padding, 0.25 transition time */ - viewport: { + viewport?: { /* Optional: The position and size of the viewport in the skeleton's world coordinates. Default: the bounding box that fits the current animation */ - x: number - y: number - width: number - height: number + x?: number + y?: number + width?: number + height?: number /* Optional: Padding around the viewport size, given as a number or percentage (eg "25%"). Default: 10% */ - padLeft: string | number - padRight: string | number - padTop: string | number - padBottom: string | number + padLeft?: string | number + padRight?: string | number + padTop?: string | number + padBottom?: string | number /* Optional: Whether to draw lines showing the viewport bounds. Default: false */ - debugRender: boolean, + debugRender?: boolean, /* Optional: When the current viewport changes, the time to animate to the new viewport. Default: 0.25 */ - transitionTime: number + transitionTime?: number /* Optional: Viewports for specific animations. Default: none */ - animations: StringMap + animations?: StringMap } /* Optional: Whether the canvas is transparent, allowing the web page behind the canvas to show through when backgroundColor alpha is < ff. Default: false */ - alpha: boolean + alpha?: boolean /* Optional: The canvas background color, given in the format #rrggbb or #rrggbbaa. Default: #000000ff (black) or when alpha is true #00000000 (transparent) */ - backgroundColor: string + backgroundColor?: string /* Optional: The background color used in fullscreen mode, given in the format #rrggbb or #rrggbbaa. Default: backgroundColor */ - fullScreenBackgroundColor: string + fullScreenBackgroundColor?: string /* Optional: An image to draw behind the skeleton. Default: none */ - backgroundImage: { + backgroundImage?: { url: string /* Optional: The position and size of the background image in the skeleton's world coordinates. Default: fills the viewport */ - x: number - y: number - width: number - height: number + x?: number + y?: number + width?: number + height?: number } /* Optional: Whether mipmapping and anisotropic filtering are used for highest quality scaling when available, otherwise the filter settings from the texture atlas are used. Default: true */ - mipmaps: true + mipmaps?: boolean /* Optional: List of bone names that the user can drag to position. Default: none */ - controlBones: string[] + controlBones?: string[] /* Optional: Callback when the skeleton and its assets have been successfully loaded. If an animation is set on track 0, the player won't set its own animation. Default: none */ - success: (player: SpinePlayer) => void + success?: (player: SpinePlayer) => void /* Optional: Callback when the skeleton could not be loaded or rendered. Default: none */ - error: (player: SpinePlayer, msg: string) => void + error?: (player: SpinePlayer, msg: string) => void /* Optional: Callback at the start of each frame, before the skeleton is posed or drawn. Default: none */ - frame: (player: SpinePlayer, delta: number) => void + frame?: (player: SpinePlayer, delta: number) => void /* Optional: Callback after the skeleton is posed each frame, before it is drawn. Default: none */ - update: (player: SpinePlayer, delta: number) => void + update?: (player: SpinePlayer, delta: number) => void /* Optional: Callback after the skeleton is drawn each frame. Default: none */ - draw: (player: SpinePlayer, delta: number) => void + draw?: (player: SpinePlayer, delta: number) => void /* Optional: Callback each frame before the skeleton is loaded. Default: none */ - loading: (player: SpinePlayer, delta: number) => void + loading?: (player: SpinePlayer, delta: number) => void /* Optional: The downloader used by the player's asset manager. Passing the same downloader to multiple players using the same assets ensures the assets are only downloaded once. Default: new instance */ - downloader: Downloader + downloader?: Downloader } export interface Viewport { @@ -181,31 +181,31 @@ export interface Viewport { export class SpinePlayer implements Disposable { public parent: HTMLElement; public dom: HTMLElement; - public canvas: HTMLCanvasElement; - public context: ManagedWebGLRenderingContext; - public sceneRenderer: SceneRenderer; - public loadingScreen: LoadingScreen; - public assetManager: AssetManager; + public canvas: HTMLCanvasElement | null = null; + public context: ManagedWebGLRenderingContext | null = null; + public sceneRenderer: SceneRenderer | null = null; + public loadingScreen: LoadingScreen | null = null; + public assetManager: AssetManager | null = null; public bg = new Color(); public bgFullscreen = new Color(); - private playerControls: HTMLElement; - private timelineSlider: Slider; - private playButton: HTMLElement; - private skinButton: HTMLElement; - private animationButton: HTMLElement; + private playerControls: HTMLElement | null = null; + private timelineSlider: Slider | null = null; + private playButton: HTMLElement | null = null; + private skinButton: HTMLElement | null = null; + private animationButton: HTMLElement | null = null; private playTime = 0; - private selectedBones: Bone[]; + private selectedBones: (Bone | null)[] = []; private cancelId = 0; - popup: Popup; + popup: Popup | null = null; /* True if the player is unable to load or render the skeleton. */ - public error: boolean; + public error: boolean = false; /* The player's skeleton. Null until loading is complete (access after config.success). */ - public skeleton: Skeleton; + public skeleton: Skeleton | null = null; /* The animation state controlling the skeleton. Null until loading is complete (access after config.success). */ - public animationState: AnimationState; + public animationState: AnimationState | null = null; public paused = true; public speed = 1; @@ -214,14 +214,15 @@ export class SpinePlayer implements Disposable { private disposed = false; private viewport: Viewport = {} as Viewport; - private currentViewport: Viewport; - private previousViewport: Viewport; + private currentViewport: Viewport = {} as Viewport; + private previousViewport: Viewport = {} as Viewport; private viewportTransitionStart = 0; private eventListeners: Array<{ target: any, event: any, func: any }> = []; constructor (parent: HTMLElement | string, private config: SpinePlayerConfig) { - this.parent = typeof parent === "string" ? document.getElementById(parent) : parent; - if (!this.parent) throw new Error("SpinePlayer parent not found: " + parent); + let parentDom = typeof parent === "string" ? document.getElementById(parent) : parent; + if (parentDom == null) throw new Error("SpinePlayer parent not found: " + parent); + this.parent = parentDom; if (config.showControls === void 0) config.showControls = true; let controls = config.showControls ? /*html*/` @@ -244,7 +245,7 @@ export class SpinePlayer implements Disposable { try { this.validateConfig(config); } catch (e) { - this.showError(e.message, e); + this.showError((e as any).message, e as any); } this.initialize(); @@ -257,9 +258,9 @@ export class SpinePlayer implements Disposable { } dispose (): void { - this.sceneRenderer.dispose(); - if (this.loadingScreen) this.loadingScreen.dispose(); - this.assetManager.dispose(); + this.sceneRenderer?.dispose(); + this.loadingScreen?.dispose(); + this.assetManager?.dispose(); for (var i = 0; i < this.eventListeners.length; i++) { var eventListener = this.eventListeners[i]; eventListener.target.removeEventListener(eventListener.event, eventListener.func); @@ -280,29 +281,38 @@ export class SpinePlayer implements Disposable { if (!config.atlasUrl) throw new Error("A URL must be specified for the atlas file."); if (!config.backgroundColor) config.backgroundColor = config.alpha ? "00000000" : "000000"; if (!config.fullScreenBackgroundColor) config.fullScreenBackgroundColor = config.backgroundColor; - if (config.backgroundImage && !config.backgroundImage.url) config.backgroundImage = null; + if (config.backgroundImage && !config.backgroundImage.url) config.backgroundImage = undefined; if (config.premultipliedAlpha === void 0) config.premultipliedAlpha = true; if (config.mipmaps === void 0) config.mipmaps = true; - if (!config.debug) config.debug = {} as any; + if (!config.debug) config.debug = { + bones: false, + clipping: false, + bounds: false, + hulls: false, + meshes: false, + paths: false, + points: false, + regions: false + }; if (config.animations && config.animation && config.animations.indexOf(config.animation) < 0) throw new Error("Animation '" + config.animation + "' is not in the config animation list: " + toString(config.animations)); if (config.skins && config.skin && config.skins.indexOf(config.skin) < 0) throw new Error("Default skin '" + config.skin + "' is not in the config skins list: " + toString(config.skins)); if (!config.viewport) config.viewport = {} as any; - if (!config.viewport.animations) config.viewport.animations = {}; - if (config.viewport.debugRender === void 0) config.viewport.debugRender = false; - if (config.viewport.transitionTime === void 0) config.viewport.transitionTime = 0.25; + if (!config.viewport!.animations) config.viewport!.animations = {}; + if (config.viewport!.debugRender === void 0) config.viewport!.debugRender = false; + if (config.viewport!.transitionTime === void 0) config.viewport!.transitionTime = 0.25; if (!config.controlBones) config.controlBones = []; if (config.showLoading === void 0) config.showLoading = true; if (config.defaultMix === void 0) config.defaultMix = 0.25; } - private initialize (): HTMLElement { + private initialize (): HTMLElement | null { let config = this.config; let dom = this.dom; if (!config.alpha) { // Prevents a flash before the first frame is drawn. - let hex = config.backgroundColor; + let hex = config.backgroundColor!; this.dom.style.backgroundColor = (hex.charAt(0) == '#' ? hex : "#" + hex).substr(0, 7); } @@ -315,7 +325,8 @@ export class SpinePlayer implements Disposable { this.sceneRenderer = new SceneRenderer(this.canvas, this.context, true); if (config.showLoading) this.loadingScreen = new LoadingScreen(this.sceneRenderer); } catch (e) { - this.showError("Sorry, your browser does not support \nPlease use the latest version of Firefox, Chrome, Edge, or Safari.", e); + this.showError("Sorry, your browser does not support \nPlease use the latest version of Firefox, Chrome, Edge, or Safari.", e as any); + return null; } // Load the assets. @@ -327,13 +338,13 @@ export class SpinePlayer implements Disposable { if (config.jsonUrl) this.assetManager.loadJson(config.jsonUrl); else - this.assetManager.loadBinary(config.binaryUrl); - this.assetManager.loadTextureAtlas(config.atlasUrl); + this.assetManager.loadBinary(config.binaryUrl!); + this.assetManager.loadTextureAtlas(config.atlasUrl!); if (config.backgroundImage) this.assetManager.loadTexture(config.backgroundImage.url); // Setup the UI elements. - this.bg.setFromString(config.backgroundColor); - this.bgFullscreen.setFromString(config.fullScreenBackgroundColor); + this.bg.setFromString(config.backgroundColor!); + this.bgFullscreen.setFromString(config.fullScreenBackgroundColor!); if (config.showControls) { this.playerControls = dom.children[1] as HTMLElement; let controls = this.playerControls.children; @@ -351,18 +362,18 @@ export class SpinePlayer implements Disposable { timeline.appendChild(this.timelineSlider.create()); this.timelineSlider.change = (percentage) => { this.pause(); - let animationDuration = this.animationState.getCurrent(0).animation.duration; + let animationDuration = this.animationState!.getCurrent(0)!.animation!.duration; let time = animationDuration * percentage; - this.animationState.update(time - this.playTime); - this.animationState.apply(this.skeleton); - this.skeleton.updateWorldTransform(); + this.animationState!.update(time - this.playTime); + this.animationState!.apply(this.skeleton!); + this.skeleton!.updateWorldTransform(); this.playTime = time; }; this.playButton.onclick = () => (this.paused ? this.play() : this.pause()); speedButton.onclick = () => this.showSpeedDialog(speedButton); - this.animationButton.onclick = () => this.showAnimationsDialog(this.animationButton); - this.skinButton.onclick = () => this.showSkinsDialog(this.skinButton); + this.animationButton.onclick = () => this.showAnimationsDialog(this.animationButton!); + this.skinButton.onclick = () => this.showSkinsDialog(this.skinButton!); settingsButton.onclick = () => this.showSettingsDialog(settingsButton); let oldWidth = this.canvas.clientWidth, oldHeight = this.canvas.clientHeight; @@ -372,13 +383,13 @@ export class SpinePlayer implements Disposable { let fullscreenChanged = () => { isFullscreen = !isFullscreen; if (!isFullscreen) { - this.canvas.style.width = oldWidth + "px"; - this.canvas.style.height = oldHeight + "px"; + this.canvas!.style.width = oldWidth + "px"; + this.canvas!.style.height = oldHeight + "px"; this.drawFrame(false); // Got to reset the style to whatever the user set after the next layouting. requestAnimationFrame(() => { - this.canvas.style.width = oldStyleWidth; - this.canvas.style.height = oldStyleHeight; + this.canvas!.style.width = oldStyleWidth; + this.canvas!.style.height = oldStyleHeight; }); } }; @@ -394,10 +405,10 @@ export class SpinePlayer implements Disposable { else if (doc.webkitExitFullscreen) doc.webkitExitFullscreen() else if (doc.msExitFullscreen) doc.msExitFullscreen(); } else { - oldWidth = this.canvas.clientWidth; - oldHeight = this.canvas.clientHeight; - oldStyleWidth = this.canvas.style.width; - oldStyleHeight = this.canvas.style.height; + oldWidth = this.canvas!.clientWidth; + oldHeight = this.canvas!.clientHeight; + oldStyleWidth = this.canvas!.style.width; + oldStyleHeight = this.canvas!.style.height; if (player.requestFullscreen) player.requestFullscreen(); else if (player.webkitRequestFullScreen) player.webkitRequestFullScreen(); else if (player.mozRequestFullScreen) player.mozRequestFullScreen(); @@ -413,18 +424,18 @@ export class SpinePlayer implements Disposable { private loadSkeleton () { if (this.error) return; - if (this.assetManager.hasErrors()) - this.showError("Error: Assets could not be loaded.\n" + toString(this.assetManager.getErrors())); + if (this.assetManager!.hasErrors()) + this.showError("Error: Assets could not be loaded.\n" + toString(this.assetManager!.getErrors())); let config = this.config; // Configure filtering, don't use mipmaps in WebGL1 if the atlas page is non-POT - let atlas = this.assetManager.require(config.atlasUrl) as TextureAtlas; - let gl = this.context.gl, anisotropic = gl.getExtension("EXT_texture_filter_anisotropic"); + let atlas = this.assetManager!.require(config.atlasUrl!) as TextureAtlas; + let gl = this.context!.gl, anisotropic = gl.getExtension("EXT_texture_filter_anisotropic"); let isWebGL1 = gl.getParameter(gl.VERSION).indexOf("WebGL 1.0") != -1; for (let page of atlas.pages) { let minFilter = page.minFilter; - var useMipMaps: boolean = config.mipmaps; + var useMipMaps: boolean = config.mipmaps!; var isPOT = MathUtils.isPowerOfTwo(page.width) && MathUtils.isPowerOfTwo(page.height); if (isWebGL1 && !isPOT) useMipMaps = false; @@ -434,7 +445,7 @@ export class SpinePlayer implements Disposable { minFilter = TextureFilter.MipMapLinearLinear; } else minFilter = TextureFilter.Linear; // Don't use mipmaps without anisotropic. - page.texture.setFilters(minFilter, TextureFilter.Nearest); + page.texture!.setFilters(minFilter, TextureFilter.Nearest); } if (minFilter != TextureFilter.Nearest && minFilter != TextureFilter.Linear) (page.texture as GLTexture).update(true); } @@ -443,7 +454,7 @@ export class SpinePlayer implements Disposable { let skeletonData: SkeletonData; if (config.jsonUrl) { try { - let jsonData = this.assetManager.remove(config.jsonUrl); + let jsonData = this.assetManager!.remove(config.jsonUrl); if (!jsonData) throw new Error("Empty JSON data."); if (config.jsonField) { jsonData = jsonData[config.jsonField]; @@ -452,32 +463,34 @@ export class SpinePlayer implements Disposable { let json = new SkeletonJson(new AtlasAttachmentLoader(atlas)); skeletonData = json.readSkeletonData(jsonData); } catch (e) { - this.showError(`Error: Could not load skeleton JSON.\n${e.message}`, e); + this.showError(`Error: Could not load skeleton JSON.\n${(e as any).message}`, e as any); + return; } } else { - let binaryData = this.assetManager.remove(config.binaryUrl); + let binaryData = this.assetManager!.remove(config.binaryUrl!); let binary = new SkeletonBinary(new AtlasAttachmentLoader(atlas)); try { skeletonData = binary.readSkeletonData(binaryData); } catch (e) { - this.showError(`Error: Could not load skeleton binary.\n${e.message}`, e); + this.showError(`Error: Could not load skeleton binary.\n${(e as any).message}`, e as any); + return; } } this.skeleton = new Skeleton(skeletonData); let stateData = new AnimationStateData(skeletonData); - stateData.defaultMix = config.defaultMix; + stateData.defaultMix = config.defaultMix!; this.animationState = new AnimationState(stateData); // Check if all control bones are in the skeleton - config.controlBones.forEach(bone => { + config.controlBones!.forEach(bone => { if (!skeletonData.findBone(bone)) this.showError(`Error: Control bone does not exist in skeleton: ${bone}`); }) // Setup skin. if (!config.skin && skeletonData.skins.length) config.skin = skeletonData.skins[0].name; - if (config.skins && config.skin.length) { + if (config.skins && config.skin!.length) { config.skins.forEach(skin => { - if (!this.skeleton.data.findSkin(skin)) + if (!this.skeleton!.data.findSkin(skin)) this.showError(`Error: Skin in config list does not exist in skeleton: ${skin}`); }); } @@ -489,7 +502,7 @@ export class SpinePlayer implements Disposable { } // Check if all animations given a viewport exist. - Object.getOwnPropertyNames(config.viewport.animations).forEach((animation: string) => { + Object.getOwnPropertyNames(config.viewport!.animations).forEach((animation: string) => { if (!skeletonData.findAnimation(animation)) this.showError(`Error: Animation for which a viewport was specified does not exist in skeleton: ${animation}`); }); @@ -497,7 +510,7 @@ export class SpinePlayer implements Disposable { // Setup the animations after the viewport, so default bounds don't get messed up. if (config.animations && config.animations.length) { config.animations.forEach(animation => { - if (!this.skeleton.data.findAnimation(animation)) + if (!this.skeleton!.data.findAnimation(animation)) this.showError(`Error: Animation in config list does not exist in skeleton: ${animation}`); }); if (!config.animation) config.animation = config.animations[0]; @@ -511,8 +524,8 @@ export class SpinePlayer implements Disposable { if (config.showControls) { // Hide skin and animation if there's only the default skin / no animation - if (skeletonData.skins.length == 1 || (config.skins && config.skins.length == 1)) this.skinButton.classList.add("spine-player-hidden"); - if (skeletonData.animations.length == 1 || (config.animations && config.animations.length == 1)) this.animationButton.classList.add("spine-player-hidden"); + if (skeletonData.skins.length == 1 || (config.skins && config.skins.length == 1)) this.skinButton!.classList.add("spine-player-hidden"); + if (skeletonData.animations.length == 1 || (config.animations && config.animations.length == 1)) this.animationButton!.classList.add("spine-player-hidden"); } if (config.success) config.success(this); @@ -525,37 +538,38 @@ export class SpinePlayer implements Disposable { } else { entry = this.animationState.setEmptyAnimation(0); entry.trackEnd = 100000000; - this.setViewport(entry.animation); + this.setViewport(entry.animation!); this.pause(); } } else if (!this.currentViewport) { - this.setViewport(entry.animation); + this.setViewport(entry.animation!); this.play(); } } private setupInput () { let config = this.config; - let controlBones = config.controlBones; + let controlBones = config.controlBones!; if (!controlBones.length && !config.showControls) return; - let selectedBones = this.selectedBones = new Array(controlBones.length); - let canvas = this.canvas; - let target: Bone = null; + let selectedBones = this.selectedBones = new Array(controlBones.length); + let canvas = this.canvas!; + let target: Bone | null = null; let offset = new Vector2(); let coords = new Vector3(); let mouse = new Vector3(); let position = new Vector2(); - let skeleton = this.skeleton; - let renderer = this.sceneRenderer; + let skeleton = this.skeleton!; + let renderer = this.sceneRenderer!; - let closest = function (x: number, y: number): Bone { + let closest = function (x: number, y: number): Bone | null { mouse.set(x, canvas.clientHeight - y, 0) offset.x = offset.y = 0; let bestDistance = 24, index = 0; - let best: Bone; + let best: Bone | null = null; for (let i = 0; i < controlBones.length; i++) { selectedBones[i] = null; let bone = skeleton.findBone(controlBones[i]); + if (!bone) continue; let distance = renderer.camera.worldToScreen( coords.set(bone.worldX, bone.worldY, 0), canvas.clientWidth, canvas.clientHeight).distance(mouse); @@ -624,17 +638,17 @@ export class SpinePlayer implements Disposable { let mouseOverControls = true, mouseOverCanvas = false; let handleHover = (mouseX: number, mouseY: number) => { let popup = findWithClass(this.dom, "spine-player-popup"); - mouseOverControls = overlap(mouseX, mouseY, this.playerControls.getBoundingClientRect()); + mouseOverControls = overlap(mouseX, mouseY, this.playerControls!.getBoundingClientRect()); mouseOverCanvas = overlap(mouseX, mouseY, canvas.getBoundingClientRect()); clearTimeout(this.cancelId); let hide = !popup && !mouseOverControls && !mouseOverCanvas && !this.paused; if (hide) - this.playerControls.classList.add("spine-player-controls-hidden"); + this.playerControls!.classList.add("spine-player-controls-hidden"); else - this.playerControls.classList.remove("spine-player-controls-hidden"); + this.playerControls!.classList.remove("spine-player-controls-hidden"); if (!mouseOverControls && !popup && !this.paused) { this.cancelId = setTimeout(() => { - if (!this.paused) this.playerControls.classList.add("spine-player-controls-hidden"); + if (!this.paused) this.playerControls!.classList.add("spine-player-controls-hidden"); }, 1000); } } @@ -646,17 +660,17 @@ export class SpinePlayer implements Disposable { let config = this.config; if (config.showControls) { this.cancelId = setTimeout(() => { - if (!this.paused) this.playerControls.classList.add("spine-player-controls-hidden"); + if (!this.paused) this.playerControls!.classList.add("spine-player-controls-hidden"); }, 1000); - this.playButton.classList.remove("spine-player-button-icon-play"); - this.playButton.classList.add("spine-player-button-icon-pause"); + this.playButton!.classList.remove("spine-player-button-icon-play"); + this.playButton!.classList.add("spine-player-button-icon-pause"); // If no config animation, set one when first clicked. if (!config.animation) { if (config.animations && config.animations.length) config.animation = config.animations[0]; - else if (this.skeleton.data.animations.length) - config.animation = this.skeleton.data.animations[0].name; + else if (this.skeleton!.data.animations.length) + config.animation = this.skeleton!.data.animations[0].name; if (config.animation) this.setAnimation(config.animation); } } @@ -665,33 +679,37 @@ export class SpinePlayer implements Disposable { pause () { this.paused = true; if (this.config.showControls) { - this.playerControls.classList.remove("spine-player-controls-hidden"); + this.playerControls!.classList.remove("spine-player-controls-hidden"); clearTimeout(this.cancelId); - this.playButton.classList.remove("spine-player-button-icon-pause"); - this.playButton.classList.add("spine-player-button-icon-play"); + this.playButton!.classList.remove("spine-player-button-icon-pause"); + this.playButton!.classList.add("spine-player-button-icon-play"); } } /* Sets a new animation and viewport on track 0. */ setAnimation (animation: string | Animation, loop: boolean = true): TrackEntry { animation = this.setViewport(animation); - return this.animationState.setAnimationWith(0, animation, loop); + return this.animationState!.setAnimationWith(0, animation, loop); } /* Adds a new animation and viewport on track 0. */ addAnimation (animation: string | Animation, loop: boolean = true, delay: number = 0): TrackEntry { animation = this.setViewport(animation); - return this.animationState.addAnimationWith(0, animation, loop, delay); + return this.animationState!.addAnimationWith(0, animation, loop, delay); } /* Sets the viewport for the specified animation. */ setViewport (animation: string | Animation): Animation { - if (typeof animation == "string") animation = this.skeleton.data.findAnimation(animation); + if (typeof animation == "string") { + let foundAnimation = this.skeleton!.data.findAnimation(animation); + if (!foundAnimation) throw new Error("Animation not found: " + animation); + animation = foundAnimation; + } this.previousViewport = this.currentViewport; // Determine the base viewport. - let globalViewport = this.config.viewport; + let globalViewport = this.config.viewport!; let viewport = this.currentViewport = { padLeft: globalViewport.padLeft !== void 0 ? globalViewport.padLeft : "10%", padRight: globalViewport.padRight !== void 0 ? globalViewport.padRight : "10%", @@ -707,7 +725,7 @@ export class SpinePlayer implements Disposable { this.calculateAnimationViewport(animation, viewport); // Override with the animation specific viewport for the final result. - let userAnimViewport = this.config.viewport.animations[animation.name]; + let userAnimViewport = this.config.viewport!.animations![animation.name]; if (userAnimViewport) { if (userAnimViewport.x !== void 0 && userAnimViewport.y !== void 0 && userAnimViewport.width && userAnimViewport.height) { viewport.x = userAnimViewport.x; @@ -738,16 +756,16 @@ export class SpinePlayer implements Disposable { } private calculateAnimationViewport (animation: Animation, viewport: Viewport) { - this.skeleton.setToSetupPose(); + this.skeleton!.setToSetupPose(); let steps = 100, stepTime = animation.duration ? animation.duration / steps : 0, time = 0; let minX = 100000000, maxX = -100000000, minY = 100000000, maxY = -100000000; let offset = new Vector2(), size = new Vector2(); for (let i = 0; i < steps; i++, time += stepTime) { - animation.apply(this.skeleton, time, time, false, null, 1, MixBlend.setup, MixDirection.mixIn); - this.skeleton.updateWorldTransform(); - this.skeleton.getBounds(offset, size); + animation.apply(this.skeleton!, time, time, false, [], 1, MixBlend.setup, MixDirection.mixIn); + this.skeleton!.updateWorldTransform(); + this.skeleton!.getBounds(offset, size); if (!isNaN(offset.x) && !isNaN(offset.y) && !isNaN(size.x) && !isNaN(size.y)) { minX = Math.min(offset.x, minX); @@ -778,13 +796,13 @@ export class SpinePlayer implements Disposable { let delta = this.time.delta; // Load the skeleton if the assets are ready. - let loading = this.assetManager.isLoadingComplete(); + let loading = this.assetManager!.isLoadingComplete(); if (!this.skeleton && loading) this.loadSkeleton(); - let skeleton = this.skeleton; - let config = this.config; + let skeleton = this.skeleton!; + let config = this.config!; if (skeleton) { // Resize the canvas. - let renderer = this.sceneRenderer; + let renderer = this.sceneRenderer!; renderer.resize(ResizeMode.Expand); let playDelta = this.paused ? 0 : delta * this.speed; @@ -792,19 +810,19 @@ export class SpinePlayer implements Disposable { // Update animation time and pose the skeleton. if (!this.paused) { - this.animationState.update(playDelta); - this.animationState.apply(skeleton); + this.animationState!.update(playDelta); + this.animationState!.apply(skeleton); skeleton.updateWorldTransform(); if (config.showControls) { this.playTime += playDelta; - let entry = this.animationState.getCurrent(0); + let entry = this.animationState!.getCurrent(0); if (entry) { - let duration = entry.animation.duration; + let duration = entry.animation!.duration; while (this.playTime >= duration && duration != 0) this.playTime -= duration; this.playTime = Math.max(0, Math.min(this.playTime, duration)); - this.timelineSlider.setValue(this.playTime / duration); + this.timelineSlider!.setValue(this.playTime / duration); } } } @@ -817,7 +835,7 @@ export class SpinePlayer implements Disposable { viewport.height = this.currentViewport.height + (this.currentViewport.padBottom as number) + (this.currentViewport.padTop as number) if (this.previousViewport) { - let transitionAlpha = (performance.now() - this.viewportTransitionStart) / 1000 / config.viewport.transitionTime; + let transitionAlpha = (performance.now() - this.viewportTransitionStart) / 1000 / config.viewport!.transitionTime!; if (transitionAlpha < 1) { let x = this.previousViewport.x - (this.previousViewport.padLeft as number); let y = this.previousViewport.y - (this.previousViewport.padBottom as number); @@ -830,13 +848,13 @@ export class SpinePlayer implements Disposable { } } - renderer.camera.zoom = this.canvas.height / this.canvas.width > viewport.height / viewport.width - ? viewport.width / this.canvas.width : viewport.height / this.canvas.height; + renderer.camera.zoom = this.canvas!.height / this.canvas!.width > viewport.height / viewport.width + ? viewport.width / this.canvas!.width : viewport.height / this.canvas!.height; renderer.camera.position.x = viewport.x + viewport.width / 2; renderer.camera.position.y = viewport.y + viewport.height / 2; // Clear the screen. - let gl = this.context.gl; + let gl = this.context!.gl; gl.clearColor(bg.r, bg.g, bg.b, bg.a); gl.clear(gl.COLOR_BUFFER_BIT); @@ -847,7 +865,7 @@ export class SpinePlayer implements Disposable { // Draw the background image. let bgImage = config.backgroundImage; if (bgImage) { - let texture = this.assetManager.require(bgImage.url); + let texture = this.assetManager!.require(bgImage.url); if (bgImage.x !== void 0 && bgImage.y !== void 0 && bgImage.width && bgImage.height) renderer.drawTexture(texture, bgImage.x, bgImage.y, bgImage.width, bgImage.height); else @@ -856,19 +874,19 @@ export class SpinePlayer implements Disposable { // Draw the skeleton and debug output. renderer.drawSkeleton(skeleton, config.premultipliedAlpha); - if ((renderer.skeletonDebugRenderer.drawBones = config.debug.bones) - || (renderer.skeletonDebugRenderer.drawBoundingBoxes = config.debug.bounds) - || (renderer.skeletonDebugRenderer.drawClipping = config.debug.clipping) - || (renderer.skeletonDebugRenderer.drawMeshHull = config.debug.hulls) - || (renderer.skeletonDebugRenderer.drawPaths = config.debug.paths) - || (renderer.skeletonDebugRenderer.drawRegionAttachments = config.debug.regions) - || (renderer.skeletonDebugRenderer.drawMeshTriangles = config.debug.meshes) + if ((renderer.skeletonDebugRenderer.drawBones = config.debug!.bones!) + || (renderer.skeletonDebugRenderer.drawBoundingBoxes = config.debug!.bounds!) + || (renderer.skeletonDebugRenderer.drawClipping = config.debug!.clipping!) + || (renderer.skeletonDebugRenderer.drawMeshHull = config.debug!.hulls!) + || (renderer.skeletonDebugRenderer.drawPaths = config.debug!.paths!) + || (renderer.skeletonDebugRenderer.drawRegionAttachments = config.debug!.regions!) + || (renderer.skeletonDebugRenderer.drawMeshTriangles = config.debug!.meshes!) ) { renderer.drawSkeletonDebug(skeleton, config.premultipliedAlpha); } // Draw the control bones. - let controlBones = config.controlBones; + let controlBones = config.controlBones!; if (controlBones.length) { let selectedBones = this.selectedBones; gl.lineWidth(2); @@ -883,7 +901,7 @@ export class SpinePlayer implements Disposable { } // Draw the viewport bounds. - if (config.viewport.debugRender) { + if (config.viewport!.debugRender) { gl.lineWidth(1); renderer.rect(false, this.currentViewport.x, this.currentViewport.y, this.currentViewport.width, this.currentViewport.height, Color.GREEN); renderer.rect(false, viewport.x, viewport.y, viewport.width, viewport.height, Color.RED); @@ -896,12 +914,12 @@ export class SpinePlayer implements Disposable { // Draw the loading screen. if (config.showLoading) { - this.loadingScreen.backgroundColor.setFromColor(bg); - this.loadingScreen.draw(loading); + this.loadingScreen!.backgroundColor.setFromColor(bg); + this.loadingScreen!.draw(loading); } if (loading && config.loading) config.loading(this, delta); } catch (e) { - this.showError(`Error: Unable to render skeleton.\n${e.message}`, e); + this.showError(`Error: Unable to render skeleton.\n${(e as any).message}`, e as any); } } @@ -910,14 +928,14 @@ export class SpinePlayer implements Disposable { } private hidePopup (id: string): boolean { - return this.popup && this.popup.hide(id); + return this.popup != null && this.popup.hide(id); } private showSpeedDialog (speedButton: HTMLElement) { let id = "speed"; if (this.hidePopup(id)) return; - let popup = new Popup(id, speedButton, this, this.playerControls, /*html*/` + let popup = new Popup(id, speedButton, this, this.playerControls!, /*html*/`
Speed

@@ -938,7 +956,7 @@ export class SpinePlayer implements Disposable { if (this.hidePopup(id)) return; if (!this.skeleton || !this.skeleton.data.animations.length) return; - let popup = new Popup(id, animationsButton, this, this.playerControls, + let popup = new Popup(id, animationsButton, this, this.playerControls!, /*html*/`
Animations

    `); let rows = findWithClass(popup.dom, "spine-player-list"); @@ -968,7 +986,7 @@ export class SpinePlayer implements Disposable { if (this.hidePopup(id)) return; if (!this.skeleton || !this.skeleton.data.animations.length) return; - let popup = new Popup(id, skinButton, this, this.playerControls, + let popup = new Popup(id, skinButton, this, this.playerControls!, /*html*/`
    Skins

      `); let rows = findWithClass(popup.dom, "spine-player-list"); @@ -984,8 +1002,8 @@ export class SpinePlayer implements Disposable { removeClass(rows.children, "selected"); row.classList.add("selected"); this.config.skin = skin.name; - this.skeleton.setSkinByName(this.config.skin); - this.skeleton.setSlotsToSetupPose(); + this.skeleton!.setSkinByName(this.config.skin); + this.skeleton!.setSlotsToSetupPose(); } }); popup.show(); @@ -996,7 +1014,7 @@ export class SpinePlayer implements Disposable { if (this.hidePopup(id)) return; if (!this.skeleton || !this.skeleton.data.animations.length) return; - let popup = new Popup(id, settingsButton, this, this.playerControls, /*html*/`
      Debug

        `); + let popup = new Popup(id, settingsButton, this, this.playerControls!, /*html*/`
        Debug

          `); let rows = findWithClass(popup.dom, "spine-player-list"); let makeItem = (label: string, name: string) => { @@ -1019,7 +1037,7 @@ export class SpinePlayer implements Disposable { popup.show(); } - private showError (message: string, error: Error = null) { + private showError (message: string, error?: Error) { if (this.error) { if (error) throw error; // Don't lose error if showError throws, is caught, and showError is called again. } else { @@ -1056,6 +1074,7 @@ class Popup { this.player.popup = null; return true; } + return false; } show () { @@ -1094,9 +1113,9 @@ class Popup { } class Switch { - private switch: HTMLElement; + private switch: HTMLElement | null = null; private enabled = false; - public change: (value: boolean) => void; + public change: (value: boolean) => void = () => { }; constructor (private text: string) { } @@ -1117,10 +1136,9 @@ class Switch { } setEnabled (enabled: boolean) { - if (enabled) this.switch.classList.add("active"); - else this.switch.classList.remove("active"); - this.enabled = enabled - ; + if (enabled) this.switch?.classList.add("active"); + else this.switch?.classList.remove("active"); + this.enabled = enabled; } isEnabled (): boolean { @@ -1129,10 +1147,10 @@ class Switch { } class Slider { - private slider: HTMLElement; - private value: HTMLElement; - private knob: HTMLElement; - public change: (percentage: number) => void; + private slider: HTMLElement | null = null; + private value: HTMLElement | null = null; + private knob: HTMLElement | null = null; + public change: (percentage: number) => void = () => { }; constructor (public snaps = 0, public snapPercentage = 0.1, public big = false) { } @@ -1150,18 +1168,18 @@ class Slider { new Input(this.slider).addListener({ down: (x, y) => { dragging = true; - this.value.classList.add("hovering"); + this.value?.classList.add("hovering"); }, up: (x, y) => { dragging = false; - if (this.change) this.change(this.setValue(x / this.slider.clientWidth)); - this.value.classList.remove("hovering"); + if (this.change) this.change(this.setValue(x / this.slider!.clientWidth)); + this.value?.classList.remove("hovering"); }, moved: (x, y) => { - if (dragging && this.change) this.change(this.setValue(x / this.slider.clientWidth)); + if (dragging && this.change) this.change(this.setValue(x / this.slider!.clientWidth)); }, dragged: (x, y) => { - if (this.change) this.change(this.setValue(x / this.slider.clientWidth)); + if (this.change) this.change(this.setValue(x / this.slider!.clientWidth)); } }); @@ -1180,7 +1198,7 @@ class Slider { percentage = percentage - modulo + snap; percentage = Math.max(0, Math.min(1, percentage)); } - this.value.style.width = "" + (percentage * 100) + "%"; + this.value!.style.width = "" + (percentage * 100) + "%"; // this.knob.style.left = "" + (-8 + percentage * this.slider.clientWidth) + "px"; return percentage; } diff --git a/spine-ts/spine-threejs/src/AssetManager.ts b/spine-ts/spine-threejs/src/AssetManager.ts index 17d1bf087..d332ebeab 100644 --- a/spine-ts/spine-threejs/src/AssetManager.ts +++ b/spine-ts/spine-threejs/src/AssetManager.ts @@ -31,8 +31,8 @@ import { AssetManagerBase, Downloader } from "@esotericsoftware/spine-core" import { ThreeJsTexture } from "./ThreeJsTexture"; export class AssetManager extends AssetManagerBase { - constructor (pathPrefix: string = "", downloader: Downloader = null) { - super((image: HTMLImageElement) => { + constructor (pathPrefix: string = "", downloader: Downloader = new Downloader()) { + super((image: HTMLImageElement | ImageBitmap) => { return new ThreeJsTexture(image); }, pathPrefix, downloader); } diff --git a/spine-ts/spine-threejs/src/MeshBatcher.ts b/spine-ts/spine-threejs/src/MeshBatcher.ts index a8d08ee45..15adec245 100644 --- a/spine-ts/spine-threejs/src/MeshBatcher.ts +++ b/spine-ts/spine-threejs/src/MeshBatcher.ts @@ -53,7 +53,7 @@ export class MeshBatcher extends THREE.Mesh { geo.setAttribute("color", new THREE.InterleavedBufferAttribute(vertexBuffer, 4, 3, false)); geo.setAttribute("uv", new THREE.InterleavedBufferAttribute(vertexBuffer, 2, 7, false)); geo.setIndex(new THREE.BufferAttribute(indices, 1)); - geo.getIndex().usage = WebGLRenderingContext.DYNAMIC_DRAW; + geo.getIndex()!.usage = WebGLRenderingContext.DYNAMIC_DRAW; geo.drawRange.start = 0; geo.drawRange.count = 0; this.geometry = geo; @@ -134,9 +134,11 @@ export class MeshBatcher extends THREE.Mesh { this.vertexBuffer.updateRange.count = this.verticesLength; let geo = (this.geometry); this.closeMaterialGroups(); - geo.getIndex().needsUpdate = this.indicesLength > 0; - geo.getIndex().updateRange.offset = 0; - geo.getIndex().updateRange.count = this.indicesLength; + let index = geo.getIndex(); + if (!index) throw new Error("BufferAttribute must not be null."); + index.needsUpdate = this.indicesLength > 0; + index.updateRange.offset = 0; + index.updateRange.count = this.indicesLength; geo.drawRange.start = 0; geo.drawRange.count = this.indicesLength; } @@ -168,7 +170,7 @@ export class MeshBatcher extends THREE.Mesh { for (let i = 0; i < this.material.length; i++) { const meshMaterial = this.material[i] as SkeletonMeshMaterial; - if (meshMaterial.uniforms.map.value === null) { + if (!meshMaterial.uniforms.map.value) { updateMeshMaterial(meshMaterial, slotTexture, blending); return i; } diff --git a/spine-ts/spine-threejs/src/SkeletonMesh.ts b/spine-ts/spine-threejs/src/SkeletonMesh.ts index 7aca10070..5fa9b23df 100644 --- a/spine-ts/spine-threejs/src/SkeletonMesh.ts +++ b/spine-ts/spine-threejs/src/SkeletonMesh.ts @@ -73,8 +73,9 @@ export class SkeletonMeshMaterial extends THREE.ShaderMaterial { alphaTest: 0.0 }; customizer(parameters); - if (parameters.alphaTest > 0) { + if (parameters.alphaTest && parameters.alphaTest > 0) { parameters.defines = { "USE_SPINE_ALPHATEST": 1 }; + if (!parameters.uniforms) parameters.uniforms = {}; parameters.uniforms["alphaTest"] = { value: parameters.alphaTest }; } super(parameters); @@ -89,7 +90,7 @@ export class SkeletonMesh extends THREE.Object3D { skeleton: Skeleton; state: AnimationState; zOffset: number = 0.1; - vertexEffect: VertexEffect; + vertexEffect: VertexEffect | null = null; private batches = new Array(); private nextBatchIndex = 0; @@ -152,17 +153,11 @@ export class SkeletonMesh extends THREE.Object3D { let tempUv = this.tempUv; let tempLight = this.tempLight; let tempDark = this.tempDark; - - var numVertices = 0; - var verticesLength = 0; - var indicesLength = 0; - - let blendMode: BlendMode = null; let clipper = this.clipper; let vertices: NumberArrayLike = this.vertices; - let triangles: Array = null; - let uvs: NumberArrayLike = null; + let triangles: Array | null = null; + let uvs: NumberArrayLike | null = null; let drawOrder = this.skeleton.drawOrder; let batch = this.nextBatch(); batch.begin(); @@ -176,8 +171,8 @@ export class SkeletonMesh extends THREE.Object3D { continue; } let attachment = slot.getAttachment(); - let attachmentColor: Color = null; - let texture: ThreeJsTexture = null; + let attachmentColor: Color | null; + let texture: ThreeJsTexture | null; let numFloats = 0; if (attachment instanceof RegionAttachment) { let region = attachment; @@ -187,7 +182,7 @@ export class SkeletonMesh extends THREE.Object3D { region.computeWorldVertices(slot, vertices, 0, vertexSize); triangles = SkeletonMesh.QUAD_TRIANGLES; uvs = region.uvs; - texture = (region.region.renderObject).page.texture; + texture = (region.region!.renderObject).page.texture; } else if (attachment instanceof MeshAttachment) { let mesh = attachment; attachmentColor = mesh.color; @@ -199,7 +194,7 @@ export class SkeletonMesh extends THREE.Object3D { mesh.computeWorldVertices(slot, 0, mesh.worldVerticesLength, vertices, 0, vertexSize); triangles = mesh.triangles; uvs = mesh.uvs; - texture = (mesh.region.renderObject).page.texture; + texture = (mesh.region!.renderObject).page.texture; } else if (attachment instanceof ClippingAttachment) { let clip = (attachment); clipper.clipStart(slot, clip); @@ -226,7 +221,7 @@ export class SkeletonMesh extends THREE.Object3D { let finalIndicesLength: number; if (clipper.isClipping()) { - clipper.clipTriangles(vertices, numFloats, triangles, triangles.length, uvs, color, null, false); + clipper.clipTriangles(vertices, numFloats, triangles, triangles.length, uvs, color, tempLight, false); let clippedVertices = clipper.clippedVertices; let clippedTriangles = clipper.clippedTriangles; if (this.vertexEffect != null) { diff --git a/spine-ts/spine-threejs/src/ThreeJsTexture.ts b/spine-ts/spine-threejs/src/ThreeJsTexture.ts index 6cb690b34..aef066548 100644 --- a/spine-ts/spine-threejs/src/ThreeJsTexture.ts +++ b/spine-ts/spine-threejs/src/ThreeJsTexture.ts @@ -33,8 +33,9 @@ import * as THREE from "three"; export class ThreeJsTexture extends Texture { texture: THREE.Texture; - constructor (image: HTMLImageElement) { + constructor (image: HTMLImageElement | ImageBitmap) { super(image); + if (image instanceof ImageBitmap) throw new Error("ImageBitmap not supported."); this.texture = new THREE.Texture(image); this.texture.flipY = false; this.texture.needsUpdate = true; diff --git a/spine-ts/spine-webgl/example/index.html b/spine-ts/spine-webgl/example/index.html index 7b8389b62..4413e82de 100644 --- a/spine-ts/spine-webgl/example/index.html +++ b/spine-ts/spine-webgl/example/index.html @@ -234,7 +234,7 @@ function setupUI() { let formatList = $("#formatList"); formatList.append($("")); - formatList.append($("")); + formatList.append($("")); let skeletonList = $("#skeletonList"); for (let skeletonName in skeletons) { let option = $(""); diff --git a/spine-ts/spine-webgl/src/AssetManager.ts b/spine-ts/spine-webgl/src/AssetManager.ts index 4bd51e87a..aded0734a 100644 --- a/spine-ts/spine-webgl/src/AssetManager.ts +++ b/spine-ts/spine-webgl/src/AssetManager.ts @@ -33,7 +33,7 @@ import { GLTexture } from "./GLTexture"; export class AssetManager extends AssetManagerBase { - constructor (context: ManagedWebGLRenderingContext | WebGLRenderingContext, pathPrefix: string = "", downloader: Downloader = null) { + constructor (context: ManagedWebGLRenderingContext | WebGLRenderingContext, pathPrefix: string = "", downloader: Downloader = new Downloader()) { super((image: HTMLImageElement | ImageBitmap) => { return new GLTexture(context, image); }, pathPrefix, downloader); diff --git a/spine-ts/spine-webgl/src/GLTexture.ts b/spine-ts/spine-webgl/src/GLTexture.ts index a4e4dc4e5..07eaeb3e5 100644 --- a/spine-ts/spine-webgl/src/GLTexture.ts +++ b/spine-ts/spine-webgl/src/GLTexture.ts @@ -32,7 +32,7 @@ import { ManagedWebGLRenderingContext } from "./WebGL"; export class GLTexture extends Texture implements Disposable, Restorable { context: ManagedWebGLRenderingContext; - private texture: WebGLTexture = null; + private texture: WebGLTexture | null = null; private boundUnit = 0; private useMipMaps = false; diff --git a/spine-ts/spine-webgl/src/Input.ts b/spine-ts/spine-webgl/src/Input.ts index ba91bf10f..0610e6382 100644 --- a/spine-ts/spine-webgl/src/Input.ts +++ b/spine-ts/spine-webgl/src/Input.ts @@ -32,8 +32,8 @@ export class Input { mouseX = 0; mouseY = 0; buttonDown = false; - touch0: Touch = null; - touch1: Touch = null; + touch0: Touch | null = null; + touch1: Touch | null = null; initialPinchDistance = 0; private listeners = new Array(); private eventListeners: Array<{ target: any, event: any, func: any }> = []; @@ -104,6 +104,7 @@ export class Input { if (!this.touch0 || !this.touch1) { var touches = ev.changedTouches; let nativeTouch = touches.item(0); + if (!nativeTouch) return; let rect = element.getBoundingClientRect(); let x = nativeTouch.clientX - rect.left; let y = nativeTouch.clientY - rect.top; @@ -180,7 +181,7 @@ export class Input { this.mouseX = this.touch0.x; this.mouseX = this.touch0.x; this.buttonDown = true; - this.listeners.map((listener) => { if (listener.down) listener.down(this.touch0.x, this.touch0.y) }); + this.listeners.map((listener) => { if (listener.down) listener.down(this.touch0!.x, this.touch0!.y) }); } } diff --git a/spine-ts/spine-webgl/src/LoadingScreen.ts b/spine-ts/spine-webgl/src/LoadingScreen.ts index 63becf79c..de3befe59 100644 --- a/spine-ts/spine-webgl/src/LoadingScreen.ts +++ b/spine-ts/spine-webgl/src/LoadingScreen.ts @@ -40,8 +40,8 @@ const logoWidth = 165, logoHeight = 108, spinnerSize = 163; export class LoadingScreen implements Disposable { private renderer: SceneRenderer; - private logo: GLTexture = null; - private spinner: GLTexture = null; + private logo: GLTexture | null = null; + private spinner: GLTexture | null = null; private angle = 0; private fadeOut = 0; private fadeIn = 0; @@ -70,8 +70,8 @@ export class LoadingScreen implements Disposable { } } dispose (): void { - this.logo.dispose(); - this.spinner.dispose(); + this.logo?.dispose(); + this.spinner?.dispose(); } draw (complete = false) { @@ -122,7 +122,7 @@ export class LoadingScreen implements Disposable { renderer.camera.zoom = Math.max(1, spinnerSize / canvas.height); renderer.begin(); renderer.drawTexture(this.logo, (canvas.width - logoWidth) / 2, (canvas.height - logoHeight) / 2, logoWidth, logoHeight, tempColor); - renderer.drawTextureRotated(this.spinner, (canvas.width - spinnerSize) / 2, (canvas.height - spinnerSize) / 2, spinnerSize, spinnerSize, spinnerSize / 2, spinnerSize / 2, this.angle, tempColor); + if (this.spinner) renderer.drawTextureRotated(this.spinner, (canvas.width - spinnerSize) / 2, (canvas.height - spinnerSize) / 2, spinnerSize, spinnerSize, spinnerSize / 2, spinnerSize / 2, this.angle, tempColor); renderer.end(); } } diff --git a/spine-ts/spine-webgl/src/Matrix4.ts b/spine-ts/spine-webgl/src/Matrix4.ts index d81dc32c1..41d4631a9 100644 --- a/spine-ts/spine-webgl/src/Matrix4.ts +++ b/spine-ts/spine-webgl/src/Matrix4.ts @@ -27,6 +27,7 @@ * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. *****************************************************************************/ +import { Vector2 } from "@esotericsoftware/spine-core"; import { Vector3 } from "./Vector3"; export const M00 = 0; @@ -50,9 +51,9 @@ export class Matrix4 { temp: Float32Array = new Float32Array(16); values: Float32Array = new Float32Array(16); - private static xAxis: Vector3 = null; - private static yAxis: Vector3 = null; - private static zAxis: Vector3 = null; + private static xAxis = new Vector3(); + private static yAxis = new Vector3(); + private static zAxis = new Vector3(); private static tmpMatrix = new Matrix4(); constructor () { @@ -305,7 +306,6 @@ export class Matrix4 { } lookAt (position: Vector3, direction: Vector3, up: Vector3) { - Matrix4.initTemps(); let xAxis = Matrix4.xAxis, yAxis = Matrix4.yAxis, zAxis = Matrix4.zAxis; zAxis.setFrom(direction).normalize(); xAxis.setFrom(direction).normalize(); @@ -331,10 +331,4 @@ export class Matrix4 { return this; } - - static initTemps () { - if (Matrix4.xAxis === null) Matrix4.xAxis = new Vector3(); - if (Matrix4.yAxis === null) Matrix4.yAxis = new Vector3(); - if (Matrix4.zAxis === null) Matrix4.zAxis = new Vector3(); - } } diff --git a/spine-ts/spine-webgl/src/Mesh.ts b/spine-ts/spine-webgl/src/Mesh.ts index c2a37cf35..d4676ef48 100644 --- a/spine-ts/spine-webgl/src/Mesh.ts +++ b/spine-ts/spine-webgl/src/Mesh.ts @@ -35,11 +35,11 @@ import { ManagedWebGLRenderingContext } from "./WebGL"; export class Mesh implements Disposable, Restorable { private context: ManagedWebGLRenderingContext; private vertices: Float32Array; - private verticesBuffer: WebGLBuffer; + private verticesBuffer: WebGLBuffer | null = null; private verticesLength = 0; private dirtyVertices = false; private indices: Uint16Array; - private indicesBuffer: WebGLBuffer; + private indicesBuffer: WebGLBuffer | null = null; private indicesLength = 0; private dirtyIndices = false; private elementsPerVertex = 0; diff --git a/spine-ts/spine-webgl/src/PolygonBatcher.ts b/spine-ts/spine-webgl/src/PolygonBatcher.ts index 267ea5500..921c6f378 100644 --- a/spine-ts/spine-webgl/src/PolygonBatcher.ts +++ b/spine-ts/spine-webgl/src/PolygonBatcher.ts @@ -35,17 +35,17 @@ import { ManagedWebGLRenderingContext } from "./WebGL"; export class PolygonBatcher implements Disposable { private context: ManagedWebGLRenderingContext; - private drawCalls: number; + private drawCalls = 0; private isDrawing = false; private mesh: Mesh; - private shader: Shader = null; - private lastTexture: GLTexture = null; + private shader: Shader | null = null; + private lastTexture: GLTexture | null = null; private verticesLength = 0; private indicesLength = 0; private srcColorBlend: number; private srcAlphaBlend: number; private dstBlend: number; - private cullWasEnabled: boolean; + private cullWasEnabled = false; constructor (context: ManagedWebGLRenderingContext | WebGLRenderingContext, twoColorTint: boolean = true, maxVertices: number = 10920) { if (maxVertices > 10920) throw new Error("Can't have more than 10920 triangles per batch: " + maxVertices); @@ -110,7 +110,8 @@ export class PolygonBatcher implements Disposable { flush () { if (this.verticesLength == 0) return; - + if (!this.lastTexture) throw new Error("No texture set."); + if (!this.shader) throw new Error("No shader set."); this.lastTexture.bind(); this.mesh.draw(this.shader, this.context.gl.TRIANGLES); diff --git a/spine-ts/spine-webgl/src/SceneRenderer.ts b/spine-ts/spine-webgl/src/SceneRenderer.ts index 27676946e..4fb1cd390 100644 --- a/spine-ts/spine-webgl/src/SceneRenderer.ts +++ b/spine-ts/spine-webgl/src/SceneRenderer.ts @@ -56,7 +56,7 @@ export class SceneRenderer implements Disposable { private batcherShader: Shader; private shapes: ShapeRenderer; private shapesShader: Shader; - private activeRenderer: PolygonBatcher | ShapeRenderer | SkeletonDebugRenderer; + private activeRenderer: PolygonBatcher | ShapeRenderer | SkeletonDebugRenderer | null = null; skeletonRenderer: SkeletonRenderer; skeletonDebugRenderer: SkeletonDebugRenderer; @@ -92,15 +92,15 @@ export class SceneRenderer implements Disposable { this.skeletonRenderer.draw(this.batcher, skeleton, slotRangeStart, slotRangeEnd); } - drawSkeletonDebug (skeleton: Skeleton, premultipliedAlpha = false, ignoredBones: Array = null) { + drawSkeletonDebug (skeleton: Skeleton, premultipliedAlpha = false, ignoredBones?: Array) { this.enableRenderer(this.shapes); this.skeletonDebugRenderer.premultipliedAlpha = premultipliedAlpha; this.skeletonDebugRenderer.draw(this.shapes, skeleton, ignoredBones); } - drawTexture (texture: GLTexture, x: number, y: number, width: number, height: number, color: Color = null) { + drawTexture (texture: GLTexture, x: number, y: number, width: number, height: number, color?: Color) { this.enableRenderer(this.batcher); - if (color === null) color = WHITE; + if (!color) color = WHITE; var i = 0; quad[i++] = x; quad[i++] = y; @@ -161,9 +161,9 @@ export class SceneRenderer implements Disposable { this.batcher.draw(texture, quad, QUAD_TRIANGLES); } - drawTextureUV (texture: GLTexture, x: number, y: number, width: number, height: number, u: number, v: number, u2: number, v2: number, color: Color = null) { + drawTextureUV (texture: GLTexture, x: number, y: number, width: number, height: number, u: number, v: number, u2: number, v2: number, color?: Color) { this.enableRenderer(this.batcher); - if (color === null) color = WHITE; + if (!color) color = WHITE; var i = 0; quad[i++] = x; quad[i++] = y; @@ -224,9 +224,9 @@ export class SceneRenderer implements Disposable { this.batcher.draw(texture, quad, QUAD_TRIANGLES); } - drawTextureRotated (texture: GLTexture, x: number, y: number, width: number, height: number, pivotX: number, pivotY: number, angle: number, color: Color = null) { + drawTextureRotated (texture: GLTexture, x: number, y: number, width: number, height: number, pivotX: number, pivotY: number, angle: number, color?: Color) { this.enableRenderer(this.batcher); - if (color === null) color = WHITE; + if (!color) color = WHITE; // bottom left and top right corner points relative to origin let worldOriginX = x + pivotX; @@ -354,9 +354,9 @@ export class SceneRenderer implements Disposable { this.batcher.draw(texture, quad, QUAD_TRIANGLES); } - drawRegion (region: TextureAtlasRegion, x: number, y: number, width: number, height: number, color: Color = null) { + drawRegion (region: TextureAtlasRegion, x: number, y: number, width: number, height: number, color?: Color) { this.enableRenderer(this.batcher); - if (color === null) color = WHITE; + if (!color) color = WHITE; var i = 0; quad[i++] = x; quad[i++] = y; @@ -417,42 +417,42 @@ export class SceneRenderer implements Disposable { this.batcher.draw(region.page.texture, quad, QUAD_TRIANGLES); } - line (x: number, y: number, x2: number, y2: number, color: Color = null, color2: Color = null) { + line (x: number, y: number, x2: number, y2: number, color?: Color, color2?: Color) { this.enableRenderer(this.shapes); this.shapes.line(x, y, x2, y2, color); } - triangle (filled: boolean, x: number, y: number, x2: number, y2: number, x3: number, y3: number, color: Color = null, color2: Color = null, color3: Color = null) { + triangle (filled: boolean, x: number, y: number, x2: number, y2: number, x3: number, y3: number, color?: Color, color2?: Color, color3?: Color) { this.enableRenderer(this.shapes); this.shapes.triangle(filled, x, y, x2, y2, x3, y3, color, color2, color3); } - quad (filled: boolean, x: number, y: number, x2: number, y2: number, x3: number, y3: number, x4: number, y4: number, color: Color = null, color2: Color = null, color3: Color = null, color4: Color = null) { + quad (filled: boolean, x: number, y: number, x2: number, y2: number, x3: number, y3: number, x4: number, y4: number, color?: Color, color2?: Color, color3?: Color, color4?: Color) { this.enableRenderer(this.shapes); this.shapes.quad(filled, x, y, x2, y2, x3, y3, x4, y4, color, color2, color3, color4); } - rect (filled: boolean, x: number, y: number, width: number, height: number, color: Color = null) { + rect (filled: boolean, x: number, y: number, width: number, height: number, color?: Color) { this.enableRenderer(this.shapes); this.shapes.rect(filled, x, y, width, height, color); } - rectLine (filled: boolean, x1: number, y1: number, x2: number, y2: number, width: number, color: Color = null) { + rectLine (filled: boolean, x1: number, y1: number, x2: number, y2: number, width: number, color?: Color) { this.enableRenderer(this.shapes); this.shapes.rectLine(filled, x1, y1, x2, y2, width, color); } - polygon (polygonVertices: ArrayLike, offset: number, count: number, color: Color = null) { + polygon (polygonVertices: ArrayLike, offset: number, count: number, color?: Color) { this.enableRenderer(this.shapes); this.shapes.polygon(polygonVertices, offset, count, color); } - circle (filled: boolean, x: number, y: number, radius: number, color: Color = null, segments: number = 0) { + circle (filled: boolean, x: number, y: number, radius: number, color?: Color, segments: number = 0) { this.enableRenderer(this.shapes); this.shapes.circle(filled, x, y, radius, color, segments); } - curve (x1: number, y1: number, cx1: number, cy1: number, cx2: number, cy2: number, x2: number, y2: number, segments: number, color: Color = null) { + curve (x1: number, y1: number, cx1: number, cy1: number, cx2: number, cy2: number, x2: number, y2: number, segments: number, color?: Color) { this.enableRenderer(this.shapes); this.shapes.curve(x1, y1, cx1, cy1, cx2, cy2, x2, y2, segments, color); } diff --git a/spine-ts/spine-webgl/src/Shader.ts b/spine-ts/spine-webgl/src/Shader.ts index e1c0b794d..db7d4e7cc 100644 --- a/spine-ts/spine-webgl/src/Shader.ts +++ b/spine-ts/spine-webgl/src/Shader.ts @@ -39,11 +39,11 @@ export class Shader implements Disposable, Restorable { public static SAMPLER = "u_texture"; private context: ManagedWebGLRenderingContext; - private vs: WebGLShader = null; + private vs: WebGLShader | null = null; private vsSource: string; - private fs: WebGLShader = null; + private fs: WebGLShader | null = null; private fsSource: string; - private program: WebGLProgram = null; + private program: WebGLProgram | null = null; private tmp2x2: Float32Array = new Float32Array(2 * 2); private tmp3x3: Float32Array = new Float32Array(3 * 3); private tmp4x4: Float32Array = new Float32Array(4 * 4); @@ -66,7 +66,9 @@ export class Shader implements Disposable, Restorable { let gl = this.context.gl; try { this.vs = this.compileShader(gl.VERTEX_SHADER, this.vertexShader); + if (!this.vs) throw new Error("Couldn't compile vertex shader."); this.fs = this.compileShader(gl.FRAGMENT_SHADER, this.fragmentShader); + if (!this.fs) throw new Error("Couldn#t compile fragment shader."); this.program = this.compileProgram(this.vs, this.fs); } catch (e) { this.dispose(); @@ -77,6 +79,7 @@ export class Shader implements Disposable, Restorable { private compileShader (type: number, source: string) { let gl = this.context.gl; let shader = gl.createShader(type); + if (!shader) throw new Error("Couldn't create shader."); gl.shaderSource(shader, source); gl.compileShader(shader); if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) { @@ -90,6 +93,7 @@ export class Shader implements Disposable, Restorable { private compileProgram (vs: WebGLShader, fs: WebGLShader) { let gl = this.context.gl; let program = gl.createProgram(); + if (!program) throw new Error("Couldn't compile program."); gl.attachShader(program, vs); gl.attachShader(program, fs); gl.linkProgram(program); @@ -152,8 +156,9 @@ export class Shader implements Disposable, Restorable { gl.uniformMatrix4fv(this.getUniformLocation(uniform), false, this.tmp4x4); } - public getUniformLocation (uniform: string): WebGLUniformLocation { + public getUniformLocation (uniform: string): WebGLUniformLocation | null { let gl = this.context.gl; + if (!this.program) throw new Error("Shader not compiled."); let location = gl.getUniformLocation(this.program, uniform); if (!location && !gl.isContextLost()) throw new Error(`Couldn't find location for uniform ${uniform}`); return location; @@ -161,6 +166,7 @@ export class Shader implements Disposable, Restorable { public getAttributeLocation (attribute: string): number { let gl = this.context.gl; + if (!this.program) throw new Error("Shader not compiled."); let location = gl.getAttribLocation(this.program, attribute); if (location == -1 && !gl.isContextLost()) throw new Error(`Couldn't find location for attribute ${attribute}`); return location; diff --git a/spine-ts/spine-webgl/src/ShapeRenderer.ts b/spine-ts/spine-webgl/src/ShapeRenderer.ts index 68140b64f..646d8a9aa 100644 --- a/spine-ts/spine-webgl/src/ShapeRenderer.ts +++ b/spine-ts/spine-webgl/src/ShapeRenderer.ts @@ -38,7 +38,7 @@ export class ShapeRenderer implements Disposable { private mesh: Mesh; private shapeType = ShapeType.Filled; private color = new Color(1, 1, 1, 1); - private shader: Shader; + private shader: Shader | null = null; private vertexIndex = 0; private tmp = new Vector2(); private srcColorBlend: number; @@ -85,28 +85,28 @@ export class ShapeRenderer implements Disposable { this.color.set(r, g, b, a); } - point (x: number, y: number, color: Color = null) { + point (x: number, y: number, color?: Color) { this.check(ShapeType.Point, 1); - if (color === null) color = this.color; + if (!color) color = this.color; this.vertex(x, y, color); } - line (x: number, y: number, x2: number, y2: number, color: Color = null) { + line (x: number, y: number, x2: number, y2: number, color?: Color) { this.check(ShapeType.Line, 2); let vertices = this.mesh.getVertices(); let idx = this.vertexIndex; - if (color === null) color = this.color; + if (!color) color = this.color; this.vertex(x, y, color); this.vertex(x2, y2, color); } - triangle (filled: boolean, x: number, y: number, x2: number, y2: number, x3: number, y3: number, color: Color = null, color2: Color = null, color3: Color = null) { + triangle (filled: boolean, x: number, y: number, x2: number, y2: number, x3: number, y3: number, color?: Color, color2?: Color, color3?: Color) { this.check(filled ? ShapeType.Filled : ShapeType.Line, 3); let vertices = this.mesh.getVertices(); let idx = this.vertexIndex; - if (color === null) color = this.color; - if (color2 === null) color2 = this.color; - if (color3 === null) color3 = this.color; + if (!color) color = this.color; + if (!color2) color2 = this.color; + if (!color3) color3 = this.color; if (filled) { this.vertex(x, y, color); this.vertex(x2, y2, color2); @@ -123,14 +123,14 @@ export class ShapeRenderer implements Disposable { } } - quad (filled: boolean, x: number, y: number, x2: number, y2: number, x3: number, y3: number, x4: number, y4: number, color: Color = null, color2: Color = null, color3: Color = null, color4: Color = null) { + quad (filled: boolean, x: number, y: number, x2: number, y2: number, x3: number, y3: number, x4: number, y4: number, color?: Color, color2?: Color, color3?: Color, color4?: Color) { this.check(filled ? ShapeType.Filled : ShapeType.Line, 3); let vertices = this.mesh.getVertices(); let idx = this.vertexIndex; - if (color === null) color = this.color; - if (color2 === null) color2 = this.color; - if (color3 === null) color3 = this.color; - if (color4 === null) color4 = this.color; + if (!color) color = this.color; + if (!color2) color2 = this.color; + if (!color3) color3 = this.color; + if (!color4) color4 = this.color; if (filled) { this.vertex(x, y, color); this.vertex(x2, y2, color2); this.vertex(x3, y3, color3); this.vertex(x3, y3, color3); this.vertex(x4, y4, color4); this.vertex(x, y, color); @@ -142,13 +142,13 @@ export class ShapeRenderer implements Disposable { } } - rect (filled: boolean, x: number, y: number, width: number, height: number, color: Color = null) { + rect (filled: boolean, x: number, y: number, width: number, height: number, color?: Color) { this.quad(filled, x, y, x + width, y, x + width, y + height, x, y + height, color, color, color, color); } - rectLine (filled: boolean, x1: number, y1: number, x2: number, y2: number, width: number, color: Color = null) { + rectLine (filled: boolean, x1: number, y1: number, x2: number, y2: number, width: number, color?: Color) { this.check(filled ? ShapeType.Filled : ShapeType.Line, 8); - if (color === null) color = this.color; + if (!color) color = this.color; let t = this.tmp.set(y2 - y1, x1 - x2); t.normalize(); width *= 0.5; @@ -181,10 +181,10 @@ export class ShapeRenderer implements Disposable { this.line(x - size, y + size, x + size, y - size); } - polygon (polygonVertices: ArrayLike, offset: number, count: number, color: Color = null) { + polygon (polygonVertices: ArrayLike, offset: number, count: number, color?: Color) { if (count < 3) throw new Error("Polygon must contain at least 3 vertices"); this.check(ShapeType.Line, count * 2); - if (color === null) color = this.color; + if (color) color = this.color; let vertices = this.mesh.getVertices(); let idx = this.vertexIndex; @@ -210,15 +210,15 @@ export class ShapeRenderer implements Disposable { y2 = polygonVertices[i + 3]; } - this.vertex(x1, y1, color); - this.vertex(x2, y2, color); + this.vertex(x1, y1, color!); + this.vertex(x2, y2, color!); } } - circle (filled: boolean, x: number, y: number, radius: number, color: Color = null, segments: number = 0) { - if (segments === 0) segments = Math.max(1, (6 * MathUtils.cbrt(radius)) | 0); + circle (filled: boolean, x: number, y: number, radius: number, color?: Color, segments: number = 0) { + if (segments == 0) segments = Math.max(1, (6 * MathUtils.cbrt(radius)) | 0); if (segments <= 0) throw new Error("segments must be > 0."); - if (color === null) color = this.color; + if (!color) color = this.color; let angle = 2 * MathUtils.PI / segments; let cos = Math.cos(angle); let sin = Math.sin(angle); @@ -256,9 +256,9 @@ export class ShapeRenderer implements Disposable { this.vertex(x + cx, y + cy, color); } - curve (x1: number, y1: number, cx1: number, cy1: number, cx2: number, cy2: number, x2: number, y2: number, segments: number, color: Color = null) { + curve (x1: number, y1: number, cx1: number, cy1: number, cx2: number, cy2: number, x2: number, y2: number, segments: number, color?: Color) { this.check(ShapeType.Line, segments * 2 + 2); - if (color === null) color = this.color; + if (color) color = this.color; // Algorithm from: http://www.antigrain.com/research/bezier_interpolation/index.html#PAGE_BEZIER_INTERPOLATION let subdiv_step = 1 / segments; @@ -289,17 +289,17 @@ export class ShapeRenderer implements Disposable { let dddfy = tmp2y * pre5; while (segments-- > 0) { - this.vertex(fx, fy, color); + this.vertex(fx, fy, color!); fx += dfx; fy += dfy; dfx += ddfx; dfy += ddfy; ddfx += dddfx; ddfy += dddfy; - this.vertex(fx, fy, color); + this.vertex(fx, fy, color!); } - this.vertex(fx, fy, color); - this.vertex(x2, y2, color); + this.vertex(fx, fy, color!); + this.vertex(x2, y2, color!); } private vertex (x: number, y: number, color: Color) { @@ -324,6 +324,7 @@ export class ShapeRenderer implements Disposable { private flush () { if (this.vertexIndex == 0) return; + if (!this.shader) throw new Error("No shader set."); this.mesh.setVerticesLength(this.vertexIndex); this.mesh.draw(this.shader, this.shapeType); this.vertexIndex = 0; diff --git a/spine-ts/spine-webgl/src/SkeletonDebugRenderer.ts b/spine-ts/spine-webgl/src/SkeletonDebugRenderer.ts index bcce09637..f6cbbd8e6 100644 --- a/spine-ts/spine-webgl/src/SkeletonDebugRenderer.ts +++ b/spine-ts/spine-webgl/src/SkeletonDebugRenderer.ts @@ -62,7 +62,7 @@ export class SkeletonDebugRenderer implements Disposable { this.context = context instanceof ManagedWebGLRenderingContext ? context : new ManagedWebGLRenderingContext(context); } - draw (shapes: ShapeRenderer, skeleton: Skeleton, ignoredBones: Array = null) { + draw (shapes: ShapeRenderer, skeleton: Skeleton, ignoredBones?: Array) { let skeletonX = skeleton.x; let skeletonY = skeleton.y; let gl = this.context.gl; diff --git a/spine-ts/spine-webgl/src/SkeletonRenderer.ts b/spine-ts/spine-webgl/src/SkeletonRenderer.ts index d8c2be925..9e9715a8a 100644 --- a/spine-ts/spine-webgl/src/SkeletonRenderer.ts +++ b/spine-ts/spine-webgl/src/SkeletonRenderer.ts @@ -41,13 +41,13 @@ export class SkeletonRenderer { static QUAD_TRIANGLES = [0, 1, 2, 2, 3, 0]; premultipliedAlpha = false; - vertexEffect: VertexEffect = null; + vertexEffect: VertexEffect | null = null; private tempColor = new Color(); private tempColor2 = new Color(); private vertices: NumberArrayLike; private vertexSize = 2 + 2 + 4; private twoColorTint = false; - private renderable: Renderable = new Renderable(null, 0, 0); + private renderable: Renderable = new Renderable([], 0, 0); private clipper: SkeletonClipping = new SkeletonClipping(); private temp = new Vector2(); private temp2 = new Vector2(); @@ -65,7 +65,7 @@ export class SkeletonRenderer { let clipper = this.clipper; let premultipliedAlpha = this.premultipliedAlpha; let twoColorTint = this.twoColorTint; - let blendMode: BlendMode = null; + let blendMode: BlendMode | null = null; let tempPos = this.temp; let tempUv = this.temp2; @@ -73,10 +73,10 @@ export class SkeletonRenderer { let tempDark = this.temp4; let renderable: Renderable = this.renderable; - let uvs: NumberArrayLike = null; - let triangles: Array = null; + let uvs: NumberArrayLike; + let triangles: Array; let drawOrder = skeleton.drawOrder; - let attachmentColor: Color = null; + let attachmentColor: Color; let skeletonColor = skeleton.color; let vertexSize = twoColorTint ? 12 : 8; let inRange = false; @@ -103,7 +103,7 @@ export class SkeletonRenderer { } let attachment = slot.getAttachment(); - let texture: GLTexture = null; + let texture: GLTexture; if (attachment instanceof RegionAttachment) { let region = attachment; renderable.vertices = this.vertices; @@ -112,7 +112,7 @@ export class SkeletonRenderer { region.computeWorldVertices(slot, renderable.vertices, 0, clippedVertexSize); triangles = SkeletonRenderer.QUAD_TRIANGLES; uvs = region.uvs; - texture = (region.region.renderObject).page.texture; + texture = (region.region!.renderObject).page.texture; attachmentColor = region.color; } else if (attachment instanceof MeshAttachment) { let mesh = attachment; @@ -124,7 +124,7 @@ export class SkeletonRenderer { } mesh.computeWorldVertices(slot, 0, mesh.worldVerticesLength, renderable.vertices, 0, clippedVertexSize); triangles = mesh.triangles; - texture = (mesh.region.renderObject).page.texture; + texture = (mesh.region!.renderObject).page.texture; uvs = mesh.uvs; attachmentColor = mesh.color; } else if (attachment instanceof ClippingAttachment) { diff --git a/spine-ts/spine-webgl/src/SpineCanvas.ts b/spine-ts/spine-webgl/src/SpineCanvas.ts index 399889c90..246b7d867 100644 --- a/spine-ts/spine-webgl/src/SpineCanvas.ts +++ b/spine-ts/spine-webgl/src/SpineCanvas.ts @@ -77,15 +77,15 @@ export class SpineCanvas { /** Constructs a new spine canvas, rendering to the provided HTML canvas. */ constructor (canvas: HTMLCanvasElement, config: SpineCanvasConfig) { - if (config.pathPrefix === undefined) config.pathPrefix = ""; - if (config.app === undefined) config.app = { + if (!config.pathPrefix) config.pathPrefix = ""; + if (!config.app) config.app = { loadAssets: () => { }, initialize: () => { }, update: () => { }, render: () => { }, error: () => { }, } - if (config.webglConfig === undefined) config.webglConfig = { alpha: true }; + if (config.webglConfig) config.webglConfig = { alpha: true }; this.htmlCanvas = canvas; this.context = new ManagedWebGLRenderingContext(canvas, config.webglConfig); @@ -94,21 +94,21 @@ export class SpineCanvas { this.assetManager = new AssetManager(this.context, config.pathPrefix); this.input = new Input(canvas); - config.app.loadAssets(this); + if (config.app.loadAssets) config.app.loadAssets(this); let loop = () => { requestAnimationFrame(loop); this.time.update(); - config.app.update(this, this.time.delta); - config.app.render(this); + if (config.app.update) config.app.update(this, this.time.delta); + if (config.app.render) config.app.render(this); } let waitForAssets = () => { if (this.assetManager.isLoadingComplete()) { if (this.assetManager.hasErrors()) { - config.app.error(this, this.assetManager.getErrors()); + if (config.app.error) config.app.error(this, this.assetManager.getErrors()); } else { - config.app.initialize(this); + if (config.app.initialize) config.app.initialize(this); loop(); } return; diff --git a/spine-ts/spine-webgl/src/WebGL.ts b/spine-ts/spine-webgl/src/WebGL.ts index 80c8e5b7d..314bdf1a2 100644 --- a/spine-ts/spine-webgl/src/WebGL.ts +++ b/spine-ts/spine-webgl/src/WebGL.ts @@ -34,29 +34,25 @@ export class ManagedWebGLRenderingContext { public gl: WebGLRenderingContext; private restorables = new Array(); - constructor (canvasOrContext: HTMLCanvasElement | WebGLRenderingContext | EventTarget, contextConfig: any = { alpha: "true" }) { - if (!((canvasOrContext instanceof WebGLRenderingContext) || (typeof WebGL2RenderingContext !== 'undefined' && canvasOrContext instanceof WebGL2RenderingContext))) - this.setupCanvas(canvasOrContext, contextConfig); - else { + constructor (canvasOrContext: HTMLCanvasElement | WebGLRenderingContext, contextConfig: any = { alpha: "true" }) { + if (!((canvasOrContext instanceof WebGLRenderingContext) || (typeof WebGL2RenderingContext !== 'undefined' && canvasOrContext instanceof WebGL2RenderingContext))) { + let canvas: HTMLCanvasElement = canvasOrContext; + this.gl = (canvas.getContext("webgl2", contextConfig) || canvas.getContext("webgl", contextConfig)); + this.canvas = canvas; + canvas.addEventListener("webglcontextlost", (e: any) => { + let event = e; + if (e) e.preventDefault(); + }); + canvas.addEventListener("webglcontextrestored", (e: any) => { + for (let i = 0, n = this.restorables.length; i < n; i++) + this.restorables[i].restore(); + }); + } else { this.gl = canvasOrContext; this.canvas = this.gl.canvas; } } - private setupCanvas (canvas: any, contextConfig: any) { - this.gl = (canvas.getContext("webgl2", contextConfig) || canvas.getContext("webgl", contextConfig)); - this.canvas = canvas; - canvas.addEventListener("webglcontextlost", (e: any) => { - let event = e; - if (e) e.preventDefault(); - }); - - canvas.addEventListener("webglcontextrestored", (e: any) => { - for (let i = 0, n = this.restorables.length; i < n; i++) - this.restorables[i].restore(); - }); - } - addRestorable (restorable: Restorable) { this.restorables.push(restorable); } diff --git a/spine-ts/tsconfig.base.json b/spine-ts/tsconfig.base.json index f4f702711..285f6a341 100644 --- a/spine-ts/tsconfig.base.json +++ b/spine-ts/tsconfig.base.json @@ -12,8 +12,7 @@ ], "declaration": true, "composite": true, - "moduleResolution": "node" - /*"strictNullChecks": true, - "strictPropertyInitialization": true*/ + "moduleResolution": "node", + "strict": true } } \ No newline at end of file From 156ef80a9cfa4752ed0c00ca37163edd9590c91d Mon Sep 17 00:00:00 2001 From: Harald Csaszar Date: Fri, 8 Apr 2022 11:16:42 +0200 Subject: [PATCH 3/7] [unity] Setting SkeletonGraphic CanvasRenderer.cullTransparentMesh to false at new instances to avoid `Add` blend mode problems. --- .../Editor/Components/SkeletonGraphicInspector.cs | 9 +++++++++ .../spine-unity/Components/SkeletonGraphic.cs | 14 +++++++++----- 2 files changed, 18 insertions(+), 5 deletions(-) diff --git a/spine-unity/Assets/Spine/Editor/spine-unity/Editor/Components/SkeletonGraphicInspector.cs b/spine-unity/Assets/Spine/Editor/spine-unity/Editor/Components/SkeletonGraphicInspector.cs index a14c6a711..e3f439147 100644 --- a/spine-unity/Assets/Spine/Editor/spine-unity/Editor/Components/SkeletonGraphicInspector.cs +++ b/spine-unity/Assets/Spine/Editor/spine-unity/Editor/Components/SkeletonGraphicInspector.cs @@ -31,6 +31,10 @@ #define NEW_PREFAB_SYSTEM #endif +#if UNITY_2018_2_OR_NEWER +#define HAS_CULL_TRANSPARENT_MESH +#endif + using UnityEditor; using UnityEngine; @@ -469,6 +473,11 @@ namespace Spine.Unity.Editor { graphic.additiveMaterial = SkeletonGraphicInspector.DefaultSkeletonGraphicAdditiveMaterial; graphic.multiplyMaterial = SkeletonGraphicInspector.DefaultSkeletonGraphicMultiplyMaterial; graphic.screenMaterial = SkeletonGraphicInspector.DefaultSkeletonGraphicScreenMaterial; + +#if HAS_CULL_TRANSPARENT_MESH + var canvasRenderer = go.GetComponent(); + canvasRenderer.cullTransparentMesh = false; +#endif return go; } diff --git a/spine-unity/Assets/Spine/Runtime/spine-unity/Components/SkeletonGraphic.cs b/spine-unity/Assets/Spine/Runtime/spine-unity/Components/SkeletonGraphic.cs index df0134b98..efc4d95e4 100644 --- a/spine-unity/Assets/Spine/Runtime/spine-unity/Components/SkeletonGraphic.cs +++ b/spine-unity/Assets/Spine/Runtime/spine-unity/Components/SkeletonGraphic.cs @@ -169,13 +169,17 @@ namespace Spine.Unity { /// Add a SkeletonGraphic component to a GameObject. /// Material for the canvas renderer to use. Usually, the default SkeletonGraphic material will work. public static SkeletonGraphic AddSkeletonGraphicComponent (GameObject gameObject, SkeletonDataAsset skeletonDataAsset, Material material) { - var c = gameObject.AddComponent(); + var skeletonGraphic = gameObject.AddComponent(); if (skeletonDataAsset != null) { - c.material = material; - c.skeletonDataAsset = skeletonDataAsset; - c.Initialize(false); + skeletonGraphic.material = material; + skeletonGraphic.skeletonDataAsset = skeletonDataAsset; + skeletonGraphic.Initialize(false); } - return c; +#if HAS_CULL_TRANSPARENT_MESH + var canvasRenderer = gameObject.GetComponent(); + if (canvasRenderer) canvasRenderer.cullTransparentMesh = false; +#endif + return skeletonGraphic; } #endregion From 70277b8f0469bdc42fc69300885a955cb3820024 Mon Sep 17 00:00:00 2001 From: Harald Csaszar Date: Wed, 13 Apr 2022 20:41:58 +0200 Subject: [PATCH 4/7] [unity] Fixed incorrect reloading behaviour (especially of deform timeline) at Domain Reload. (Fix of previous commit 0820bd7). Closes #2066. --- .../Asset Types/SkeletonDataAssetInspector.cs | 2 +- .../spine-unity/Editor/Utility/AssetUtility.cs | 4 ++-- .../Editor/Utility/BlendModeMaterialsUtility.cs | 4 ++-- .../Editor/Utility/DataReloadHandler.cs | 15 ++++++++++++--- .../Editor/Utility/SpineEditorUtilities.cs | 15 ++++++++++++--- .../Asset Types/AnimationReferenceAsset.cs | 7 ++++++- 6 files changed, 35 insertions(+), 12 deletions(-) diff --git a/spine-unity/Assets/Spine/Editor/spine-unity/Editor/Asset Types/SkeletonDataAssetInspector.cs b/spine-unity/Assets/Spine/Editor/spine-unity/Editor/Asset Types/SkeletonDataAssetInspector.cs index 2fa99cdf6..c3d286f9a 100644 --- a/spine-unity/Assets/Spine/Editor/spine-unity/Editor/Asset Types/SkeletonDataAssetInspector.cs +++ b/spine-unity/Assets/Spine/Editor/spine-unity/Editor/Asset Types/SkeletonDataAssetInspector.cs @@ -154,7 +154,7 @@ namespace Spine.Unity.Editor { void Clear () { preview.Clear(); - targetSkeletonDataAsset.Clear(); + SpineEditorUtilities.ClearSkeletonDataAsset(targetSkeletonDataAsset); targetSkeletonData = null; } diff --git a/spine-unity/Assets/Spine/Editor/spine-unity/Editor/Utility/AssetUtility.cs b/spine-unity/Assets/Spine/Editor/spine-unity/Editor/Utility/AssetUtility.cs index f7d260373..a1836306f 100644 --- a/spine-unity/Assets/Spine/Editor/spine-unity/Editor/Utility/AssetUtility.cs +++ b/spine-unity/Assets/Spine/Editor/spine-unity/Editor/Utility/AssetUtility.cs @@ -462,7 +462,7 @@ namespace Spine.Unity.Editor { } Debug.LogFormat("Changes to '{0}' or atlas detected. Clearing SkeletonDataAsset: {1}", skeletonJSONPath, localPath); - skeletonDataAsset.Clear(); + SpineEditorUtilities.ClearSkeletonDataAsset(skeletonDataAsset); string guid = AssetDatabase.AssetPathToGUID(AssetDatabase.GetAssetPath(skeletonDataAsset)); string lastHash = EditorPrefs.GetString(guid + "_hash"); @@ -947,7 +947,7 @@ namespace Spine.Unity.Editor { AssetDatabase.CreateAsset(skeletonDataAsset, filePath); } else { skeletonDataAsset.atlasAssets = atlasAssets; - skeletonDataAsset.Clear(); + SpineEditorUtilities.ClearSkeletonDataAsset(skeletonDataAsset); } var skeletonData = skeletonDataAsset.GetSkeletonData(true); if (skeletonData != null) diff --git a/spine-unity/Assets/Spine/Editor/spine-unity/Editor/Utility/BlendModeMaterialsUtility.cs b/spine-unity/Assets/Spine/Editor/spine-unity/Editor/Utility/BlendModeMaterialsUtility.cs index 8fc2eeee3..e86e21ea4 100644 --- a/spine-unity/Assets/Spine/Editor/spine-unity/Editor/Utility/BlendModeMaterialsUtility.cs +++ b/spine-unity/Assets/Spine/Editor/spine-unity/Editor/Utility/BlendModeMaterialsUtility.cs @@ -96,7 +96,7 @@ namespace Spine.Unity.Editor { } } - skeletonDataAsset.Clear(); + SpineEditorUtilities.ClearSkeletonDataAsset(skeletonDataAsset); skeletonData = skeletonDataAsset.GetSkeletonData(true); if (anyMaterialsChanged) ReloadSceneSkeletons(skeletonDataAsset); @@ -164,7 +164,7 @@ namespace Spine.Unity.Editor { var skinEntries = new List(); - skeletonDataAsset.Clear(); + SpineEditorUtilities.ClearSkeletonDataAsset(skeletonDataAsset); skeletonDataAsset.isUpgradingBlendModeMaterials = true; SkeletonData skeletonData = skeletonDataAsset.GetSkeletonData(true); diff --git a/spine-unity/Assets/Spine/Editor/spine-unity/Editor/Utility/DataReloadHandler.cs b/spine-unity/Assets/Spine/Editor/spine-unity/Editor/Utility/DataReloadHandler.cs index 206520587..bf2bd9af6 100644 --- a/spine-unity/Assets/Spine/Editor/spine-unity/Editor/Utility/DataReloadHandler.cs +++ b/spine-unity/Assets/Spine/Editor/spine-unity/Editor/Utility/DataReloadHandler.cs @@ -91,8 +91,7 @@ namespace Spine.Unity.Editor { } foreach (var skeletonDataAsset in skeletonDataAssetsToReload) { - skeletonDataAsset.Clear(); - skeletonDataAsset.GetSkeletonData(true); + ReloadSkeletonDataAsset(skeletonDataAsset, false); } foreach (var skeletonRenderer in activeSkeletonRenderers) @@ -119,14 +118,24 @@ namespace Spine.Unity.Editor { } } + public static void ClearAnimationReferenceAssets (SkeletonDataAsset skeletonDataAsset) { + ForEachAnimationReferenceAsset(skeletonDataAsset, (referenceAsset) => referenceAsset.Clear()); + } + public static void ReloadAnimationReferenceAssets (SkeletonDataAsset skeletonDataAsset) { + ForEachAnimationReferenceAsset(skeletonDataAsset, (referenceAsset) => referenceAsset.Initialize()); + } + + private static void ForEachAnimationReferenceAsset (SkeletonDataAsset skeletonDataAsset, + System.Action func) { + string[] guids = UnityEditor.AssetDatabase.FindAssets("t:AnimationReferenceAsset"); foreach (string guid in guids) { string path = UnityEditor.AssetDatabase.GUIDToAssetPath(guid); if (!string.IsNullOrEmpty(path)) { var referenceAsset = UnityEditor.AssetDatabase.LoadAssetAtPath(path); if (referenceAsset.SkeletonDataAsset == skeletonDataAsset) - referenceAsset.Initialize(); + func(referenceAsset); } } } diff --git a/spine-unity/Assets/Spine/Editor/spine-unity/Editor/Utility/SpineEditorUtilities.cs b/spine-unity/Assets/Spine/Editor/spine-unity/Editor/Utility/SpineEditorUtilities.cs index d8ebdbf21..37848229f 100644 --- a/spine-unity/Assets/Spine/Editor/spine-unity/Editor/Utility/SpineEditorUtilities.cs +++ b/spine-unity/Assets/Spine/Editor/spine-unity/Editor/Utility/SpineEditorUtilities.cs @@ -266,14 +266,23 @@ namespace Spine.Unity.Editor { ReinitializeComponent(component); } - public static void ReloadSkeletonDataAsset (SkeletonDataAsset skeletonDataAsset) { - if (skeletonDataAsset != null) { + public static void ClearSkeletonDataAsset (SkeletonDataAsset skeletonDataAsset) { + skeletonDataAsset.Clear(); + DataReloadHandler.ClearAnimationReferenceAssets(skeletonDataAsset); + } + + public static void ReloadSkeletonDataAsset (SkeletonDataAsset skeletonDataAsset, bool clearAtlasAssets = true) { + if (skeletonDataAsset == null) + return; + + if (clearAtlasAssets) { foreach (AtlasAssetBase aa in skeletonDataAsset.atlasAssets) { if (aa != null) aa.Clear(); } - skeletonDataAsset.Clear(); } + ClearSkeletonDataAsset(skeletonDataAsset); skeletonDataAsset.GetSkeletonData(true); + DataReloadHandler.ReloadAnimationReferenceAssets(skeletonDataAsset); } public static void ReinitializeComponent (SkeletonRenderer component) { diff --git a/spine-unity/Assets/Spine/Runtime/spine-unity/Asset Types/AnimationReferenceAsset.cs b/spine-unity/Assets/Spine/Runtime/spine-unity/Asset Types/AnimationReferenceAsset.cs index f84152e30..49d661c9b 100644 --- a/spine-unity/Assets/Spine/Runtime/spine-unity/Asset Types/AnimationReferenceAsset.cs +++ b/spine-unity/Assets/Spine/Runtime/spine-unity/Asset Types/AnimationReferenceAsset.cs @@ -48,11 +48,16 @@ namespace Spine.Unity { if (animation == null) Initialize(); #endif - return animation; } } + /// Clears the cached animation corresponding to a loaded SkeletonData object. + /// Use this to force a reload for the next time Animation is called. + public void Clear () { + animation = null; + } + public void Initialize () { if (skeletonDataAsset == null) return; SkeletonData skeletonData = skeletonDataAsset.GetSkeletonData(AnimationReferenceAsset.QuietSkeletonData); From b7d6cf819cffc5eb68db75cc774bce5625342176 Mon Sep 17 00:00:00 2001 From: Nathan Sweet Date: Sat, 16 Apr 2022 14:33:50 -0400 Subject: [PATCH 5/7] [gdx] Throw if SkeletonRenderer batch doesn't support clipping. --- .../com/esotericsoftware/spine/SkeletonRenderer.java | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/SkeletonRenderer.java b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/SkeletonRenderer.java index 4f66bef09..bc7ed9984 100644 --- a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/SkeletonRenderer.java +++ b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/SkeletonRenderer.java @@ -91,10 +91,7 @@ public class SkeletonRenderer { Object[] drawOrder = skeleton.drawOrder.items; for (int i = 0, n = skeleton.drawOrder.size; i < n; i++) { Slot slot = (Slot)drawOrder[i]; - if (!slot.bone.active) { - clipper.clipEnd(slot); - continue; - } + if (!slot.bone.active) continue; Attachment attachment = slot.attachment; if (attachment instanceof RegionAttachment) { RegionAttachment region = (RegionAttachment)attachment; @@ -129,8 +126,8 @@ public class SkeletonRenderer { batch.draw(region.getRegion().getTexture(), vertices, 0, 20); } else if (attachment instanceof ClippingAttachment) { - clipper.clipStart(slot, (ClippingAttachment)attachment); - continue; + throw new RuntimeException(batch.getClass().getSimpleName() + + " cannot perform clipping, PolygonSpriteBatch or TwoColorPolygonBatch is required."); } else if (attachment instanceof MeshAttachment) { throw new RuntimeException(batch.getClass().getSimpleName() @@ -140,10 +137,7 @@ public class SkeletonRenderer { Skeleton attachmentSkeleton = ((SkeletonAttachment)attachment).getSkeleton(); if (attachmentSkeleton != null) draw(batch, attachmentSkeleton); } - - clipper.clipEnd(slot); } - clipper.clipEnd(); if (vertexEffect != null) vertexEffect.end(); } From 14af269a25c6c7a47e419f1f7511bf6ee594595c Mon Sep 17 00:00:00 2001 From: Adrian Seeley Date: Tue, 19 Apr 2022 12:09:55 -0400 Subject: [PATCH 6/7] Fixed unpkg link for threejs (#2070) --- spine-ts/README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/spine-ts/README.md b/spine-ts/README.md index c9e2bb5aa..13b72e6c7 100644 --- a/spine-ts/README.md +++ b/spine-ts/README.md @@ -56,8 +56,8 @@ You can include a module in your project via a `