Skip to content

Commit

Permalink
Rasterization work
Browse files Browse the repository at this point in the history
  • Loading branch information
JeremyLeland committed Feb 3, 2024
1 parent 1e8eec2 commit 4fb841c
Show file tree
Hide file tree
Showing 15 changed files with 405 additions and 289 deletions.
180 changes: 89 additions & 91 deletions games/Frogger/editor.html
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@
import { Tiles } from './src/Tiles.js';
import { Direction, Dir } from './src/Entity.js';
import { Entities } from './src/Entities.js';
import { Entity } from './src/Entity.js';
import * as Entity from './src/Entity.js';
import { Froggy } from './src/Froggy.js';
import { Level } from './src/Level.js';
import { World } from './src/World.js';
Expand All @@ -85,6 +85,77 @@

let DebugGrid = true;

//
// UI
//

const worldDiv = document.getElementById( 'world' );

const uiDiv = document.getElementById( 'ui' );

const tilesDiv = document.getElementById( 'tiles' );
for ( const tile in Tiles ) {
tilesDiv.appendChild( getCanvasButton( Tiles[ tile ].draw, tile, 'Tiles' ) );
}

const entitiesDiv = document.getElementById( 'entities' );
for ( const id in Entities ) {
entitiesDiv.appendChild( getCanvasButton( Entities[ id ].draw, id, 'Entities' ) );
}

function getCanvasButton( drawFunc, brush, type ) {
const button = document.createElement( 'button' );

// TODO: Use AnimatedCanvas for these, and just don't start it?
const icon = new Canvas( 48, 48 );
icon.ctx.scale( 48, 48 );
icon.ctx.translate( 0.5, 0.5 );
icon.ctx.lineWidth = 1 / 48; // TODO: Can we set this once and not deal with it anymore? bug #38
drawFunc( icon.ctx );
button.appendChild( icon.canvas );
button.appendChild( document.createElement( 'br' ) );

const text = document.createElement( 'div' );
text.innerHTML = brush;
button.appendChild( text );

button.dataset.brush = brush;
button.dataset.type = type;

return button;
}

tilesDiv.addEventListener( 'click', brushClick );
entitiesDiv.addEventListener( 'click', brushClick );

function brushClick( e ) {
const button = e.target.closest( 'button' );

if ( button ) {
activeBrush = button.dataset.brush;
activeType = button.dataset.type;
}
}

const timeUI = document.getElementById( 'time' );
timeUI.addEventListener( 'input', e => level.time = parseInt( e.target.value ) );


const buttonFuncs = {
'path': _ => activeType = EditType.Directions,
'clear': clearLevel,
'load': loadLevel,
'save': saveLevel,
'play': startPlay,
'pause': pausePlay,
'stop': stopPlay,
}

for ( const id in buttonFuncs ) {
document.getElementById( id ).addEventListener( 'click', buttonFuncs[ id ] );
}


const Mode = { Edit: 0, Play: 1 };
let mode = Mode.Edit;

Expand All @@ -94,7 +165,8 @@
let level = Object.assign( getEmptyLevel(), JSON.parse( localStorage.getItem( EditorLevelKey ) ) );

const canvas = new FroggerCanvas( document.getElementById( 'canvas' ) );
canvas.scale = TILE_SIZE;
canvas.ctx.scaleVal = TILE_SIZE;
levelResized();


//
Expand All @@ -109,6 +181,8 @@

function clearLevel() {
level = getEmptyLevel();

timeUI.value = level.time;

levelResized();
}
Expand Down Expand Up @@ -165,11 +239,13 @@
let currentDirection = Direction.None;

