css Audio - Active file-generic CSS - Active Generic - Active HTML - Active JS - Active SVG - Active Text - Active file-generic Video - Active header Love html icon-new-collection icon-person icon-team numbered-list123 pop-out spinner split-screen star tv

Pen Settings

CSS Base

Vendor Prefixing

Add External Stylesheets/Pens

Any URL's added here will be added as <link>s in order, and before the CSS in the editor. If you link to another Pen, it will include the CSS from that Pen. If the preprocessor matches, it will attempt to combine them before processing.

+ add another resource

You're using npm packages, so we've auto-selected Babel for you here, which we require to process imports and make it all work. If you need to use a different JavaScript preprocessor, remove the packages in the npm tab.

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

Use npm Packages

We can make npm packages available for you to use in your JavaScript. We use webpack to prepare them and make them available to import. We'll also process your JavaScript with Babel.

⚠ī¸ This feature can only be used by logged in users.

Code Indentation

     

Save Automatically?

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.

HTML Settings

Here you can Sed posuere consectetur est at lobortis. Donec ullamcorper nulla non metus auctor fringilla. Maecenas sed diam eget risus varius blandit sit amet non magna. Donec id elit non mi porta gravida at eget metus. Praesent commodo cursus magna, vel scelerisque nisl consectetur et.

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

            
          
!
            
              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
🕑 One or more of the npm packages you are using needs to be built. You're the first person to ever need it! We're building it right now and your preview will start updating again when it's ready.
Loading ..................

Console