Pen Settings

HTML

CSS

CSS Base

Vendor Prefixing

Add External Stylesheets/Pens

Any URLs added here will be added as <link>s in order, and before the CSS in the editor. You can use the CSS from another Pen by using its URL and the proper URL extension.

+ add another resource

JavaScript

Babel includes JSX processing.

Add External Scripts/Pens

Any URL's added here will be added as <script>s in order, and run before the JavaScript in the editor. You can use the URL of any other Pen and it will include the JavaScript from that Pen.

+ add another resource

Packages

Add Packages

Search for and use JavaScript packages from npm here. By selecting a package, an import statement will be added to the top of the JavaScript editor for this package.

Behavior

Auto Save

If active, Pens will autosave every 30 seconds after being saved once.

Auto-Updating Preview

If enabled, the preview panel updates automatically as you code. If disabled, use the "Run" button to update.

Format on Save

If enabled, your code will be formatted when you actively save your Pen. Note: your code becomes un-folded during formatting.

Editor Settings

Code Indentation

Want to change your Syntax Highlighting theme, Fonts and more?

Visit your global Editor Settings.

HTML

              
                
              
            
!

CSS

              
                html,
body {
    margin: 0;
    padding: 0;
    height: 100%;
}
body {
    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;
}

              
            
!

JS

              
                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
    );
  }
}

class Scene {
  constructor(name) {
    this.name = name;
    this.loop = true;
    this.init_once = false;
  }
  giveWorld(world) {
    this.world = world;
    this.ctx = world.ctx;
    // add default camera
    this.camera = new Camera(this, 0, 0);
  }
  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: this.world.tile_size,
      y: this.world.tile_size
    };
    this.half = {
      x: this.size.x / 2,
      y: this.size.y / 2
    };
    this.collision = {
      left: false,
      top: false,
      right: false,
      bottom: false
    };
  }
  setSize(x, y) {
    this.size.x = x;
    this.size.y = y;
    this.half = {
      x: this.size.x / 2,
      y: this.size.y / 2
    };
  }
  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);
  }
  AABB(tile) {
    tile.position.x *= this.world.tile_size;
    tile.position.y *= this.world.tile_size;

    // Distance from the center of a box
    let tile_half = this.world.tile_size / 2;
    let distX = Math.abs(
      this.position.x + this.half.x - (tile.position.x + tile_half)
    );
    let distY = Math.abs(
      this.position.y + this.half.y - (tile.position.y + tile_half)
    );
    // Gap between each boxes
    let gapX = distX - this.half.x - tile_half;
    let gapY = distY - this.half.y - tile_half;
    //collision on the X or Y axis
    let offset = this.world.tile_size;
    if (gapX < 0 || gapY < 0) {
      // prevent equality if square
      if (gapX === gapY) {
        gapY = -1;
      }
      if (gapX < 0 && gapX > gapY) {
        if (this.position.x > tile.position.x) {
          if (tile.neighbors[1]) return false;
          this.position.x -= gapX;
          this.collision.left = true;
        } else {
          if (tile.neighbors[0]) return false;
          this.position.x += gapX;
          this.collision.right = true;
        }
      }
      if (gapY < 0 && gapY > gapX) {
        if (this.position.y > tile.position.y) {
          if (tile.neighbors[3]) return false;
          this.position.y -= gapY;
          this.collision.top = true;
        } else {
          if (tile.neighbors[2]) return false;
          this.position.y += gapY;
          this.collision.bottom = true;
        }
      }
    }
  }
}

