Sign in to follow this  
feudalwars

Zoom into an object in the world (trouble with the camera)

Recommended Posts

I've found all sorts of fun old threads related to this problem e.g.:

http://www.html5gamedevs.com/topic/7150-how-to-zoom-out-from-center-of-gameworld/

and this strange tutorial:

https://medium.com/@netcell/writing-a-better-camera-for-phaser-104f13d1c467

 

But I haven't found any workable code. Is there a simple way to increase/decrease the game.world size while keeping the camera fixed on an object or x/y? I can't seem to control the camera after scaling the world.

 

Would appreciate any workarounds/code examples. 

Share this post


Link to post
Share on other sites

My game uses two canvases and in my main state's overridden render function it copies and scales my game to the secondary canvas. I'm making a very tiny pixel-art game (or, tiny-pixel art-game? anyway) and any use of the Phaser ScaleManager made me have blurry pixels. Seriously, could not find one configuration that resulted in crisp blown-up pixels. This seemed like an easier way to handle it.

 

The side effect, though, is that zoom effects are fairly trivial since I'm already drawing the whole darn game once anyway. Is something like that available to you? This really only works because I'm using the CANVAS renderer, not WEBGL (again, pixel art).

Share this post


Link to post
Share on other sites

My game uses two canvases and in my main state's overridden render function it copies and scales my game to the secondary canvas. I'm making a very tiny pixel-art game (or, tiny-pixel art-game? anyway) and any use of the Phaser ScaleManager made me have blurry pixels. Seriously, could not find one configuration that resulted in crisp blown-up pixels. This seemed like an easier way to handle it.

 

The side effect, though, is that zoom effects are fairly trivial since I'm already drawing the whole darn game once anyway. Is something like that available to you? This really only works because I'm using the CANVAS renderer, not WEBGL (again, pixel art).

I'm curious to see how you did this. Would I be able to see some code? I have a need to create a mini-map and haven't quite figured it out. Perhaps your method would work. I don't understand though how redrawing the entire game helps with camera control when scaling. 

 

Also, about the pixel art not looking how it should. Have you tried turning off anti-aliasing? You could also try render crisp

Share this post


Link to post
Share on other sites

Jesus, dude, I tried EVERYTHING to get that pixel art showing crisp. Nothing worked for me but this. It's really annoying. I really wanted a WebGL pixel art game (shaders!!!) and that just didn't work because of the built-in bilinear filtering that I couldn't figure out how to turn off. Next, whenever I used the scale manager to scale up the game to the browser window size it would blur the pixels just by setting size values on it the <canvas> element itself.

 

The "crisp" example works great when scaling the image, but I had a lot of issues getting the scaled sprite, its scaled body, the camera, and my tilemaps to all work together in a sane way. Things were flying all over the place, the tilemap wasn't rendering to the whole canvas when the camera moved from (0,0)... I abandoned this approach, too.

 

Here's what I've got now. Taken from a blog post by Rich.

 

I don't think this will work for you, though. I only care about the visible portion of the Phaser canvas, not the whole world. But I've seen your other posts and have been digging around in the code to try and figure out what's going on. Is your game WebGL or canvas?

Share this post


Link to post
Share on other sites

It's webGL which unfortunately, creates some complications. I think I will need to create the mini map inside the same canvas. If I set preserveDrawingBuffer to true, I can actually draw the entire canvas into a Bitmap data object:

bmd.copy(game.canvas, 0, 0, game.world.width, game.world.height, 0, 0, null, null, 0, 0, 0);

It works just fine. Problem is, when I try to scale it as an image, the image only shows the visible portion of the canvas:

 

bmd.copy(game.canvas, 0, 0, game.world.width, game.world.height, 0, 0, null, null, 0, 0, 0); var image = game.add.image(200, 200, bmd); // this is a clone of the ENTIRE canvasimage.scale.setTo(.1); // as soon as I scale, the clone magically turns into a picture of only the visible portion of the canvas
It's really weird. What I haven't tried yet is copying the bitmap data into another bitmap data (instead of an image) and scaling during the process. 
 
In regards to the camera thing, I think I've found a very simple solution. The basic idea is this:
* Place an object in the game world that appears in the center of the screen. When you move the camera, the object always stays in the game world in the center of the screen. This object can be turned invisible later on. 
* Just before scaling game.world, set the camera to follow the centered object with game.camera.follow. 
* When you want to move the camera manually, set game.camera.target = null, so the camera is no longer following. 
 
This solution works really well for zooming in a top-down game. A major kink I have to work out is that when the game scale is changed, you cannot use the same function to keep the centered object in the game world but also in the center of the screen. So when I move the camera after the game has been scaled, the centered object is no longer centered, it jumps all over the place. I haven't figured out a formula yet which takes scaling into consideration when updating the location of the centered object. Another kink is that for some reason inputs (such as game.input.activePointer) are still acting as if the game was never scaled. 

