Little update, I've updated the visuals during the translation and cleaning of the code :) Made with Diorama
Created
June 1, 2023 00:15
-
-
Save tientq64/c363294758083e014845c49e181bf936 to your computer and use it in GitHub Desktop.
[game] Copycat
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!-- But it's a dark age, a dangerous time --> |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// ---------- | |
// 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(); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<script src="https://codepen.io/Gthibaud/pen/dybzvNw.js"></script> |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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