Split out editor drawing and slightly speed up normal drawing

This commit is contained in:
Eevee (Evelyn Woods) 2021-01-03 18:03:58 -07:00
parent 6fc4f6b58f
commit 90fa352a50
2 changed files with 106 additions and 58 deletions

View File

@ -282,7 +282,7 @@ class EditorLevelBrowserOverlay extends DialogOverlay {
let stored_level = this.conductor.stored_game.load_level(index); let stored_level = this.conductor.stored_game.load_level(index);
this.renderer.set_level(stored_level); this.renderer.set_level(stored_level);
this.renderer.set_viewport_size(stored_level.size_x, stored_level.size_y); this.renderer.set_viewport_size(stored_level.size_x, stored_level.size_y);
this.renderer.draw(); this.renderer.draw_static_region(0, 0, stored_level.size_x, stored_level.size_y);
let canvas = mk('canvas', { let canvas = mk('canvas', {
width: stored_level.size_x * this.conductor.tileset.size_x / 4, width: stored_level.size_x * this.conductor.tileset.size_x / 4,
height: stored_level.size_y * this.conductor.tileset.size_y / 4, height: stored_level.size_y * this.conductor.tileset.size_y / 4,
@ -2424,7 +2424,7 @@ export class Editor extends PrimaryView {
ev.stopPropagation(); ev.stopPropagation();
// FIXME eventually this should be automatic // FIXME eventually this should be automatic
this.renderer.draw(); this.redraw_entire_level();
} }
else if (ev.button === 1) { else if (ev.button === 1) {
// Middle button: always pan // Middle button: always pan
@ -2443,7 +2443,7 @@ export class Editor extends PrimaryView {
ev.preventDefault(); ev.preventDefault();
ev.stopPropagation(); ev.stopPropagation();
this.renderer.draw(); this.redraw_entire_level();
} }
}); });
// Once the mouse is down, we should accept mouse movement anywhere // Once the mouse is down, we should accept mouse movement anywhere
@ -2471,7 +2471,7 @@ export class Editor extends PrimaryView {
this.mouse_op.do_mousemove(ev); this.mouse_op.do_mousemove(ev);
// FIXME !!! // FIXME !!!
this.renderer.draw(); this.redraw_entire_level();
}); });
// TODO should this happen for a mouseup anywhere? // TODO should this happen for a mouseup anywhere?
this.viewport_el.addEventListener('mouseup', ev => { this.viewport_el.addEventListener('mouseup', ev => {
@ -2720,7 +2720,7 @@ export class Editor extends PrimaryView {
activate() { activate() {
super.activate(); super.activate();
this.renderer.draw(); this.redraw_entire_level();
} }
// Level creation, management, and saving // Level creation, management, and saving
@ -2934,7 +2934,7 @@ export class Editor extends PrimaryView {
this.renderer.set_level(stored_level); this.renderer.set_level(stored_level);
if (this.active) { if (this.active) {
this.renderer.draw(); this.redraw_entire_level();
} }
if (this.save_button) { if (this.save_button) {
@ -3073,6 +3073,8 @@ export class Editor extends PrimaryView {
this.palette_actor_direction = DIRECTIONS[this.palette_actor_direction].left; this.palette_actor_direction = DIRECTIONS[this.palette_actor_direction].left;
} }
// -- Drawing --
mark_tile_dirty(tile) { mark_tile_dirty(tile) {
// TODO partial redraws! until then, redraw everything // TODO partial redraws! until then, redraw everything
if (tile === this.palette_selection) { if (tile === this.palette_selection) {
@ -3081,14 +3083,20 @@ export class Editor extends PrimaryView {
this.selected_tile_el.append(this.renderer.create_tile_type_canvas(tile.type.name, tile)); this.selected_tile_el.append(this.renderer.create_tile_type_canvas(tile.type.name, tile));
} }
else { else {
this.renderer.draw(); this.redraw_entire_level();
} }
} }
mark_cell_dirty(cell) { mark_cell_dirty(cell) {
this.renderer.draw(); this.redraw_entire_level();
} }
redraw_entire_level() {
this.renderer.draw_static_region(0, 0, this.stored_level.size_x, this.stored_level.size_y);
}
// -- Utility/inspection --
is_in_bounds(x, y) { is_in_bounds(x, y) {
return 0 <= x && x < this.stored_level.size_x && 0 <= y && y < this.stored_level.size_y; return 0 <= x && x < this.stored_level.size_x && 0 <= y && y < this.stored_level.size_y;
} }
@ -3102,6 +3110,8 @@ export class Editor extends PrimaryView {
} }
} }
// -- Mutation --
place_in_cell(x, y, tile) { place_in_cell(x, y, tile) {
// TODO weird api? // TODO weird api?
if (! tile) if (! tile)
@ -3167,6 +3177,8 @@ export class Editor extends PrimaryView {
this.mark_cell_dirty(cell); this.mark_cell_dirty(cell);
} }
// -- Misc?? --
open_tile_prop_overlay(tile, x0, y0) { open_tile_prop_overlay(tile, x0, y0) {
this.cancel_mouse_operation(); this.cancel_mouse_operation();
// FIXME keep these around, don't recreate them constantly // FIXME keep these around, don't recreate them constantly
@ -3222,6 +3234,6 @@ export class Editor extends PrimaryView {
this.stored_level.size_x = size_x; this.stored_level.size_x = size_x;
this.stored_level.size_y = size_y; this.stored_level.size_y = size_y;
this.update_viewport_size(); this.update_viewport_size();
this.renderer.draw(); this.redraw_entire_level();
} }
} }

View File

@ -105,21 +105,11 @@ export class CanvasRenderer {
// 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?
let xmargin = (this.viewport_size_x - 1) / 2; let xmargin = (this.viewport_size_x - 1) / 2;
let ymargin = (this.viewport_size_y - 1) / 2; let ymargin = (this.viewport_size_y - 1) / 2;
let px, py; let [px, py] = this.level.player.visual_position(tic_offset);
// FIXME editor vs player
if (this.level.player) {
[px, py] = this.level.player.visual_position(tic_offset);
}
else {
[px, py] = [0, 0];
}
// Figure out where to start drawing // Figure out where to start drawing
// TODO support overlapping regions better // TODO support overlapping regions better
let x0 = px - xmargin; let x0 = px - xmargin;
let y0 = py - ymargin; let y0 = py - ymargin;
// FIXME editor vs player again ugh, which is goofy since none of this is even relevant;
// maybe need to have a separate positioning method
if (this.level.stored_level) {
for (let region of this.level.stored_level.camera_regions) { for (let region of this.level.stored_level.camera_regions) {
if (px >= region.left && px < region.right && if (px >= region.left && px < region.right &&
py >= region.top && py < region.bottom) py >= region.top && py < region.bottom)
@ -128,7 +118,6 @@ export class CanvasRenderer {
y0 = Math.max(region.top, Math.min(region.bottom - this.viewport_size_y, y0)); y0 = Math.max(region.top, Math.min(region.bottom - this.viewport_size_y, y0));
} }
} }
}
// Always keep us within the map bounds // Always keep us within the map bounds
x0 = Math.max(0, Math.min(this.level.size_x - this.viewport_size_x, x0)); x0 = Math.max(0, Math.min(this.level.size_x - this.viewport_size_x, x0));
y0 = Math.max(0, Math.min(this.level.size_y - this.viewport_size_y, y0)); y0 = Math.max(0, Math.min(this.level.size_y - this.viewport_size_y, y0));
@ -152,55 +141,67 @@ export class CanvasRenderer {
// include the tiles just outside it, so we allow this fencepost problem to fly // include the tiles just outside it, so we allow this fencepost problem to fly
let x1 = Math.min(this.level.size_x - 1, Math.ceil(x0 + this.viewport_size_x)); let x1 = Math.min(this.level.size_x - 1, Math.ceil(x0 + this.viewport_size_x));
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 // Tiles in motion (i.e., actors) don't want to be overdrawn by neighboring tiles' terrain,
// so draw in three passes: everything below actors, actors, and everything above actors
// neighboring terrain // neighboring terrain
// FIXME this is a bit inefficient when there are a lot of rarely-used layers; consider
// instead drawing everything under actors, then actors, then everything above actors?
// (note: will need to first fix the game to ensure everything is stacked correctly!)
for (let layer = 0; layer < LAYERS.MAX; layer++) {
for (let x = xf0; x <= x1; x++) { for (let x = xf0; x <= x1; x++) {
for (let y = yf0; y <= y1; y++) { for (let y = yf0; y <= y1; y++) {
let cell = this.level.cell(x, y); let cell = this.level.cell(x, y);
for (let layer = 0; layer < LAYERS.actor; layer++) {
let tile = cell[layer]; let tile = cell[layer];
if (! tile) if (! tile)
continue; continue;
let vx, vy; this.tileset.draw(
if (tile.type.is_actor && tile, tic, this.perception,
// FIXME kind of a hack for the editor, which uses bare tile objects this._make_tileset_blitter(this.ctx, x - x0, y - y0));
tile.visual_position) }
{ }
}
for (let x = xf0; x <= x1; x++) {
for (let y = yf0; y <= y1; y++) {
let cell = this.level.cell(x, y);
let actor = cell[LAYERS.actor];
if (! actor)
continue;
// Handle smooth scrolling // Handle smooth scrolling
[vx, vy] = tile.visual_position(tic_offset); let [vx, vy] = actor.visual_position(tic_offset);
// Round this to the pixel grid too! // Round this to the pixel grid too!
vx = Math.floor(vx * tw + 0.5) / tw; vx = Math.floor(vx * tw + 0.5) / tw;
vy = Math.floor(vy * th + 0.5) / th; vy = Math.floor(vy * th + 0.5) / th;
}
else {
// Non-actors can't move
vx = x;
vy = y;
}
// For actors (i.e., blocks), perception only applies if there's something // For actors (i.e., blocks), perception only applies if there's something of
// of potential interest underneath // potential interest underneath
let perception = this.perception; let perception = this.perception;
if (perception !== 'normal' && tile.type.is_actor && if (perception !== 'normal' &&
! cell.some(t => ! cell.some(t => t && t.type.layer < layer && ! (
t && t.type.layer < layer && t.type.name === 'floor' && (t.wire_directions | t.wire_tunnel_directions) === 0)))
! (t.type.name === 'floor' && (t.wire_directions | t.wire_tunnel_directions) === 0)))
{ {
perception = 'normal'; perception = 'normal';
} }
this.tileset.draw( this.tileset.draw(
tile, tic, perception, actor, tic, perception,
this._make_tileset_blitter(this.ctx, vx - x0, vy - y0)); this._make_tileset_blitter(this.ctx, vx - x0, vy - y0));
} }
} }
for (let x = xf0; x <= x1; x++) {
for (let y = yf0; y <= y1; y++) {
let cell = this.level.cell(x, y);
for (let layer = LAYERS.actor + 1; layer < LAYERS.MAX; layer++) {
let tile = cell[layer];
if (! tile)
continue;
this.tileset.draw(
tile, tic, this.perception,
this._make_tileset_blitter(this.ctx, x - x0, y - y0));
}
}
} }
if (this.show_actor_bboxes && this.level.constructor.name === 'Level') { // FIXME dumb hack so this doesn't happen in editor if (this.show_actor_bboxes) {
this.ctx.fillStyle = '#f004'; this.ctx.fillStyle = '#f004';
for (let x = xf0; x <= x1; x++) { for (let x = xf0; x <= x1; x++) {
for (let y = yf0; y <= y1; y++) { for (let y = yf0; y <= y1; y++) {
@ -233,6 +234,41 @@ export class CanvasRenderer {
} }
} }
// Used by the editor and map previews. Draws a region of the level (probably a StoredLevel),
// assuming nothing is moving.
draw_static_region(x0, y0, x1, y1, destx = x0, desty = y0) {
for (let x = x0; x <= x1; x++) {
for (let y = y0; y <= y1; y++) {
let cell = this.level.cell(x, y);
if (! cell)
continue;
let seen_anything_interesting;
for (let tile of cell) {
if (! tile)
continue;
// For actors (i.e., blocks), perception only applies if there's something
// of potential interest underneath
let perception = this.perception;
if (perception !== 'normal' && tile.type.is_actor && ! seen_anything_interesting) {
perception = 'normal';
}
if (tile.type.layer < LAYERS.actor && ! (
tile.type.name === 'floor' && (tile.wire_directions | tile.wire_tunnel_directions) === 0))
{
seen_anything_interesting = true;
}
this.tileset.draw(
tile, 0, perception,
this._make_tileset_blitter(this.ctx, destx + x - x0, desty + y - y0));
}
}
}
}
create_tile_type_canvas(name, tile = null) { create_tile_type_canvas(name, tile = null) {
let canvas = mk('canvas', {width: this.tileset.size_x, height: this.tileset.size_y}); let canvas = mk('canvas', {width: this.tileset.size_x, height: this.tileset.size_y});
let ctx = canvas.getContext('2d'); let ctx = canvas.getContext('2d');