Share this post


Link to post
Share on other sites

Okay, I think I've solved it. Instead of drawing the entire game.canvas, I'm using drawGroup to draw a group containing just the objects I need. In theory, you could use drawGroup on game.world (since game.world is an extended group). Doesn't work in my case because I actually have child groups for various kinds of game objects. But using drawGroup on a group will not limit you to the visible portion of the canvas: 

 

var bmd = game.add.bitmapData(game.world.width, game.world.height);
bmd.drawGroup(topLayer);
var image = game.add.image(200, 200, bmd);
image.scale.setTo(.1);

 

 

 
That code will create a mini map sized to 10% of the game size at 200,200. I can update it however I choose by redrawing the group or drawing another group on top of it.. this is THE solution for me! 

Share this post


Link to post
Share on other sites

Awesome, I'm glad you solved it!

 

Yeah, I ran into the "parent group doesn't enumerate child group contents" thing before. And that's really good to know.

 

I wanted to create repeating background tilesprite from a tilemap layer and I thought the code would look a lot like your example but no dice. :( So if you could give me some of your luck that'd be great.

Share this post


Link to post
Share on other sites

Awesome, I'm glad you solved it!

 

Yeah, I ran into the "parent group doesn't enumerate child group contents" thing before. And that's really good to know.

 

I wanted to create repeating background tilesprite from a tilemap layer and I thought the code would look a lot like your example but no dice. :( So if you could give me some of your luck that'd be great.

hmm, can I see your code? Perhaps I can help.

 

A tilemap layer is just an extended Phaser.Image. You should be able to draw it into a bitmap data and then use that to create the tilesprite. You might even be able to take the Pixi.texture directly from the tilemap layer and use it to create the tilesprite (as both objects types have a texture). 

Share this post


Link to post
Share on other sites

 

But I haven't found any workable code. Is there a simple way to increase/decrease the game.world size while keeping the camera fixed on an object or x/y? I can't seem to control the camera after scaling the world.

 

Would appreciate any workarounds/code examples. 

 

http://jsfiddle.net/valueerror/pdx0px0w/

 

working code :)  

 

zoom in/out with mousewheel (it will zoom to the pointer) 

or with pinch2zoom  (it will zoom to the center between your fingers)

Share this post


Link to post
Share on other sites

Hi Valueerror,

 

I actually saw your code a while back and planned to implement it as a 'last resort'. It works perfectly and I love it - only problem is that it's very complicated and involved creating a lot of new objects/variables. The camera also shakes a lot in chrome when you zoom in/out (actually you can fix that quite easily if you use a tween to scale the world). I was hoping to come up with a simpler solution myself. 

 

Here's what I have so far. Perhaps you could assist since you've obviously got a lot of experience in this area?

 

In create:

centeredObject = game.add.sprite(500,500,'object1'); centeredObject.anchor.setTo(.5);centerX = game.camera.x + (game.camera.view.width / 2);centerY = game.camera.y + (game.camera.view.height / 2); 
 
In update:
moveCamera();centeredObject.x = centerX;centeredObject.y = centerY;  
moveCamera and setCenter:
function setCenter() { // keeps the centered object in the center of the screen    centerX = (game.camera.x + (game.camera.view.width / 2));    centerY = (game.camera.y + (game.camera.view.height / 2));}function moveCamera() {    if (cursors.up.isDown) {        game.camera.target = null;        game.camera.y -= scrollSpeed; // this just moves the camera        setCenter();    } else if (cursors.down.isDown) {        game.camera.target = null;        game.camera.y += scrollSpeed;        setCenter();    }    if (cursors.left.isDown) {        game.camera.target = null;        game.camera.x -= scrollSpeed;        setCenter();    } else if (cursors.right.isDown) {        game.camera.target = null;        game.camera.x += scrollSpeed;        setCenter();    }}
 
zooming on mouseWheel:
 
function mouseWheel() {    if (game.input.mouse.wheelDelta == 1) {        var scale = game.world.scale.x - .06;        game.add.tween(game.world.scale).to({            x: scale,            y: scale        }, 400).start();    } else {        var scale = game.world.scale.x + .06;        game.add.tween(game.world.scale).to({            x: scale,            y: scale        }, 400).start();    }    game.camera.follow(centeredObject);}

It works well for top-down zooming... only problem is, when you zoom in and then move the camera, the setCenter() function is no longer able to keep the centerObject in the center of the screen. I know there needs to be a provision for scaling in that function, I just can't figure out what it is. I've tried multiplying centerX, centerY by the world scale factor - no dice. Any idea what I need to do?

Share this post


Link to post
Share on other sites

oke.. some thoughts:

 

first of all.. it shakes? there is easing activated in the current code so the zooming motion is smooth - this leads to a little overlap..  the camera should reposition itself but the easing function isn't quite at the target scale position.. so the easing is going to finish and afterwards the camera is repositioned..  it's way better with pinch to zoom but the only way (or better the easiest way) you can get rid of this is by removing the easing. 

set easing to 1 and this "shaking" (i believe i figured out what you mean by that) is gone.. 

 

# that leads me to the first thought:   don't use easing and definitely don't use tweens!  this just delays the userinput and leads to situations where the user triggers an action but the tween is still running..  also your functions will have to wait for the tween to finish or deal with a constantly changing variable..   further you will create a hundred tweens in a second with your mouswheel function and every tween will try to do it's part..  what if the user suddenly wants to scroll out but the scroll-in tween is still running? what if the second tween is started but the first is still running?  and btw. because you are not reusing your tweens but creating new ones the garbage collegtor will have a lot of work to do - but that's probably a problem for the future  :ph34r:

 

 

#  use:   game.camera.view.centerX  and you can get rid of your setCenter() function :)

 

# basically you are centering the object all the time into the center of the camera by moving the object..   this seems to be very problematic.. what if the object is moving on its own?  what if there are a hundred objects..  instead try to center the camera over the object(s) - your current code seems to try something completely different than just zoom in or out of your map.

 

# instead of scaling the world you should think of scaling a group..  that way you can define a seperate group that will not zoom (for the UI for example)

 

# what about creating a jsfiddle or a phaser sandbox with a working example where everyone can try your stuff and test with real code?

 

BTW. if you remove the touchzoom from my example and add the necessary code to your example to compensate for the scaling factor there will be no difference in complexity ;-)  but it's always a good thing to write your own solution - a solution you fully understand !

Share this post


Link to post
Share on other sites

oh.. and another thing..  the easiest way of all ways to do this is to just zoom to the current center of the camera..  do not change the camera position at all..  the user can position the wanted object manually into the center of the curren view and just zoom in and then move the camera manually again if the wanted object is not exactly there..  that's totally usable and reliable :)

Share this post


Link to post
Share on other sites

oh.. and another thing..  the easiest way of all ways to do this is to just zoom to the current center of the camera..  do not change the camera position at all..  the user can position the wanted object manually into the center of the curren view and just zoom in and then move the camera manually again if the wanted object is not exactly there..  that's totally usable and reliable :)

Thanks for the dump of knowledge. The purpose of the centered game object wasn't to be a real game object. It was a fake invisible object there to assist the camera in staying centered when zoomed. Perhaps that will help you understand what I was trying to accomplish?

 

anyway, I've ripped everything out. Right now my code is just:

function mouseWheel() {    if (game.input.mouse.wheelDelta == 1) {        var scale = game.world.scale.x - .01;        game.world.scale.setTo(scale);    } else {        var scale = game.world.scale.x + .01;        game.world.scale.setTo(scale);    }}

I mean, it does what it's supposed to do. The only problem is that the camera seems to pan up when the world scale increases. If I could just keep the damn thing centered on the area of the map that was in the center when the game scale changed. That was what I was originally trying to accomplish with that fake centered game object. 

Share this post


Link to post
Share on other sites

ah.. ok thx..  i think i understand :)

 

