KoalaForce

Tilemap "Zoom" Camera Issue

Recommended Posts

Edit: Sorry if I broke your margins.

I'm trying to implement some type of camera zoom into my project at the moment and I'm having a few issues.

 

I'm working with a tilemap and tilelayer that is 96 tiles by 72 tiles big, each tile is 32x32. 

 

When I go to zoom out, my tilelayer compresses, but the camera doesn't seem to adjust and or the full tilemap/tilemap layer are not being rendered.

 

Initial View with painted tiles for reference. Layer Scale is set to 1

post-9499-0-07738000-1436906595_thumb.pn

 

Layer Scale set to 0.33

post-9499-0-04281200-1436906619_thumb.pn

 

Layer Scale : 1, with some newly painted tiles

post-9499-0-72197500-1436906641.png

 

Back to 0.33. I would like to be able to paint new tiles from this screen, but the tilemap layer isn't fully shown/rendered.(I think I can solve the paint issue by scaling the input up);

post-9499-0-65567800-1436906652.png

 

Moving the Camera ever so slightly to show the painted Tiles

post-9499-0-12350700-1436906663.png

 

Code looks something like this using lewster32's implementation.

xport class Sim extends Phaser.State{			map: Phaser.Tilemap;		layer: Phaser.TilemapLayer;		marker: Phaser.Graphics;		currentTile: Phaser.Tile;		cursors: any;		inputScale: number;		layerScale: number;				create(){						this.stage.backgroundColor = "#4DBD33"; 						this.map = this.add.tilemap();			this.map.addTilesetImage('tiles');			this.layer = this.map.create('layer', 96, 72, 32, 32);			this.layer.resizeWorld();			//this.layer.fixedToCamera = false;						this.currentTile = new Phaser.Tile(this.layer, 5,                                     this.input.activePointer.worldX, 				              this.input.activePointer.worldY, 32, 32 );			this.marker = this.add.graphics(0, 0);			this.marker.lineStyle(2, 0x000000, 1);			this.marker.drawRect(0, 0, 32, 32);						this.input.addMoveCallback(this.updateMarker, this);			this.cursors = this.input.keyboard.createCursorKeys();			this.layerScale = 1;					}				updateMarker(){						this.marker.x = this.layer.getTileX(this.input.activePointer.worldX) * 32;			this.marker.y = this.layer.getTileY(this.input.activePointer.worldY) * 32;						if(this.input.mousePointer.isDown){								this.map.putTile(this.currentTile, this.layer.getTileX(this.marker.x), 					this.layer.getTileY(this.marker.y), this.layer);							}								}				update() {									if(this.input.keyboard.isDown(Phaser.Keyboard.Q)){				this.layerScale += 0.02;			}			if(this.input.keyboard.isDown(Phaser.Keyboard.A)){                                 this.layerScale -= 0.02;			}                        this.layerScale = Phaser.Math.clamp(this.layerScale, 0.33, 1);                        this.layer.scale.set(this.layerScale);			if (this.cursors.left.isDown)		    {		        this.camera.x -= 20;		    }		    else if (this.cursors.right.isDown)		    {		        this.camera.x += 20;		    }				    if (this.cursors.up.isDown)		    {		        this.camera.y -= 20;		    }		    else if (this.cursors.down.isDown)		    {		        this.camera.y += 20;		    }					}						render() {			this.game.debug.cameraInfo(this.camera, 16, 16);			this.game.debug.inputInfo( 16, 100);			}			}}

 

 

Any help would be appreciated and I'll try to reply when I get home from work later tonight.

Thanks in advance. 

Share this post


Link to post
Share on other sites

KoalaForce,

 

I was having the same problem in my game. I found that using the TilemapLayer resize function solved my problem with the tilemap layer being "cropped" on zoom.

As the docs mention, it should be used sparingly as it's an expensive request, but I only call it when the user activates zooming rather than on an update cycle. I haven't noticed any noticeable lag from it, yet.

 

If anyone else knows of a less expensive solution to this problem I'd love to know about it!

handleZoom: function(zoomDirection) {    if (zoomDirection > 0) {      this.zoomScale += this.zoomScaleIncrement;    } else if (zoomDirection < 0) {      this.zoomScale -= this.zoomScaleIncrement;    }    this.zoomScale = Phaser.Math.clamp(this.zoomScale,this.zoomScaleMin,this.zoomScaleMax);    // Set world zoom (this is a group I have sprites in that will need to be scaled)    this.zoomGroup.scale.set(this.zoomScale);    // Zoom background layer & resize    this.backgroundLayer.scale.set(this.zoomScale);    this.backgroundLayer.resize(this.game.scale.width,this.game.scale.height);     // My game area scales so I use the ScaleManager to get the actual canvas dimensions to resize the background to    // Adjust body size of scaled sprites    this.playerCharacter.setBodyScale(this.zoomScale);    this.triggerGroup.forEach(function(trigger) {      trigger.setBodyScale(this.zoomScale);    },this)  },

Share this post


Link to post
Share on other sites

I've also been having this issue; the function .resize works perfectly well for resizing the browser window and having the content of the tileMapLayer adapt to it, but it doesn't work as it i'd like it too after the scaling that supposed to be a zoom, like the original commenter said. bars are appearing on the sides; this only happens on the zooming event; i'm wondering if this has something to do with cropRect, or the size of the tilemapLayer inside the phaser.game element.

I'm going to try going through this to find a viable; easy, snippet applicable to all code bases for doing this with the latest phaser
 

Share this post


Link to post
Share on other sites

For zoom functionality, you should absolutely not scale all of your game objects, input, etc. it's impossible to get it right that way, unless the display list is extremely simple. Instead, resize the renderer. game.renderer.resize. It's that simple! 

Share this post


Link to post
Share on other sites

So, how would you go about this? Put it in an update function linked to the zoom key event for example?

Something like this?

	update: function () {


if (this.input.keyboard.isDown(Phaser.Keyboard.Q)) {

        terrainScale += 0.05;
       
        
        this.renderer.resize(terrainScale,terrainScale)
    }

    else if (this.input.keyboard.isDown(Phaser.Keyboard.A))    
    {
        terrainScale -= 0.05;

        this.renderer.resize(terrainScale,terrainScale)
   
    }
        
	},

 

Share this post


Link to post
Share on other sites

I wouldn't put it in update, because you don't want to call zoom any more than necessary. Instead, put it in a keyboard or mouse handler (like the mouse wheel):

function zoomByKey() {
    if (game.input.keyboard.isDown(187)) {
        zoomIn();
    }
    if (game.input.keyboard.isDown(189)) {
        zoomOut();
    }
}

function zoomIn() {
    if (!allowZoom) {
        return;
    };
    if (rendererScale == 1) {
        return;
    }

    if (rendererScale < minRendererScale) {
        return;
    }

    var worldScale = 1 - scaleIncrement;
    rendererScale = rendererScale * worldScale;

    var newWidth = (game.renderer.width * worldScale);
    var newHeight = (game.renderer.height * worldScale);

    if (rendererScale < 1.10) {
        rendererScale = 1;
        newHeight = $(window).height();
        newWidth = $(window).width();

    }

    updateGameOnRendererResize(newWidth, newHeight);

    game.renderer.resize(newWidth, newHeight);

}

function zoomOut() {
    if (!allowZoom) {
        return;
    };

    if (rendererScale > maxRendererScale) {
        return;
    }

    //   console.log(rendererScale);
    var worldScale = 1 + scaleIncrement;
    rendererScale = rendererScale * worldScale;

    var newWidth = (game.renderer.width * worldScale);
    var newHeight = (game.renderer.height * worldScale);

    //  game.camera.x = game.camera.x - ((game.camera.width * .25) / 2);
    //  game.camera.y = game.camera.y - ((game.camera.height * .25) / 2);
    updateGameOnRendererResize(newWidth, newHeight);

    game.renderer.resize(newWidth, newHeight);

}

Every time you zoom in or zoom out, you need to update the game, stage, camera and and input dimensions/scale, based on the new renderer size. Note that rendererScale is a global variable updated on zoom. 

function updateGameOnRendererResize(newWidth, newHeight) {

    game.width = newWidth;
    game.height = newHeight;
    game.stage.width = newWidth;
    game.stage.height = newHeight;
    game.camera.setSize(newWidth, newHeight);
    game.input.scale.setTo(rendererScale);

}

 

I have a few bells and whistles added, like a minimum world scale of 1 (you can't zoom in further than that), but for the most part, you should be able to copy my code and use directly in your game. 

You can see this zoom in action on this demo I made (the map editor for an hiatus project called feudal wars). The zoom is controlled by the mouse wheel in the demo: 

http://www.feudalwars.net/play

Share this post


Link to post
Share on other sites
On 12/1/2016 at 10:03 AM, Babsobar said:

I'm still looking into this, the above code doesn't work, "this.renderer is not a function"

How do you proceed for zooming  @feudalwars?

Oh btw, if you're getting that error, than "this" isn't Phaser.Game ... what I do, and what a lot of people do, is store the Phaser.Game instance in a global variable named "game." So when you create the game:

game = new Phaser.Game('100', '100', Phaser.AUTO, 'game');

Then you can call game.renderer. 
 

Share this post


Link to post
Share on other sites

I've been having problems trying to implement this myself, and Phaser 2 really isn't cut out for it.

While your implementation would work, @feudalwars, it also means rendering the scene at a different resolution. This isn't zoom the way it should work, unfortunately.

As a consumer of an API, I'd expect game.camera.scale to handle this correctly. Instead, all sorts of things break. :( Specifically tilemap layer rendering and collisions, and world bounds collisions.

Share this post


Link to post
Share on other sites

Hey@samme,

That's neat, and produces quality zooming without too much memory use compared to resizing the tilemaplayer each time.
I've been trying to implement it on my end and all i'm getting is a scaling of the game window itself. It's been driving me mad, The tilemap doesn't scale, but the game bounds inside the browser are changing on the calling of the tween, and I can't figure out how it works in your example.

Would you mind breaking it down a little more? like setting the scaling on a key maybe?

Thanks,
Bab


 

 

Share this post


Link to post
Share on other sites

Hi @Babsobar, see 

The gist is

var zoom     = new Phaser.Point(1, 1);
var gameSize = Object.freeze(new Phaser.Point(game.width, game.height));
var newSize  = gameSize.clone();

function updateDimensions() {
  Phaser.Point.divide(gameSize, zoom, newSize);
  newSize.floor();  
  game.scale.updateDimensions(newSize.x, newSize.y, /*resize=*/ true);
  game.input.scale.set(1 / zoom.x, 1 / zoom.y);
}

gameSize is the original size and never changes.

Share this post


Link to post
Share on other sites

Hey @samme;

I've managed to implement this fine but there's a few remaining issue that sprung up.
Like the OP, this technique makes bars appear on the side of the rendered element when zoomed out, so I call resize on the tileMapLayers every time updateDimensions is called . The problem is that is too expensive as a call, seeing as the zoom feature is for an turnbased strategy game; the player is going to be zooming all the time, and I noticed It makes the display choppy when the tiles become small and phaser is rendering a lot of them: 120*120 ( 14400 tiles). Moving the camera around on the map becomes tedious and choppy.
I'm wondering what I can do about that other than limiting the scale factor?
 

The other issue is that I've got a HUD with buttons whose positions are relative to this.game.width and height, so when I scale the renderer, so does the HUD, which isn't wanted. I'm not final on implementing the HUD with phaser and i'm wondering if I could get a plugin to do it for me, but that's not really what I had planned.
 

Another thing is that this implementation seems to break the responsiveness I had going with this snippet

 resize: function (width, height) {

        //  If the game container is resized this function will be called automatically.
        //  You can use it to align sprites that should be fixed in place and other responsive display things.

        console.log('Resizing...')
        
        terrain.resize(this.game.width,this.game.height)
        terrainPop.resize(this.game.width,this.game.height)
        terrainCons.resize(this.game.width,this.game.height)

        menuX = this.game.width;
        menuY = this.game.height;
        buttonGroup.x = this.game.width;
        buttonGroup.y = this.game.height;

    },
    


When the window resizes, it reverts back to the original renderer scale.

Any idea on code that would keep the responsiveness? And for HUDs do you use a plugin?

Thanks a lot for helping me out



 

Share this post


Link to post
Share on other sites

Basically, if you have this set

this.scale.scaleMode = Phaser.ScaleManager.RESIZE;

then you you get scrolling bars on zoom, it resizes the game area.

Something strange I noticed is that if you don't call a specific scalemode and use the zoom then resize the window, the game is suddenly very large and scroll bars appear.

How do you handle responsiveness and resizing in your games  @samme?

Thanks

 

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

  • Recently Browsing   0 members

    No registered users viewing this page.