function setEntityDirection( dir ) {
const entity = level.entities.find( e => e.x == mouseCol && e.y == mouseRow );
if ( entity ) {
entity.dir = dir;
redraw();
level.entities.filter( e => e.x == mouseCol && e.y == mouseRow ).forEach( e => e.dir = dir );

if ( level.spawn.x == mouseCol && level.spawn.y == mouseRow ) {
level.spawn.dir = dir;
}

redraw();
}

function toggleGrid() {
Expand All @@ -195,8 +271,11 @@
function redraw() {
const ctx = canvas.ctx;

ctx.clearRect( 0, 0, ctx.canvas.width, ctx.canvas.height );

ctx.save(); {
ctx.scale( TILE_SIZE, TILE_SIZE );
ctx.setTransform( ctx.scaleVal * devicePixelRatio, 0, 0, ctx.scaleVal * devicePixelRatio, 0, 0 );

ctx.translate( 0.5, 0.5 );
level.draw( ctx );
level.entities.forEach( entity =>
Expand All @@ -207,8 +286,7 @@
ctx.fillStyle = ctx.strokeStyle = ARROW_COLOR;
ctx.lineWidth = TILE_BORDER;
ctx.textAlign = 'center';
ctx.font = '10px Arial'; // work around https://bugzilla.mozilla.org/show_bug.cgi?id=1845828

ctx.font = '0.2px Arial';

for ( let row = 0; row < level.rows; row ++ ) {
ctx.save(); {
Expand All @@ -224,11 +302,8 @@
ctx.restore();
}

ctx.save();
ctx.scale( 0.02, 0.02 ); // work around https://bugzilla.mozilla.org/show_bug.cgi?id=1845828
ctx.fillText( `(${ col },${ row })`, 0, 20 );
ctx.restore();

ctx.fillText( `(${ col },${ row })`, 0, 0.4 );

ctx.translate( 1, 0 );
}

Expand All @@ -239,79 +314,6 @@
}
}
ctx.restore();

timeUI.value = level.time;
}


//
// UI
//

const worldDiv = document.getElementById( 'world' );

const uiDiv = document.getElementById( 'ui' );

const tilesDiv = document.getElementById( 'tiles' );
for ( const tile in Tiles ) {
tilesDiv.appendChild( getCanvasButton( Tiles[ tile ].draw, tile, 'Tiles' ) );
}

const entitiesDiv = document.getElementById( 'entities' );
for ( const id in Entities ) {
entitiesDiv.appendChild( getCanvasButton( Entities[ id ].draw, id, 'Entities' ) );
}

function getCanvasButton( drawFunc, brush, type ) {
const button = document.createElement( 'button' );

// TODO: Use AnimatedCanvas for these, and just don't start it?
const icon = new Canvas( 48, 48 );
icon.ctx.scale( 48, 48 );
icon.ctx.translate( 0.5, 0.5 );
icon.ctx.lineWidth = 1 / 48; // TODO: Can we set this once and not deal with it anymore? bug #38
drawFunc( icon.ctx );
button.appendChild( icon.canvas );
button.appendChild( document.createElement( 'br' ) );

const text = document.createElement( 'div' );
text.innerHTML = brush;
button.appendChild( text );

button.dataset.brush = brush;
button.dataset.type = type;

return button;
}

tilesDiv.addEventListener( 'click', brushClick );
entitiesDiv.addEventListener( 'click', brushClick );

function brushClick( e ) {
const button = e.target.closest( 'button' );

if ( button ) {
activeBrush = button.dataset.brush;
activeType = button.dataset.type;
}
}

const timeUI = document.getElementById( 'time' );
timeUI.addEventListener( 'input', e => level.time = parseInt( e.target.value ) );


const buttonFuncs = {
'path': _ => activeType = EditType.Directions,
'clear': clearLevel,
'load': loadLevel,
'save': saveLevel,
'play': startPlay,
'pause': pausePlay,
'stop': stopPlay,
}

for ( const id in buttonFuncs ) {
document.getElementById( id ).addEventListener( 'click', buttonFuncs[ id ] );
}


Expand Down Expand Up @@ -408,10 +410,6 @@

document.addEventListener( 'keydown', e => KeyBindings[ e.code ]?.() );


levelResized();
// redraw();

window.addEventListener( 'beforeunload', ( e ) =>
localStorage.setItem( EditorLevelKey, JSON.stringify( level ) )
);
Expand Down
22 changes: 20 additions & 2 deletions games/Frogger/index.html
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<title>Frogger v0.9</title>
<title>Frogger v0.92</title>
<head>
<meta charset="UTF-8">
<link rel="stylesheet" href="./ui.css">
Expand Down Expand Up @@ -100,6 +100,8 @@
import { Direction } from './src/Entity.js';
import { Level } from './src/Level.js';
import { World } from './src/World.js';
import * as TileMap from './src/TileMap.js';
import * as Entity from './src/Entity.js';
import * as Utility from './src/common/Utility.js';

import { FroggerCanvas } from './src/FroggerCanvas.js';
Expand Down Expand Up @@ -277,12 +279,28 @@
}
} );

