Move responsibility for drawing out of Tileset and into the renderer

This commit is contained in:
Eevee (Evelyn Woods) 2020-09-16 14:10:10 -06:00
parent 2e0519f802
commit e70e92b931
3 changed files with 56 additions and 58 deletions

View File

@ -229,11 +229,6 @@ class Player extends PrimaryView {
this._redraw(); this._redraw();
}); });
// Populate inventory
this._inventory_tiles = {};
let floor_tile = this.render_inventory_tile('floor');
this.inventory_el.style.backgroundImage = `url(${floor_tile})`;
this.renderer = new CanvasRenderer(this.conductor.tileset); this.renderer = new CanvasRenderer(this.conductor.tileset);
this.level_el.append(this.renderer.canvas); this.level_el.append(this.renderer.canvas);
this.renderer.canvas.addEventListener('auxclick', ev => { this.renderer.canvas.addEventListener('auxclick', ev => {
@ -244,6 +239,11 @@ class Player extends PrimaryView {
this.level.move_to(this.level.player, this.level.cells[y][x], 1); this.level.move_to(this.level.player, this.level.cells[y][x], 1);
}); });
// Populate inventory
this._inventory_tiles = {};
let floor_tile = this.render_inventory_tile('floor');
this.inventory_el.style.backgroundImage = `url(${floor_tile})`;
let last_key; let last_key;
this.pending_player_move = null; this.pending_player_move = null;
this.next_player_move = null; this.next_player_move = null;
@ -526,10 +526,8 @@ class Player extends PrimaryView {
render_inventory_tile(name) { render_inventory_tile(name) {
if (! this._inventory_tiles[name]) { if (! this._inventory_tiles[name]) {
// TODO put this on the renderer
// TODO reuse the canvas for data urls // TODO reuse the canvas for data urls
let canvas = mk('canvas', {width: this.conductor.tileset.size_x, height: this.conductor.tileset.size_y}); let canvas = this.renderer.create_tile_type_canvas(name);
this.conductor.tileset.draw({type: TILE_TYPES[name]}, null, canvas.getContext('2d'), 0, 0);
this._inventory_tiles[name] = canvas.toDataURL(); this._inventory_tiles[name] = canvas.toDataURL();
} }
return this._inventory_tiles[name]; return this._inventory_tiles[name];
@ -1026,13 +1024,9 @@ class Editor extends PrimaryView {
let section_el = mk('section'); let section_el = mk('section');
palette_el.append(mk('h2', sectiondef.title), section_el); palette_el.append(mk('h2', sectiondef.title), section_el);
for (let name of sectiondef.tiles) { for (let name of sectiondef.tiles) {
let entry = mk('canvas.palette-entry', { let entry = this.renderer.create_tile_type_canvas(name);
width: this.conductor.tileset.size_x, entry.setAttribute('data-tile-name', name);
height: this.conductor.tileset.size_y, entry.classList = 'palette-entry';
'data-tile-name': name,
});
let ctx = entry.getContext('2d');
this.conductor.tileset.draw_type(name, null, null, ctx, 0, 0);
this.palette[name] = entry; this.palette[name] = entry;
section_el.append(entry); section_el.append(entry);
} }

View File

@ -22,6 +22,7 @@ export class CanvasRenderer {
this.canvas = mk('canvas', {width: tileset.size_x * this.viewport_size_x, height: tileset.size_y * this.viewport_size_y}); this.canvas = mk('canvas', {width: tileset.size_x * this.viewport_size_x, height: tileset.size_y * this.viewport_size_y});
this.canvas.style.setProperty('--viewport-width', this.viewport_size_x); this.canvas.style.setProperty('--viewport-width', this.viewport_size_x);
this.canvas.style.setProperty('--viewport-height', this.viewport_size_y); this.canvas.style.setProperty('--viewport-height', this.viewport_size_y);
this.ctx = this.canvas.getContext('2d');
this.viewport_x = 0; this.viewport_x = 0;
this.viewport_y = 0; this.viewport_y = 0;
} }
@ -40,6 +41,16 @@ export class CanvasRenderer {
return [x, y]; return [x, y];
} }
// Draw to a canvas using tile coordinates
blit(ctx, sx, sy, dx, dy, w = 1, h = w) {
let tw = this.tileset.size_x;
let th = this.tileset.size_y;
ctx.drawImage(
this.tileset.image,
sx * tw, sy * th, w * tw, h * th,
dx * tw, dy * th, w * tw, h * th);
}
draw(tic_offset = 0) { draw(tic_offset = 0) {
if (! this.level) { if (! this.level) {
console.warn("CanvasRenderer.draw: No level to render"); console.warn("CanvasRenderer.draw: No level to render");
@ -48,9 +59,9 @@ export class CanvasRenderer {
// TODO StoredLevel may not have a tic_counter // TODO StoredLevel may not have a tic_counter
let tic = (this.level.tic_counter ?? 0) + tic_offset; let tic = (this.level.tic_counter ?? 0) + tic_offset;
let tw = this.tileset.size_x;
let ctx = this.canvas.getContext('2d'); let th = this.tileset.size_y;
ctx.clearRect(0, 0, this.canvas.width, this.canvas.height); this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
// TODO only recompute if the player moved? // TODO only recompute if the player moved?
// TODO what about levels smaller than the viewport...? shrink the canvas in set_level? // TODO what about levels smaller than the viewport...? shrink the canvas in set_level?
@ -68,8 +79,8 @@ export class CanvasRenderer {
let x0 = Math.max(0, Math.min(this.level.size_x - this.viewport_size_x, px - xmargin)); let x0 = Math.max(0, Math.min(this.level.size_x - this.viewport_size_x, px - xmargin));
let y0 = Math.max(0, Math.min(this.level.size_y - this.viewport_size_y, py - ymargin)); let y0 = Math.max(0, Math.min(this.level.size_y - this.viewport_size_y, py - ymargin));
// Round to the pixel grid // Round to the pixel grid
x0 = Math.floor(x0 * this.tileset.size_x + 0.5) / this.tileset.size_x; x0 = Math.floor(x0 * tw + 0.5) / tw;
y0 = Math.floor(y0 * this.tileset.size_y + 0.5) / this.tileset.size_y; y0 = Math.floor(y0 * th + 0.5) / th;
this.viewport_x = x0; this.viewport_x = x0;
this.viewport_y = y0; this.viewport_y = y0;
// The viewport might not be aligned to the grid, so split off any fractional part. // The viewport might not be aligned to the grid, so split off any fractional part.
@ -89,10 +100,11 @@ export class CanvasRenderer {
let y1 = Math.min(this.level.size_y - 1, Math.ceil(y0 + this.viewport_size_y)); let y1 = Math.min(this.level.size_y - 1, Math.ceil(y0 + this.viewport_size_y));
// Draw one layer at a time, so animated objects aren't overdrawn by // Draw one layer at a time, so animated objects aren't overdrawn by
// neighboring terrain // neighboring terrain
let x, y;
// XXX layer count hardcoded here // XXX layer count hardcoded here
for (let layer = 0; layer < 4; layer++) { for (let layer = 0; layer < 4; layer++) {
for (let x = xf0; x <= x1; x++) { for (x = xf0; x <= x1; x++) {
for (let y = yf0; y <= y1; y++) { for (y = yf0; y <= y1; y++) {
for (let tile of this.level.cells[y][x]) { for (let tile of this.level.cells[y][x]) {
if (tile.type.draw_layer !== layer) if (tile.type.draw_layer !== layer)
continue; continue;
@ -104,14 +116,15 @@ export class CanvasRenderer {
// Handle smooth scrolling // Handle smooth scrolling
let [vx, vy] = tile.visual_position(tic_offset); let [vx, vy] = tile.visual_position(tic_offset);
// Round this to the pixel grid too! // Round this to the pixel grid too!
vx = Math.floor(vx * this.tileset.size_x + 0.5) / this.tileset.size_x; vx = Math.floor(vx * tw + 0.5) / tw;
vy = Math.floor(vy * this.tileset.size_y + 0.5) / this.tileset.size_y; vy = Math.floor(vy * th + 0.5) / th;
this.tileset.draw(tile, tic, ctx, vx - x0, vy - y0); this.tileset.draw(tile, tic, (sx, sy, dx = 0, dy = 0, w = 1, h = w) =>
this.blit(this.ctx, sx, sy, vx - x0 + dx, vy - y0 + dy, w, h));
} }
else { else {
// Non-actors can't move // Non-actors can't move
this.tileset.draw(tile, tic, ctx, x - x0, y - y0); this.tileset.draw(tile, tic, (sx, sy, dx = 0, dy = 0, w = 1, h = w) =>
} this.blit(this.ctx, sx, sy, x - x0 + dx, y - y0 + dy, w, h));
} }
} }
} }
@ -119,4 +132,13 @@ export class CanvasRenderer {
} }
} }
create_tile_type_canvas(name) {
let canvas = mk('canvas', {width: this.tileset.size_x, height: this.tileset.size_y});
let ctx = canvas.getContext('2d');
this.tileset.draw_type(name, null, 0, (sx, sy, dx = 0, dy = 0, w = 1, h = w) =>
this.blit(ctx, sx, sy, dx, dy, w, h));
return canvas;
}
}
export default CanvasRenderer; export default CanvasRenderer;

View File

@ -491,23 +491,13 @@ export class Tileset {
this.size_y = size_y; this.size_y = size_y;
} }
// Helper to draw to a canvas using tile coordinates draw(tile, tic, blit) {
blit(ctx, sx, sy, dx, dy, scale_x = 1, scale_y = scale_x) { this.draw_type(tile.type.name, tile, tic, blit);
let w = this.size_x * scale_x;
let h = this.size_y * scale_y;
ctx.drawImage(
this.image,
sx * this.size_x, sy * this.size_y, w, h,
dx * this.size_x, dy * this.size_y, w, h);
}
draw(tile, tic, ctx, x, y) {
this.draw_type(tile.type.name, tile, tic, ctx, x, y);
} }
// Draws a tile type, given by name. Passing in a tile is optional, but // Draws a tile type, given by name. Passing in a tile is optional, but
// without it you'll get defaults. // without it you'll get defaults.
draw_type(name, tile, tic, ctx, x, y) { draw_type(name, tile, tic, blit) {
let drawspec = this.layout[name]; let drawspec = this.layout[name];
if (! drawspec) { if (! drawspec) {
console.error(`Don't know how to draw tile type ${name}!`); console.error(`Don't know how to draw tile type ${name}!`);
@ -519,9 +509,9 @@ export class Tileset {
// southeast thin walls. Draw the base (a type name), then draw // southeast thin walls. Draw the base (a type name), then draw
// the overlay (either a type name or a regular draw spec). // the overlay (either a type name or a regular draw spec).
// TODO chance of infinite recursion here // TODO chance of infinite recursion here
this.draw_type(drawspec.base, tile, tic, ctx, x, y); this.draw_type(drawspec.base, tile, tic, blit);
if (typeof drawspec.overlay === 'string') { if (typeof drawspec.overlay === 'string') {
this.draw_type(drawspec.overlay, tile, tic, ctx, x, y); this.draw_type(drawspec.overlay, tile, tic, blit);
return; return;
} }
else { else {
@ -539,7 +529,7 @@ export class Tileset {
if (tile && tile.wire_directions !== undefined && tile.wire_directions !== 0) { if (tile && tile.wire_directions !== undefined && tile.wire_directions !== 0) {
// TODO all four is a different thing entirely // TODO all four is a different thing entirely
// Draw the appropriate wire underlay // Draw the appropriate wire underlay
this.draw_type('#unpowered', tile, tic, ctx, x, y); this.draw_type('#unpowered', tile, tic, blit);
// Draw a masked part of the base tile // Draw a masked part of the base tile
let wiredir = tile.wire_directions; let wiredir = tile.wire_directions;
@ -548,16 +538,16 @@ export class Tileset {
let wire1 = 0.5 + wire_radius; let wire1 = 0.5 + wire_radius;
let [bx, by] = drawspec.base; let [bx, by] = drawspec.base;
if ((wiredir & DIRECTIONS['north'].bit) === 0) { if ((wiredir & DIRECTIONS['north'].bit) === 0) {
this.blit(ctx, bx, by, x, y, 1, wire0); blit(bx, by, 0, 0, 1, wire0);
} }
if ((wiredir & DIRECTIONS['south'].bit) === 0) { if ((wiredir & DIRECTIONS['south'].bit) === 0) {
this.blit(ctx, bx, by + wire1, x, y + wire1, 1, wire0); blit(bx, by + wire1, 0, wire1, 1, wire0);
} }
if ((wiredir & DIRECTIONS['west'].bit) === 0) { if ((wiredir & DIRECTIONS['west'].bit) === 0) {
this.blit(ctx, bx, by, x, y, wire0, 1); blit(bx, by, 0, 0, wire0, 1);
} }
if ((wiredir & DIRECTIONS['east'].bit) === 0) { if ((wiredir & DIRECTIONS['east'].bit) === 0) {
this.blit(ctx, bx + wire1, by, x + wire1, y, wire0, 1); blit(bx + wire1, by, wire1, 0, wire0, 1);
} }
// Then draw the wired tile as normal // Then draw the wired tile as normal
@ -571,7 +561,7 @@ export class Tileset {
coords = drawspec.base; coords = drawspec.base;
} }
else { else {
this.blit(ctx, drawspec.base[0], drawspec.base[1], x, y); blit(drawspec.base[0], drawspec.base[1]);
coords = drawspec.wired; coords = drawspec.wired;
} }
} }
@ -621,17 +611,11 @@ export class Tileset {
// Continue on with masking // Continue on with masking
coords = drawspec.tile; coords = drawspec.tile;
let [x0, y0, w, h] = drawspec.mask; let [x0, y0, w, h] = drawspec.mask;
this.blit( blit(coords[0] + x0, coords[1] + y0, x0, y0, w, h);
ctx,
coords[0] + x0,
coords[1] + y0,
x + x0,
y + y0,
w, h);
} }
else { else {
if (!coords) console.error(name, tile); if (!coords) console.error(name, tile);
this.blit(ctx, coords[0], coords[1], x, y); blit(coords[0], coords[1]);
} }
// Special behavior for special objects // Special behavior for special objects
@ -661,9 +645,7 @@ export class Tileset {
sy = (letter_spec.y0 + Math.floor(n / w)) * scale; sy = (letter_spec.y0 + Math.floor(n / w)) * scale;
} }
let offset = (1 - scale) / 2; let offset = (1 - scale) / 2;
this.blit( blit(sx, sy, offset, offset, scale, scale);
ctx, sx, sy,
x + offset, y + offset, scale);
} }
} }
} }