class Camera extends Entity{
	constructor(scene,x,y){
		super(scene,x,y);
		this.target = {
			position : new Vector(this.world.W/2,this.world.H/2),
			size : {x:0,y:0},
		};
		this.boundless = true;
		this.bounds = {
			x:this.world.W,
			y:this.world.H
		}
		this.angle = 0;
	}
	setBounds(x,y){
		this.bounds.x = x - this.world.W;
		this.bounds.y = y - this.world.H;
	}
	setTarget(target){
		this.target = target;
		this.target = {
			position : target.body.position,
			size : (target.sprite == undefined) ? target.body.size : target.sprite.size,
		};
	}
	checkBounds(){
		if(this.boundless) return false;
		if(this.body.position.x < 0){this.body.position.x = 0;}
		if(this.body.position.y < 0){this.body.position.y = 0;}
		if(this.body.position.x > this.bounds.x ){this.body.position.x = this.bounds.x}
		if(this.body.position.y > this.bounds.y ){this.body.position.y = this.bounds.y}
	}
	update(){
		this.body.position.x = this.target.position.x + this.target.size.x/2 - this.world.W/2;
		this.body.position.y = this.target.position.y + this.target.size.x/2 - this.world.H/2;
		// bound
		this.checkBounds();
		
		this.world.ctx.translate(this.world.W/2,this.world.H/2);
		this.world.ctx.rotate(this.angle);
		this.world.ctx.translate(-this.body.position.x- this.world.W/2 ,-this.body.position.y - this.world.H/2);

	}
}
// ----------
// 🕹ī¸ 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.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;
    this.setScale();
  }
  // ---
  // 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, colorID);
        y_offset += line_height;
        line = "";
      } else {
        this.writeLine(line, x, y + y_offset, 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.key.toLowerCase()] = true;
    if (this.keys.f) {
      this.fullScreen();
    }
    if (this.keys.m) {
      this.mute();
    }
    this.current_scene.keyEvents(event);
  }
  keyUp(event) {
    this.keys[event.key.toLowerCase()] = 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.ctx.save();
    this.current_scene.camera.update();
    this.current_scene.render();
    this.ctx.restore();
  }
  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.maxWidth = this.W * this.scale + "px";
    this.canvas.style.maxHeight = this.H * this.scale + "px";
    this.canvas.style.width = "100%";
    this.canvas.style.height = "100%";
  }
  fullScreen() {
    this.full = !this.full;
    if (!this.full) {
      this.setScale();
    } else {
      // reset
      this.canvas.style.maxWidth = "";
      this.canvas.style.maxHeight = "";
      this.canvas.style.width = "";
      this.canvas.style.height = "";

      // set full screen
      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;
    return this.terrain.layers[layer_id].geometry[y][x];
  }
  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.size = this.terrain.layers[0].size;
    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.drawLayer(ctx, layer);
  }
  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
          );
        });
      });
    });
  }
  drawLayer(ctx, 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].visibility === false) {
          continue;
        }
        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

        ctx.drawImage(
          this.terrain.tileset,
          sourceX,
          sourceY,
          this.tile_size,
          this.tile_size,
          positionX,
          positionY,
          this.tile_size,
          this.tile_size
        );
      }
    }
  }
}

