Bläddra i källkod

[ts] Enable TS strict mode. Closes #2058

Mario Zechner 3 år sedan
förälder
incheckning
e4372e5329
57 ändrade filer med 802 tillägg och 658 borttagningar
  1. 2 2
      spine-ts/spine-canvas/src/AssetManager.ts
  2. 1 1
      spine-ts/spine-canvas/src/CanvasTexture.ts
  3. 5 4
      spine-ts/spine-canvas/src/SkeletonRenderer.ts
  4. 29 27
      spine-ts/spine-core/src/Animation.ts
  5. 57 47
      spine-ts/spine-core/src/AnimationState.ts
  6. 1 1
      spine-ts/spine-core/src/AnimationStateData.ts
  7. 16 16
      spine-ts/spine-core/src/AssetManagerBase.ts
  8. 6 5
      spine-ts/spine-core/src/AtlasAttachmentLoader.ts
  9. 4 4
      spine-ts/spine-core/src/Bone.ts
  10. 3 3
      spine-ts/spine-core/src/BoneData.ts
  11. 2 2
      spine-ts/spine-core/src/Event.ts
  12. 3 3
      spine-ts/spine-core/src/EventData.ts
  13. 13 6
      spine-ts/spine-core/src/IkConstraint.ts
  14. 6 1
      spine-ts/spine-core/src/IkConstraintData.ts
  15. 13 8
      spine-ts/spine-core/src/PathConstraint.ts
  16. 9 4
      spine-ts/spine-core/src/PathConstraintData.ts
  17. 25 21
      spine-ts/spine-core/src/Skeleton.ts
  18. 50 24
      spine-ts/spine-core/src/SkeletonBinary.ts
  19. 1 1
      spine-ts/spine-core/src/SkeletonBounds.ts
  20. 5 5
      spine-ts/spine-core/src/SkeletonClipping.ts
  21. 6 6
      spine-ts/spine-core/src/SkeletonData.ts
  22. 75 25
      spine-ts/spine-core/src/SkeletonJson.ts
  23. 4 4
      spine-ts/spine-core/src/Skin.ts
  24. 9 9
      spine-ts/spine-core/src/Slot.ts
  25. 5 5
      spine-ts/spine-core/src/SlotData.ts
  26. 54 50
      spine-ts/spine-core/src/TextureAtlas.ts
  27. 11 6
      spine-ts/spine-core/src/TransformConstraint.ts
  28. 6 1
      spine-ts/spine-core/src/TransformConstraintData.ts
  29. 2 2
      spine-ts/spine-core/src/Utils.ts
  30. 3 4
      spine-ts/spine-core/src/attachments/Attachment.ts
  31. 2 2
      spine-ts/spine-core/src/attachments/AttachmentLoader.ts
  32. 1 1
      spine-ts/spine-core/src/attachments/ClippingAttachment.ts
  33. 2 2
      spine-ts/spine-core/src/attachments/HasTextureRegion.ts
  34. 14 14
      spine-ts/spine-core/src/attachments/MeshAttachment.ts
  35. 1 1
      spine-ts/spine-core/src/attachments/PathAttachment.ts
  36. 7 6
      spine-ts/spine-core/src/attachments/RegionAttachment.ts
  37. 211 193
      spine-ts/spine-player/src/Player.ts
  38. 2 2
      spine-ts/spine-threejs/src/AssetManager.ts
  39. 7 5
      spine-ts/spine-threejs/src/MeshBatcher.ts
  40. 10 15
      spine-ts/spine-threejs/src/SkeletonMesh.ts
  41. 2 1
      spine-ts/spine-threejs/src/ThreeJsTexture.ts
  42. 1 1
      spine-ts/spine-webgl/example/index.html
  43. 1 1
      spine-ts/spine-webgl/src/AssetManager.ts
  44. 1 1
      spine-ts/spine-webgl/src/GLTexture.ts
  45. 4 3
      spine-ts/spine-webgl/src/Input.ts
  46. 5 5
      spine-ts/spine-webgl/src/LoadingScreen.ts
  47. 4 10
      spine-ts/spine-webgl/src/Matrix4.ts
  48. 2 2
      spine-ts/spine-webgl/src/Mesh.ts
  49. 6 5
      spine-ts/spine-webgl/src/PolygonBatcher.ts
  50. 18 18
      spine-ts/spine-webgl/src/SceneRenderer.ts
  51. 10 4
      spine-ts/spine-webgl/src/Shader.ts
  52. 31 30
      spine-ts/spine-webgl/src/ShapeRenderer.ts
  53. 1 1
      spine-ts/spine-webgl/src/SkeletonDebugRenderer.ts
  54. 9 9
      spine-ts/spine-webgl/src/SkeletonRenderer.ts
  55. 8 8
      spine-ts/spine-webgl/src/SpineCanvas.ts
  56. 14 18
      spine-ts/spine-webgl/src/WebGL.ts
  57. 2 3
      spine-ts/tsconfig.base.json

