Skip to content

Commit

Permalink
fix(client): avoid unnecessary draws
Browse files Browse the repository at this point in the history
  • Loading branch information
Veradictus committed Sep 25, 2023
1 parent 359724d commit 39a38f3
Show file tree
Hide file tree
Showing 6 changed files with 56 additions and 49 deletions.
4 changes: 2 additions & 2 deletions packages/client/src/controllers/joystick.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ export default class JoystickController {
dx = Math.max(0, Math.min(1, (clientX - left) / width)),
dy = Math.max(0, Math.min(1, (clientY - top) / height)),
// Calculate and restrict the distance from the center of the joystick (from 0 to 0.75).
distance = Math.min(0.75, Math.sqrt((dx - 0.5) ** 2 + (dy - 0.5) ** 2) * 2),
distance = Math.min(0.75, Math.hypot(dx - 0.5, dy - 0.5) * 2),
// Using the distance, calculate the angle of the joystick (from -PI to PI).
angle = Math.atan2(dy - 0.5, dx - 0.5),
// Unit vector of the angle (from -1 to 1).
Expand Down Expand Up @@ -92,7 +92,7 @@ export default class JoystickController {

input.player.disableAction = false;

input.move({ x, y, gridX, gridY });
input.move({ x, y, gridX, gridY }, false);

if (this.moving) requestAnimationFrame(this.move.bind(this));
}
Expand Down
3 changes: 2 additions & 1 deletion packages/client/src/entity/entity.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import type Player from './character/player/player';
import type Item from './objects/item';
import type Sprite from './sprite';
import type Pet from './character/pet/pet';
import type Projectile from './objects/projectile';
import type { EntityDisplayInfo } from '@kaetram/common/types/entity';

export default abstract class Entity {
Expand Down Expand Up @@ -413,7 +414,7 @@ export default abstract class Entity {
* @returns Whether or not the entity is a projectile type.
*/

public isProjectile(): boolean {
public isProjectile(): this is Projectile {
return this.type === Modules.EntityType.Projectile;
}

Expand Down
13 changes: 12 additions & 1 deletion packages/client/src/map/map.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ export default class Map {
public grid: number[][] = []; // Two dimensional grid array for collisions/pathing

private high: number[] = mapData.high;
private cachedHighTiles: { [index: number]: 0 } = {};
private objects: number[] = [];
private lights: number[] = [];

Expand Down Expand Up @@ -419,7 +420,17 @@ export default class Map {
*/

public isHighTile(tileId: number): boolean {
return this.high.includes(tileId);
// Check if the tileId is cached.
if (this.cachedHighTiles[tileId]) return true;

// Cache the tileId if it's in our high tiles hashmap
if (this.high.includes(tileId)) {
this.cachedHighTiles[tileId] = 0;

return true;
}

return false;
}

/**
Expand Down
76 changes: 35 additions & 41 deletions packages/client/src/renderer/canvas.ts
Original file line number Diff line number Diff line change
Expand Up @@ -111,21 +111,18 @@ export default class Canvas extends Renderer {
* tile or not. If it is, we handle the animated tile logic.
* @param tile The tileId of the tile we are drawing, used to access the animated tile.
* @param index The index of the tile on the map.
* @param clearRect Whether we should clear the cell before drawing.
*/

private drawVisibleTile(tile: ClientTile, index: number, clearRect = false): void {
private drawVisibleTile(tile: ClientTile, index: number): void {
let flips: number[] = this.getFlipped(tile as TransformedTile);

// Extract the tileId from the animated region tile.
if (flips.length > 0) tile = (tile as TransformedTile).tileId;

// Determine the layer of the tile depending on if it is a high tile or not.
let isHighTile = this.map.isHighTile(tile as number),
animated = this.map.isAnimatedTile(tile as number),
context = (
isHighTile ? this.foreContext : this.backContext
) as CanvasRenderingContext2D;
let context = (
this.map.isHighTile(tile as number) ? this.foreContext : this.backContext
) as CanvasRenderingContext2D;

// Only do the lighting logic if there is an overlay.
if (this.game.overlays.hasOverlay()) {
Expand All @@ -135,26 +132,45 @@ export default class Canvas extends Renderer {
}

// Draw animated tiles if the tile is animated and we're animating tiles.
if (this.animateTiles && animated)
this.drawAnimatedTile(tile as number, index, flips, clearRect);
if (this.animateTiles && this.map.isAnimatedTile(tile as number))
this.drawAnimatedTile(tile as number, index, flips);
else this.drawTile(context, tile as number, index, flips);
}

/**
* We iterate through the animated indexes for each animated tile and draw them.
* We store the animated tiles in a dictionary since it makes it easier to delete
* them when they are no longer used. For each index we render the whole tile information
* at that index to ensure that the tile is drawn correctly.
* at that index to ensure that the tile is drawn correctly. We also check if the tile
* has been previously uploaded (whether or not the last frame changed).
*/

private drawAnimatedIndexes(): void {
this.saveDrawing();
this.updateDrawingView();

for (let tileId in this.animatedTileIndexes) {
let animatedTile = this.animatedTiles[tileId];

// Skip if the tile has been uploaded.
if (animatedTile?.uploaded) continue;

let indexes = this.animatedTileIndexes[tileId];

for (let index of indexes) this.parseTile(this.map.data[index], index, true);
for (let index of indexes) {
let cell = this.cells[index];

// Clear the context at the cell of the index we are drawing.
if (cell) {
this.backContext.clearRect(cell.dx, cell.dy, cell.width, cell.height);
this.foreContext.clearRect(cell.dx, cell.dy, cell.width, cell.height);
}

this.parseTile(this.map.data[index], index);
}

// Mark the tile as uploaded.
animatedTile.uploaded = true;
}

this.restoreDrawing();
Expand All @@ -166,15 +182,9 @@ export default class Canvas extends Renderer {
* @param tile The tileId of the tile we are drawing, used to access the animated tile.
* @param index The index of the tile on the map.
* @param flips An array containing transformations the tile will undergo.
* @param clearRect Whether we should clear the cell before drawing.
*/

private drawAnimatedTile(
tile: number,
index: number,
flips: number[] = [],
clearRect = false
): void {
private drawAnimatedTile(tile: number, index: number, flips: number[] = []): void {
// No drawing if we aren't animating tiles.
if (!this.animateTiles) return;

Expand Down Expand Up @@ -207,7 +217,7 @@ export default class Canvas extends Renderer {
let context = animatedTile.isHighTile ? this.foreContext : this.backContext;

// Draw the tile given its context (determined when we initialize the tile).
this.drawTile(context, animatedTile.id + 1, index, flips, clearRect);
this.drawTile(context, animatedTile.id + 1, index, flips);
}

// ---------- Primitive Drawing Functions ----------
Expand All @@ -226,8 +236,7 @@ export default class Canvas extends Renderer {
context: CanvasRenderingContext2D,
tileId: number,
index: number,
flips: number[] = [],
clearCell = false
flips: number[] = []
): void {
let tileset = this.map.getTilesetFromId(tileId);

Expand Down Expand Up @@ -268,15 +277,6 @@ export default class Canvas extends Renderer {
height: this.ceilActualTileSize
};

// Forcibly clear a cell if we need to.
if (clearCell)
context.clearRect(
this.cells[index].dx,
this.cells[index].dy,
this.cells[index].width,
this.cells[index].height
);

this.drawImage(context, tileset, this.tiles[tileId], this.cells[index], flips);
}

Expand Down Expand Up @@ -466,22 +466,16 @@ export default class Canvas extends Renderer {
* each tile. Otherwise, we draw the tile.
* @param tile The tile we are parsing, raw from the client map.
* @param index The index of the tile on the map.
* @param clearRect Whether we should clear the cell before drawing.
*/

private parseTile(tile: ClientTile, index: number, clearRect = false): void {
if (tile === 0) return;

private parseTile(tile: ClientTile, index: number): void {
// Check for transformed tiles and draw them.
if ((tile as TransformedTile).tileId) {
this.drawVisibleTile(tile as TransformedTile, index, clearRect);
return;
}
if ((tile as TransformedTile).tileId)
return this.drawVisibleTile(tile as TransformedTile, index);

// This is a hackfix to check if the tile is an array at the index.
if (~~tile === 0)
for (let info of tile as number[]) this.drawVisibleTile(info, index, clearRect);
else this.drawVisibleTile(tile, index, clearRect);
if (~~tile === 0) for (let info of tile as number[]) this.drawVisibleTile(info, index);
else this.drawVisibleTile(tile, index);
}

// ---------- Getters and Checkers ----------
Expand Down
7 changes: 4 additions & 3 deletions packages/client/src/renderer/renderer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -212,6 +212,9 @@ export default class Renderer {
canvas.height = this.canvasHeight;
});

// Clear the default canvas anti-alias smoothing.
this.removeSmoothing();

// Remove the player's light source and re-add it.
this.resizeLights();
}
Expand Down Expand Up @@ -255,8 +258,6 @@ export default class Renderer {
this.clear();
this.save();

this.removeSmoothing();

this.drawDebugging();

this.drawOverlays();
Expand Down Expand Up @@ -397,7 +398,7 @@ export default class Renderer {
this.setCameraView(this.entitiesForeContext);

this.forEachVisibleEntity((entity: Entity) => {
// Skip entities that aren't properly loaded or are invisible.
// Check that the entity is loaded, has an animation, and is visible.
if (!entity.sprite?.loaded || !entity.animation || !entity.isVisible()) return;

// Handle tree drawing as separate from other entities.
Expand Down
2 changes: 1 addition & 1 deletion packages/client/src/renderer/updater.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ export default class Updater {
entity.animation?.update(this.game.time);

// Handle projectile instances separately.
if (entity instanceof Projectile) {
if (entity.isProjectile()) {
let mDistance = entity.speed * entity.getTimeDiff(),
dx = entity.target.x - entity.x, // delta x current position to target
dy = entity.target.y - entity.y, // delta y current position to target
Expand Down

0 comments on commit 39a38f3

Please sign in to comment.