// TODO: Remind me why AnimatedCanvas couldn't handle this directly?
const wrapper = document.getElementById( 'wrapper' );
window.onresize = ( e ) => {
const width = Math.floor( wrapper.clientWidth );
const height = Math.floor( wrapper.clientHeight );
canvas.ctx.scaleVal = height / 16; // TODO: Account for width < height?
canvas.setSize( width, height );
canvas.scale = height / 16;

TileMap.Rasterized.image = null;

for ( const type in Entity.Rasterized ) {
Entity.Rasterized[ type ] = null;
// const imgWidth = canvas.ctx.scaleVal * devicePixelRatio;
// const imgHeight = canvas.ctx.scaleVal * devicePixelRatio;

// Entity.Rasterized[ type ].image.width = imgWidth;
// Entity.Rasterized[ type ].image.height = imgHeight;
// Entity.Rasterized[ type ].needsRedraw = true;

// Entity.Rasterized[ type ].ctx.scale( imgWidth, imgHeight );
// Entity.Rasterized[ type ].ctx.translate( 0.5, 0.5 );
}
};
window.onresize();

Expand Down
2 changes: 1 addition & 1 deletion games/Frogger/levels/new/bridge.json
Original file line number Diff line number Diff line change
@@ -1 +1 @@
{"cols":15,"rows":15,"tileInfoKeys":["Grass","Water","Road","Bush","Lilypad"],"tiles":[1,1,0,0,0,0,3,0,3,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,1,1,4,1,1,1,4,1,1,1,4,1,1,0,0,0,1,1,1,4,1,1,1,4,1,1,1,1,0,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,0,3,1,1,1,1,3,0,3,1,1,1,1,3,0,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,0,1,1,1,1,1,1,1,1,1,1,1,1,0,0,1,1,1,1,1,1,1,1,1,1,1,4,1,1,3,1,4,1,0,0,3,0,0,3,0,1,1,4,1,0,4,1,1,0,0,0,0,3,0,0,0,1,1,1,1,1,1,0,3,0,0,3,0,0,3,0,1,1,1,1],"directions":[0,1,0,0,0,0,0,0,0,0,0,0,0,3,0,0,1,2,2,2,2,2,2,2,2,2,2,2,2,0,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,0,3,2,2,2,2,2,2,2,2,2,2,2,0,0,2,2,4,4,4,4,4,4,4,4,3,0,1,2,0,0,0,1,0,0,0,0,0,0,0,4,3,0,1,0,0,4,1,0,0,0,0,0,0,0,0,3,0,1,2,0,1,0,0,0,0,0,0,0,0,0,3,0,0,0],"entities":[{"type":"BlueCar","x":3,"y":6},{"type":"BlueCar","x":9,"y":6},{"type":"Froggy1","x":3,"y":0,"dir":3},{"type":"Froggy2","x":10,"y":0,"dir":3},{"type":"Froggy3","x":0,"y":7,"dir":4},{"type":"Froggy4","x":14,"y":7,"dir":2},{"type":"Froggy5","x":4,"y":13,"dir":1},{"type":"RedCar","x":12,"y":5},{"type":"RedCar","x":6,"y":5},{"type":"RedCar","x":1,"y":5},{"type":"LogMiddle","x":2,"y":2},{"type":"LogStart","x":7,"y":2},{"type":"LogStart","x":1,"y":2},{"type":"LogEnd","x":3,"y":2},{"type":"LogEnd","x":8,"y":2},{"type":"Turtle","x":3,"y":10},{"type":"Turtle","x":7,"y":10},{"type":"Turtle","x":11,"y":10},{"type":"Turtle","x":3,"y":11},{"type":"Turtle","x":7,"y":11},{"type":"Turtle","x":10,"y":12},{"type":"GreenCar","x":12,"y":8},{"type":"GreenCar","x":8,"y":8},{"type":"GreenCar","x":3,"y":8},{"type":"YellowCar","x":10,"y":9},{"type":"YellowCar","x":5,"y":9},{"type":"YellowCar","x":0,"y":9},{"type":"Turtle","x":13,"y":12},{"type":"Turtle","x":0,"y":11},{"type":"LogEnd","x":14,"y":2},{"type":"LogMiddle","x":13,"y":2},{"type":"LogStart","x":12,"y":2},{"type":"Turtle","x":13,"y":1},{"type":"Turtle","x":9,"y":1},{"type":"Turtle","x":5,"y":1},{"type":"Turtle","x":1,"y":1},{"type":"Turtle","x":1,"y":13},{"type":"Froggy6","x":8,"y":14}],"spawn":{"x":7,"y":7,"dir":0},"time":15000}
{"cols":15,"rows":15,"tileInfoKeys":["Grass","Water","Road","Bush","Lilypad"],"tiles":[1,1,0,0,0,0,3,0,3,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,1,1,4,1,1,1,4,1,1,1,4,1,1,0,0,0,1,1,1,4,1,1,1,4,1,1,1,1,0,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,0,3,1,1,1,1,3,0,3,1,1,1,1,3,0,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,0,1,1,1,1,1,1,1,1,1,1,1,1,0,0,1,1,1,1,1,1,1,1,1,1,1,4,1,1,3,1,4,1,0,0,3,0,0,3,0,1,1,4,1,0,4,1,1,0,0,0,0,3,0,0,0,1,1,1,1,1,1,0,3,0,0,3,0,0,3,0,1,1,1,1],"directions":[0,1,0,0,0,0,0,0,0,0,0,0,0,3,0,0,1,2,2,2,2,2,2,2,2,2,2,2,2,0,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,0,3,2,2,2,2,2,2,2,2,2,2,2,0,0,2,2,4,4,4,4,4,4,4,4,3,0,1,2,0,0,0,1,0,0,0,0,0,0,0,4,3,0,1,0,0,4,1,0,0,0,0,0,0,0,0,3,0,1,2,0,1,0,0,0,0,0,0,0,0,0,3,0,0,0],"entities":[{"type":"BlueCar","x":3,"y":6,"dir":4},{"type":"BlueCar","x":9,"y":6,"dir":4},{"type":"Froggy1","x":3,"y":0,"dir":3},{"type":"Froggy2","x":10,"y":0,"dir":3},{"type":"Froggy3","x":0,"y":7,"dir":4},{"type":"Froggy4","x":14,"y":7,"dir":2},{"type":"Froggy5","x":4,"y":13,"dir":1},{"type":"RedCar","x":12,"y":5,"dir":4},{"type":"RedCar","x":6,"y":5,"dir":4},{"type":"RedCar","x":1,"y":5,"dir":4},{"type":"LogMiddle","x":2,"y":2,"dir":4},{"type":"LogStart","x":7,"y":2,"dir":4},{"type":"LogStart","x":1,"y":2,"dir":4},{"type":"LogEnd","x":3,"y":2,"dir":4},{"type":"LogEnd","x":8,"y":2,"dir":4},{"type":"Turtle","x":3,"y":10,"dir":2},{"type":"Turtle","x":7,"y":10,"dir":2},{"type":"Turtle","x":11,"y":10,"dir":2},{"type":"Turtle","x":3,"y":11,"dir":4},{"type":"Turtle","x":7,"y":11,"dir":4},{"type":"Turtle","x":10,"y":12,"dir":4},{"type":"GreenCar","x":12,"y":8,"dir":2},{"type":"GreenCar","x":8,"y":8,"dir":2},{"type":"GreenCar","x":3,"y":8,"dir":2},{"type":"YellowCar","x":10,"y":9,"dir":2},{"type":"YellowCar","x":5,"y":9,"dir":2},{"type":"YellowCar","x":0,"y":9,"dir":2},{"type":"Turtle","x":0,"y":11,"dir":2},{"type":"LogEnd","x":14,"y":2,"dir":4},{"type":"LogMiddle","x":13,"y":2,"dir":4},{"type":"LogStart","x":12,"y":2,"dir":4},{"type":"Turtle","x":13,"y":1,"dir":2},{"type":"Turtle","x":9,"y":1,"dir":2},{"type":"Turtle","x":5,"y":1,"dir":2},{"type":"Turtle","x":1,"y":1,"dir":1},{"type":"Turtle","x":1,"y":13,"dir":4},{"type":"Froggy6","x":8,"y":14,"dir":1},{"type":"Turtle","x":13,"y":12,"dir":1}],"spawn":{"x":7,"y":7,"dir":1},"time":15000}
59 changes: 47 additions & 12 deletions games/Frogger/src/Entity.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,18 +13,53 @@ export const Dir = [
/*Right:*/ { x: 1, y: 0, angle: 0 , dist: ( x, y ) => Math.floor( x + 1 ) - x },
];