+ 2 - 2
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);
 	}
 }

+ 1 - 1
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);
 	}
 

+ 5 - 4
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<number> = this.vertices;
-		let triangles: Array<number> = null;
+		let triangles: Array<number> | 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 = <MeshAttachment>attachment;
 				vertices = this.computeMeshVertices(slot, mesh, false);
 				triangles = mesh.triangles;
-				texture = (<TextureAtlasRegion>mesh.region.renderObject).page.texture.getImage() as HTMLImageElement;
+				let region = (<TextureAtlasRegion>mesh.region!.renderObject);
+				texture = region.page.texture!.getImage() as HTMLImageElement;
 			} else
 				continue;
 

+ 29 - 27
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<Timeline> = null;
-	timelineIds: StringSet = null;
+	timelines: Array<Timeline> = [];
+	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<Timeline>) {
 		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<Event>, alpha: number, blend: MixBlend, direction: MixDirection): void;
+	abstract apply (skeleton: Skeleton, lastTime: number, time: number, events: Array<Event> | 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<Event>, alpha: number, blend: MixBlend, direction: MixDirection) {
+	apply (skeleton: Skeleton, lastTime: number, time: number, events: Array<Event> | 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<string>;
+	attachmentNames: Array<string | null>;
 
 	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<NumberArrayLike> = null;
+	vertices: Array<NumberArrayLike>;
 
 	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<Event>, 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) || (<VertexAttachment>slotAttachment).timelineAttahment != this.attachment) return;
 
 		let deform: Array<number> = slot.deform;