let parameters = {
  name: "Title",
  start_screen: "inGame",
  background_color: "white",
  width: 160,
  height: 160,
  scale: 3,
  assets: [
    // Images
    {
      type: "img",
      name: "nano_font",
      path: "https://image.ibb.co/h3309e/nano.png"
    },
    {
      type: "img",
      name: "player",
      path: "https://image.ibb.co/epOdvK/player.png"
    },
    {
      type: "img",
      name: "shadow",
      path: "https://image.ibb.co/mWUU2z/shadow.png"
    },
    {
      type: "img",
      name: "tileset",
      path: "https://image.ibb.co/fGop2z/tileset.png"
    }

    // Bitmap font
  ],
  fonts: [
    // basic font
    {
      name: "nano",
      image: "nano_font",
      size: { x: 4, y: 6 }
    }
  ],
  // box system

  tiles: [{ name: "empty", id: 0, collision: false, visibility: false }],

  maps: [
    {
      name: "map_1",
      tileset: "tileset",
      // ground
      layers: [
        // ground layer
        {
          name: "ground",
          offset: {
            x: 0,
            y: 0
          },
          geometry: [
            [12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 29, 12, 12, 12, 29, 12],
            [12, 12, 12, 12, 12, 12, 12, 12, 29, 12, 12, 12, 12, 12, 12, 12],
            [12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 29, 12],
            [12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12],
            [12, 12, 12, 12, 12, 12, 12, 12, 12, 29, 12, 12, 12, 12, 12, 12],
            [12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 29, 12, 12, 12, 12],
            [12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 29, 12, 12],
            [12, 12, 12, 12, 12, 12, 29, 12, 12, 12, 12, 12, 12, 12, 12, 12],
            [12, 12, 12, 29, 29, 29, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12],
            [12, 12, 12, 12, 29, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12],
            [12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12],
            [12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12],
            [12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 29, 12, 29, 12, 12, 12],
            [12, 12, 12, 12, 12, 12, 12, 12, 29, 12, 12, 12, 12, 12, 29, 12],
            [12, 12, 12, 12, 12, 12, 12, 29, 29, 12, 12, 12, 29, 12, 12, 12],
            [12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12]
          ]
        },
        {
          name: "grass",
          offset: {
            x: 0,
            y: 0
          },
          geometry: [
            [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 44, 45],
            [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 52, 53],
            [63, 53, 53, 54, 0, 0, 37, 0, 0, 0, 0, 0, 0, 0, 0, 0],
            [54, 0, 0, 0, 0, 36, 55, 45, 45, 45, 46, 0, 0, 0, 0, 0],
            [0, 0, 0, 0, 0, 44, 45, 45, 63, 53, 54, 0, 0, 0, 0, 0],
            [0, 0, 36, 37, 38, 52, 53, 53, 54, 0, 0, 0, 0, 0, 0, 0],
            [0, 0, 44, 45, 46, 0, 0, 36, 37, 0, 0, 0, 0, 0, 0, 0],
            [0, 0, 52, 53, 54, 0, 0, 44, 45, 37, 37, 37, 38, 0, 0, 0],
            [0, 0, 0, 0, 0, 0, 0, 44, 45, 45, 45, 45, 62, 0, 0, 0],
            [0, 62, 38, 0, 0, 0, 0, 52, 53, 61, 45, 45, 45, 0, 0, 0],
            [0, 45, 62, 37, 0, 0, 0, 0, 0, 52, 53, 53, 53, 0, 0, 0],
            [0, 45, 45, 45, 38, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
            [0, 45, 45, 45, 54, 0, 0, 0, 0, 0, 0, 36, 37, 37, 37, 37],
            [0, 45, 45, 45, 38, 0, 0, 0, 0, 0, 0, 44, 45, 45, 45, 45],
            [0, 45, 45, 45, 62, 37, 37, 37, 37, 0, 36, 55, 45, 45, 45, 63],
            [0, 0, 0, 0, 0, 0, 0, 0, 0, 36, 55, 45, 45, 45, 63, 54]
          ]
        },
        {
          name: "shadow",
          offset: {
            x: 0,
            y: 0
          },
          geometry: [
            [0, 0, 0, 0, 0, 0, 0, 0, 23, 22, 0, 0, 0, 0, 0, 0],
            [0, 0, 0, 23, 22, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
            [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
            [0, 0, 0, 0, 23, 22, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
            [0, 30, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
            [0, 30, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
            [0, 30, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
            [0, 30, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
            [0, 30, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
            [0, 30, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
            [0, 30, 0, 0, 0, 23, 30, 0, 0, 0, 0, 0, 0, 0, 0, 0],
            [0, 30, 0, 0, 0, 23, 22, 0, 0, 0, 0, 0, 0, 0, 0, 0],
            [0, 30, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
            [0, 30, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
            [0, 30, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
            [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
          ]
        },
        {
          name: "walls",
          offset: {
            x: 0,
            y: 0
          },
          geometry: [
            [0, 0, 0, 6, 20, 20, 20, 20, 21, 0, 0, 0, 0, 0, 0, 0],
            [20, 20, 20, 21, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
            [0, 0, 0, 0, 6, 20, 20, 20, 20, 20, 20, 20, 20, 7, 0, 0],
            [6, 20, 20, 20, 21, 0, 0, 0, 0, 0, 0, 0, 0, 11, 0, 0],
            [13, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 14, 0, 0],
            [13, 0, 0, 0, 0, 0, 0, 0, 0, 3, 4, 4, 14, 0, 0, 0],
            [13, 0, 0, 0, 0, 0, 0, 0, 0, 11, 0, 0, 0, 0, 0, 0],
            [13, 0, 0, 0, 0, 0, 0, 0, 0, 11, 0, 0, 0, 0, 0, 0],
            [13, 0, 0, 0, 0, 0, 0, 0, 0, 19, 48, 0, 0, 7, 0, 0],
            [13, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 11, 0, 0],
            [13, 0, 0, 0, 3, 5, 0, 0, 0, 0, 0, 0, 0, 11, 0, 0],
            [13, 0, 0, 0, 19, 21, 0, 0, 0, 0, 0, 0, 0, 11, 0, 0],
            [13, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 11, 0, 0],
            [13, 0, 0, 0, 0, 0, 0, 0, 0, 3, 4, 4, 4, 14, 0, 0],
            [13, 0, 0, 0, 0, 0, 0, 0, 0, 11, 0, 0, 0, 0, 0, 0],
            [15, 4, 4, 4, 4, 4, 4, 4, 4, 14, 0, 0, 0, 0, 0, 0]
          ]
        }
      ]
    }
  ]
};

// inGame scene !
let inGame = new Scene("inGame");
inGame.init = function() {
  let sprite_data = {
    image: "player",
    size: {
      x: 16,
      y: 20
    }
  };
  this.player = new Entity(this, 100, 100);
  this.player.setSprite(sprite_data);
  this.player.sprite.addAnimation("run_left", [8, 9, 10, 11, 12, 13, 14, 15]);
  this.player.sprite.addAnimation("run_right", [
    16,
    17,
    18,
    19,
    20,
    21,
    22,
    23
  ]);
  this.player.sprite.addAnimation("idle", [0, 1]);
  this.player.sprite.addAnimation("curb", [2, 3]);
  this.animation_toplay = "run_right";
  this.player.body.setSize(8, 4);
  this.player.sprite.offset.x = -4;
  this.player.sprite.offset.y = -16;
  this.player.body.drag = 0.98;
  this.acceleration = 6;
  this.top_speed = 40;
  this.thrust = new Vector(0, 0);
  // need to init a map first
  this.world.initMap("map_1");
  this.camera.setTarget(this.player);
  this.camera.boundless = false;
  this.camera.setBounds(256,256);
};
inGame.getTileCollisionData = function(x, y) {
  let tile_pos = {
    x: Math.floor(x / this.world.tile_size),
    y: Math.floor(y / this.world.tile_size)
  };
  let tile_id = this.world.getTile(3, tile_pos.x, tile_pos.y);
  if (tile_id !== 0) {
    let neighbors = [
      {
        x: tile_pos.x - 1,
        y: tile_pos.y
      },
      {
        x: tile_pos.x + 1,
        y: tile_pos.y
      },
      {
        x: tile_pos.x,
        y: tile_pos.y - 1
      },
      {
        x: tile_pos.x,
        y: tile_pos.y + 1
      }
    ].map(tile => {
      let n_id = this.world.getTile(3, tile.x, tile.y);
      if (n_id !== 0) {
        return true;
      } else {
        return false;
      }
    });
    return {
      position: tile_pos,
      neighbors: neighbors
    };
  }
  return false;
};
inGame.render = function() {
  this.world.renderMap(this.world.terrain.layers[0]);
  this.movePlayer();
  this.player.body.integration();
  // map collision
  let tX = this.player.body.position.x + this.player.body.stepped_velocity.x,
    tY = this.player.body.position.y + this.player.body.stepped_velocity.y;
  let top_left = this.getTileCollisionData(tX, tY);
  let top_right = this.getTileCollisionData(tX + this.player.body.size.x, tY);
  let bottom_left = this.getTileCollisionData(tX, tY + this.player.body.size.y);
  let bottom_right = this.getTileCollisionData(
    tX + this.player.body.size.x,
    tY + this.player.body.size.y
  );
  this.player.body.collision.left = false;
  this.player.body.collision.top = false;
  this.player.body.collision.right = false;
  this.player.body.collision.bottom = false;
  if (top_left) {
    this.player.body.AABB(top_left);
  }
  if (top_right) {
    this.player.body.AABB(top_right);
  }
  if (bottom_left) {
    this.player.body.AABB(bottom_left);
  }
  if (bottom_right) {
    this.player.body.AABB(bottom_right);
  }
  // end
  this.player.body.velocity.limit(this.top_speed);
  if (this.player.body.velocity.x > 0) {
    this.animation_toplay = "run_right";
  } else if (this.player.body.velocity.x < 0) {
    this.animation_toplay = "run_left";
  }
  if (this.player.body.velocity.mag() > 10) {
    if (this.thrust.x > 0 && this.player.body.velocity.x < 0) {
      this.player.sprite.current_animation = "curb";
      this.player.sprite.current_frame = 1;
    } else if (this.thrust.x < 0 && this.player.body.velocity.x > 0) {
      this.player.sprite.current_animation = "curb";
      this.player.sprite.current_frame = 0;
    } else {
      this.player.sprite.animate(this.animation_toplay);
    }
  } else if (this.thrust.mag() < 1) {
    this.player.body.velocity = new Vector(0, 0);
    this.player.sprite.current_animation = "idle";
    if (this.animation_toplay === "run_right") {
      this.player.sprite.current_frame = 0;
    } else {
      this.player.sprite.current_frame = 1;
    }
  }
  this.world.ctx.drawImage(
    this.world.assets.image["shadow"].image,
    this.player.body.position.x - 2,
    this.player.body.position.y + 2
  );
  this.player.display();
  this.world.setFont("nano");
  this.world.write("Arrow keys to move", 40, 70, "left");
  this.world.write("hold shift to run faster", 40, 80, "left");
};
inGame.movePlayer = function() {
  this.thrust = new Vector(0, 0);
  if (this.world.keys["arrowup"]) {
    this.thrust.y = -this.acceleration;
  } else if (this.world.keys["arrowdown"]) {
    this.thrust.y = this.acceleration;
  }
  if (this.world.keys["arrowleft"]) {
    this.thrust.x = -this.acceleration;
  } else if (this.world.keys["arrowright"]) {
    this.thrust.x = this.acceleration;
  }
  if (this.world.keys["shift"] && this.thrust.mag() > 1) {
    this.top_speed = 80;
    this.player.body.drag = 0.98;
    this.player.sprite.speed = Util.map(
      this.player.body.velocity.mag(),
      0,
      this.top_speed,
      0,
      0.4
    );
  } else {
    this.top_speed = 40;
    this.player.body.drag = 0.96;
    this.player.sprite.speed = Util.map(
      this.player.body.velocity.mag(),
      0,
      this.top_speed,
      0,
      0.2
    );
  }
  this.player.body.applyForce(this.thrust);
};

let game = new Diorama(parameters);
// Add the different scenes here
// the addScene function link the scene with the world (game)
game.addScene(inGame);
game.ready();
// everything start being loaded now !
// the ready function must be called last !
game.fullScreen();
              
            
!
999px

Console