export class Entity {
static draw( entity, ctx, { dir, action, time } = {} ) {
ctx.save();
ctx.translate( entity.x, entity.y );
ctx.rotate( Dir[ ( dir > 0 ? dir : null ) ?? entity.dir ]?.angle ?? 0 );
// ctx.scale( entity.size, entity.size ); // nothing changes size for now

ctx.strokeStyle = 'black';
ctx.lineWidth = 0.02;

Entities[ entity.type ].draw( ctx, action ?? entity.animationAction, time ?? entity.animationTime ?? 0 );
export const Rasterized = {};

// TODO: Don't use {} for parameter here (save heap?)

// NOTE: Using 1.5 to give extra space for log center, animated frog legs, etc
const SIZE = 1.5;

export function draw( entity, ctx, { dir, action, time } = {} ) {
let rasterized = Rasterized[ entity.type ];

if ( !rasterized ) {
const image = new OffscreenCanvas( SIZE * ctx.scaleVal * devicePixelRatio, SIZE * ctx.scaleVal * devicePixelRatio );
const offscreenCtx = image.getContext( '2d' );

offscreenCtx.scale( image.width / SIZE, image.height / SIZE );
offscreenCtx.translate( SIZE / 2, SIZE / 2 );

offscreenCtx.strokeStyle = 'black';
offscreenCtx.lineWidth = 0.02;

rasterized = Rasterized[ entity.type ] = {
image: image,
ctx: offscreenCtx,
needsRedraw: true,
}
}

if ( rasterized.needsRedraw ) {
rasterized.ctx.clearRect( -SIZE / 2, -SIZE / 2, SIZE, SIZE );

Entities[ entity.type ].draw( rasterized.ctx, action ?? entity.animationAction, time ?? entity.animationTime ?? 0 );

rasterized.needsRedraw = false;
}

const rotate = Dir[ ( dir > 0 ? dir : null ) ?? entity.dir ]?.angle ?? 0;

ctx.restore();
ctx.translate( entity.x, entity.y );
ctx.rotate( rotate );
ctx.translate( -SIZE / 2, -SIZE / 2 );
ctx.scale( SIZE / rasterized.image.width, SIZE / rasterized.image.height );
{
ctx.drawImage( Rasterized[ entity.type ].image, 0, 0 );
}
ctx.scale( rasterized.image.width / SIZE, rasterized.image.height / SIZE );
ctx.translate( SIZE / 2, SIZE / 2 );
ctx.rotate( -rotate );
ctx.translate( -entity.x, -entity.y );

}
Loading

0 comments on commit 4fb841c

Please sign in to comment.