@@ -1685,7 +1686,7 @@ export class EventTimeline extends Timeline {
 	static propertyIds = ["" + Property.event];
 
 	/** The event for each key frame. */
-	events: Array<Event> = null;
+	events: Array<Event>;
 
 	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<Array<number>> = null;
+	drawOrders: Array<Array<number> | null>;
 
 	constructor (frameCount: number) {
 		super(frameCount, DrawOrderTimeline.propertyIds);
-		this.drawOrders = new Array<Array<number>>(frameCount);
+		this.drawOrders = new Array<Array<number> | 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<number>) {
+	setFrame (frame: number, time: number, drawOrder: Array<number> | 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);

+ 57 - 47
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("<empty>", [], 0);
 	private static emptyAnimation (): Animation {
-		if (!_emptyAnimation) _emptyAnimation = new Animation("<empty>", [], 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<TrackEntry>();
+	tracks = new Array<TrackEntry | null>();
 
 	/** 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<any> = [];
 	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;

+ 1 - 1
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<number> = {};
 

+ 16 - 16
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<any> = {};
@@ -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}`);

+ 6 - 5
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 {

+ 4 - 4
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<Bone>();
@@ -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;

+ 3 - 3
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;

+ 2 - 2
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;

+ 3 - 3
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;
 

+ 13 - 6
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<Bone> = null;
+	bones: Array<Bone>;
 
 	/** 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<Bone>();
-		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;

+ 6 - 1
spine-ts/spine-core/src/IkConstraintData.ts

@@ -39,7 +39,12 @@ export class IkConstraintData extends ConstraintData {
 	bones = new Array<BoneData>();
 
 	/** 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;

+ 13 - 8
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<Bone> = null;
+	bones: Array<Bone>;
 
 	/** 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<Bone>();
-		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<number> = scale ? this.lengths = Utils.setArraySize(this.lengths, boneCount) : null;
+		let spaces = Utils.setArraySize(this.spaces, spacesCount), lengths: Array<number> = 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<number> = null;
+		let spaces = this.spaces, out = Utils.setArraySize(this.positions, spacesCount * 3 + 2), world: Array<number> = this.world;
 		let closed = path.closed;
 		let verticesLength = path.worldVerticesLength, curveCount = verticesLength / 6, prevCurve = PathConstraint.NONE;
 

+ 9 - 4
spine-ts/spine-core/src/PathConstraintData.ts

@@ -41,16 +41,21 @@ export class PathConstraintData extends ConstraintData {
 	bones = new Array<BoneData>();
 
 	/** 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;

+ 25 - 21
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<Bone> = null;
+	bones: Array<Bone>;
 
 	/** The skeleton's slots. */
-	slots: Array<Slot> = null;
+	slots: Array<Slot>;
 
 	/** The skeleton's slots in the order they should be drawn. The returned array may be modified to change the draw order. */
-	drawOrder: Array<Slot> = null;
+	drawOrder: Array<Slot>;
 
 	/** The skeleton's IK constraints. */
-	ikConstraints: Array<IkConstraint> = null;
+	ikConstraints: Array<IkConstraint>;
 
 	/** The skeleton's transform constraints. */
-	transformConstraints: Array<TransformConstraint> = null;
+	transformConstraints: Array<TransformConstraint>;
 
 	/** The skeleton's path constraints. */
-	pathConstraints: Array<PathConstraint> = null;
+	pathConstraints: Array<PathConstraint>;
 
 	/** The list of bones and constraints, sorted in the order they should be updated, as computed by {@link #updateCache()}. */
 	_updateCache = new Array<Updatable>();
 
 	/** 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;

+ 50 - 24
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<LinkedMesh>();
 
 	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<number> = null, public vertices: Array<number> | Float32Array = null) { }
+	constructor (public bones: Array<number> | null = null, public vertices: Array<number> | Float32Array | null = null) { }
 }
 
 enum AttachmentType { Region, BoundingBox, Mesh, LinkedMesh, Path, Point, Clipping }

+ 1 - 1
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];

+ 5 - 5
spine-ts/spine-core/src/SkeletonClipping.ts

@@ -40,8 +40,8 @@ export class SkeletonClipping {
 	clippedTriangles = new Array<number>();
 	private scratch = new Array<number>();
 
-	private clipAttachment: ClippingAttachment;
-	private clippingPolygons: Array<Array<number>>;
+	private clipAttachment: ClippingAttachment | null = null;
+	private clippingPolygons: Array<Array<number>> | 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<number> = null;
+		let input: Array<number>;
 		if (clippingArea.length % 4 >= 2) {
 			input = output;
 			output = this.scratch;

+ 6 - 6
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<BoneData>(); // 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<EventData>();
@@ -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.

+ 75 - 25
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 ? <VertexAttachment>parent : <VertexAttachment>linkedMesh.mesh;
 			linkedMesh.mesh.setParentMesh(<MeshAttachment>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 = <VertexAttachment>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<number> = null;
+				let drawOrder: Array<number> | null = null;
 				let offsets = getValue(drawOrderMap, "offsets", null);
 				if (offsets) {
 					drawOrder = Utils.newArray<number>(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);

+ 4 - 4
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<StringMap<Attachment>>();
 	bones = Array<BoneData>();
@@ -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. */

+ 9 - 9
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)
 			|| (<VertexAttachment>attachment).timelineAttahment != (<VertexAttachment>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 {

+ 5 - 5
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.");

+ 54 - 50
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<string>(4);
-		let page: TextureAtlasPage = null;
-		let region: TextureAtlasRegion = null;
 
-		let pageFields: StringMap<Function> = {};
-		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<Function> = {};
-		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<string> = null;
+	lines: Array<string>;
 	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;
+	}
 }

+ 11 - 6
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<Bone> = null;
+	bones: Array<Bone>;
 
 	/** 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<Bone>();
-		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 () {

+ 6 - 1
spine-ts/spine-core/src/TransformConstraintData.ts

@@ -39,7 +39,12 @@ export class TransformConstraintData extends ConstraintData {
 	bones = new Array<BoneData>();
 
 	/** 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;

+ 2 - 2
spine-ts/spine-core/src/Utils.ts

@@ -35,7 +35,7 @@ export interface StringMap<T> {
 }
 
 export class IntSet {
-	array = new Array<number>();
+	array = new Array<number | undefined>();
 
 	add (value: number): boolean {
 		let contains = this.contains(value);
@@ -354,7 +354,7 @@ export class Pool<T> {
 	}
 
 	obtain () {
-		return this.items.length > 0 ? this.items.pop() : this.instantiator();
+		return this.items.length > 0 ? this.items.pop()! : this.instantiator();
 	}
 
 	free (item: T) {

+ 3 - 4
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<number> = null;
+	bones: Array<number> | 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;

+ 2 - 2
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;

+ 1 - 1
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

+ 2 - 2
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;
 }

+ 14 - 14
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<number> = null;
+	triangles: Array<number> = [];
 
 	/** 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<number> = null;
+	edges: Array<number> = [];
 
-	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);

+ 1 - 1
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<number> = null;
+	lengths: Array<number> = [];
 
 	/** If true, the start and end knots are connected. */
 	closed = false;

+ 7 - 6
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 <code>x,y</code> 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;

+ 211 - 193
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<string>
+	rawDataURIs?: StringMap<string>
 
 	/* 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<Viewport>
+		animations?: StringMap<Viewport>
 	}
 
 	/* 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<Bone>(controlBones.length);
-		let canvas = this.canvas;
-		let target: Bone = null;
+		let selectedBones = this.selectedBones = new Array<Bone | null>(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*/`
 <div class="spine-player-popup-title">Speed</div>
 <hr>
 <div class="spine-player-row" style="align-items:center;padding:8px">
@@ -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*/`<div class="spine-player-popup-title">Animations</div><hr><ul class="spine-player-list"></ul>`);
 
 		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*/`<div class="spine-player-popup-title">Skins</div><hr><ul class="spine-player-list"></ul>`);
 
 		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*/`<div class="spine-player-popup-title">Debug</div><hr><ul class="spine-player-list"></li>`);
+		let popup = new Popup(id, settingsButton, this, this.playerControls!, /*html*/`<div class="spine-player-popup-title">Debug</div><hr><ul class="spine-player-list"></li>`);
 
 		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;
 	}

+ 2 - 2
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);
 	}

+ 7 - 5
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 = (<THREE.BufferGeometry>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;
 				}

+ 10 - 15
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<MeshBatcher>();
 	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<number> = null;
-		let uvs: NumberArrayLike = null;
+		let triangles: Array<number> | 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 = <RegionAttachment>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 = <ThreeJsTexture>(<TextureAtlasRegion>region.region.renderObject).page.texture;
+				texture = <ThreeJsTexture>(<TextureAtlasRegion>region.region!.renderObject).page.texture;
 			} else if (attachment instanceof MeshAttachment) {
 				let mesh = <MeshAttachment>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 = <ThreeJsTexture>(<TextureAtlasRegion>mesh.region.renderObject).page.texture;
+				texture = <ThreeJsTexture>(<TextureAtlasRegion>mesh.region!.renderObject).page.texture;
 			} else if (attachment instanceof ClippingAttachment) {
 				let clip = <ClippingAttachment>(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) {

+ 2 - 1
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;

+ 1 - 1
spine-ts/spine-webgl/example/index.html

@@ -234,7 +234,7 @@
 		function setupUI() {
 			let formatList = $("#formatList");
 			formatList.append($("<option>Binary</option>"));
-			formatList.append($("<option>JSON</option>"));
+			formatList.append($("<option selected>JSON</option>"));
 			let skeletonList = $("#skeletonList");
 			for (let skeletonName in skeletons) {
 				let option = $("<option></option>");

+ 1 - 1
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);

+ 1 - 1
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;
 

+ 4 - 3
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<InputListener>();
 	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) });
 						}
 					}
 

+ 5 - 5
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();
 	}
 }

+ 4 - 10
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();
-	}
 }

+ 2 - 2
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;

+ 6 - 5
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);
 

+ 18 - 18
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<string> = null) {
+	drawSkeletonDebug (skeleton: Skeleton, premultipliedAlpha = false, ignoredBones?: Array<string>) {
 		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(<GLTexture>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<number>, offset: number, count: number, color: Color = null) {
+	polygon (polygonVertices: ArrayLike<number>, 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);
 	}

+ 10 - 4
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;

+ 31 - 30
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<number>, offset: number, count: number, color: Color = null) {
+	polygon (polygonVertices: ArrayLike<number>, 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;

+ 1 - 1
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<string> = null) {
+	draw (shapes: ShapeRenderer, skeleton: Skeleton, ignoredBones?: Array<string>) {
 		let skeletonX = skeleton.x;
 		let skeletonY = skeleton.y;
 		let gl = this.context.gl;

+ 9 - 9
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<number> = null;
+		let uvs: NumberArrayLike;
+		let triangles: Array<number>;
 		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 = <RegionAttachment>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 = <GLTexture>(<TextureAtlasRegion>region.region.renderObject).page.texture;
+				texture = <GLTexture>(<TextureAtlasRegion>region.region!.renderObject).page.texture;
 				attachmentColor = region.color;
 			} else if (attachment instanceof MeshAttachment) {
 				let mesh = <MeshAttachment>attachment;
@@ -124,7 +124,7 @@ export class SkeletonRenderer {
 				}
 				mesh.computeWorldVertices(slot, 0, mesh.worldVerticesLength, renderable.vertices, 0, clippedVertexSize);
 				triangles = mesh.triangles;
-				texture = <GLTexture>(<TextureAtlasRegion>mesh.region.renderObject).page.texture;
+				texture = <GLTexture>(<TextureAtlasRegion>mesh.region!.renderObject).page.texture;
 				uvs = mesh.uvs;
 				attachmentColor = mesh.color;
 			} else if (attachment instanceof ClippingAttachment) {

+ 8 - 8
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;

+ 14 - 18
spine-ts/spine-webgl/src/WebGL.ts

@@ -34,29 +34,25 @@ export class ManagedWebGLRenderingContext {
 	public gl: WebGLRenderingContext;
 	private restorables = new Array<Restorable>();
 
-	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 = <WebGLRenderingContext>(canvas.getContext("webgl2", contextConfig) || canvas.getContext("webgl", contextConfig));
+			this.canvas = canvas;
+			canvas.addEventListener("webglcontextlost", (e: any) => {
+				let event = <WebGLContextEvent>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 = <WebGLRenderingContext>(canvas.getContext("webgl2", contextConfig) || canvas.getContext("webgl", contextConfig));
-		this.canvas = canvas;
-		canvas.addEventListener("webglcontextlost", (e: any) => {
-			let event = <WebGLContextEvent>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);
 	}

+ 2 - 3
spine-ts/tsconfig.base.json

@@ -12,8 +12,7 @@
 		],
 		"declaration": true,
 		"composite": true,
-		"moduleResolution": "node"
-		/*"strictNullChecks": true,
-		"strictPropertyInitialization": true*/
+		"moduleResolution": "node",
+		"strict": true
 	}
 }