Jump to content

Camera - rendered area / Move TilemapLayer


Recommended Posts



currently my game renders like this



but I need to find some way to render it like this (the game/world needs to be 64px offset from the top, a hud will be placed there)



The world you see is generated from a large Tiled-map (I've attached it and the tilemap sprite), moving it seems to be impossible at the current state of Phaser?

I played around with it's camera offset but then the bottom 64 pixels won't be rendered of course (and the collisions are handled at the non offset locations).

Using Phaser.Camera.setSize didn't work either, still the complete screen was rendered but moving was a bit screwed up :(

Do you have any ideas how I could solve my problem?

As a sidenote: I also need to find a way to create a 8 by 8 pixel collision grid, the current one is 16 by 16 pixels in size (created with Phaser.Tilemap.setCollision*), just as the tiles.

I've extracted my current code for loading the map and creating the layer:

game.load.tilemap('overworld', 'worlds/overworld.json', null, Phaser.Tilemap.TILED_JSON);game.load.image('tiles', 'img/game/overworld.png');game.load.spritesheet('link', 'img/game/link.png', 16, 16);game.physics.startSystem Phaser.Physics.ARCADEthis.map = game.add.tilemap('overworld');this.map.addTilesetImage('overworld', 'tiles');this.map.setCollisionByExclusion([1, 3, 7, 12, 14, 18, 22, 24, 29, 56, 62, 68, 140]);this.map.setCollisionBetween(73, 75, false);this.map.setCollisionBetween(79, 81, false);this.map.setCollisionBetween(85, 87, false);this.map.setCollisionBetween(91, 93, false);this.map.setCollisionBetween(97, 99, false);this.map.setCollisionBetween(103, 105, false);this.map.setCollisionBetween(109, 111, false);this.map.setCollisionBetween(115, 117, false);this.map.setCollisionBetween(121, 127, false);this.map.setCollisionBetween(129, 132, false);this.map.setCollisionBetween(134, 138, false);this.layer = this.map.createLayer('world');this.layer.resizeWorld();this.link = game.add.sprite(1912, 1312, 'link');game.physics.enable(this.link);this.link.body.collideWorldBounds = true;/*TODO: Move camera position on cell basisCode in update():	this.currentTile = this.layer.getTileXY(this.link.x, this.link.y, {});	this.currentCell = this.currentTile;	this.currentCell = {		x: Math.floor(this.currentTile.x / 16),		y: Math.floor(this.currentTile.y / 11)	};	game.camera.setPosition(this.currentCell.x * 256, this.currentCell.y * 176);*/game.camera.follow(this.link);


Thanks in advance.



Link to comment
Share on other sites

As far as I konw you can not move tilemap-layer-sprites without breaking collision at this point.

(I think I have read Rich posting this.)


Another thought: maybe instead of covering the top part of the tilemap with a black sprite (like valueerror sugested) you can crop the tilemap sprite and remove the top part.


As for breaking the tilemap into 8x8 collision pieces:

The easiest solution is to go back to tiled, set the tile size to 8x8 and when drawing the map always select the 4 8x8 tiles that belong together and paint with them.

This way you can natively set each of the 4 pieces to colliding or not colliding.

If you don't do it you will have to implement the collision code completely by hand, as there is no way to split the tiles later on. (Except maybe by parsing the json-tiled file yourself and setting up the phaser-tilemap with 8x8 pieces form the json data.. but I guess redoing the map is less effort).

Link to comment
Share on other sites


I used the following approach now:

  • Padding the map with 4 empty tiles at the top
  • Cropping the layer (this.layer.crop = {x: 0, y: 64, width: this.layer.width, height: this.layer.height - 64})
  • Setting the layer's y-camera offset to 64px (this.layer.cameraOffset = {x: 0, y: 64})

It works, but I don't really like this approach, as my map is now 256 by 92 tiles large.
It takes multiple seconds to load the map (ok, Phaser has to check 23552 tiles and afterwars render the layer to an 4096*1472px canvas ...) :/

Here's a small screencast of it http://screencast.com/t/9MyYGkjI (it runs fluently, the recording is quite choppy) :)


Edit: I also tried reducing the tilesize to 8px; map loading takes ages :(

Link to comment
Share on other sites

Wait, mapsize should have no big performance impact, since the rendered layer is only the size of the phaser game (for example 800x600).


When moving the camera phaser renders an new image to this layer (from the tilemap images combined with the tile information of the tilemap).


Under no circumstances should there be a 4k x 14k canvas involved.


Except if you are doing something that tilemaplayers are not supposed to do.

Width & height params in the layer creation function are the renderable-width and renderable-height .. so they should match the game size (or smaller).

Link to comment
Share on other sites

Wait, mapsize should have no big performance impact, since the rendered layer is only the size of the phaser game (for example 800x600).


You're right, I was (partially) wrong. :rolleyes:

The map is just being rendered on a canvas that matches the game size (or smaller), as you said.

But the setCollision* functions are really slow, and loading times increase as the map size increases :)


I've attached a screenshot, you can see that all calls of setCollisionBetween take 1490ms, the calls to setCollisionByExclusion take 312ms; the complete create function of my GameState takes 1884ms to complete, with the 8px-per-tile-tilemap it took even longer, afair :|


'setCollision*' is slow because of the call to 'calculateFaces'.

I think this could be drastically improved, if one could skip this call, e.g. we should add one parameter (boolean) to these functions ('recalculate'; defaults to true).

Edit: This is already done in 'setCollisionByIndex', I changed the other functions according to it.



Mid-post update:

I've patched my Phaser-version to allow me to do this, instead of 1900ms it now takes less than 500ms :)



I'll make an PR on GitHub.

Edit: https://github.com/photonstorm/phaser/pull/819

Link to comment
Share on other sites

I'll quote myself ;)


I've patched my Phaser-version to allow me to do this, instead of 1900ms it now takes less than 500ms :)

Without the patch my complete "create" function took ~1900ms to complete, after patching only ~440ms.

This patch just affects 'setCollision', 'setCollisionBetween' and 'setCollisionByExclusion'.

It doesn't break existing code but the code needs to be updated.

So now my code looks like this:

this.map = game.add.tilemap('overworld');this.map.addTilesetImage('overworld', 'tiles');this.map.setCollisionByExclusion([1, 3, 7, 12, 14, 18, 22, 24, 29, 56, 62, 68, 140], true, undefined, false); // the last parameter tells this functions to not recalculate the collision facesthis.map.setCollisionBetween(73, 75, false, undefined, false);this.map.setCollisionBetween(79, 81, false, undefined, false);this.map.setCollisionBetween(85, 87, false, undefined, false);this.map.setCollisionBetween(91, 93, false, undefined, false);this.map.setCollisionBetween(97, 99, false, undefined, false);this.map.setCollisionBetween(103, 105, false, undefined, false);this.map.setCollisionBetween(109, 111, false, undefined, false);this.map.setCollisionBetween(115, 117, false, undefined, false);this.map.setCollisionBetween(121, 127, false, undefined, false);this.map.setCollisionBetween(129, 132, false, undefined, false);this.map.setCollisionBetween(134, 138, false); // this call will recalculate the collision faces
Link to comment
Share on other sites

Here is my solution for a zelda-like camera:



var game = new Phaser.Game(384, 384, Phaser.AUTO, 'game_div');var main_state = {    preload: function() {        // Function called first to load all the assets        game.load.tilemap('map', 'levels/test.json', null, Phaser.Tilemap.TILED_JSON);        game.load.image('tiles', 'img/tiles.png');        game.load.spritesheet('player', 'img/player.png', 40, 40);    },    create: function() {        /* globals */        this.tileSize = 16; // tile is 16x16        this.tilePerScreen = 12; // height/tileSize        this.battleEncounter = 0;        /* physics*/        game.physics.startSystem(Phaser.Physics.ARCADE);        /* input and listener */        this.cursor = this.game.input.keyboard.createCursorKeys();        /* map */        this.map = game.add.tilemap('map');        this.map.addTilesetImage('tiles');        this.layer1 = this.map.createLayer('background');        this.layer2 = this.map.createLayer('battlezone');        this.layer3 = this.map.createLayer('obstacles');        //this.layer1.scale = {x:2,y:2};        this.layer1.resizeWorld();        //this.layer2.scale = {x:2,y:2};        this.layer2.resizeWorld();        //this.layer3.scale = {x:2,y:2};        this.layer3.resizeWorld();        game.physics.arcade.enable(this.layer2);        game.physics.arcade.enable(this.layer3);        /*set collision*/        this.map.setTileIndexCallback([7410], this.inBattlezone, this, this.layer2); //if hit, battlezone is entered        this.map.setCollisionByExclusion([],true,this.layer3);        //this.map.setCollisionCallback(function() {return true;}, this.layer2);        /*camera*/        this.camera = {x:0, y:0, direction:'', isMoving:false};        /*player*/        this.player = this.game.add.sprite(160, 160, 'player');        this.player.scale.x = 0.5;        this.player.scale.y = 0.5;        this.player.anchor.setTo(0.5, 0.5);        game.physics.arcade.enable(this.player);    },    update: function() {        game.physics.arcade.collide(this.player, this.layer3);        game.physics.arcade.collide(this.player, this.layer2);        this.moveCamera();        this.movePlayer();        //this.checkBattlezone();    },    moveCamera: function(){        if (this.camera.isMoving)            return;        this.camera.isMoving = true;        var mustMove = false;        if (this.player.y > game.camera.y + game.height) {            this.camera.y += 1;            mustMove = true;        }        else if (this.player.y < game.camera.y) {            this.camera.y -= 1;            mustMove = true;        }        else if (this.player.x > game.camera.x + game.width) {            this.camera.x += 1;            mustMove = true;        }        else if (this.player.x < game.camera.x) {            this.camera.x -= 1;            mustMove = true;        }        if (mustMove) {            var t = game.add.tween(game.camera).to({x:this.camera.x*game.width, y:this.camera.y*game.height}, 600);            t.start();            t.onComplete.add(function(){this.camera.isMoving = false;}, this);        }        else {            this.camera.isMoving = false;        }    },    movePlayer: function() {        this.player.body.velocity.x = 0;        this.player.body.velocity.y = 0;        var speed = 230;        if (this.cursor.left.isDown) {            if (this.tween) this.player.body.velocity.x = -50;            else this.player.body.velocity.x = -speed;        }        else if (this.cursor.right.isDown) {            if (this.tween) this.player.body.velocity.x = 50;            else this.player.body.velocity.x = speed;        }        else if (this.cursor.up.isDown) {            if (this.tween) this.player.body.velocity.y = -50;            else this.player.body.velocity.y = -speed;        }        else if (this.cursor.down.isDown) {            if (this.tween) this.player.body.velocity.y = 50;            else this.player.body.velocity.y = speed;        }    },    showCords: function() {        var screen = this.tileSize*this.tilePerScreen;        var result = 'player stats:';        result += '\nx: '+this.player.x;        result += '\ny: '+this.player.y;        result += '\ncamera stats:';        result += '\nx: '+this.camera.x;        result += '\ny: '+this.camera.y;        result += '\nbounds: ';        result += '\nx: '+(0+game.camera.x+screen);        result +='\ny: '+(0+game.camera.y+screen);        return result;    },    inBattlezone: function() {        if (this.cursor.up.isDown || this.cursor.down.isDown || this.cursor.left.isDown || this.cursor.right.isDown) {            this.battleEncounter++;            console.log('random battle tick');            if (Math.floor((this.battleEncounter/1000)*Math.random()) >= 1) {                console.log('random battle occured');                this.battleEncounter = 0;            }        }    }};game.state.add('main', main_state);game.state.start('main');
Link to comment
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.
Note: Your post will require moderator approval before it will be visible.

Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.


  • Recently Browsing   0 members

    • No registered users viewing this page.
  • Create New...