diff --git a/CHANGELOG.md b/CHANGELOG.md index 8dda6a05d..e1f047ad6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -30,6 +30,7 @@ This project adheres to [Semantic Versioning](http://semver.org/). ### Fixed +- Fixed invalid graphics types around `ex.Graphic.tint` - improve types to disallow invalid combo of collider/width/height/radius in actor args - only add default color graphic for the respective collider used - Fixed issue where `ex.SpriteFont` did not respect scale when measuring text @@ -63,6 +64,7 @@ This project adheres to [Semantic Versioning](http://semver.org/). ### Changed +- Applied increased TS strictness for the Graphics API subtree - Applied increased TS strictness for the TileMap API subtree diff --git a/src/engine/Graphics/Animation.ts b/src/engine/Graphics/Animation.ts index b85b2f028..c77b720f9 100644 --- a/src/engine/Graphics/Animation.ts +++ b/src/engine/Graphics/Animation.ts @@ -186,7 +186,7 @@ export class Animation extends Graphic implements HasTick { public override get width(): number { const maybeFrame = this.currentFrame; - if (maybeFrame) { + if (maybeFrame && maybeFrame.graphic) { return Math.abs(maybeFrame.graphic.width * this.scale.x); } return 0; @@ -194,7 +194,7 @@ export class Animation extends Graphic implements HasTick { public override get height(): number { const maybeFrame = this.currentFrame; - if (maybeFrame) { + if (maybeFrame && maybeFrame.graphic) { return Math.abs(maybeFrame.graphic.height * this.scale.y); } return 0; @@ -512,7 +512,7 @@ export class Animation extends Graphic implements HasTick { } protected _drawImage(ctx: ExcaliburGraphicsContext, x: number, y: number) { - if (this.currentFrame) { + if (this.currentFrame && this.currentFrame.graphic) { this.currentFrame.graphic.draw(ctx, x, y); } } diff --git a/src/engine/Graphics/Context/ExcaliburGraphicsContext.ts b/src/engine/Graphics/Context/ExcaliburGraphicsContext.ts index d8a6083ce..2d0cf99fd 100644 --- a/src/engine/Graphics/Context/ExcaliburGraphicsContext.ts +++ b/src/engine/Graphics/Context/ExcaliburGraphicsContext.ts @@ -130,8 +130,8 @@ export interface ExcaliburGraphicsContextOptions { export interface ExcaliburGraphicsContextState { opacity: number; z: number; - tint: Color; - material: Material; + tint: Color | null | undefined; + material: Material | null | undefined; } export interface LineGraphicsOptions { color?: Color; @@ -223,7 +223,7 @@ export interface ExcaliburGraphicsContext { /** * Sets the tint color to be multiplied by any images drawn, default is black 0xFFFFFFFF */ - tint: Color; + tint: Color | null | undefined; /** * Resets the current transform to the identity matrix @@ -368,7 +368,7 @@ export interface ExcaliburGraphicsContext { * This allows customs shaders to be used but draw calls are no longer batched by default. * @param material */ - material: Material; + material: Material | null | undefined; /** * Creates and initializes the material which compiles the internal shader diff --git a/src/engine/Graphics/Context/ExcaliburGraphicsContext2DCanvas.ts b/src/engine/Graphics/Context/ExcaliburGraphicsContext2DCanvas.ts index 6771d8ad4..ebdf81489 100644 --- a/src/engine/Graphics/Context/ExcaliburGraphicsContext2DCanvas.ts +++ b/src/engine/Graphics/Context/ExcaliburGraphicsContext2DCanvas.ts @@ -43,7 +43,7 @@ class ExcaliburGraphicsContext2DCanvasDebug implements DebugDraw { drawLine(start: Vector, end: Vector, lineOptions: LineGraphicsOptions = { color: Color.Black }): void { this._ex.__ctx.save(); this._ex.__ctx.beginPath(); - this._ex.__ctx.strokeStyle = lineOptions.color.toString(); + this._ex.__ctx.strokeStyle = lineOptions.color?.toString() ?? ''; this._ex.__ctx.moveTo( this._ex.snapToPixel ? ~~(start.x + pixelSnapEpsilon) : start.x, this._ex.snapToPixel ? ~~(start.y + pixelSnapEpsilon) : start.y @@ -88,7 +88,7 @@ export class ExcaliburGraphicsContext2DCanvas implements ExcaliburGraphicsContex * Meant for internal use only. Access the internal context at your own risk and no guarantees this will exist in the future. * @internal */ - public __ctx: CanvasRenderingContext2D; + public __ctx!: CanvasRenderingContext2D; public get width() { return this.__ctx.canvas.width; } @@ -119,11 +119,11 @@ export class ExcaliburGraphicsContext2DCanvas implements ExcaliburGraphicsContex this._state.current.opacity = value; } - public get tint(): Color { + public get tint(): Color | null | undefined { return this._state.current.tint; } - public set tint(color: Color) { + public set tint(color: Color | null | undefined) { this._state.current.tint = color; } @@ -141,9 +141,9 @@ export class ExcaliburGraphicsContext2DCanvas implements ExcaliburGraphicsContex const { canvasElement, context, enableTransparency, snapToPixel, antialiasing: smoothing, backgroundColor } = options; this.__ctx = context ?? - canvasElement.getContext('2d', { + (canvasElement.getContext('2d', { alpha: enableTransparency ?? true - }); + }) as CanvasRenderingContext2D); if (!this.__ctx) { throw new Error('Cannot build new ExcaliburGraphicsContext2D for some reason!'); } @@ -209,7 +209,7 @@ export class ExcaliburGraphicsContext2DCanvas implements ExcaliburGraphicsContex const args = [image, sx, sy, swidth, sheight, dx, dy, dwidth, dheight] .filter((a) => a !== undefined) .map((a) => (typeof a === 'number' && this.snapToPixel ? ~~a : a)); - this.__ctx.drawImage.apply(this.__ctx, args); + this.__ctx.drawImage.apply(this.__ctx, args as any); GraphicsDiagnostics.DrawCallCount++; GraphicsDiagnostics.DrawnImagesCount = 1; } @@ -341,17 +341,17 @@ export class ExcaliburGraphicsContext2DCanvas implements ExcaliburGraphicsContex // pass } - public set material(material: Material | null) { + public set material(material: Material | undefined | null) { this._state.current.material = material; } - public get material(): Material | null { + public get material(): Material | undefined | null { return this._state.current.material; } public createMaterial(options: Omit): Material { // pass - return null; + return null as any; } clear(): void { @@ -370,6 +370,6 @@ export class ExcaliburGraphicsContext2DCanvas implements ExcaliburGraphicsContex } dispose(): void { - this.__ctx = null; + this.__ctx = undefined as any; } } diff --git a/src/engine/Graphics/Context/ExcaliburGraphicsContextWebGL.ts b/src/engine/Graphics/Context/ExcaliburGraphicsContextWebGL.ts index 8ba263da4..63ed7f089 100644 --- a/src/engine/Graphics/Context/ExcaliburGraphicsContextWebGL.ts +++ b/src/engine/Graphics/Context/ExcaliburGraphicsContextWebGL.ts @@ -61,8 +61,8 @@ class ExcaliburGraphicsContextWebGLDebug implements DebugDraw { * @param end * @param lineOptions */ - drawLine(start: Vector, end: Vector, lineOptions: LineGraphicsOptions = { color: Color.Black }): void { - this._webglCtx.draw('ex.line', start, end, lineOptions.color); + drawLine(start: Vector, end: Vector, lineOptions?: LineGraphicsOptions): void { + this._webglCtx.draw('ex.line', start, end, lineOptions?.color ?? Color.Black); } /** @@ -103,8 +103,8 @@ export class ExcaliburGraphicsContextWebGL implements ExcaliburGraphicsContext { (instance) => { instance.priority = 0; instance.z = 0; - instance.renderer = undefined; - instance.args = undefined; + instance.renderer = undefined as any; + instance.args = undefined as any; return instance; }, 4000 @@ -114,15 +114,15 @@ export class ExcaliburGraphicsContextWebGL implements ExcaliburGraphicsContext { private _drawCalls: DrawCall[] = new Array(4000).fill(null); // Main render target - private _renderTarget: RenderTarget; + private _renderTarget!: RenderTarget; // Quad boundary MSAA - private _msaaTarget: RenderTarget; + private _msaaTarget!: RenderTarget; // Postprocessing is a tuple with 2 render targets, these are flip-flopped during the postprocessing process private _postProcessTargets: RenderTarget[] = []; - private _screenRenderer: ScreenPassPainter; + private _screenRenderer!: ScreenPassPainter; private _postprocessors: PostProcessor[] = []; /** @@ -160,7 +160,7 @@ export class ExcaliburGraphicsContextWebGL implements ExcaliburGraphicsContext { public textureLoader: TextureLoader; - public materialScreenTexture: WebGLTexture; + public materialScreenTexture!: WebGLTexture | null; public get z(): number { return this._state.current.z; @@ -178,11 +178,11 @@ export class ExcaliburGraphicsContextWebGL implements ExcaliburGraphicsContext { this._state.current.opacity = value; } - public get tint(): Color { + public get tint(): Color | undefined | null { return this._state.current.tint; } - public set tint(color: Color) { + public set tint(color: Color | undefined | null) { this._state.current.tint = color; } @@ -234,13 +234,13 @@ export class ExcaliburGraphicsContextWebGL implements ExcaliburGraphicsContext { } = options; this.__gl = context ?? - canvasElement.getContext('webgl2', { + (canvasElement.getContext('webgl2', { antialias: antialiasing ?? this.smoothing, premultipliedAlpha: false, alpha: enableTransparency ?? this.transparency, depth: false, powerPreference: powerPreference ?? 'high-performance' - }); + }) as WebGL2RenderingContext); if (!this.__gl) { throw Error('Failed to retrieve webgl context from browser'); } @@ -287,7 +287,7 @@ export class ExcaliburGraphicsContextWebGL implements ExcaliburGraphicsContext { this._renderers.clear(); this._drawCallPool.dispose(); this._drawCalls.length = 0; - this.__gl = null; + this.__gl = null as any; } } @@ -323,6 +323,9 @@ export class ExcaliburGraphicsContextWebGL implements ExcaliburGraphicsContext { this.register(new LineRenderer()); this.materialScreenTexture = gl.createTexture(); + if (!this.materialScreenTexture) { + throw new Error(''); + } gl.bindTexture(gl.TEXTURE_2D, this.materialScreenTexture); gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, this.width, this.height, 0, gl.RGBA, gl.UNSIGNED_BYTE, null); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST); @@ -370,11 +373,11 @@ export class ExcaliburGraphicsContextWebGL implements ExcaliburGraphicsContext { renderer.initialize(this.__gl, this); } - public get(rendererName: string): RendererPlugin { + public get(rendererName: string): RendererPlugin | undefined { return this._renderers.get(rendererName); } - private _currentRenderer: RendererPlugin; + private _currentRenderer: RendererPlugin | undefined; private _isCurrentRenderer(renderer: RendererPlugin): boolean { if (!this._currentRenderer || this._currentRenderer === renderer) { @@ -609,11 +612,11 @@ export class ExcaliburGraphicsContextWebGL implements ExcaliburGraphicsContext { } } - public set material(material: Material) { + public set material(material: Material | null | undefined) { this._state.current.material = material; } - public get material(): Material | null { + public get material(): Material | null | undefined { return this._state.current.material; } @@ -665,7 +668,7 @@ export class ExcaliburGraphicsContextWebGL implements ExcaliburGraphicsContext { if (this.useDrawSorting) { // null out unused draw calls for (let i = this._drawCallIndex; i < this._drawCalls.length; i++) { - this._drawCalls[i] = null; + this._drawCalls[i] = null as any; } // sort draw calls // Find the original order of the first instance of the draw call @@ -685,7 +688,7 @@ export class ExcaliburGraphicsContextWebGL implements ExcaliburGraphicsContext { return 0; } const zIndex = a.z - b.z; - const originalSortOrder = originalSort.get(a.renderer) - originalSort.get(b.renderer); + const originalSortOrder = originalSort.get(a.renderer)! - originalSort.get(b.renderer)!; const priority = a.priority - b.priority; if (zIndex === 0) { // sort by z first @@ -711,21 +714,21 @@ export class ExcaliburGraphicsContextWebGL implements ExcaliburGraphicsContext { if (this._drawCalls[i].renderer !== currentRendererName) { // switching graphics renderer means we must flush the previous - currentRenderer.flush(); + currentRenderer!.flush(); currentRendererName = this._drawCalls[i].renderer; currentRenderer = this._renderers.get(currentRendererName); } // ! hack to grab screen texture before materials run because they might want it - if (currentRenderer instanceof MaterialRenderer && this.material.isUsingScreenTexture) { - currentTarget.copyToTexture(this.materialScreenTexture); + if (currentRenderer instanceof MaterialRenderer && this.material?.isUsingScreenTexture) { + currentTarget.copyToTexture(this.materialScreenTexture!); currentTarget.use(); } // If we are still using the same renderer we can add to the current batch - currentRenderer.draw(...this._drawCalls[i].args); + currentRenderer!.draw(...this._drawCalls[i].args); } - if (currentRenderer.hasPendingDraws()) { - currentRenderer.flush(); + if (currentRenderer!.hasPendingDraws()) { + currentRenderer!.flush(); } } diff --git a/src/engine/Graphics/Context/circle-renderer/circle-renderer.ts b/src/engine/Graphics/Context/circle-renderer/circle-renderer.ts index ea287b702..09e802a66 100644 --- a/src/engine/Graphics/Context/circle-renderer/circle-renderer.ts +++ b/src/engine/Graphics/Context/circle-renderer/circle-renderer.ts @@ -17,12 +17,12 @@ export class CircleRenderer implements RendererPlugin { private _maxCircles: number = 10922; // max(uint16) / 6 verts - private _shader: Shader; - private _context: ExcaliburGraphicsContextWebGL; - private _gl: WebGLRenderingContext; - private _buffer: VertexBuffer; - private _layout: VertexLayout; - private _quads: QuadIndexBuffer; + private _shader!: Shader; + private _context!: ExcaliburGraphicsContextWebGL; + private _gl!: WebGLRenderingContext; + private _buffer!: VertexBuffer; + private _layout!: VertexLayout; + private _quads!: QuadIndexBuffer; private _circleCount: number = 0; private _vertexIndex: number = 0; @@ -68,8 +68,8 @@ export class CircleRenderer implements RendererPlugin { this._buffer.dispose(); this._quads.dispose(); this._shader.dispose(); - this._context = null; - this._gl = null; + this._context = null as any; + this._gl = null as any; } private _isFull() { diff --git a/src/engine/Graphics/Context/debug-text.ts b/src/engine/Graphics/Context/debug-text.ts index a11fa6ac6..05f066317 100644 --- a/src/engine/Graphics/Context/debug-text.ts +++ b/src/engine/Graphics/Context/debug-text.ts @@ -6,7 +6,7 @@ import { Vector } from '../../Math/vector'; import debugFont from './debug-font.png'; /** - * Internal debugtext helper + * Internal debug text helper */ export class DebugText { constructor() { @@ -20,9 +20,9 @@ export class DebugText { */ public readonly fontSheet = debugFont; public size: number = 16; - private _imageSource: ImageSource; - private _spriteSheet: SpriteSheet; - private _spriteFont: SpriteFont; + private _imageSource!: ImageSource; + private _spriteSheet!: SpriteSheet; + private _spriteFont!: SpriteFont; public load() { this._imageSource = new ImageSource(this.fontSheet); return this._imageSource.load().then(() => { diff --git a/src/engine/Graphics/Context/draw-call.ts b/src/engine/Graphics/Context/draw-call.ts index 4a95e0d24..cd6f6eddb 100644 --- a/src/engine/Graphics/Context/draw-call.ts +++ b/src/engine/Graphics/Context/draw-call.ts @@ -5,7 +5,7 @@ import { ExcaliburGraphicsContextState } from './ExcaliburGraphicsContext'; export class DrawCall { public z: number = 0; public priority: number = 0; - public renderer: string; + public renderer: string = ''; public transform: AffineMatrix = AffineMatrix.identity(); public state: ExcaliburGraphicsContextState = { z: 0, @@ -13,5 +13,5 @@ export class DrawCall { tint: Color.White, material: null }; - public args: any[]; + public args!: any[]; } diff --git a/src/engine/Graphics/Context/image-renderer/image-renderer.ts b/src/engine/Graphics/Context/image-renderer/image-renderer.ts index 595837185..6021de77c 100644 --- a/src/engine/Graphics/Context/image-renderer/image-renderer.ts +++ b/src/engine/Graphics/Context/image-renderer/image-renderer.ts @@ -1,3 +1,4 @@ +import { Color } from '../../../Color'; import { sign } from '../../../Math/util'; import { parseImageFiltering } from '../../Filtering'; import { GraphicsDiagnostics } from '../../GraphicsDiagnostics'; @@ -28,12 +29,12 @@ export class ImageRenderer implements RendererPlugin { private _maxImages: number = 10922; // max(uint16) / 6 verts private _maxTextures: number = 0; - private _context: ExcaliburGraphicsContextWebGL; - private _gl: WebGLRenderingContext; - private _shader: Shader; - private _buffer: VertexBuffer; - private _layout: VertexLayout; - private _quads: QuadIndexBuffer; + private _context!: ExcaliburGraphicsContextWebGL; + private _gl!: WebGLRenderingContext; + private _shader!: Shader; + private _buffer!: VertexBuffer; + private _layout!: VertexLayout; + private _quads!: QuadIndexBuffer; // Per flush vars private _imageCount: number = 0; @@ -101,8 +102,8 @@ export class ImageRenderer implements RendererPlugin { this._quads.dispose(); this._shader.dispose(); this._textures.length = 0; - this._context = null; - this._gl = null; + this._context = null as any; + this._gl = null as any; } private _transformFragmentSource(source: string, maxTextures: number): string { @@ -127,9 +128,9 @@ export class ImageRenderer implements RendererPlugin { return; } const maybeFiltering = image.getAttribute(ImageSourceAttributeConstants.Filtering); - const filtering = maybeFiltering ? parseImageFiltering(maybeFiltering) : null; - const wrapX = parseImageWrapping(image.getAttribute(ImageSourceAttributeConstants.WrappingX)); - const wrapY = parseImageWrapping(image.getAttribute(ImageSourceAttributeConstants.WrappingY)); + const filtering = maybeFiltering ? parseImageFiltering(maybeFiltering) : undefined; + const wrapX = parseImageWrapping(image.getAttribute(ImageSourceAttributeConstants.WrappingX) as any); + const wrapY = parseImageWrapping(image.getAttribute(ImageSourceAttributeConstants.WrappingY) as any); const force = image.getAttribute('forceUpload') === 'true' ? true : false; const texture = this._context.textureLoader.load( @@ -139,7 +140,7 @@ export class ImageRenderer implements RendererPlugin { wrapping: { x: wrapX, y: wrapY } }, force - ); + )!; // remove force attribute after upload image.removeAttribute('forceUpload'); if (this._textures.indexOf(texture) === -1) { @@ -198,6 +199,7 @@ export class ImageRenderer implements RendererPlugin { private _view = [0, 0, 0, 0]; private _dest = [0, 0]; private _quad = [0, 0, 0, 0, 0, 0, 0, 0]; + private _defaultTint = Color.White; draw( image: HTMLImageSource, sx: number, @@ -282,7 +284,7 @@ export class ImageRenderer implements RendererPlugin { this._quad[7] = ~~(this._quad[7] + sign(this._quad[7]) * pixelSnapEpsilon); } - const tint = this._context.tint; + const tint = this._context.tint || this._defaultTint; const textureId = this._getTextureIdForImage(image); const imageWidth = maybeImageWidth || width; diff --git a/src/engine/Graphics/Context/line-renderer/line-renderer.ts b/src/engine/Graphics/Context/line-renderer/line-renderer.ts index bc1b9272d..5190d43b6 100644 --- a/src/engine/Graphics/Context/line-renderer/line-renderer.ts +++ b/src/engine/Graphics/Context/line-renderer/line-renderer.ts @@ -15,12 +15,12 @@ export interface LineOptions { export class LineRenderer implements RendererPlugin { public readonly type = 'ex.line'; public priority: number = 0; - private _context: ExcaliburGraphicsContextWebGL; - private _gl: WebGL2RenderingContext; - private _shader: Shader; + private _context!: ExcaliburGraphicsContextWebGL; + private _gl!: WebGL2RenderingContext; + private _shader!: Shader; private _maxLines: number = 10922; - private _vertexBuffer: VertexBuffer; - private _layout: VertexLayout; + private _vertexBuffer!: VertexBuffer; + private _layout!: VertexLayout; private _vertexIndex = 0; private _lineCount = 0; initialize(gl: WebGL2RenderingContext, context: ExcaliburGraphicsContextWebGL): void { @@ -56,8 +56,8 @@ export class LineRenderer implements RendererPlugin { public dispose() { this._vertexBuffer.dispose(); this._shader.dispose(); - this._context = null; - this._gl = null; + this._context = null as any; + this._gl = null as any; } draw(start: Vector, end: Vector, color: Color): void { diff --git a/src/engine/Graphics/Context/material-renderer/material-renderer.ts b/src/engine/Graphics/Context/material-renderer/material-renderer.ts index 45dcfaaaa..494d1a932 100644 --- a/src/engine/Graphics/Context/material-renderer/material-renderer.ts +++ b/src/engine/Graphics/Context/material-renderer/material-renderer.ts @@ -14,12 +14,12 @@ export class MaterialRenderer implements RendererPlugin { public readonly type: string = 'ex.material'; public priority: number = 0; // private _maxTextures = 8; - private _context: ExcaliburGraphicsContextWebGL; - private _gl: WebGL2RenderingContext; + private _context!: ExcaliburGraphicsContextWebGL; + private _gl!: WebGL2RenderingContext; private _textures: WebGLTexture[] = []; private _quads: any; - private _buffer: VertexBuffer; - private _layout: VertexLayout; + private _buffer!: VertexBuffer; + private _layout!: VertexLayout; initialize(gl: WebGL2RenderingContext, context: ExcaliburGraphicsContextWebGL): void { this._gl = gl; this._context = context; @@ -51,8 +51,8 @@ export class MaterialRenderer implements RendererPlugin { this._buffer.dispose(); this._quads.dispose(); this._textures.length = 0; - this._context = null; - this._gl = null; + this._context = null as any; + this._gl = null as any; } draw( @@ -70,12 +70,15 @@ export class MaterialRenderer implements RendererPlugin { // Extract context info const material = this._context.material; + if (!material) { + return; + } const transform = this._context.getTransform(); const opacity = this._context.opacity; // material shader - const shader = material.getShader(); + const shader = material.getShader()!; // construct geometry, or hold on to it in the material? // geometry primitive for drawing rectangles? @@ -158,7 +161,7 @@ export class MaterialRenderer implements RendererPlugin { // apply material material.use(); - this._layout.shader = shader; + this._layout.shader = shader!; // apply layout and geometry this._layout.use(true); @@ -208,9 +211,9 @@ export class MaterialRenderer implements RendererPlugin { private _addImageAsTexture(image: HTMLImageSource) { const maybeFiltering = image.getAttribute(ImageSourceAttributeConstants.Filtering); - const filtering = maybeFiltering ? parseImageFiltering(maybeFiltering) : null; - const wrapX = parseImageWrapping(image.getAttribute(ImageSourceAttributeConstants.WrappingX)); - const wrapY = parseImageWrapping(image.getAttribute(ImageSourceAttributeConstants.WrappingY)); + const filtering = maybeFiltering ? parseImageFiltering(maybeFiltering) : undefined; + const wrapX = parseImageWrapping(image.getAttribute(ImageSourceAttributeConstants.WrappingX) as any); + const wrapY = parseImageWrapping(image.getAttribute(ImageSourceAttributeConstants.WrappingY) as any); const force = image.getAttribute('forceUpload') === 'true' ? true : false; const texture = this._context.textureLoader.load( @@ -220,7 +223,7 @@ export class MaterialRenderer implements RendererPlugin { wrapping: { x: wrapX, y: wrapY } }, force - ); + )!; // remove force attribute after upload image.removeAttribute('forceUpload'); if (this._textures.indexOf(texture) === -1) { diff --git a/src/engine/Graphics/Context/material.ts b/src/engine/Graphics/Context/material.ts index 23f5ce90a..ea2547060 100644 --- a/src/engine/Graphics/Context/material.ts +++ b/src/engine/Graphics/Context/material.ts @@ -106,7 +106,7 @@ export interface MaterialImageOptions { export class Material { private _logger = Logger.getInstance(); private _name: string; - private _shader: Shader; + private _shader!: Shader; private _color: Color = Color.Transparent; private _initialized = false; private _fragmentSource: string; @@ -114,8 +114,8 @@ export class Material { private _images = new Map(); private _textures = new Map(); - private _maxTextureSlots: number; - private _graphicsContext: ExcaliburGraphicsContextWebGL; + private _maxTextureSlots!: number; + private _graphicsContext!: ExcaliburGraphicsContextWebGL; constructor(options: MaterialOptions) { const { color, name, vertexSource, fragmentSource, graphicsContext, images } = options; @@ -187,16 +187,16 @@ export class Material { removeImageSource(textureName: string) { const image = this._images.get(textureName); - this._graphicsContext.textureLoader.delete(image.image); + this._graphicsContext.textureLoader.delete(image!.image); this._images.delete(textureName); } private _loadImageSource(image: ImageSource) { const imageElement = image.image; const maybeFiltering = imageElement.getAttribute(ImageSourceAttributeConstants.Filtering); - const filtering = maybeFiltering ? parseImageFiltering(maybeFiltering) : null; - const wrapX = parseImageWrapping(imageElement.getAttribute(ImageSourceAttributeConstants.WrappingX)); - const wrapY = parseImageWrapping(imageElement.getAttribute(ImageSourceAttributeConstants.WrappingY)); + const filtering = maybeFiltering ? parseImageFiltering(maybeFiltering) : undefined; + const wrapX = parseImageWrapping(imageElement.getAttribute(ImageSourceAttributeConstants.WrappingX) as any); + const wrapY = parseImageWrapping(imageElement.getAttribute(ImageSourceAttributeConstants.WrappingY) as any); const force = imageElement.getAttribute('forceUpload') === 'true' ? true : false; const texture = this._graphicsContext.textureLoader.load( @@ -210,7 +210,7 @@ export class Material { // remove force attribute after upload imageElement.removeAttribute('forceUpload'); if (!this._textures.has(image)) { - this._textures.set(image, texture); + this._textures.set(image, texture!); } return texture; diff --git a/src/engine/Graphics/Context/point-renderer/point-renderer.ts b/src/engine/Graphics/Context/point-renderer/point-renderer.ts index 23aadfef4..4058c3b1c 100644 --- a/src/engine/Graphics/Context/point-renderer/point-renderer.ts +++ b/src/engine/Graphics/Context/point-renderer/point-renderer.ts @@ -12,12 +12,12 @@ import { GraphicsDiagnostics } from '../../GraphicsDiagnostics'; export class PointRenderer implements RendererPlugin { public readonly type = 'ex.point'; public priority: number = 0; - private _shader: Shader; + private _shader!: Shader; private _maxPoints: number = 10922; - private _buffer: VertexBuffer; - private _layout: VertexLayout; - private _gl: WebGLRenderingContext; - private _context: ExcaliburGraphicsContextWebGL; + private _buffer!: VertexBuffer; + private _layout!: VertexLayout; + private _gl!: WebGLRenderingContext; + private _context!: ExcaliburGraphicsContextWebGL; private _pointCount: number = 0; private _vertexIndex: number = 0; initialize(gl: WebGL2RenderingContext, context: ExcaliburGraphicsContextWebGL): void { @@ -52,8 +52,8 @@ export class PointRenderer implements RendererPlugin { public dispose() { this._buffer.dispose(); this._shader.dispose(); - this._context = null; - this._gl = null; + this._context = null as any; + this._gl = null as any; } draw(point: Vector, color: Color, size: number): void { diff --git a/src/engine/Graphics/Context/quad-index-buffer.ts b/src/engine/Graphics/Context/quad-index-buffer.ts index 07a0d2793..83de91abc 100644 --- a/src/engine/Graphics/Context/quad-index-buffer.ts +++ b/src/engine/Graphics/Context/quad-index-buffer.ts @@ -20,7 +20,7 @@ export class QuadIndexBuffer { /** * Depending on the browser this is either gl.UNSIGNED_SHORT or gl.UNSIGNED_INT */ - public bufferGlType: number; + public bufferGlType!: number; /** * @param gl WebGL2RenderingContext this layout will be attached to, these cannot be reused across contexts. @@ -29,7 +29,7 @@ export class QuadIndexBuffer { */ constructor(gl: WebGL2RenderingContext, numberOfQuads: number, useUint16?: boolean) { this._gl = gl; - this.buffer = gl.createBuffer(); + this.buffer = gl.createBuffer()!; gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.buffer); const totalVertices = numberOfQuads * 6; @@ -90,6 +90,6 @@ export class QuadIndexBuffer { public dispose() { const gl = this._gl; gl.deleteBuffer(this.buffer); - this._gl = null; + this._gl = null as any; } } diff --git a/src/engine/Graphics/Context/rectangle-renderer/rectangle-renderer.ts b/src/engine/Graphics/Context/rectangle-renderer/rectangle-renderer.ts index 09494acd2..079d79d5f 100644 --- a/src/engine/Graphics/Context/rectangle-renderer/rectangle-renderer.ts +++ b/src/engine/Graphics/Context/rectangle-renderer/rectangle-renderer.ts @@ -17,12 +17,12 @@ export class RectangleRenderer implements RendererPlugin { private _maxRectangles: number = 10922; // max(uint16) / 6 verts - private _shader: Shader; - private _gl: WebGLRenderingContext; - private _context: ExcaliburGraphicsContextWebGL; - private _buffer: VertexBuffer; - private _layout: VertexLayout; - private _quads: QuadIndexBuffer; + private _shader!: Shader; + private _gl!: WebGLRenderingContext; + private _context!: ExcaliburGraphicsContextWebGL; + private _buffer!: VertexBuffer; + private _layout!: VertexLayout; + private _quads!: QuadIndexBuffer; private _rectangleCount: number = 0; private _vertexIndex: number = 0; @@ -68,8 +68,8 @@ export class RectangleRenderer implements RendererPlugin { this._buffer.dispose(); this._quads.dispose(); this._shader.dispose(); - this._context = null; - this._gl = null; + this._context = null as any; + this._gl = null as any; } private _isFull() { @@ -81,9 +81,9 @@ export class RectangleRenderer implements RendererPlugin { draw(...args: any[]): void { if (args[0] instanceof Vector && args[1] instanceof Vector) { - this.drawLine.apply(this, args); + this.drawLine.apply(this, args as any); } else { - this.drawRectangle.apply(this, args); + this.drawRectangle.apply(this, args as any); } } @@ -106,7 +106,7 @@ export class RectangleRenderer implements RendererPlugin { /** * +---------------------^----------------------+ * | | (normal) | - * (startx, starty)------------------>(endx, endy) + * (startX, startY)------------------>(endX, endY) * | | * + -------------------------------------------+ */ diff --git a/src/engine/Graphics/Context/render-target.ts b/src/engine/Graphics/Context/render-target.ts index 7144a5f36..ebd5347b9 100644 --- a/src/engine/Graphics/Context/render-target.ts +++ b/src/engine/Graphics/Context/render-target.ts @@ -84,20 +84,20 @@ export class RenderTarget { } } - private _renderBuffer: WebGLRenderbuffer; + private _renderBuffer!: WebGLRenderbuffer; public get renderBuffer() { return this._renderBuffer; } - private _renderFrameBuffer: WebGLFramebuffer; + private _renderFrameBuffer!: WebGLFramebuffer; public get renderFrameBuffer() { return this._renderFrameBuffer; } - private _frameBuffer: WebGLFramebuffer; + private _frameBuffer!: WebGLFramebuffer; public get frameBuffer() { return this._frameBuffer; } - private _frameTexture: WebGLTexture; + private _frameTexture!: WebGLTexture; public get frameTexture() { return this._frameTexture; } @@ -106,8 +106,8 @@ export class RenderTarget { if (this.antialias) { const gl = this._gl; // Render buffers can be used as an input to a shader - this._renderBuffer = gl.createRenderbuffer(); - this._renderFrameBuffer = gl.createFramebuffer(); + this._renderBuffer = gl.createRenderbuffer()!; + this._renderFrameBuffer = gl.createFramebuffer()!; gl.bindRenderbuffer(gl.RENDERBUFFER, this._renderBuffer); gl.renderbufferStorageMultisample( gl.RENDERBUFFER, @@ -124,7 +124,7 @@ export class RenderTarget { private _setupFramebuffer() { // Allocates frame buffer const gl = this._gl; - this._frameTexture = gl.createTexture(); + this._frameTexture = gl.createTexture()!; gl.bindTexture(gl.TEXTURE_2D, this._frameTexture); gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, this.width, this.height, 0, gl.RGBA, gl.UNSIGNED_BYTE, null); @@ -138,7 +138,7 @@ export class RenderTarget { const attachmentPoint = gl.COLOR_ATTACHMENT0; // After this bind all draw calls will draw to this framebuffer texture - this._frameBuffer = gl.createFramebuffer(); + this._frameBuffer = gl.createFramebuffer()!; gl.bindFramebuffer(gl.FRAMEBUFFER, this._frameBuffer); gl.framebufferTexture2D(gl.FRAMEBUFFER, attachmentPoint, gl.TEXTURE_2D, this._frameTexture, 0); diff --git a/src/engine/Graphics/Context/shader.ts b/src/engine/Graphics/Context/shader.ts index 547cd5ea9..ea1a9d227 100644 --- a/src/engine/Graphics/Context/shader.ts +++ b/src/engine/Graphics/Context/shader.ts @@ -27,7 +27,7 @@ export type UniformTypeNames = | 'uniformMatrix4fv'; type RemoveFirstFromTuple = T['length'] extends 0 - ? undefined + ? [] : ((...b: T) => void) extends (a: any, ...b: infer I) => void ? I : []; @@ -88,10 +88,10 @@ export interface ShaderOptions { } export class Shader { - private static _ACTIVE_SHADER_INSTANCE: Shader = null; + private static _ACTIVE_SHADER_INSTANCE: Shader | null = null; private _logger = Logger.getInstance(); private _gl: WebGL2RenderingContext; - public program: WebGLProgram; + public program!: WebGLProgram; public uniforms: { [variableName: string]: UniformDefinition } = {}; public attributes: { [variableName: string]: VertexAttributeDefinition } = {}; private _compiled = false; @@ -106,7 +106,7 @@ export class Shader { * Create a shader program in excalibur * @param options specify shader vertex and fragment source */ - constructor(options?: ShaderOptions) { + constructor(options: ShaderOptions) { const { gl, vertexSource, fragmentSource } = options; this._gl = gl; this.vertexSource = vertexSource; @@ -116,7 +116,7 @@ export class Shader { dispose() { const gl = this._gl; gl.deleteProgram(this.program); - this._gl = null; + this._gl = null as any; } /** @@ -159,8 +159,8 @@ export class Shader { const uniformCount = gl.getProgramParameter(this.program, gl.ACTIVE_UNIFORMS); const uniforms: UniformDefinition[] = []; for (let i = 0; i < uniformCount; i++) { - const uniform = gl.getActiveUniform(this.program, i); - const uniformLocation = gl.getUniformLocation(this.program, uniform.name); + const uniform = gl.getActiveUniform(this.program, i)!; + const uniformLocation = gl.getUniformLocation(this.program, uniform.name)!; uniforms.push({ name: uniform.name, glType: uniform.type, @@ -175,7 +175,7 @@ export class Shader { const attributeCount = gl.getProgramParameter(this.program, gl.ACTIVE_ATTRIBUTES); const attributes: VertexAttributeDefinition[] = []; for (let i = 0; i < attributeCount; i++) { - const attribute = gl.getActiveAttrib(this.program, i); + const attribute = gl.getActiveAttrib(this.program, i)!; const attributeLocation = gl.getAttribLocation(this.program, attribute.name); attributes.push({ name: attribute.name, @@ -393,7 +393,7 @@ export class Shader { const location = gl.getUniformLocation(this.program, name); if (location) { const args = [location, ...value]; - this._gl[uniformType].apply(this._gl, args); + (this._gl as any)[uniformType].apply(this._gl, args); } else { throw Error( `Uniform ${uniformType}:${name} doesn\'t exist or is not used in the shader source code,` + @@ -431,7 +431,7 @@ export class Shader { const location = gl.getUniformLocation(this.program, name); if (location) { const args = [location, ...value]; - this._gl[uniformType].apply(this._gl, args); + (this._gl as any)[uniformType].apply(this._gl, args); } else { return false; } @@ -472,7 +472,7 @@ export class Shader { const success = gl.getShaderParameter(shader, gl.COMPILE_STATUS); if (!success) { const errorInfo = gl.getShaderInfoLog(shader); - throw Error(`Could not compile ${typeName} shader:\n\n${errorInfo}${this._processSourceForError(source, errorInfo)}`); + throw Error(`Could not compile ${typeName} shader:\n\n${errorInfo}${this._processSourceForError(source, errorInfo!)}`); } return shader; } diff --git a/src/engine/Graphics/Context/state-stack.ts b/src/engine/Graphics/Context/state-stack.ts index fa6de9a03..f180ef08c 100644 --- a/src/engine/Graphics/Context/state-stack.ts +++ b/src/engine/Graphics/Context/state-stack.ts @@ -6,8 +6,8 @@ import { Material } from './material'; export class ContextState implements ExcaliburGraphicsContextState { opacity: number = 1; z: number = 0; - tint: Color = Color.White; - material: Material = null; + tint: Color | null | undefined = Color.White; + material: Material | null | undefined = null; } export class StateStack { @@ -28,7 +28,7 @@ export class StateStack { private _cloneState(dest: ContextState) { dest.opacity = this.current.opacity; dest.z = this.current.z; - dest.tint = this.current.tint.clone(); // TODO remove color alloc + dest.tint = this.current.tint?.clone(); // TODO remove color alloc dest.material = this.current.material; // TODO is this going to cause problems when cloning return dest; } @@ -40,6 +40,6 @@ export class StateStack { public restore(): void { this._pool.return(this.current); - this.current = this._states.pop(); + this.current = this._states.pop()!; } } diff --git a/src/engine/Graphics/Context/texture-loader.ts b/src/engine/Graphics/Context/texture-loader.ts index 84187b6fc..f70d32b44 100644 --- a/src/engine/Graphics/Context/texture-loader.ts +++ b/src/engine/Graphics/Context/texture-loader.ts @@ -20,7 +20,7 @@ export class TextureLoader { this.delete(image); } this._textureMap.clear(); - this._gl = null; + this._gl = null as any; } /** @@ -40,7 +40,7 @@ export class TextureLoader { * @param image */ public get(image: HTMLImageSource): WebGLTexture { - return this._textureMap.get(image); + return this._textureMap.get(image)!; } /** @@ -48,7 +48,7 @@ export class TextureLoader { * @param image */ public has(image: HTMLImageSource): boolean { - return this._textureMap.has(image); + return this._textureMap.has(image)!; } /** @@ -57,7 +57,7 @@ export class TextureLoader { * @param options {ImageSourceOptions} Optionally configure the ImageFiltering and ImageWrapping mode to apply to the loaded texture * @param forceUpdate Optionally force a texture to be reloaded, useful if the source graphic has changed */ - public load(image: HTMLImageSource, options?: ImageSourceOptions, forceUpdate = false): WebGLTexture { + public load(image: HTMLImageSource, options?: ImageSourceOptions, forceUpdate = false): WebGLTexture | null { // Ignore loading if webgl is not registered const gl = this._gl; if (!gl) { @@ -66,7 +66,7 @@ export class TextureLoader { const { filtering, wrapping } = { ...options }; - let tex: WebGLTexture = null; + let tex: WebGLTexture | null = null; // If reuse the texture if it's from the same source if (this.has(image)) { tex = this.get(image); @@ -91,7 +91,7 @@ export class TextureLoader { gl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, true); - let wrappingConfig: ImageWrapConfiguration; + let wrappingConfig: ImageWrapConfiguration | undefined; if (wrapping) { if (typeof wrapping === 'string') { wrappingConfig = { @@ -141,7 +141,7 @@ export class TextureLoader { gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, image); - this._textureMap.set(image, tex); + this._textureMap.set(image, tex as any); return tex; } @@ -149,10 +149,10 @@ export class TextureLoader { // Ignore loading if webgl is not registered const gl = this._gl; if (!gl) { - return null; + return; } - let tex: WebGLTexture = null; + let tex: WebGLTexture | null = null; if (this.has(image)) { tex = this.get(image); gl.deleteTexture(tex); diff --git a/src/engine/Graphics/Context/transform-stack.ts b/src/engine/Graphics/Context/transform-stack.ts index f63825664..23b4a162a 100644 --- a/src/engine/Graphics/Context/transform-stack.ts +++ b/src/engine/Graphics/Context/transform-stack.ts @@ -18,7 +18,7 @@ export class TransformStack { public restore(): void { this._pool.return(this._currentTransform); - this._currentTransform = this._transforms.pop(); + this._currentTransform = this._transforms.pop()!; } public translate(x: number, y: number): AffineMatrix { diff --git a/src/engine/Graphics/Context/vertex-buffer.ts b/src/engine/Graphics/Context/vertex-buffer.ts index 667cba828..af76bfe5d 100644 --- a/src/engine/Graphics/Context/vertex-buffer.ts +++ b/src/engine/Graphics/Context/vertex-buffer.ts @@ -49,13 +49,13 @@ export class VertexBuffer { constructor(options: VertexBufferOptions) { const { gl, size, type, data } = options; this._gl = gl; - this.buffer = this._gl.createBuffer(); + this.buffer = this._gl.createBuffer()!; if (!data && !size) { throw Error('Must either provide data or a size to the VertexBuffer'); } if (!data) { - this.bufferData = new Float32Array(size); + this.bufferData = new Float32Array(size!); } else { this.bufferData = data; } @@ -95,6 +95,6 @@ export class VertexBuffer { dispose() { const gl = this._gl; gl.deleteBuffer(this.buffer); - this._gl = null; + this._gl = null as any; } } diff --git a/src/engine/Graphics/Context/vertex-layout.ts b/src/engine/Graphics/Context/vertex-layout.ts index 9fbd0e5a2..df1a4c883 100644 --- a/src/engine/Graphics/Context/vertex-layout.ts +++ b/src/engine/Graphics/Context/vertex-layout.ts @@ -46,7 +46,7 @@ export class VertexLayout { private _layout: VertexAttributeDefinition[] = []; private _attributes: [name: string, numberOfComponents: number][] = []; private _vertexBuffer: VertexBuffer; - private _vao: WebGLVertexArrayObject; + private _vao!: WebGLVertexArrayObject; public get vertexBuffer() { return this._vertexBuffer; @@ -61,8 +61,8 @@ export class VertexLayout { this._gl = gl; this._vertexBuffer = vertexBuffer; this._attributes = attributes; - this._shader = shader; - this._suppressWarnings = suppressWarnings; + this._shader = shader!; + this._suppressWarnings = suppressWarnings!; if (shader) { this.initialize(); } @@ -166,7 +166,7 @@ export class VertexLayout { // create VAO const gl = this._gl; - this._vao = gl.createVertexArray(); + this._vao = gl.createVertexArray()!; gl.bindVertexArray(this._vao); this._vertexBuffer.bind(); diff --git a/src/engine/Graphics/Context/webgl-util.ts b/src/engine/Graphics/Context/webgl-util.ts index f03f28ffc..5903b12e8 100644 --- a/src/engine/Graphics/Context/webgl-util.ts +++ b/src/engine/Graphics/Context/webgl-util.ts @@ -27,7 +27,7 @@ export function isAttributeInSource(source: string, variable: string) { const attributeRegexTemplate = `(?[a-z0-9]+)\\s+${variable};`; const regex = new RegExp(attributeRegexTemplate, 'g'); const matches = regex.exec(source); - return matches?.length > 0; + return matches?.length! > 0; } /** diff --git a/src/engine/Graphics/Filtering.ts b/src/engine/Graphics/Filtering.ts index 040f941ba..8267a9d01 100644 --- a/src/engine/Graphics/Filtering.ts +++ b/src/engine/Graphics/Filtering.ts @@ -18,13 +18,13 @@ export enum ImageFiltering { /** * Parse the image filtering attribute value, if it doesn't match returns null */ -export function parseImageFiltering(val: string): ImageFiltering | null { +export function parseImageFiltering(val: string): ImageFiltering | undefined { switch (val) { case ImageFiltering.Pixel: return ImageFiltering.Pixel; case ImageFiltering.Blended: return ImageFiltering.Blended; default: - return null; + return undefined; } } diff --git a/src/engine/Graphics/Font.ts b/src/engine/Graphics/Font.ts index c962a742f..22163b56e 100644 --- a/src/engine/Graphics/Font.ts +++ b/src/engine/Graphics/Font.ts @@ -70,7 +70,7 @@ export class Font extends Graphic implements FontRenderer { offset: this.shadow.offset, color: this.shadow.color } - : null + : undefined }); } @@ -90,7 +90,7 @@ export class Font extends Graphic implements FontRenderer { public lineWidth = 1; public lineDash: number[] = []; public color: Color = Color.Black; - public strokeColor: Color; + public strokeColor?: Color; public family: string = 'sans-serif'; public style: FontStyle = FontStyle.Normal; @@ -104,7 +104,7 @@ export class Font extends Graphic implements FontRenderer { */ public lineHeight: number | undefined = undefined; public size: number = 10; - public shadow: { blur?: number; offset?: Vector; color?: Color } = null; + public shadow?: { blur?: number; offset?: Vector; color?: Color }; public get fontString() { return `${this.style} ${this.bold ? 'bold' : ''} ${this.size}${this.unit} ${this.family}`; diff --git a/src/engine/Graphics/FontCache.ts b/src/engine/Graphics/FontCache.ts index 3ec165d1a..4f75cf9c9 100644 --- a/src/engine/Graphics/FontCache.ts +++ b/src/engine/Graphics/FontCache.ts @@ -11,10 +11,10 @@ export class FontCache { private static _TEXT_CACHE = new Map(); private static _MEASURE_CACHE = new Map(); - static measureText(text: string, font: Font, maxWidth?: number) { + static measureText(text: string, font: Font, maxWidth?: number): BoundingBox { const hash = FontTextInstance.getHashCode(font, text); if (FontCache._MEASURE_CACHE.has(hash)) { - return FontCache._MEASURE_CACHE.get(hash); + return FontCache._MEASURE_CACHE.get(hash)!; } FontCache._LOGGER.debug('Font text measurement cache miss'); const measurement = font.measureTextWithoutCache(text, maxWidth); @@ -22,7 +22,7 @@ export class FontCache { return measurement; } - static getTextInstance(text: string, font: Font, color: Color) { + static getTextInstance(text: string, font: Font, color: Color): FontTextInstance { const hash = FontTextInstance.getHashCode(font, text, color); let textInstance = FontCache._TEXT_CACHE.get(hash); if (!textInstance) { @@ -66,7 +66,7 @@ export class FontCache { const newTextMeasurementCache = new Map(); for (const current of currentHashCodes) { if (FontCache._MEASURE_CACHE.has(current)) { - newTextMeasurementCache.set(current, FontCache._MEASURE_CACHE.get(current)); + newTextMeasurementCache.set(current, FontCache._MEASURE_CACHE.get(current)!); } } this._MEASURE_CACHE.clear(); diff --git a/src/engine/Graphics/FontTextInstance.ts b/src/engine/Graphics/FontTextInstance.ts index 4b45e6ed1..1ac2f805d 100644 --- a/src/engine/Graphics/FontTextInstance.ts +++ b/src/engine/Graphics/FontTextInstance.ts @@ -20,7 +20,11 @@ export class FontTextInstance { public readonly maxWidth?: number ) { this.canvas = document.createElement('canvas'); - this.ctx = this.canvas.getContext('2d'); + const ctx = this.canvas.getContext('2d'); + if (!ctx) { + throw new Error('Unable to create FontTextInstance, internal canvas failed to create'); + } + this.ctx = ctx; this.dimensions = this.measureText(text); this._setDimension(this.dimensions, this.ctx); this._lastHashCode = this.getHashCode(); @@ -102,7 +106,7 @@ export class FontTextInstance { ctx.imageSmoothingEnabled = this.font.smoothing; ctx.lineWidth = this.font.lineWidth; ctx.setLineDash(this.font.lineDash ?? ctx.getLineDash()); - ctx.strokeStyle = this.font.strokeColor?.toString(); + ctx.strokeStyle = this.font.strokeColor?.toString() ?? ''; ctx.fillStyle = this.color.toString(); } @@ -116,10 +120,16 @@ export class FontTextInstance { ctx.direction = this.font.direction; if (this.font.shadow) { - ctx.shadowColor = this.font.shadow.color.toString(); - ctx.shadowBlur = this.font.shadow.blur; - ctx.shadowOffsetX = this.font.shadow.offset.x; - ctx.shadowOffsetY = this.font.shadow.offset.y; + if (this.font.shadow.color) { + ctx.shadowColor = this.font.shadow.color.toString(); + } + if (this.font.shadow.blur) { + ctx.shadowBlur = this.font.shadow.blur; + } + if (this.font.shadow.offset) { + ctx.shadowOffsetX = this.font.shadow.offset.x; + ctx.shadowOffsetY = this.font.shadow.offset.y; + } } } @@ -164,6 +174,9 @@ export class FontTextInstance { canvas.width = width; canvas.height = height; const ctx = canvas.getContext('2d'); + if (!ctx) { + throw new Error('Unable to split internal FontTextInstance bitmap, failed to create internal canvas'); + } // draw current slice to new bitmap in < 4k chunks ctx.drawImage(bitmap.canvas, currentX, currentY, width, height, 0, 0, width, height); @@ -181,7 +194,7 @@ export class FontTextInstance { this._dirty = true; } private _dirty = true; - private _ex: ExcaliburGraphicsContext; + private _ex?: ExcaliburGraphicsContext; public render(ex: ExcaliburGraphicsContext, x: number, y: number, maxWidth?: number) { if (this.disposed) { throw Error('Accessing disposed text instance! ' + this.text); @@ -239,9 +252,9 @@ export class FontTextInstance { dispose() { this.disposed = true; - this.dimensions = undefined; - this.canvas = undefined; - this.ctx = undefined; + this.dimensions = undefined as any; + this.canvas = undefined as any; + this.ctx = undefined as any; if (this._ex instanceof ExcaliburGraphicsContextWebGL) { for (const frag of this._textFragments) { this._ex.textureLoader.delete(frag.canvas); @@ -255,11 +268,11 @@ export class FontTextInstance { * @param text * @param maxWidth */ - private _cachedText: string; - private _cachedLines: string[]; - private _cachedRenderWidth: number; - private _getLinesFromText(text: string, maxWidth?: number) { - if (this._cachedText === text && this._cachedRenderWidth === maxWidth) { + private _cachedText?: string; + private _cachedLines?: string[]; + private _cachedRenderWidth?: number; + private _getLinesFromText(text: string, maxWidth?: number): string[] { + if (this._cachedText === text && this._cachedRenderWidth === maxWidth && this._cachedLines?.length) { return this._cachedLines; } diff --git a/src/engine/Graphics/Graphic.ts b/src/engine/Graphics/Graphic.ts index 91819d36a..d37cde2dd 100644 --- a/src/engine/Graphics/Graphic.ts +++ b/src/engine/Graphics/Graphic.ts @@ -57,7 +57,7 @@ export abstract class Graphic { readonly id = Graphic._ID++; public transform: AffineMatrix = AffineMatrix.identity(); - public tint: Color = null; + public tint?: Color; private _transformStale = true; public isStale() { @@ -128,18 +128,20 @@ export abstract class Graphic { this._transformStale = true; } - private _origin: Vector | null = null; + private _origin?: Vector; /** * Gets or sets the origin of the graphic, if not set the center of the graphic is the origin */ - public get origin(): Vector | null { + public get origin(): Vector | undefined { return this._origin; } - public set origin(value: Vector | null) { - this._origin = watch(value, () => { - this._transformStale = true; - }); + public set origin(value: Vector | undefined) { + if (value) { + this._origin = watch(value, () => { + this._transformStale = true; + }); + } this._transformStale = true; } @@ -159,13 +161,13 @@ export abstract class Graphic { return { width: this.width / this.scale.x, height: this.height / this.scale.y, - origin: this.origin ? this.origin.clone() : null, + origin: this.origin ? this.origin.clone() : undefined, flipHorizontal: this.flipHorizontal, flipVertical: this.flipVertical, rotation: this.rotation, opacity: this.opacity, - scale: this.scale ? this.scale.clone() : null, - tint: this.tint ? this.tint.clone() : null + scale: this.scale ? this.scale.clone() : undefined, + tint: this.tint ? this.tint.clone() : undefined }; } diff --git a/src/engine/Graphics/GraphicsComponent.ts b/src/engine/Graphics/GraphicsComponent.ts index 457306480..2dc3c8d82 100644 --- a/src/engine/Graphics/GraphicsComponent.ts +++ b/src/engine/Graphics/GraphicsComponent.ts @@ -57,7 +57,7 @@ export interface GraphicsComponentOptions { /** * List of graphics and optionally the options per graphic */ - graphics?: { [graphicName: string]: Graphic | { graphic: Graphic; options: GraphicsShowOptions } }; + graphics?: { [graphicName: string]: Graphic | { graphic: Graphic; options?: GraphicsShowOptions | undefined } }; /** * Optional offset in absolute pixels to shift all graphics in this component from each graphic's anchor (default is top left corner) @@ -78,29 +78,29 @@ export class GraphicsComponent extends Component { private _current: string = 'default'; private _graphics: Record = {}; - private _options: Record = {}; + private _options: Record = {}; public material: Material | null = null; /** * Draws after the entity transform has been applied, but before graphics component graphics have been drawn */ - public onPreDraw: (ctx: ExcaliburGraphicsContext, elapsedMilliseconds: number) => void; + public onPreDraw?: (ctx: ExcaliburGraphicsContext, elapsedMilliseconds: number) => void; /** * Draws after the entity transform has been applied, and after graphics component graphics has been drawn */ - public onPostDraw: (ctx: ExcaliburGraphicsContext, elapsedMilliseconds: number) => void; + public onPostDraw?: (ctx: ExcaliburGraphicsContext, elapsedMilliseconds: number) => void; /** * Draws before the entity transform has been applied before any any graphics component drawing */ - public onPreTransformDraw: (ctx: ExcaliburGraphicsContext, elapsedMilliseconds: number) => void; + public onPreTransformDraw?: (ctx: ExcaliburGraphicsContext, elapsedMilliseconds: number) => void; /** * Draws after the entity transform has been applied, and after all graphics component drawing */ - public onPostTransformDraw: (ctx: ExcaliburGraphicsContext, elapsedMilliseconds: number) => void; + public onPostTransformDraw?: (ctx: ExcaliburGraphicsContext, elapsedMilliseconds: number) => void; /** * Sets or gets wether any drawing should be visible in this component @@ -177,7 +177,7 @@ export class GraphicsComponent extends Component { onPostTransformDraw } = options; - for (const [key, graphicOrOptions] of Object.entries(graphics)) { + for (const [key, graphicOrOptions] of Object.entries(graphics as GraphicsComponentOptions)) { if (graphicOrOptions instanceof Graphic) { this._graphics[key] = graphicOrOptions; } else { @@ -239,7 +239,7 @@ export class GraphicsComponent extends Component { /** * Returns all graphics options associated with this component */ - public get options(): { [graphicName: string]: GraphicsShowOptions } { + public get options(): { [graphicName: string]: GraphicsShowOptions | undefined } { return this._options; } @@ -251,7 +251,7 @@ export class GraphicsComponent extends Component { public add(name: string, graphic: Graphic, options?: GraphicsShowOptions): Graphic; public add(nameOrGraphic: string | Graphic, graphicOrOptions?: Graphic | GraphicsShowOptions, options?: GraphicsShowOptions): Graphic { let name = 'default'; - let graphicToSet: Graphic = null; + let graphicToSet: Graphic | null = null; let optionsToSet: GraphicsShowOptions | undefined = undefined; if (typeof nameOrGraphic === 'string' && graphicOrOptions instanceof Graphic) { name = nameOrGraphic; @@ -263,6 +263,9 @@ export class GraphicsComponent extends Component { optionsToSet = graphicOrOptions; } + if (!graphicToSet) { + throw new Error('Need to provide a graphic or valid graphic string'); + } this._graphics[name] = this.copyGraphics ? graphicToSet.clone() : graphicToSet; this._options[name] = this.copyGraphics ? { ...optionsToSet } : optionsToSet; if (name === 'default') { @@ -330,7 +333,7 @@ export class GraphicsComponent extends Component { this._current = 'ex.none'; } - private _localBounds: BoundingBox = null; + private _localBounds?: BoundingBox; public set localBounds(bounds: BoundingBox) { this._localBounds = bounds; } @@ -371,7 +374,7 @@ export class GraphicsComponent extends Component { if (!this._localBounds || this._localBounds.hasZeroDimensions()) { this.recalculateBounds(); } - return this._localBounds; + return this._localBounds as BoundingBox; // recalc guarantees type } /** diff --git a/src/engine/Graphics/GraphicsSystem.ts b/src/engine/Graphics/GraphicsSystem.ts index 59e8e2f42..04e3fd67a 100644 --- a/src/engine/Graphics/GraphicsSystem.ts +++ b/src/engine/Graphics/GraphicsSystem.ts @@ -22,9 +22,10 @@ export class GraphicsSystem extends System { public readonly systemType = SystemType.Draw; public priority = SystemPriority.Average; private _token = 0; - private _graphicsContext: ExcaliburGraphicsContext; - private _camera: Camera; - private _engine: Engine; + // Set in the initialize + private _graphicsContext!: ExcaliburGraphicsContext; + private _camera!: Camera; + private _engine!: Engine; private _sortedTransforms: TransformComponent[] = []; query: Query; public get sortedTransforms() { diff --git a/src/engine/Graphics/ImageSource.ts b/src/engine/Graphics/ImageSource.ts index 8e53fc693..78fc2f424 100644 --- a/src/engine/Graphics/ImageSource.ts +++ b/src/engine/Graphics/ImageSource.ts @@ -27,8 +27,8 @@ export const ImageSourceAttributeConstants = { export class ImageSource implements Loadable { private _logger = Logger.getInstance(); private _resource: Resource; - public filtering: ImageFiltering; - public wrapping: ImageWrapConfiguration; + public filtering?: ImageFiltering; + public wrapping?: ImageWrapConfiguration; /** * The original size of the source image in pixels @@ -44,7 +44,7 @@ export class ImageSource implements Loadable { return this.image.naturalHeight; } - private _src: string; + private _src?: string; /** * Returns true if the Texture is completely loaded and is ready * to be drawn. @@ -86,10 +86,10 @@ export class ImageSource implements Loadable { * @param filtering {ImageFiltering} Optionally override the image filtering set by [[EngineOptions.antialiasing]] */ constructor(path: string, bustCache: boolean, filtering?: ImageFiltering); - constructor(path: string, bustCacheOrOptions: boolean | ImageSourceOptions, filtering?: ImageFiltering) { + constructor(path: string, bustCacheOrOptions: boolean | ImageSourceOptions | undefined, filtering?: ImageFiltering) { this.path = path; - let bustCache = false; - let wrapping: ImageWrapConfiguration | ImageWrapping; + let bustCache: boolean | undefined = false; + let wrapping: ImageWrapConfiguration | ImageWrapping | undefined; if (typeof bustCacheOrOptions === 'boolean') { bustCache = bustCacheOrOptions; } else { @@ -200,11 +200,11 @@ export class ImageSource implements Loadable { // emit warning if potentially too big TextureLoader.checkImageSizeSupportedAndLog(this.data); - } catch (error) { + } catch (error: any) { throw `Error loading ImageSource from path '${this.path}' with error [${error.message}]`; } // Do a bad thing to pass the filtering as an attribute - this.data.setAttribute(ImageSourceAttributeConstants.Filtering, this.filtering); + this.data.setAttribute(ImageSourceAttributeConstants.Filtering, this.filtering as any); // TODO fix type this.data.setAttribute(ImageSourceAttributeConstants.WrappingX, this.wrapping?.x ?? ImageWrapping.Clamp); this.data.setAttribute(ImageSourceAttributeConstants.WrappingY, this.wrapping?.y ?? ImageWrapping.Clamp); diff --git a/src/engine/Graphics/OffscreenSystem.ts b/src/engine/Graphics/OffscreenSystem.ts index 608222662..457774d99 100644 --- a/src/engine/Graphics/OffscreenSystem.ts +++ b/src/engine/Graphics/OffscreenSystem.ts @@ -14,9 +14,9 @@ import { Query, SystemPriority, World } from '../EntityComponentSystem'; export class OffscreenSystem extends System { public systemType = SystemType.Draw; priority: number = SystemPriority.Higher; - private _camera: Camera; - private _screen: Screen; - private _worldBounds: BoundingBox; + private _camera!: Camera; + private _screen!: Screen; + private _worldBounds!: BoundingBox; query: Query; constructor(public world: World) { @@ -33,14 +33,14 @@ export class OffscreenSystem extends System { this._worldBounds = this._screen.getWorldBounds(); let transform: TransformComponent; let graphics: GraphicsComponent; - let maybeParallax: ParallaxComponent; + let maybeParallax: ParallaxComponent | undefined; for (const entity of this.query.entities) { graphics = entity.get(GraphicsComponent); transform = entity.get(TransformComponent); maybeParallax = entity.get(ParallaxComponent); - let parallaxOffset: Vector; + let parallaxOffset: Vector | undefined; if (maybeParallax) { // We use the Tiled formula // https://doc.mapeditor.org/en/latest/manual/layers/#parallax-scrolling-factor @@ -63,7 +63,7 @@ export class OffscreenSystem extends System { } } - private _isOffscreen(transform: TransformComponent, graphics: GraphicsComponent, parallaxOffset: Vector) { + private _isOffscreen(transform: TransformComponent, graphics: GraphicsComponent, parallaxOffset: Vector | undefined) { if (transform.coordPlane === CoordPlane.World) { let bounds = graphics.localBounds; if (parallaxOffset) { diff --git a/src/engine/Graphics/Polygon.ts b/src/engine/Graphics/Polygon.ts index 248936eb3..f1eec277e 100644 --- a/src/engine/Graphics/Polygon.ts +++ b/src/engine/Graphics/Polygon.ts @@ -12,7 +12,7 @@ export interface PolygonOptions { * Polygons default to [[ImageFiltering.Blended]] */ export class Polygon extends Raster { - private _points: Vector[]; + private _points: Vector[] = []; public get points(): Vector[] { return this._points; } diff --git a/src/engine/Graphics/PostProcessor/ColorBlindnessPostProcessor.ts b/src/engine/Graphics/PostProcessor/ColorBlindnessPostProcessor.ts index 756b98920..fc746079a 100644 --- a/src/engine/Graphics/PostProcessor/ColorBlindnessPostProcessor.ts +++ b/src/engine/Graphics/PostProcessor/ColorBlindnessPostProcessor.ts @@ -6,7 +6,7 @@ import { VertexLayout } from '../Context/vertex-layout'; import { ScreenShader } from './ScreenShader'; export class ColorBlindnessPostProcessor implements PostProcessor { - private _shader: ScreenShader; + private _shader!: ScreenShader; private _simulate = false; constructor( private _colorBlindnessMode: ColorBlindnessMode, diff --git a/src/engine/Graphics/Raster.ts b/src/engine/Graphics/Raster.ts index 2fbc42f3d..8846a4812 100644 --- a/src/engine/Graphics/Raster.ts +++ b/src/engine/Graphics/Raster.ts @@ -67,7 +67,7 @@ export interface RasterOptions { * Implementors must implement the [[Raster.execute]] method to rasterize their drawing. */ export abstract class Raster extends Graphic { - public filtering: ImageFiltering = null; + public filtering?: ImageFiltering; public lineCap: 'butt' | 'round' | 'square' = 'butt'; public quality: number = 1; @@ -76,7 +76,7 @@ export abstract class Raster extends Graphic { private _dirty: boolean = true; constructor(options?: GraphicOptions & RasterOptions) { - super(omit(options, ['width', 'height'])); // rasters do some special sauce with width/height + super(omit({ ...options }, ['width', 'height'])); // rasters do some special sauce with width/height if (options) { this.quality = options.quality ?? this.quality; this.color = options.color ?? Color.Black; @@ -105,8 +105,8 @@ export abstract class Raster extends Graphic { public cloneRasterOptions(): RasterOptions { return { - color: this.color ? this.color.clone() : null, - strokeColor: this.strokeColor ? this.strokeColor.clone() : null, + color: this.color ? this.color.clone() : undefined, + strokeColor: this.strokeColor ? this.strokeColor.clone() : undefined, smoothing: this.smoothing, lineWidth: this.lineWidth, lineDash: this.lineDash, @@ -131,7 +131,7 @@ export abstract class Raster extends Graphic { this._dirty = true; } - private _originalWidth: number; + private _originalWidth?: number; /** * Gets or sets the current width of the Raster graphic. Setting the width will cause the raster * to be flagged dirty causing a re-raster on the next draw. @@ -148,7 +148,7 @@ export abstract class Raster extends Graphic { this.flagDirty(); } - private _originalHeight: number; + private _originalHeight?: number; /** * Gets or sets the current height of the Raster graphic. Setting the height will cause the raster * to be flagged dirty causing a re-raster on the next draw. @@ -207,7 +207,7 @@ export abstract class Raster extends Graphic { this._color = watch(value, () => this.flagDirty()); } - private _strokeColor: Color; + private _strokeColor: Color | undefined; /** * Gets or sets the strokeStyle of the Raster graphic. Setting the strokeStyle will cause the raster to be * flagged dirty causing a re-raster on the next draw. @@ -215,9 +215,11 @@ export abstract class Raster extends Graphic { public get strokeColor() { return this._strokeColor; } - public set strokeColor(value) { + public set strokeColor(value: Color | undefined) { this.flagDirty(); - this._strokeColor = watch(value, () => this.flagDirty()); + if (value) { + this._strokeColor = watch(value, () => this.flagDirty()); + } } private _lineWidth: number = 1; @@ -270,7 +272,7 @@ export abstract class Raster extends Graphic { this._bitmap.width = this._getTotalWidth() * this.quality; this._bitmap.height = this._getTotalHeight() * this.quality; // Do a bad thing to pass the filtering as an attribute - this._bitmap.setAttribute('filtering', this.filtering); + this._bitmap.setAttribute('filtering', this.filtering as any); this._bitmap.setAttribute('forceUpload', 'true'); ctx.scale(this.quality, this.quality); ctx.translate(this.padding, this.padding); @@ -278,7 +280,7 @@ export abstract class Raster extends Graphic { ctx.lineWidth = this.lineWidth; ctx.setLineDash(this.lineDash ?? ctx.getLineDash()); ctx.lineCap = this.lineCap; - ctx.strokeStyle = this.strokeColor?.toString(); + ctx.strokeStyle = this.strokeColor?.toString() ?? ''; ctx.fillStyle = this.color?.toString(); } diff --git a/src/engine/Graphics/SpriteFont.ts b/src/engine/Graphics/SpriteFont.ts index bd99d6a8b..9f5a649b5 100644 --- a/src/engine/Graphics/SpriteFont.ts +++ b/src/engine/Graphics/SpriteFont.ts @@ -6,7 +6,6 @@ import { Graphic, GraphicOptions } from './Graphic'; import { Sprite } from './Sprite'; import { SpriteSheet } from './SpriteSheet'; import { BoundingBox } from '../Collision/BoundingBox'; -import { Color } from '../Color'; export interface SpriteFontOptions { /** @@ -41,7 +40,7 @@ export class SpriteFont extends Graphic implements FontRenderer { public alphabet: string = ''; public spriteSheet: SpriteSheet; - public shadow: { offset: Vector } = null; + public shadow?: { offset: Vector } = undefined; public caseInsensitive = false; public spacing: number = 0; public lineHeight: number | undefined = undefined; @@ -119,7 +118,7 @@ export class SpriteFont extends Graphic implements FontRenderer { } } - render(ex: ExcaliburGraphicsContext, text: string, _color: Color, x: number, y: number, maxWidth?: number) { + render(ex: ExcaliburGraphicsContext, text: string, _color: any, x: number, y: number, maxWidth?: number) { // SpriteFont doesn't support _color, yet... this._text = text; const bounds = this.measureText(text, maxWidth); @@ -152,11 +151,11 @@ export class SpriteFont extends Graphic implements FontRenderer { * @param text * @param maxWidth */ - private _cachedText: string; - private _cachedLines: string[]; - private _cachedRenderWidth: number; + private _cachedText?: string; + private _cachedLines?: string[]; + private _cachedRenderWidth?: number; private _getLinesFromText(text: string, maxWidth?: number) { - if (this._cachedText === text && this._cachedRenderWidth === maxWidth) { + if (this._cachedText === text && this._cachedRenderWidth === maxWidth && this._cachedLines?.length) { return this._cachedLines; } diff --git a/src/engine/Graphics/Text.ts b/src/engine/Graphics/Text.ts index d3cb3b245..0a33e9e12 100644 --- a/src/engine/Graphics/Text.ts +++ b/src/engine/Graphics/Text.ts @@ -63,7 +63,7 @@ export class Text extends Graphic { this._calculateDimension(); } - private _font: Font | SpriteFont; + private _font!: Font | SpriteFont; public get font(): Font | SpriteFont { return this._font; } diff --git a/src/engine/Graphics/tsconfig.json b/src/engine/Graphics/tsconfig.json new file mode 100644 index 000000000..20d5e2e03 --- /dev/null +++ b/src/engine/Graphics/tsconfig.json @@ -0,0 +1,6 @@ +{ + "extends": "../tsconfig.json", + "compilerOptions": { + "strict": true + } +}