Skip to content

Instantly share code, notes, and snippets.

@tientq64
Created June 1, 2023 00:15
Show Gist options
  • Save tientq64/c363294758083e014845c49e181bf936 to your computer and use it in GitHub Desktop.
Save tientq64/c363294758083e014845c49e181bf936 to your computer and use it in GitHub Desktop.
[game] Copycat

[game] Copycat

Little update, I've updated the visuals during the translation and cleaning of the code :) Made with Diorama

A Pen by Tibo on CodePen.

License.

<!-- But it's a dark age, a dangerous time -->
// ----------
// Utility
// ----------
Util = {};
Util.timeStamp = function() {
return window.performance.now();
};
Util.random = function(min, max) {
return min + Math.random() * (max - min);
};
Util.array2D = function(tableau, largeur) {
var result = [];
for (var i = 0; i < tableau.length; i += largeur)
result.push(tableau.slice(i, i + largeur));
return result;
};
Util.toDio = function(array) {
let tab = array.map(x => {
if (x !== 0) {
return x - 1;
} else {
return x;
}
});
let render = Util.array2D(tab, 16);
return JSON.stringify(render);
};
Util.map = function(a, b, c, d, e) {
return (a - b) / (c - b) * (e - d) + d;
};
Util.lerp = function(value1, value2, amount) {
return value1 + (value2 - value1) * amount;
};
Util.linearTween = function(currentTime, start, degreeOfChange, duration) {
return degreeOfChange * currentTime / duration + start;
};
Util.easeInOutQuad = function(t, b, c, d) {
t /= d / 2;
if (t < 1) return c / 2 * t * t + b;
t--;
return -c / 2 * (t * (t - 2) - 1) + b;
};
Util.easeInOutExpo = function(t, b, c, d) {
t /= d / 2;
if (t < 1) return c / 2 * Math.pow(2, 10 * (t - 1)) + b;
t--;
return c / 2 * (-Math.pow(2, -10 * t) + 2) + b;
};
// ----------
// Scene
// ----------
class Scene {
constructor(name) {
this.name = name;
this.loop = true;
this.init_once = false;
}
giveWorld(world) {
this.world = world;
this.ctx = world.ctx;
}
keyEvents(event) {}
init() {}
render() {}
addEntity() {}
}
class Entity {
constructor(scene, x, y) {
this.scene = scene;
this.world = scene.world;
this.ctx = this.world.ctx;
this.body = new Body(this, x, y);
}
setSprite(sprite_data) {
this.sprite = new Sprite(this, sprite_data);
}
display() {
if (this.sprite === undefined) {
this.ctx.strokeStyle = "#000";
this.ctx.strokeRect(
this.body.position.x,
this.body.position.y,
this.body.size.x,
this.body.size.y
);
} else {
this.sprite.display();
}
}
integration() {
this.body.integration();
}
}
// class for animated sprites !
class Sprite {
constructor(entity, sprite_data) {
this.entity = entity;
this.world = this.entity.world;
this.tile_size = this.world.tile_size;
this.ctx = this.world.ctx;
// image data
this.image = this.world.assets.image[sprite_data.image].image;
// sprite
this.size = sprite_data.size;
this.current_frame = 0;
this.animations = {};
this.current_animation = undefined;
this.width = this.image.width / this.size.x;
this.height = this.image.height / this.size.y;
// timer
this.tick = 0;
this.speed = 0.2;
// offset
this.offset = {
x: 0,
y: 0
};
}
addAnimation(name, frames) {
this.animations[name] = frames;
this.current_animation = name;
}
animate(animation_name) {
this.current_animation = animation_name;
if (this.tick < 1) {
this.tick += this.speed;
} else {
this.tick = 0;
if (this.current_frame < this.animations[animation_name].length - 1) {
this.current_frame += 1;
} else {
this.current_frame = 0;
}
}
}
display() {
this.ctx.drawImage(
this.image,
Math.floor(
this.animations[this.current_animation][this.current_frame] % this.width
) * this.size.x,
Math.floor(
this.animations[this.current_animation][this.current_frame] / this.width
) * this.size.y,
this.size.x,
this.size.y,
this.entity.body.position.x +
(this.tile_size / 2 - this.size.x / 2) +
this.offset.x,
this.entity.body.position.y +
(this.tile_size / 2 - this.size.x / 2) +
this.offset.y,
this.size.x,
this.size.y
);
}
}
class Body {
constructor(entity, x, y) {
this.world = entity.world;
this.step = this.world.FPS.step;
this.position = new Vector(x, y);
this.next_position = new Vector(x, y);
this.velocity = new Vector(0, 0);
this.stepped_velocity = new Vector(0, 0);
this.acceleration = new Vector(0, 0);
this.drag = 0.98;
this.size = {
x: 16,
y: 16
};
}
setSize(x, y) {
this.size.x = x;
this.size.y = y;
}
updateVelocity() {
this.velocity.add(this.acceleration);
this.velocity.mult(this.drag);
this.stepped_velocity = this.velocity.copy();
this.stepped_velocity.mult(this.step);
this.next_position = this.position.copy();
this.next_position.add(this.stepped_velocity);
// reset acceleration
this.acceleration.mult(0);
}
updatePosition() {
this.position.add(this.stepped_velocity);
}
integration() {
this.updateVelocity();
this.updatePosition();
}
applyForce(force_vector) {
this.acceleration.add(force_vector);
}
}
class Vector {
constructor(x, y) {
this.x = x || 0;
this.y = y || 0;
}
set(x, y) {
this.x = x;
this.y = y;
}
add(vector) {
this.x += vector.x;
this.y += vector.y;
}
sub(vector) {
this.x -= vector.x;
this.y -= vector.y;
}
mult(scalar) {
this.x *= scalar;
this.y *= scalar;
}
div(scalar) {
this.x /= scalar;
this.y /= scalar;
}
limit(limit_value) {
if (this.mag() > limit_value) this.setMag(limit_value);
}
mag() {
return Math.hypot(this.x, this.y);
}
setMag(new_mag) {
if (this.mag() > 0) {
this.normalize();
} else {
this.x = 1;
this.y = 0;
}
this.mult(new_mag);
}
dist(vector) {
return new Vector(this.x - vector.x, this.y - vector.y).mag();
}
normalize() {
let mag = this.mag();
if (mag > 0) {
this.x /= mag;
this.y /= mag;
}
}
heading() {
return Math.atan2(this.x, this.y);
}
setHeading(angle) {
let mag = this.mag();
this.x = Math.cos(angle) * mag;
this.y = Math.sin(angle) * mag;
}
copy() {
return new Vector(this.x, this.y);
}
}
class Box {
constructor(world, box_data) {
this.world = world;
this.ctx = world.ctx;
this.c_ctx = world.c_ctx;
this.box_data = box_data;
this.resolution = box_data.resolution;
this.image = world.assets.image[box_data.image].image;
}
display(x, y, width, height) {
// background
this.ctx.fillRect(x + 1, y + 1, width - 2, height - 2);
// corners
this.ctx.lineWidth = 2;
let coners = [0, 2, 6, 8];
for (let i = 0; i < 4; i++) {
let pos_x = x + Math.floor(i % 2) * (width - this.resolution),
pos_y = y + Math.floor(i / 2) * (height - this.resolution);
let clip_x = Math.floor(i % 2) * (this.resolution * 2),
clip_y = Math.floor(i / 2) * (this.resolution * 2);
this.ctx.drawImage(
this.image,
clip_x,
clip_y,
this.resolution,
this.resolution,
pos_x,
pos_y,
this.resolution,
this.resolution
);
}
let offset = this.resolution * 3;
// top
this.ctx.drawImage(
this.image,
8,
0,
this.resolution,
this.resolution,
x + 8,
y,
this.resolution + width - offset,
this.resolution
);
// bottom
this.ctx.drawImage(
this.image,
8,
16,
this.resolution,
this.resolution,
x + 8,
y + height - this.resolution,
this.resolution + width - offset,
this.resolution
);
// left
this.ctx.drawImage(
this.image,
0,
8,
this.resolution,
this.resolution,
x,
y + 8,
this.resolution,
this.resolution + height - offset
);
// right
this.ctx.drawImage(
this.image,
16,
8,
this.resolution,
this.resolution,
x + width - this.resolution,
y + this.resolution,
this.resolution,
this.resolution + height - offset
);
}
}
// ----------
// 🕹️ Diorama.js
// ----------
class Diorama {
constructor(parameters) {
this.parameters = parameters;
// Game and author's name
this.game_info = {
name: parameters.name || "Untitled",
author: parameters.author || "Anonymous"
};
// canvas
this.background_color = parameters.background_color || "#000";
this.initCanvas(parameters);
// Assets
this.counter = 0;
this.toLoad = parameters.assets.length;
this.assets = {
image: {},
audio: {}
};
this.audio_muted = false;
// keyboard event
this.keys = {};
// Scenes
this.scenes = {};
this.start_screen = parameters.start_screen || undefined;
this.current_scene = "";
// Bitmap font Data
this.alphabet =
"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789 ?!:',.()<>[]";
this.fonts = {};
// Maps
this.tile_size = parameters.tile_size || 16;
this.tiles_data = {};
if (parameters.tiles !== undefined) {
parameters.tiles.map(tile => {
this.tiles_data[tile.id] = tile;
});
}
this.mapsMax = parameters.maps.length;
this.maps = {};
if (parameters.maps !== undefined) {
parameters.maps.map(map => {
this.maps[map.name] = map;
});
}
// Box system
this.boxes = {};
// By default the current font is the first font you create
this.currentFont = undefined;
// Game loop Data
this.FPS = {
now: 0,
delta: 0,
last: Util.timeStamp(),
step: 1 / (parameters.frame_rate || 60)
};
this.requestChange = {
value: false,
action: ""
};
this.main_loop = undefined;
}
// ---
// Setup & Loading
// ---
ready() {
this.loadAssets(this.parameters.assets);
}
initCanvas(parameters) {
this.canvas = document.createElement("canvas");
this.ctx = this.canvas.getContext("2d");
this.W = this.canvas.width = parameters.width || 256;
this.H = this.canvas.height = parameters.height || 256;
this.scale = parameters.scale || 1;
this.full = false;
this.ctx.imageSmoothingEnabled = false;
this.canvas.classList.add("crisp");
document.body.appendChild(this.canvas);
// cache canvas
this.cache = document.createElement("canvas");
this.c_ctx = this.cache.getContext("2d");
}
loader() {
// increment loader
this.clear("#222");
this.counter += 1;
let padding = 20;
let width = this.W - padding * 2,
x = padding,
y = this.H - padding * 2;
this.ctx.fillStyle = "#111";
this.ctx.fillRect(x, y, width, 20);
this.ctx.fillStyle = "#333";
this.ctx.fillRect(x, y, this.counter * width / this.toLoad, 20);
this.ctx.strokeStyle = "#000";
this.ctx.lineWidth = 4;
this.ctx.strokeRect(x, y, width, 20);
if (this.counter === this.toLoad) {
this.launch();
}
}
loadAssets(assets) {
if (assets === undefined) console.log("Nothing to load");
assets.map(obj => this.checkAssets(obj));
}
checkAssets(obj) {
let subject = obj;
switch (obj.type) {
case "img":
let img = new Image();
img.onload = () => {
this.loader();
};
img.onerror = () => {
console.log("can't load Image: " + obj.name);
};
img.src = obj.path;
subject.image = img;
this.assets.image[obj.name] = subject;
break;
case "audio":
let audio = new Audio(obj.path);
audio.addEventListener("canplaythrough", this.loader());
audio.onerror = () => {
console.log("can't load audio: " + obj.name);
};
subject.audio = audio;
this.assets.audio[obj.name] = subject;
break;
case undefined:
console.log(obj.name, " doesn't have any type");
break;
default:
console.log(obj.name, " has a none known type");
}
}
launch() {
this.eventSetup();
this.initBoxes(this.parameters.boxes);
this.initFonts(this.parameters.fonts);
this.startScene(this.start_screen);
}
initBoxes(boxes_data) {
if (boxes_data === undefined) return false;
boxes_data.map(box => {
this.boxes[box.name] = new Box(this, box);
});
}
drawBox(box_name, x, y, width, height) {
this.boxes[box_name].display(x, y, width, height);
}
// ---
// Font manager
// ---
setFont(font_name) {
this.currentFont = font_name;
}
initFonts(fonts_data) {
if (fonts_data === undefined && fonts_data.length > 0) return false;
fonts_data.map(font => {
if (this.assets.image[font.image] === undefined) {
console.log("can't load font, " + font.image + " doesn't exist");
return false;
}
font.image = this.assets.image[font.image].image;
this.fonts[font.name] = font;
});
// set current font to the first font !
this.currentFont = Object.keys(this.fonts)[0];
}
write(text, x, y, justify, colorID) {
if (this.currentFont === undefined) {
console.log("No bitmap_font");
return false;
}
if (typeof justify === "string") {
switch (justify) {
case "center":
x -= text.length * this.fonts[this.currentFont].size.x / 2;
break;
case "right":
x -= text.length * this.fonts[this.currentFont].size.x;
break;
default:
}
this.writeLine(text, x, y, colorID || 0);
} else {
this.writeParagraph(text, x, y, justify, colorID || 0);
}
}
writeParagraph(text, x, y, justify, colorID) {
let y_offset = 0,
line_height = this.fonts[this.currentFont].size.y + 5,
size_x = this.fonts[this.currentFont].size.x,
words = text.split(" "),
line = "";
for (let i = 0; i < words.length; i++) {
line += words[i] + " ";
let nextword_width = 0,
next_word = words[i + 1],
line_length = line.length * size_x;
next_word ? (nextword_width = next_word.length * size_x) : 0;
if (line_length + nextword_width > justify) {
this.writeLine(line, x, y + y_offset, 0, colorID);
y_offset += line_height;
line = "";
} else {
this.writeLine(line, x, y + y_offset, 0, colorID);
}
}
}
writeLine(text, x, y, colorID) {
// write line
let size_x = this.fonts[this.currentFont].size.x,
size_y = this.fonts[this.currentFont].size.y,
font_img = this.fonts[this.currentFont].image;
for (let i = 0; i < text.length; i++) {
let index = this.alphabet.indexOf(text.charAt(i)),
clipX = size_x * index,
posX = x + i * size_x;
this.ctx.drawImage(
font_img,
clipX,
colorID * size_y,
size_x,
size_y,
posX,
y,
size_x,
size_y
);
}
}
// -----------------
// Events
// -----------------
eventSetup() {
document.addEventListener("keydown", event => this.keyDown(event), false);
document.addEventListener("keyup", event => this.keyUp(event), false);
}
keyDown(event) {
event.preventDefault();
this.keys[event.code] = true;
if (this.keys.KeyF) {
this.fullScreen();
}
if (this.keys.KeyM) {
this.mute();
}
this.current_scene.keyEvents(event);
}
keyUp(event) {
event.preventDefault();
this.keys[event.code] = false;
}
// ---
// Scene Manager
// ---
startScene(scene_name) {
// check if the scene exist
if (this.scenes[scene_name] === undefined)
return scene_name + " - doesn't exist";
// request the change of scene if this.main_loop is active
if (this.main_loop !== undefined) {
this.requestChange.value = true;
this.requestChange.action = scene_name;
return false;
}
this.requestChange.value = false;
this.requestChange.action = "";
this.FPS.last = Util.timeStamp();
this.current_scene = this.scenes[scene_name];
this.initScene();
// does this scenes needs a gameloop ?
if (this.current_scene.loop === true) {
this.gameLoop();
} else {
this.mainRender();
}
}
initScene() {
if (this.current_scene.init_once) return false;
this.current_scene.init();
}
addScene(scene) {
// links this world to this scene
scene.giveWorld(this);
this.scenes[scene.name] = scene;
}
// ---
// Main Loop
// ---
mainRender() {
this.clear();
this.current_scene.render();
}
loopCheck() {
if (this.requestChange.value === false) {
this.main_loop = requestAnimationFrame(() => this.gameLoop());
} else {
cancelAnimationFrame(this.main_loop);
this.main_loop = undefined;
this.startScene(this.requestChange.action);
}
}
gameLoop() {
this.FPS.now = Util.timeStamp();
this.FPS.delta += Math.min(1, (this.FPS.now - this.FPS.last) / 1000);
while (this.FPS.delta > this.FPS.step) {
this.FPS.delta -= this.FPS.step;
this.mainRender();
}
this.FPS.last = this.FPS.now;
this.loopCheck();
}
// Basic functions
soundLevel(volume) {
for (let [k, v] of Object.entries(this.assets.audio)) {
v.audio.volume = volume;
}
}
mute() {
this.audio_muted = !this.audio_muted;
for (let [k, v] of Object.entries(this.assets.audio)) {
v.audio.muted = this.audio_muted;
}
}
clear(custom_color) {
this.ctx.fillStyle = custom_color || this.background_color;
this.ctx.fillRect(0, 0, this.W, this.H);
}
setScale() {
this.canvas.style.width = this.W * this.scale + "px";
this.canvas.style.height = this.H * this.scale + "px";
}
fullScreen() {
this.full = !this.full;
if (!this.full) {
this.setScale();
} else {
this.canvas.style.width = "100%";
this.canvas.style.height = "100%";
}
}
// ---
// Tile map
// ---
getTile(layer_id, x, y) {
if (x < 0 || x > this.terrain.layers[layer_id].size.x - 1) return false;
if (y < 0 || y > this.terrain.layers[layer_id].size.y - 1) return false;
let tile = this.tiles_data[this.terrain.layers[layer_id].geometry[y][x]];
if (tile === undefined) return false;
return tile;
}
findTile(layer_id, tile_id) {
let layer = this.terrain.layers[layer_id];
let result = [];
for (let y = 0; y < layer.size.y; y++) {
for (let x = 0; x < layer.size.x; x++) {
let id = layer.geometry[y][x];
if (id === tile_id) {
result.push({ x: x, y: y });
}
}
}
return result;
}
initMap(map_name) {
this.terrain = JSON.parse(JSON.stringify(this.maps[map_name]));
// give size to layers
for (var i = 0; i < this.terrain.layers.length; i++) {
this.terrain.layers[i].size = {
x: this.terrain.layers[i].geometry[0].length,
y: this.terrain.layers[i].geometry.length
};
}
this.terrain.tileset = this.assets.image[this.maps[map_name].tileset].image;
this.terrain.tileset_size = {
width: this.terrain.tileset.width / this.tile_size,
height: this.terrain.tileset.height / this.tile_size + 1
};
this.terrain.layers.forEach((layer, index) => {
this.marchingSquare(layer);
this.bitMasking(layer);
// create a cache for reducing draw call in the gameLoop
this.terrainCache(layer);
// prepare animated tiles
layer.animated = [];
for (var id in this.tiles_data) {
if (this.tiles_data[id].animated === true) {
let tiles = this.findTile(index, parseInt(id));
layer.animated.push({
id: id,
spritesheet: this.assets.image[this.tiles_data[id].spritesheet]
.image,
positions: tiles,
current: 0,
steps: this.tiles_data[id].steps,
max_frame:
this.assets.image[this.tiles_data[id].spritesheet].image.width /
this.tile_size
});
}
}
});
this.clear("black");
}
terrainCache(layer) {
layer.cache = {};
let c = (layer.cache.c = document.createElement("canvas"));
let ctx = (layer.cache.ctx = layer.cache.c.getContext("2d"));
let W = (c.width = layer.size.x * this.tile_size),
H = (c.height = layer.size.y * this.tile_size);
// Draw on cache
this.ctx.clearRect(0, 0, W, H);
this.drawLayer(layer);
ctx.drawImage(this.canvas, 0, 0);
this.clear();
}
marchingSquare(layer) {
layer.square = [];
for (let y = 0; y < layer.size.y; y++) {
for (let x = 0; x < layer.size.x; x++) {
let p1 = 0,
p2 = 0,
p3 = 0,
p4 = 0;
if (y + 1 < layer.size.y && x + 1 < layer.size.x) {
p1 = layer.geometry[y][x];
p2 = layer.geometry[y][x + 1];
p3 = layer.geometry[y + 1][x + 1];
p4 = layer.geometry[y + 1][x];
}
let id = p1 * 8 + p2 * 4 + p3 * 2 + p4;
layer.square.push(id);
}
}
layer.square = Util.array2D(layer.square, layer.size.x);
}
bitMasking(layer) {
layer.bitMask = [];
for (let y = 0; y < layer.size.y; y++) {
for (let x = 0; x < layer.size.x; x++) {
let id = layer.geometry[y][x];
let neighbor = [0, 0, 0, 0];
if (y - 1 > -1) {
if (id === layer.geometry[y - 1][x]) {
//top
neighbor[0] = 1;
}
} else {
neighbor[0] = 1;
}
if (x - 1 > -1) {
if (id === layer.geometry[y][x - 1]) {
// left
neighbor[1] = 1;
}
} else {
neighbor[1] = 1;
}
if (x + 1 < layer.size.x) {
if (id === layer.geometry[y][x + 1]) {
// right
neighbor[2] = 1;
}
} else {
neighbor[2] = 1;
}
if (y + 1 < layer.size.y) {
if (id === layer.geometry[y + 1][x]) {
//down
neighbor[3] = 1;
}
} else {
neighbor[3] = 1;
}
id =
1 * neighbor[0] + 2 * neighbor[1] + 4 * neighbor[2] + 8 * neighbor[3];
layer.bitMask.push(id);
}
}
layer.bitMask = Util.array2D(layer.bitMask, layer.size.x);
}
renderMap() {
this.terrain.layers.forEach(layer => {
this.ctx.drawImage(layer.cache.c, 0, 0);
// draw animated layer
layer.animated.forEach(tile => {
if (tile.current < tile.max_frame - 1) {
tile.current += tile.steps;
} else {
tile.current = 0;
}
// render animated tiles
tile.positions.forEach(position => {
let x = position.x * this.tile_size,
y = position.y * this.tile_size;
this.ctx.drawImage(
tile.spritesheet,
Math.floor(tile.current) * this.tile_size,
0,
this.tile_size,
this.tile_size,
x,
y,
this.tile_size,
this.tile_size
);
});
});
});
}
drawMap() {
this.terrain.layers.forEach(layer => {
this.drawLayer(layer);
});
}
drawLayer(layer) {
for (let y = 0; y < layer.size.y; y++) {
for (let x = 0; x < layer.size.x; x++) {
// ID of the tile
let id = layer.geometry[y][x];
// Don't draw invisible tiles
// Position of the tile :)
let positionX = x * this.tile_size + layer.offset.x,
positionY = y * this.tile_size + layer.offset.y;
let sourceX =
Math.floor(id % this.terrain.tileset_size.width) * this.tile_size,
sourceY =
Math.floor(id / this.terrain.tileset_size.width) * this.tile_size;
if (this.tiles_data[id] && this.tiles_data[id].look === "bitmask") {
sourceX = Math.floor(layer.bitMask[y][x]) * this.tile_size;
sourceY = this.tiles_data[id].line * this.tile_size;
}
if (layer.look === "square") {
if (layer.square[y][x] === 0) continue;
positionX += this.tile_size / 2;
positionY += this.tile_size / 2;
sourceX = Math.floor(layer.square[y][x] % 16) * 16;
sourceY = 7 * this.tile_size;
}
if (this.tiles_data[id] && this.tiles_data[id].animated === true) {
// hide animated sprites on the cache
continue;
}
// render tile
this.ctx.drawImage(
this.terrain.tileset,
sourceX,
sourceY,
this.tile_size,
this.tile_size,
positionX,
positionY,
this.tile_size,
this.tile_size
);
}
}
}
}
let parameters = {
name: "Copycat",
start_screen: "menu",
background_color: "#223d8c",
width: 256,
height: 256,
tile_size: 16,
assets: [
// Images
{
type: "img",
name: "coderscrux_font",
path: "https://image.ibb.co/fCOd7T/coderscrux_font.png"
},
{
type: "img",
name: "controls",
path: "https://image.ibb.co/nApwu8/controls.png"
},
{
type: "img",
name: "player_sprite",
path: "https://image.ibb.co/co3NZ8/player.png"
},
{
type: "img",
name: "spawn_effect",
path: "https://image.ibb.co/njVQnT/spawn_effect.png"
},
{
type: "img",
name: "water_splash",
path: "https://image.ibb.co/jm7hZ8/water_splash.png"
},
{
type: "img",
name: "shadow",
path: "https://image.ibb.co/djchZ8/shadow.png"
},
{
type: "img",
name: "main_title",
path: "https://image.ibb.co/mrBLMo/main_title.png"
},
{
type: "img",
name: "origami_dark",
path: "https://image.ibb.co/gzk2Z8/origami_dark.png"
},
{
type: "img",
name: "origami_light",
path: "https://image.ibb.co/jruknT/origami_light.png"
},
{
type: "img",
name: "box_texture",
path: "https://image.ibb.co/kpO0Mo/box.png"
},
{
type: "img",
name: "selection",
path: "https://image.ibb.co/fmJpE8/selection.png"
},
{
type: "img",
name: "flat_frame",
path: "https://image.ibb.co/hqSugo/flat_frame.png"
},
{
type: "img",
name: "pattern",
path: "https://image.ibb.co/cv02Z8/pattern.png"
},
{
type: "img",
name: "cursor",
path: "https://image.ibb.co/bFiNZ8/cursor.png"
},
{
type: "img",
name: "demo_tileset",
path: "https://image.ibb.co/b8rLMo/demo_tileset.png"
},
{
type: "img",
name: "exit",
path: "https://image.ibb.co/esCS1o/exit.png"
},
{
type: "img",
name: "water_sprite",
path: "https://image.ibb.co/cSFEgo/water_sprite.png"
},
{
type: "img",
name: "dust_effect",
path: "https://image.ibb.co/mKy0Mo/dust.png"
},
// Audio
{
type: "audio",
name: "jingle",
path: "http://www.noiseforfun.com/waves/musical-and-jingles/NFF-bravo.wav"
},
{
type: "audio",
name: "mouvement",
path:
"http://www.noiseforfun.com/waves/interface-and-media/NFF-select-04.wav"
},
{
type: "audio",
name: "selection",
path:
"http://www.noiseforfun.com/waves/interface-and-media/NFF-select.wav"
},
{
type: "audio",
name: "apparition",
path:
"http://www.noiseforfun.com/waves/interface-and-media/NFF-bubble-input.wav"
},
{
type: "audio",
name: "eboulement",
path:
"http://www.noiseforfun.com/waves/action-and-game/NFF-moving-block.wav"
},
{
type: "audio",
name: "splash",
path:
"http://www.noiseforfun.com/waves/action-and-game/NFF-mud-splash.wav"
},
{
type: "audio",
name: "bump",
path: "http://www.noiseforfun.com/waves/action-and-game/NFF-bump.wav"
}
// Bitmap font
],
fonts: [
// basic font
{
name: "coderscrux",
image: "coderscrux_font",
size: { x: 6, y: 9 }
},
{
name: "origami_dark",
image: "origami_dark",
size: { x: 8, y: 9 }
},
{
name: "origami_light",
image: "origami_light",
size: { x: 8, y: 9 }
}
],
// box system
boxes: [
{
name: "box",
resolution: 8,
image: "box_texture"
},
{
name: "selection",
resolution: 8,
image: "selection"
},
{
name: "flat_frame",
resolution: 8,
image: "flat_frame"
}
],
tiles: [
{ name: "empty", id: 0, collision: false, visibility: false },
{ name: "water", id: 1, collision: false, look: "square", line: 7 },
{ name: "shores", id: 2, collision: false, look: "bitmask", line: 6 },
{ name: "ground", id: 3, collision: false, look: "bitmask", line: 1 },
{ name: "wall", id: 4, collision: true, look: "bitmask", line: 2 },
{ name: "fence", id: 11, collision: true, look: "bitmask", line: 4 },
{ name: "bush", id: 5, collision: true },
{ name: "ice", id: 6, collision: false, look: "bitmask", line: 3 },
{ name: "spawn", id: 7, collision: false },
{
name: "exit",
id: 8,
collision: false,
animated: true,
spritesheet: "exit",
steps: 0.4
},
{
name: "waves",
id: 16,
collision: false,
animated: true,
spritesheet: "water_sprite",
steps: 0.2
},
{ name: "trap", id: 9, collision: false },
{ name: "hole", id: 10, collision: true },
// arrows
{ name: "arrowLeft", id: 12, collision: false },
{ name: "arrowUp", id: 13, collision: false },
{ name: "arrowRight", id: 14, collision: false },
{ name: "arrowDown", id: 15, collision: false }
],
maps: [
// map 1
{
name: "map_1",
tileset: "demo_tileset",
// ground
layers: [
// ground layer
{
name: "ground",
offset: {
x: 0,
y: 4
},
geometry: [
[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, 0, 0],
[0, 0, 3, 3, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 3, 3, 0, 0, 0, 3, 3, 3, 3, 3, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 3, 3, 3, 3, 3, 3, 3, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 3, 3, 3, 3, 3, 3, 3, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 3, 3, 3, 3, 3, 3, 3, 3, 3, 0, 0],
[0, 0, 0, 0, 0, 0, 3, 3, 3, 3, 3, 3, 3, 3, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 3, 3, 3, 3, 3, 3, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 3, 3, 3, 3, 3, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 3, 3, 3, 3, 3, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 3, 3, 3, 3, 3, 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, 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, 0, 0, 0, 0, 0, 0, 0, 0]
]
},
// ice / arrows / layer
{
name: "onGround",
offset: {
x: 0,
y: 0
},
geometry: [
[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, 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, 11, 11, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 11, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 8, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 5, 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, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 5, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 5, 5, 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, 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, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
]
}
// wall layer
]
},
// map 2
{
name: "map_2",
tileset: "demo_tileset",
// ground
layers: [
// ground layer
{
name: "ground",
offset: {
x: 0,
y: 4
},
geometry: [
[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, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 3, 3, 3, 3, 3, 0, 0, 3, 3, 3, 3, 3, 0, 0],
[0, 0, 3, 3, 3, 3, 3, 0, 0, 3, 3, 3, 3, 3, 0, 0],
[0, 0, 3, 3, 3, 3, 3, 0, 0, 3, 3, 3, 3, 3, 0, 0],
[0, 0, 3, 3, 3, 3, 3, 0, 0, 3, 3, 3, 3, 3, 0, 0],
[0, 0, 3, 3, 3, 3, 3, 0, 0, 3, 3, 3, 3, 3, 0, 0],
[0, 0, 3, 3, 3, 3, 3, 0, 0, 3, 3, 3, 3, 3, 0, 0],
[0, 0, 3, 3, 3, 3, 3, 0, 0, 3, 3, 3, 3, 3, 0, 0],
[0, 0, 3, 3, 3, 3, 3, 0, 0, 3, 3, 3, 3, 3, 0, 0],
[0, 0, 3, 3, 3, 3, 3, 0, 0, 3, 3, 3, 3, 3, 0, 0],
[0, 0, 3, 3, 3, 3, 3, 0, 0, 3, 3, 3, 3, 3, 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, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
]
},
// ice / arrows / layer
{
name: "onGround",
offset: {
x: 0,
y: 0
},
geometry: [
[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, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 11, 11, 11, 11, 11, 0, 0, 11, 11, 11, 11, 11, 0, 0],
[0, 0, 11, 0, 0, 0, 11, 0, 0, 11, 0, 0, 0, 11, 0, 0],
[0, 0, 11, 0, 8, 0, 11, 0, 0, 11, 0, 8, 0, 11, 0, 0],
[0, 0, 11, 0, 0, 0, 11, 0, 0, 11, 0, 0, 0, 11, 0, 0],
[0, 0, 11, 0, 0, 0, 11, 0, 0, 11, 0, 0, 0, 11, 0, 0],
[0, 0, 11, 4, 4, 0, 11, 0, 0, 11, 0, 0, 0, 11, 0, 0],
[0, 0, 11, 0, 0, 0, 11, 0, 0, 11, 0, 4, 4, 11, 0, 0],
[0, 0, 11, 0, 0, 0, 11, 0, 0, 11, 0, 0, 0, 11, 0, 0],
[0, 0, 11, 0, 7, 0, 11, 0, 0, 11, 0, 7, 0, 11, 0, 0],
[0, 0, 11, 11, 11, 11, 11, 0, 0, 11, 11, 11, 11, 11, 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, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
]
}
// wall layer
]
},
{
name: "map_3",
tileset: "demo_tileset",
// ground
layers: [
// ground layer
{
name: "ground",
offset: {
x: 0,
y: 4
},
geometry: [
[3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3],
[3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3],
[3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3],
[3, 3, 3, 3, 3, 3, 3, 0, 0, 3, 3, 3, 3, 3, 3, 3],
[3, 3, 3, 3, 3, 3, 3, 0, 0, 3, 3, 3, 3, 3, 3, 3],
[3, 3, 3, 3, 3, 3, 3, 0, 0, 3, 3, 3, 3, 3, 3, 3],
[3, 3, 3, 3, 3, 3, 3, 0, 0, 3, 3, 3, 3, 3, 3, 3],
[3, 3, 3, 3, 3, 3, 3, 0, 0, 3, 3, 3, 3, 3, 3, 3],
[3, 3, 3, 3, 3, 3, 3, 0, 0, 3, 3, 3, 3, 3, 3, 3],
[3, 3, 3, 3, 3, 3, 3, 0, 0, 3, 3, 3, 3, 3, 3, 3],
[3, 3, 3, 3, 3, 3, 3, 0, 0, 3, 3, 3, 3, 3, 3, 3],
[3, 3, 3, 3, 3, 3, 3, 0, 0, 3, 3, 3, 3, 3, 3, 3],
[3, 3, 3, 3, 3, 3, 3, 0, 0, 3, 3, 3, 3, 3, 3, 3],
[3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3],
[3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3],
[3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3]
]
},
// ice / arrows / layer
{
name: "onGround",
offset: {
x: 0,
y: 0
},
geometry: [
[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, 0, 0],
[0, 0, 4, 4, 4, 4, 4, 0, 0, 4, 4, 4, 4, 4, 0, 0],
[0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0],
[0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0],
[0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 8, 0, 0, 4, 0, 0],
[0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0],
[0, 0, 4, 8, 0, 0, 0, 0, 0, 0, 5, 0, 0, 4, 0, 0],
[0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0],
[0, 0, 4, 11, 11, 0, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0],
[0, 0, 4, 7, 0, 0, 0, 0, 0, 0, 0, 7, 0, 4, 0, 0],
[0, 0, 4, 4, 4, 4, 4, 0, 0, 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, 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, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
]
}
// wall layer
]
},
{
name: "map_4",
tileset: "demo_tileset",
// ground
layers: [
// ground layer
{
name: "ground",
offset: {
x: 0,
y: 4
},
geometry: [
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 3, 3, 3, 3, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 3, 3, 3, 3, 3, 3, 3, 3, 0, 0, 0],
[0, 0, 0, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 0, 0],
[0, 0, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 0, 0],
[0, 0, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 0, 0],
[0, 0, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 0, 0],
[0, 0, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 0, 0],
[0, 0, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 0, 0],
[0, 0, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 0, 0],
[0, 0, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 0, 0],
[0, 0, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 0, 0, 0],
[0, 0, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 0, 0, 0, 0],
[0, 0, 0, 3, 3, 3, 3, 3, 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, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
]
},
// ice / arrows / layer
{
name: "onGround",
offset: {
x: 0,
y: 0
},
geometry: [
[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, 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, 11, 11, 11, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 11, 0, 11, 0, 0, 0],
[0, 0, 0, 0, 11, 11, 11, 0, 0, 0, 11, 0, 11, 0, 0, 0],
[0, 0, 0, 0, 11, 0, 11, 0, 0, 0, 11, 0, 11, 0, 0, 0],
[0, 0, 0, 0, 11, 0, 11, 0, 0, 0, 11, 9, 11, 0, 0, 0],
[0, 0, 0, 0, 11, 8, 11, 0, 0, 0, 11, 8, 11, 0, 0, 0],
[0, 0, 0, 0, 11, 0, 11, 0, 0, 0, 11, 7, 11, 0, 0, 0],
[0, 0, 0, 0, 11, 0, 11, 0, 0, 0, 11, 0, 11, 0, 0, 0],
[0, 0, 0, 0, 11, 0, 11, 5, 0, 0, 11, 11, 11, 0, 0, 0],
[0, 0, 0, 0, 11, 7, 11, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 11, 11, 11, 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, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
]
} // wall layer
]
},
{
name: "map_5",
tileset: "demo_tileset",
// ground
layers: [
// ground layer
{
name: "ground",
offset: {
x: 0,
y: 4
},
geometry: [
[3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3],
[3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3],
[3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3],
[3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3],
[3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3],
[3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3],
[3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3],
[3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3],
[3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3],
[3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3],
[3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3],
[3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3],
[3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3],
[3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3],
[3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3],
[3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3]
]
},
// ice / arrows / layer
{
name: "onGround",
offset: {
x: 0,
y: 0
},
geometry: [
[4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4],
[4, 0, 5, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4],
[4, 0, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4],
[4, 4, 4, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 4, 4, 4],
[4, 4, 4, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 4, 4, 4],
[4, 4, 4, 6, 5, 6, 6, 6, 6, 6, 6, 6, 6, 4, 4, 4],
[4, 4, 4, 6, 6, 6, 6, 6, 6, 6, 6, 5, 6, 4, 4, 4],
[4, 4, 4, 6, 6, 6, 6, 6, 4, 6, 6, 6, 6, 8, 4, 4],
[4, 4, 4, 6, 6, 4, 6, 6, 4, 6, 4, 4, 6, 4, 4, 4],
[4, 4, 4, 4, 6, 6, 6, 6, 4, 6, 6, 6, 6, 4, 4, 4],
[4, 4, 4, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 4, 4, 4],
[4, 4, 4, 6, 6, 6, 5, 6, 6, 6, 6, 0, 0, 4, 4, 4],
[4, 4, 4, 6, 6, 6, 6, 6, 6, 6, 6, 0, 7, 4, 4, 4],
[4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 0, 4],
[4, 5, 0, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 0, 4],
[4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4]
]
} // wall layer
]
},
{
name: "map_6",
tileset: "demo_tileset",
// ground
layers: [
// ground layer
{
name: "ground",
offset: {
x: 0,
y: 4
},
geometry: [
[3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3],
[3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3],
[3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3],
[3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3],
[3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3],
[3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3],
[3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3],
[3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3],
[3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3],
[3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3],
[3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3],
[3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3],
[3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3],
[3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3],
[3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3],
[3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3]
]
},
// ice / arrows / layer
{
name: "onGround",
offset: {
x: 0,
y: 0
},
geometry: [
[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, 4, 4],
[4, 4, 4, 6, 6, 14, 0, 6, 6, 6, 6, 15, 4, 4, 4, 4],
[4, 4, 4, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 4, 4, 4],
[4, 4, 4, 6, 6, 6, 8, 6, 6, 6, 4, 6, 6, 4, 4, 4],
[4, 4, 4, 0, 6, 6, 6, 6, 6, 6, 6, 6, 6, 4, 4, 4],
[4, 4, 4, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 4, 4, 4],
[4, 4, 4, 6, 6, 6, 6, 6, 6, 6, 6, 7, 6, 4, 4, 4],
[4, 4, 4, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 4, 4, 4],
[4, 4, 4, 6, 6, 14, 6, 6, 6, 6, 6, 6, 4, 4, 4, 4],
[4, 4, 4, 6, 6, 6, 6, 6, 6, 6, 0, 6, 4, 4, 4, 4],
[4, 4, 4, 6, 6, 0, 6, 6, 6, 6, 6, 6, 6, 4, 4, 4],
[4, 4, 4, 6, 6, 6, 6, 6, 6, 6, 6, 8, 13, 4, 4, 4],
[4, 4, 4, 6, 6, 6, 7, 6, 6, 6, 6, 6, 6, 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, 4, 4, 4, 4, 4]
]
} // wall layer
]
}
]
};
// Don't mind me
// just too lazy to modify the maps by hand
parameters.maps.forEach(map => {
new_layer = {};
new_layer.name = "water";
new_layer.look = "square";
new_layer.offset = { x: 0, y: 8 };
new_layer.geometry = Array(16)
.fill()
.map(() => Array(16).fill(0));
map.layers.unshift(new_layer);
//
new_layer = {};
new_layer.name = "splash";
new_layer.offset = { x: 0, y: 8 };
new_layer.geometry = Array(16)
.fill()
.map(() => Array(16).fill(0));
map.layers.splice(2, 0, new_layer);
let water = map.layers[0];
let ground = map.layers[1];
let splash = map.layers[2];
for (let y = 0; y < ground.geometry.length; y++) {
for (let x = 0; x < ground.geometry[0].length; x++) {
if (
y - 1 > 0 &&
ground.geometry[y][x] !== 3 &&
ground.geometry[y - 1][x] == 3
) {
ground.geometry[y][x] = 2;
}
}
}
for (let y = 0; y < ground.geometry.length; y++) {
for (let x = 0; x < ground.geometry[0].length; x++) {
if (ground.geometry[y][x] == 2) {
splash.geometry[y][x] = 16;
}
}
}
for (let y = 0; y < water.geometry.length; y++) {
for (let x = 0; x < water.geometry[0].length; x++) {
if (ground.geometry[y][x] == 3) {
water.geometry[y][x] = 1;
}
if (ground.geometry[y][x] !== 3 && ground.geometry[y][x + 1] == 3) {
water.geometry[y][x] = 1;
}
if (ground.geometry[y][x] !== 3 && ground.geometry[y][x - 1] == 3) {
water.geometry[y][x] = 1;
}
if (
y + 1 < water.geometry.length &&
ground.geometry[y][x] !== 3 &&
ground.geometry[y + 1][x] == 3
) {
water.geometry[y][x] = 1;
}
if (
y - 1 > 0 &&
ground.geometry[y][x] !== 3 &&
ground.geometry[y - 1][x] == 3
) {
water.geometry[y][x] = 1;
}
}
}
for (let y = 0; y < water.geometry.length; y++) {
for (let x = 0; x < water.geometry[0].length; x++) {
if (water.geometry[y][x] == -1) {
water.geometry[y][x] = 1;
}
}
}
});
// menu scene
let menu = new Scene("menu");
menu.keyEvents = function(event) {
if (this.world.keys.ArrowDown && this.selection < this.button.length - 1) {
this.world.assets.audio.selection.audio.play();
this.selection += 1;
} else if (this.world.keys.ArrowUp && this.selection > 0) {
this.world.assets.audio.selection.audio.play();
this.selection -= 1;
}
if (this.world.keys.KeyX) {
this.world.assets.audio.selection.audio.play();
this.world.startScene(this.button[this.selection].link);
}
};
menu.init = function() {
this.init_once = true;
// custom data
this.button = [
{
name: "PLAY",
link: "inGame"
},
{
name: "SELECT",
link: "levels"
},
{
name: "CONTROLS",
link: "controls"
}
];
this.texteMax =
Math.max(...this.button.map(button => button.name.length)) * 6;
this.selection = 0;
this.select_pos = {
x: this.world.W / 2,
y: 110
};
this.cursor_phase = 0;
this.cursor = this.world.assets.image.cursor.image;
// background
let background_image = this.world.assets.image.pattern.image;
this.pattern = this.world.ctx.createPattern(background_image, "repeat");
this.offset = {
x: 0,
y: 0
};
// add cat on
this.cat = new Entity(this, -this.world.tile_size, -this.world.tile_size);
let sprite_data = {
image: "player_sprite",
size: {
x: 18,
y: 18
}
};
this.cat.setSprite(sprite_data);
this.cat.sprite.addAnimation("idle", [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]);
this.cat.sprite.speed = 0.2;
this.cat.sprite.offset.y = -3;
};
menu.render = function() {
this.animatedBackground();
this.ctx.drawImage(this.world.assets.image["main_title"].image, 0, 0);
this.displaySelection();
// notice
this.world.ctx.fillStyle = "rgba(0,0,0,0.6)";
this.world.ctx.fillRect(0, this.world.H - 50, this.world.W, 33);
this.world.setFont("origami_light");
this.world.write(
"Arrow keys to select",
this.world.W / 2,
this.world.H - 46,
"center"
);
this.world.write(
"[x] to Confirm",
this.world.W / 2,
this.world.H - 30,
"center"
);
};
menu.displaySelection = function() {
// display box
this.ctx.fillStyle = "#82769e";
this.world.drawBox(
"box",
this.select_pos.x - (this.texteMax + 60) / 2,
this.select_pos.y - 16,
this.texteMax + 60,
this.button.length * 20 + 20
);
// display text and cursor
for (i in this.button) {
if (i == this.selection) {
this.world.setFont("origami_light");
} else {
this.world.setFont("origami_dark");
}
let title = this.button[i].name;
this.world.write(
title,
this.select_pos.x,
this.select_pos.y + i * 20,
"center"
);
}
this.cursor_phase += 0.1;
if (this.cursor_phase > 1 / Math.sin(0.2)) {
this.cursor_phase = -1;
}
let x = this.select_pos.x + Math.sin(this.cursor_phase) * 2 - 20;
this.world.ctx.drawImage(
this.cursor,
x - this.button[this.selection].name.length * 10 / 2,
this.select_pos.y + 20 * this.selection - 2
);
};
menu.animatedBackground = function() {
this.offset.x += 0.8;
this.offset.y += 0.6;
if (this.offset.x > 63) {
this.offset.x = 0;
}
if (this.offset.y > 63) {
this.offset.y = 0;
}
let ctx = this.world.ctx;
ctx.save();
ctx.translate(this.offset.x, this.offset.y);
ctx.fillStyle = this.pattern;
ctx.fillRect(-this.offset.x, -this.offset.y, this.world.W, this.world.H);
ctx.restore();
};
let levels = new Scene("levels");
levels.keyEvents = function(event) {
if (this.world.keys.KeyE) {
this.world.assets.audio.selection.audio.play();
this.world.startScene("menu");
}
if (this.world.keys.ArrowDown && this.selection + 5 < this.world.mapsMax) {
this.world.assets.audio.selection.audio.play();
this.selection += 5;
}
if (this.world.keys.ArrowUp && this.selection - 5 >= 0) {
this.world.assets.audio.selection.audio.play();
this.selection -= 5;
}
if (this.world.keys.ArrowRight && this.selection + 1 < this.world.mapsMax) {
this.world.assets.audio.selection.audio.play();
this.selection += 1;
}
if (this.world.keys.ArrowLeft && this.selection - 1 >= 0) {
this.world.assets.audio.selection.audio.play();
this.selection -= 1;
}
if (this.world.keys.KeyX) {
this.world.assets.audio.selection.audio.play();
this.world.current_level = this.selection + 1;
this.world.startScene("inGame");
}
};
levels.init = function() {
this.init_once = true;
this.selection = 0;
this.scale = 0;
};
levels.render = function() {
this.world.clear("black");
// animate selection
this.scale += 0.1;
if (this.scale > 1 / Math.sin(0.2)) {
this.scale = -1;
}
let offset = Math.sin(this.scale) * 2;
// display box
this.ctx.fillStyle = "#82769e";
this.world.drawBox("box", 16, 16, this.world.W - 32, this.world.H - 46 - 32);
this.world.setFont("origami_light");
this.world.setFont("origami_dark");
let show = Math.min(this.world.mapsMax, 20);
for (let i = 0; i < show; i++) {
let level_id = i + 20 * Math.floor(this.selection / 20);
let position_x = 32 + Math.floor(i % 5) * 40,
position_y = 32 + Math.floor(i / 5) * 40;
if (level_id == this.selection) {
this.world.setFont("origami_light");
this.world.drawBox(
"selection",
position_x - offset / 2,
position_y - offset / 2,
24 + offset,
24 + offset
);
} else {
this.world.setFont("origami_dark");
this.world.drawBox("flat_frame", position_x, position_y, 24, 24);
}
this.world.write(
(level_id + 1).toString(),
position_x + 13,
position_y + 8,
"center"
);
}
// notice
this.world.ctx.fillStyle = "rgba(0,0,0,0.6)";
this.world.ctx.fillRect(0, this.world.H - 50, this.world.W, 33);
this.world.setFont("origami_light");
this.world.write(
"Arrow keys to select",
this.world.W / 2,
this.world.H - 46,
"center"
);
this.world.write(
"[x] to Confirm, [E] to exit",
this.world.W / 2,
this.world.H - 30,
"center"
);
};
let inGame = new Scene("inGame");
inGame.keyEvents = function(event) {
if (this.world.keys.KeyE && this.userInput) {
this.transition.start(
0,
Math.max(this.world.W / 2, this.world.H / 2),
() => {
this.world.startScene("menu");
}
);
}
if (this.world.keys.KeyR && this.userInput) {
this.transition.start(
0,
Math.max(this.world.W / 2, this.world.H / 2),
() => {
this.world.startScene("inGame");
}
);
}
};
inGame.init = function() {
this.won = false;
this.userInput = true;
this.world.initMap("map_" + this.world.current_level);
this.cats = [];
let spawn_cat = () => {
// add cats on spawn tile_size
let spawns = this.world.findTile(3, 7);
spawns.forEach(spawn => {
this.addCat(spawn.x, spawn.y);
});
};
// effects
this.effects = [];
// transition effects
this.transition = {
scene: this,
active: true,
// between 0 and 100
state: 0,
value: 0,
duration: 500,
start: 0,
// between whatever and whatever
from: 0,
to: Math.max(this.world.W, this.world.H),
//
start: function(from, to, callback) {
this.scene.userInput = false;
this.active = true;
this.from = from;
this.start_time = new Date();
this.to = to;
this.callback = callback;
},
update: function() {
let time = new Date() - this.start_time;
if (time < this.duration) {
this.value = Util.easeInOutQuad(
time,
this.from,
this.to - this.from,
this.duration
);
} else {
this.active = false;
this.scene.userInput = true;
if (this.callback !== undefined) {
this.callback();
}
}
},
render: function() {
this.scene.ctx.fillStyle = "black";
this.scene.ctx.fillRect(0, 0, this.scene.world.W, this.value);
this.scene.ctx.fillRect(
0,
this.scene.world.H,
this.scene.world.W,
-this.value
);
this.scene.ctx.fillRect(0, 0, this.value, this.scene.world.H);
this.scene.ctx.fillRect(
this.scene.world.W,
0,
-this.value,
this.scene.world.H
);
}
};
this.transition.start(
Math.max(this.world.W / 2, this.world.H / 2),
0,
spawn_cat
);
};
inGame.addCat = function(x, y) {
let cat = new Cat(this, x, y);
let sprite_data = {
image: "player_sprite",
size: {
x: 18,
y: 18
}
};
cat.setSprite(sprite_data);
cat.sprite.addAnimation("idle", [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]);
cat.sprite.speed = 0.2;
cat.sprite.offset.y = -3;
let spawn_data = {
image: "spawn_effect",
frames: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12],
size: {
x: 20,
y: 40
}
};
let spawn_effect = new Effect(this, spawn_data, x, y - 1, () => {
this.cats.push(cat);
this.world.assets.audio.apparition.audio.play();
});
spawn_effect.trigger = 4;
this.effects.push(spawn_effect);
};
inGame.render = function() {
this.control();
this.world.renderMap();
for (let i = this.cats.length; i--; ) {
this.cats[i].sprite.animate("idle");
// draw shadow and cat
this.ctx.drawImage(
this.world.assets.image["shadow"].image,
this.cats[i].body.position.x,
this.cats[i].body.position.y + 2
);
this.cats[i].display();
this.cats[i].translation();
}
for (let i = this.effects.length; i--; ) {
this.effects[i].render();
}
if (this.transition.active) {
this.transition.update();
this.transition.render();
}
};
inGame.control = function() {
if (this.userInput == false) return false;
if (this.world.keys.ArrowUp) {
this.moveCats(0, -1);
}
if (this.world.keys.ArrowDown) {
this.moveCats(0, 1);
}
if (this.world.keys.ArrowLeft) {
this.moveCats(-1, 0);
}
if (this.world.keys.ArrowRight) {
this.moveCats(1, 0);
}
};
inGame.moveCats = function(x, y) {
// see if every cat are ready to move
let canMove = this.cats.every(cat => {
return cat.inTranslation == false;
});
if (!canMove) return false;
this.cats.forEach(cat => {
if (cat.canBeControlled === false) return false;
if (cat.isDead) return false;
cat.move(x, y);
});
this.collisionCats();
this.cats.forEach(cat => {
cat.applyMove();
});
};
inGame.collisionCats = function() {
// check for other cats !
let need_to_check = true;
while (need_to_check === true) {
need_to_check = false;
this.cats.forEach(cat => {
if (cat.checkOthers()) {
cat.target = cat.old_position.copy();
need_to_check = true;
}
});
}
};
inGame.checkWin = function() {
if (this.cats.length === 0) {
// everyone is dead :/
this.transition.start(
0,
Math.max(this.world.W / 2, this.world.H / 2),
() => {
this.world.startScene("inGame");
}
);
return false;
}
let win = this.cats.every(cat => {
let tile = this.world.getTile(3, cat.target.x, cat.target.y);
return tile.name == "exit";
});
if (
win === true &&
this.cats.length >= this.world.findTile(3, 8).length &&
!this.won
) {
this.won = true;
this.world.assets.audio.jingle.audio.play();
if (
this.world.maps["map_" + (this.world.current_level + 1)] !== undefined
) {
this.transition.start(
0,
Math.max(this.world.W / 2, this.world.H / 2),
() => {
this.world.current_level += 1;
this.world.startScene("inGame");
}
);
} else {
this.transition.start(
0,
Math.max(this.world.W / 2, this.world.H / 2),
() => {
this.world.startScene("menu");
}
);
}
}
};
// destroy itself when animation is finish
class Effect extends Entity {
constructor(scene, sprite_data, x, y, callback) {
super(scene, x * scene.world.tile_size, y * scene.world.tile_size);
this.setSprite(sprite_data);
this.sprite.addAnimation("full", sprite_data.frames);
this.sprite.speed = 0.4;
this.sprite.offset.y = -3;
this.trigger = sprite_data.frames.length;
this.callback = callback || undefined;
}
render() {
if (this.sprite.current_frame + 1 === this.trigger) {
if (this.callback !== undefined) {
this.callback();
this.callback = undefined;
}
}
if (
this.sprite.current_frame + 1 ===
this.sprite.animations[this.sprite.current_animation].length
) {
this.scene.effects.splice(this.scene.effects.indexOf(this), 1);
}
this.sprite.animate("full");
this.display();
}
}
class Cat extends Entity {
constructor(scene, x, y) {
super(scene, x * scene.world.tile_size, y * scene.world.tile_size);
this.old_position = new Vector(x, y);
this.target = new Vector(x, y);
this.canBeControlled = true;
this.inTranslation = false;
this.lastDirection = new Vector(0, 0);
this.isDead = false;
// Trasnlation of the cat when they move
this.transition = {
start: new Date(),
duration: 300,
type: Util.easeInOutQuad,
start_pos: new Vector()
};
}
// apply translation on cat when necessary
translation() {
if (this.inTranslation) {
// get current time !
let time = new Date() - this.transition.start;
if (time < this.transition.duration) {
let x = this.transition.type(
time,
this.transition.start_pos.x,
this.transition.target.x - this.transition.start_pos.x,
this.transition.duration
),
y = this.transition.type(
time,
this.transition.start_pos.y,
this.transition.target.y - this.transition.start_pos.y,
this.transition.duration
);
this.body.position = new Vector(x, y);
} else {
// apply position when translation is finish :) !
this.old_position = this.target.copy();
let next_move = this.target.copy();
next_move.mult(this.world.tile_size);
this.body.position = next_move;
this.inTranslation = false;
if (this.isDead) {
// delete cat
let spawn_data = {
image: "water_splash",
frames: [0, 1, 2, 3, 4, 5, 6, 7, 8],
size: {
x: 20,
y: 32
}
};
let spawn_effect = new Effect(
this.scene,
spawn_data,
this.target.x,
this.target.y - 1,
() => {
this.scene.cats.splice(this.scene.cats.indexOf(this), 1);
this.world.assets.audio.splash.audio.play();
this.scene.checkWin();
}
);
spawn_effect.sprite.offset.y = 0;
spawn_effect.trigger = 2;
this.scene.effects.push(spawn_effect);
}
if (this.canBeControlled === false) {
this.move(this.lastDirection.x, this.lastDirection.y);
this.scene.collisionCats();
this.applyMove();
} else {
this.world.assets.audio.mouvement.audio.play();
// check arrows
let current_tile = this.world.getTile(
3,
this.target.x,
this.target.y
);
switch (current_tile.name) {
case "arrowRight":
this.move(1, 0);
this.scene.collisionCats();
this.applyMove();
break;
case "arrowLeft":
this.move(-1, 0);
this.scene.collisionCats();
this.applyMove();
break;
case "arrowUp":
this.move(0, -1);
this.scene.collisionCats();
this.applyMove();
break;
case "arrowDown":
this.move(0, 1);
this.scene.collisionCats();
this.applyMove();
break;
default:
}
}
// check if we won when a cat finish a step
this.scene.checkWin();
}
}
}
move(x, y) {
this.target = this.old_position.copy();
let direction = new Vector(x, y);
// get future position
let future_position = this.target.copy();
future_position.add(direction);
let layers = this.world.terrain.layers;
let future_tile = layers.map(layer => {
let index = layers.indexOf(layer);
return this.world.getTile(index, future_position.x, future_position.y);
});
let collision = future_tile.every(tile => {
if (tile == false) {
return tile == false;
} else {
return tile.collision === false;
}
});
if (collision == true) {
this.target.add(direction);
}
if (future_tile[3].name === "ice") {
this.canBeControlled = false;
this.transition.type = Util.linearTween;
this.transition.duration = 100;
return false;
}
if (future_tile[3].name === "trap") {
let dust_data = {
image: "dust_effect",
frames: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12],
size: {
x: 32,
y: 32
}
};
let dust_effect = new Effect(
this.scene,
dust_data,
this.target.x,
this.target.y
);
this.scene.effects.push(dust_effect);
this.world.assets.audio.eboulement.audio.play();
this.world.terrain.layers[3].geometry[future_position.y][
future_position.x
] = 10;
// cache the map
this.world.terrainCache(this.world.terrain.layers[3]);
return false;
}
if (future_tile[1].name !== "ground") {
this.transition.type = Util.easeInOutQuad;
this.transition.duration = 200;
this.isDead = true;
return false;
} else {
this.canBeControlled = true;
this.transition.type = Util.easeInOutQuad;
this.transition.duration = 200;
return false;
}
}
applyMove() {
// prevent cat to move if his target equal his actual position :V
if (
this.old_position.x === this.target.x &&
this.old_position.y === this.target.y
) {
this.canBeControlled = true;
this.world.assets.audio.bump.audio.play();
return false;
}
this.lastDirection = new Vector(
this.target.x - this.old_position.x,
this.target.y - this.old_position.y
);
this.shouldMove = false;
this.transition.start_pos = this.old_position.copy();
this.transition.start_pos.mult(this.world.tile_size);
this.transition.target = this.target.copy();
this.transition.target.mult(this.world.tile_size);
this.transition.start = new Date();
this.inTranslation = true;
}
checkOthers() {
let others = this.scene.cats;
let result = false;
for (let i = 0; i < others.length; i++) {
if (this === others[i]) continue;
if (
others[i].target.x === this.target.x &&
others[i].target.y === this.target.y
) {
result = true;
break;
}
}
return result;
}
}
let controls = new Scene("controls");
controls.keyEvents = function(event) {
if (this.world.keys.KeyE) {
this.world.startScene("menu");
}
};
controls.init = function() {
this.loop = false;
this.controls = this.world.assets.image.controls.image;
};
controls.render = function() {
this.world.clear("black");
this.ctx.drawImage(this.controls, 0, 0);
// notice
this.world.setFont("origami_light");
this.world.write(
"[E] to exit",
this.world.W / 2,
this.world.H - 46,
"center"
);
};
let game = new Diorama(parameters);
// global variables
game.current_level = 1;
// Add the different scenes here
// the addScene function link the scene with the world (game)
game.addScene(menu);
game.addScene(levels);
game.addScene(controls);
game.addScene(inGame);
game.ready();
// everything start being loaded now !
// the ready function must be called last !
// Making the game full screen and with a 10% audio volume by default
game.soundLevel(0.2);
game.fullScreen();
<script src="https://codepen.io/Gthibaud/pen/dybzvNw.js"></script>
html,
body {
margin: 0;
padding: 0;
height: 100%;
}
body {
color:white;
background-color: #000;
display: flex;
align-items: center;
justify-content: center;
overflow: hidden;
}
canvas {
flex-shrink: 0;
background-color: #000;
object-fit: contain;
}
.crisp{
image-rendering: -moz-crisp-edges;
image-rendering: -webkit-crisp-edges;
image-rendering: pixelated;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment