Isometric worlds and Real Time games!

JSConf Colombia 2017

Speaker

Workshop

About Me

Isometric worlds!

Tile-Based Games

tile

2D arrays to create the world

[[1,1,1,1,1,1],
 [1,0,0,0,0,1],
 [1,0,0,0,0,1],
 [1,0,0,0,0,1],
 [1,0,0,0,0,1],
 [1,1,1,1,1,1]]

TileSet

Tiled, flexible map editor

Phaser uses top left, Tiled bottom left

Phaser TileMap utilities

create() {
  this.map = this.game.add.tilemap('world');
  this.map.addTilesetImage('flower');
  this.map.addTilesetImage('star');
}

Create the tilemap

this.game.load.image('flower', 'assets/flower.png');
this.game.load.image('star', 'assets/star.png');
this.game.load.tilemap('world', 'assets/world.json', null, Phaser.Tilemap.TILED_JSON);

Load images and JSON file

create(){
  this.coinsLayer = this.map.createLayer('coinsLayer');
  this.backgroundLayer = this.map.createLayer('backgroundLayer');
  this.worldLayer = this.map.createLayer('worldLayer');		
  this.worldLayer.resizeWorld();

  //this.map.setCollision([4, 5, 8, 9]);
  this.map.setCollisionByExclusion([], true, this.worldLayer);
}
update(){
  this.game.physics.arcade.collide(this.player, this.worldLayer);
}

Create layers, collisions, resize the world!

create() {
  this.enemiesLayer = this.map.createLayer('enemiesLayer');
}
createEnemies() {
  this.enemies = this.game.add.group();
  this.enemies.enableBody = true;
  this.map.createFromTiles(63, null, 'enemy1', this.enemiesLayer, this.enemies);
  this.map.createFromTiles(57, null, 'enemy2', this.enemiesLayer, this.enemies);
		
  this.enemies.forEach(function (enemy) {
    enemy.anchor.set(0.5);
    enemy.x += enemy.width/2;
    enemy.y += enemy.height/2;
    //A enemy without movement
    enemy.body.immovable = true;
    enemy.body.allowGravity = false;
    //Assign a body to the enemy
    enemy.body.setSize(enemy.width, enemy.height);
  });
	
  //Manipulate properties of all objects of a group
  //this.enemies.setAll("visible", false);
}

Create enemies from a layer

Extra Utilities!

//find objects in a Tiled layer that containt a property called "type" equal to a certain value
findObjectsByType(type, map, layer) {
  var result = new Array();
  map.objects[layer].forEach(element => {
    if(element.properties.type === type) {
      //Phaser uses top left, Tiled bottom left so we have to adjust the y position
      //also keep in mind that the cup images are a bit smaller than the tile
      //so they might not be placed in the exact pixel position as in Tiled
      element.y -= map.tileHeight;
      result.push(element);
    }      
  });
  return result;
}
//create a sprite from an object
createFromTiledObject(element, group) {
  var sprite = group.create(
    element.x, 
    element.y, 
    element.properties.sprite
  );
		
  //copy all properties to the sprite
  Object.keys(element.properties).forEach(key => {
    sprite[key] = element.properties[key];
  });
}
//Remove tiles
removeEnemyTiles() {
  var tilesToRemove = [57, 58, 59, 60]
  var width = this.game.world.width
  var height = this.game.world.height
  var tiles = this.enemiesLayer.getTiles(0, 0, width, height)
		
  this.enemiesLayer.tiles.forEach(tile => {
    if(tilesToRemove.indexOf(tile.index) >= 0) {
      this.map.removeTile(tile.x, tile.y, this.enemiesLayer)
    }
  });
}

Isometric Projection

We tilt our camera along two axes (roll the camera 45 degrees to the side, then 30 degrees down). This creates a diamond (diamond) grid where the grid spaces are twice as wide as they are high. This style was popularized by strategy games and action role-playing games. If we look at a cube in this view, three sides are visible (top side and two facing sides).

Pathfinding

EasyStar.js
asynchronous pathfinding in javascript

Our goal is to find nodes connecting a start node and an end node. From the start node we visit the eight neighboring nodes and mark them as visited; This core process is repeated for each newly visited node, recursively. Each thread tracks the visited nodes. When jumping to neighboring nodes, nodes that have already been visited are skipped (recursion stops); otherwise, the process continues until we reach the end node, where the recursion ends and the full path followed is returned as an array of nodes. Sometimes the end node is never reached, in which case the route search fails. Usually, we end up finding multiple routes between the two nodes, in which case we take the one with the fewest nodes.

Resources

Real time games!

Send and receive messages...

33~66

times per second!

HTTP requests must die!

Welcome WebSockets!

Players will try to cheat!

And they will!

The server must put the room in order!

Time and again you must enforce your authority ...

- Input prediction 🔎
- Lag compensation 🙅

- Client interpolation 📈

Increase complexity

Desired interaction

  • The client presses the key -> therefore it must move immediately to the right.
  • The server receives the position message and saves it for the next update. 
  • In the next server update, the player's position is applied by moving it in the direction of the server state.
  • All state changes are sent to all clients.
  • The client receives the message immediately configuring the positions of the players (authorized)
  • Customer can correct prediction errors from the first step

Authority Server

Constant updates from the Server

Resources

- Workshop repository: 

  https://github.com/proyecto26/Phaser-Workshop

- Real Time Multiplayer in HTML5:

  buildnewgames.com/real-time-multiplayer/

- Fast-Paced Multiplayer:

  www.gabrielgambetta.com/client-server-game-architecture.html

Made with Slides.com