Debug Collider Rendering
Tracking Debug Mode
Let's toggle drawing the colliders when we press the d
key.
We're going to create a new file: game.ts
, to track whether or not we're in debug mode.
It's pretty common to create features that are only available in debug mode or debug builds. These are additional tooling or features to help during the development process.
1export class Game {2debug = false;3}
Very minimal for now, but we'll add more by the end of this section.
Now create a new instance of the Game
class, and add a keypress listener beneath our canvas click listener.
1import spriteSheetUrl from "#/assets/image/spritesheet.png";2import { BoxCollider } from "#/components/box-collider";3import { CircleCollider } from "#/components/circle-collider";4import { SpriteAnimation } from "#/components/sprite-animation";5import { SpriteAnimationDetails } from "#/components/sprite-animation-details";6import { SpriteData } from "#/components/sprite-data";7import { Vector2d } from "#/components/vector2d";8import { config } from "#/config";9import { Bird } from "#/entities/bird";10import { Ground } from "#/entities/ground";+import { Game } from "#/game";12import { loadImage } from "#/lib/asset-loader";13import { circleRectangleIntersects } from "#/lib/collision";14import { spriteMap } from "#/sprite-map";1516const spriteSheet = await loadImage(spriteSheetUrl);+const game = new Game();1819// ...2021// Add event listener to trigger bird flap22canvas.addEventListener("click", () => {23bird.flap();24});25+window.addEventListener("keypress", (event) => {+if (event.code === "KeyD") {+game.debug = !game.debug;+}+});3132// ...
Now that we're tracking this, we need to react to it in the Ground and Bird classes.
Debug Rendering in the Bird
Let's make a few changes to the Bird
class to support debug rendering.
1import { CircleCollider } from "#/components/circle-collider";2import { SpriteAnimation } from "#/components/sprite-animation";3import { SpriteData } from "#/components/sprite-data";4import { Vector2d } from "#/components/vector2d";+import { Game } from "#/game";67type BirdOptions = {+game: Game;9spriteSheet: HTMLImageElement;10spriteData: SpriteData;11position: Vector2d;12flapAnimation: SpriteAnimation;13circlCollider: CircleCollider;14};1516// ...1718export class Bird {19state = BirdState.Idle;20spriteSheet: HTMLImageElement;21spriteData: SpriteData;22position: Vector2d;23flapAnimation: SpriteAnimation;24velocity = new Vector2d();25circleCollider: CircleCollider;+game: Game;2728// ...2930constructor(options: BirdOptions) {31this.spriteSheet = options.spriteSheet;32this.spriteData = options.spriteData;33this.position = options.position;34this.flapAnimation = options.flapAnimation;35this.circleCollider = options.circlCollider;+this.game = options.game;37}3839public draw(context: CanvasRenderingContext2D) {40context.translate(this.position.x, this.position.y);41const rotation = (this.rotation * Math.PI) / 180;42context.rotate(rotation);4344const currentFrame = this.flapAnimation.getCurrentFrame();4546context.drawImage(47this.spriteSheet,48currentFrame.sourceX,49currentFrame.sourceY,50currentFrame.width,51currentFrame.height,52-currentFrame.width / 2,53-currentFrame.height / 2,54currentFrame.width,55currentFrame.height56);57+if (this.game.debug) {+context.fillStyle = "red";+context.globalAlpha = 0.5;+context.beginPath();+context.arc(0, 0, this.circleCollider.radius, 0, 2 * Math.PI);+context.fill();+context.globalAlpha = 1;+}6667context.resetTransform();68}69}
Then update the instance in main.ts
:
1const bird = new Bird({+game,3spriteSheet: spriteSheet,4position: new Vector2d(config.gameWidth / 4, config.gameHeight / 2),5spriteData: new SpriteData(6spriteMap.bird.idle.sourceX,7spriteMap.bird.idle.sourceY,8spriteMap.bird.idle.width,9spriteMap.bird.idle.height10),11flapAnimation: new SpriteAnimation(12new SpriteAnimationDetails(13spriteMap.bird.animations.flap.sourceX,14spriteMap.bird.animations.flap.sourceY,15spriteMap.bird.animations.flap.width,16spriteMap.bird.animations.flap.height,17spriteMap.bird.animations.flap.frameWidth,18spriteMap.bird.animations.flap.frameHeight19),200.321),22circlCollider: new CircleCollider(0, 0, 12),23});
Once the window has reloaded, go ahead and press the d
key to toggle the debug rendering. Looking pretty good 😎
Debug Rendering in the Ground
Let's make a few changes to the Ground class to support debug rendering.
1import { BoxCollider } from "#/components/box-collider";2import { SpriteData } from "#/components/sprite-data";3import { Vector2d } from "#/components/vector2d";+import { Game } from "#/game";56type GroundOptions = {7boxCollider: BoxCollider;+game: Game;9position: Vector2d;10spriteData: SpriteData;11spriteSheet: HTMLImageElement;1213/**14* This is in pixels **per frame**.15*/16scrollSpeed: number;17};1819export class Ground {20boxCollider: BoxCollider;+game: Game;22position: Vector2d;23spriteData: SpriteData;24spriteSheet: HTMLImageElement;25scrollSpeed: number;2627// Track the current scroll position separately from the position.28public scrollPositionX = 0;2930constructor(options: GroundOptions) {31this.boxCollider = options.boxCollider;+this.game = options.game;33this.position = options.position;34this.spriteData = options.spriteData;35this.spriteSheet = options.spriteSheet;36this.scrollSpeed = options.scrollSpeed;37}3839public draw(context: CanvasRenderingContext2D) {40// First track how far the image is offscreen.41const diff = Math.abs(this.scrollPositionX);4243// Draw the visible portion of the image.44context.drawImage(45this.spriteSheet,46this.spriteData.sourceX + diff,47this.spriteData.sourceY,48this.spriteData.width - diff,49this.spriteData.height,50this.position.x,51this.position.y,52this.spriteData.width - diff,53this.spriteData.height54);5556// Draw the remaining portion of the image (what is currently offscreen).57context.drawImage(58this.spriteSheet,59this.spriteData.sourceX,60this.spriteData.sourceY,61diff,62this.spriteData.height,63context.canvas.width - diff,64this.position.y,65diff,66this.spriteData.height67);68+if (this.game.debug) {+context.fillStyle = "red";+context.globalAlpha = 0.5;+context.fillRect(+this.position.x,+this.position.y,+this.boxCollider.width,+this.boxCollider.height+);++context.globalAlpha = 1;+}81}82}
Then update the instance in main.ts:
1const ground = new Ground({2boxCollider: new BoxCollider(30,40,5spriteMap.ground.width,6spriteMap.ground.height7),+game,9position: new Vector2d(0, config.gameHeight - spriteMap.ground.height),10spriteData: spriteMap.ground,11spriteSheet: spriteSheet,12scrollSpeed: 120,13});
On window reload, press the d
key and you should also see the ground collider rendering. Sweet stuff!
Next, let's finally detect collision between the bird and the ground.