if the camera starts to pan it probably reached it's bounds and tries to stay within them ?!  you could try.. game.camera.bounds = null;  just to find out if the bounds are the reason?  and if you decide to put up an example on the sandbox i'l definitely give it a try :)

Share this post


Link to post
Share on other sites

ah.. ok thx..  i think i understand :)

 

if the camera starts to pan it probably reached it's bounds and tries to stay within them ?!  you could try.. game.camera.bounds = null;  just to find out if the bounds are the reason?  and if you decide to put up an example on the sandbox i'l definitely give it a try :)

 

Valuerror, I implemented your code and got it working after a few adjustments (i didn't need the touch functionality, for example). What I do is multiple any calculations that factor in a pointer by the rescale factors (rescalefactorx and rescalefactory). If, for example, I want to place an object on the map where the pointer is, after it's been zoomed out, I will multiply the pointer coordinates by rescalex/y and place it there. It works like a charm. 

 

One problem I did run into however is when I remove the min/max zoom which you are using Phaser.Math.Clamp to limit:

 if (wheelDelt < 0) {     mapSizeCurrent -= 400;      mapSizeCurrent = Phaser.Math.clamp(mapSizeCurrent, worldwidth , mapSizeMax); } else {     mapSizeCurrent += 400;     mapSizeCurrent = Phaser.Math.clamp(mapSizeCurrent, worldwidth , mapSizeMax); }
If I comment out the clamping, I can basically zoom in/out as much as I want, but there's a problem. It seems like 30% of the time when you zoom all the way in, the camera starts to move incorrectly. You can see for yourself here:
 
 
Any idea why this might be happening? It only happens when zooming in.

Share this post


Link to post
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.

Guest
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.

Loading...
Sign in to follow this  

  • Recently Browsing   0 members

    No registered users viewing this page.