FlxCamera help

490 views
Skip to first unread message

Gimmicky Apps

unread,
Feb 23, 2016, 8:00:37 AM2/23/16
to HaxeFlixel
Hi everyone,

I'm wondering if what I'm trying to do in a new project with FlxCamera is viable or not.

Unlike another project I'm working on, I'm avoiding any non-default stage scale modes and cameras with zoom < 1.

I have a play area which, at zoom=1, takes up much less than the full screen, on purpose.  I don't want the user to have to scroll, because it's a point n click kids' game.

I want it so that each time the player accomplishes something, the zoom increases a little bit, until they reach their goal (which will be within a number of accomplishments that is predefined such that the zoom wouldn't put any of the play area off the screen.  I.e. they still don't have to scroll, even after any zooming that occurs.)

The play area is from mapOffsetX, mapOffsetY to those + 550.  Stuff above mapOffsetY I don't want to be seen in the zooming camera because it's a back button and a title text.

I'm starting simply with this:

        dolphinCam = new FlxCamera(0, mapOffsetY, 1920, 1080 - mapOffsetY, 1);
        dolphinCam.setScrollBoundsRect(mapOffsetX, mapOffsetY, 1920, 1080 - mapOffsetY);

And then the zoom code is just dolphinCam.zoom += 0.3.

With this code, I have two problems:

  1. The play area appears in the upper-left hand corner.  I would like it to be centered.  (In fact, the map was centered, before I added this camera to it.)  Better yet, even finer control would be better, so that I can have the zooming effect be accompanied by the play area moving down slightly, to give a 3D-ish impression of the play area moving closer to the user.
  2. Upon zooming, the play area now has it's upper-left area off-screen.

I thought to try the following change: keep track of a sprite that happens to be in the exact center of the play area (call it centerWave) and then follow it by doing this after the above two lines of code:


        dolphinCam.follow(centerWave, FlxCameraFollowStyle.LOCKON);


Surprisingly, this made no difference whatsoever to the outcome.  I still have those exact same two problems.


Is there a way to do what I envision here with FlxCamera, or should I be looking at my own blitting code, or otherwise giving up the above idea?  I was going for something quick and dirty, trying to do something I thought was more 'normal' than before so I wouldn't have to get into undebugged nether-usecases.  Maybe it's nevertheless an undebugged nether-usercase to try to use a camera on something smaller than the screen, even if I'm going about it differently this time.


Thoughts?  Experience?  Help?  I'm aware that I could change the camera and scroll bounds each time I change the zoom, but for some reason I have an inkling that if what I imagine is intended to be doable with FlxCamera, then it would be a lot more straightforward than that.


Thanks,

GA

Gimmicky Apps

unread,
Feb 23, 2016, 11:54:57 AM2/23/16
to HaxeFlixel
OK, so I did something else and then came back to this.

I studied the HaxeFlixel tutorial's HUD, and then also the one from the FlxCamera demo.  I see from the former that scrolling can be had simply by adding sprites as a top layer.  But if you zoom, your HUD gets zoomed too.  Not what I'm looking for.

The latter creates an offscreen HUD and then a separate camera to view it, so that its zoom can be different from the main zoom.  This seems promising, since it works in the demo.

However, I'm very confused as to how it's supposed to work, both from looking at the code, and from trying it out on my own project.

I tried this, to make a 100-px-high hud at the top of the 1920x1080 game screen:

class GenericMiniGameHUD extends FlxTypedGroup<FlxSprite>
{
    public var titleBar:FlxText;
    public var background:FlxSprite;
    public function new()
    {
        super();
        background = cast add(new FlxSprite( 0, -5000 ));
        background.makeGraphic( 1920, 100, FlxColor.BLACK );
       
        add(new FlxButton(0, -5000, "BACK", goBackOneState));

        titleBar = new FlxText(80, -5000, 1920, "My title!", 40);
        add(titleBar);
       
        var hudCam = new FlxCamera(0, 0, background.frameWidth, background.frameHeight, 1);
        hudCam.follow(background);
        hudCam.alpha = 0.9;
        FlxG.cameras.add(hudCam);
    }
    function goBackOneState() {    FlxG.switchState( new PickCharState() ); }
}

...then in my game state:

add(new GenericMiniGameHUD());

The first problem I see running this is the "My title!" does not visually start at 0,0 but more like at 0,-20--the top half of the title is offscreen.

So, I go study the FlxCamera demo.  It looks right when you run it, but how were these numbers figured out?

background = new FlxSprite(10000 -50, -175);
background.makeGraphic(300, 360, FlxColor.BLACK);

What?  10000...minus 50?  Why minus 50?  And how does -175 relate to anything?  (Let alone, why would you need to offset it at all?)  I guess it's the 360px-tall background graphic, minus 10 px...um...because that's how far the first text is offset from the top...divided by two...even though zoom = 1?
And why is it 300x360 when the hud cam is then 200x180?

As they say, I don't really need to be driven crazy when I'm already within walking distance ;)

Thanks for any pointers anyone is able to provide...
GA

Gimmicky Apps

unread,
Feb 23, 2016, 1:04:28 PM2/23/16
to HaxeFlixel
One other thing on my HUD attempt, which sort of works now (I guessed at numbers until it sort of looked right), is that a FlxButton on it is invisible for some reason.  I put it somewhere in the main playing field, and I can see it no problem.  I put it in the same x,y as my title (and add it after my title, so it should be on top, or don't add my title at all) and I can't see it.  That one is even more perplexing than the rest here.  Ideas?

Thanks,
Gimmicky


On Tuesday, February 23, 2016 at 2:00:37 PM UTC+1, Gimmicky Apps wrote:

Gimmicky Apps

unread,
Feb 26, 2016, 1:32:24 PM2/26/16
to HaxeFlixel
In case anyone is ever curious about how I proceeded, I gave up on the idea of zooming using FlxCamera and related HUD concept, and reverted to the HUD style of the RPG tutorial, which doesn't allow zooming, but does allow scrolling, and FlxButtons.  Instead of zooming, I manually scale all the sprites in my scene to taste.

GA

Domagoj Štrekelj

unread,
Feb 27, 2016, 7:02:45 AM2/27/16
to HaxeFlixel
Hello,

I recently faced a similar situation and resolved it a little differently. I realise you already decided on a solution for your predicament, but I thought I should share regardless of that.

The requirements put forth dictated that the main camera of the play state should by default be zoomed in by a factor of two. Under certain circumstances, the camera would zoom out to a minimum factor of one before returning back to the default factor. Analogous to that, the camera would also be able to zoom in up to a factor of three.

As a reference, I looked through the FlxCamera demo, FlxCamera sourceFlxZoomCamera source, CoinFlipStudios' blog posts on HaxeFlixel cameras, and the Bullet Time Ninja post on zoom cameras. Despite preparing myself for the worst, it only took a bit of tinkering to figure out the basics of FlxCamera.

Before briefly describing the reasoning behind my custom camera class, I will share the class in its entirety. Be warned, however, that it needs some further refinement (e.g. notice the repetition when updating scroll bounds).

package;

import flixel.FlxCamera;
import flixel.FlxG;
import flixel.math.FlxRect;
import flixel.util.FlxDestroyUtil;
import openfl.Lib;

/**
 * A custom camera that handles zoom values of 1 and greater.
 */
class CustomCamera extends FlxCamera
{
/**
* Boundary within which the camera can scroll / move around.
* Covers stage dimensions by default.
*/
public var boundary : FlxRect;
/**
* Horizontal length of screen cut off by zoom from top-left
* corner to center of screen.
*/
public var dx : Float;
/**
* Vertical length of screen cut off by zoom from top-left
* corner to center of screen.
*/
public var dy : Float;
/**
* Bounding box value. Furthest left point to scroll to.
*/
public var minX : Null<Float>;
/**
* Bounding box value. Furthest right point to scroll to.
*/
public var maxX : Null<Float>;
/**
* Bounding box value. Furthest top point to scroll to.
*/
public var minY : Null<Float>;
/**
* Bounding box value. Furthest bottom point to scroll to.
*/
public var maxY : Null<Float>;
/**
* Creates new camera and sets up the default boundary to cover
* stage dimensions.
* @param x X coordinate of origin, from top-left
* @param y Y coordinate of origin, from top-left
* @param width Width of camera viewport
* @param height Height of camera viewport
* @param zoom Zoom factor
*/
public function new(x : Int, y : Int, width : Int, height : Int, zoom : Float = 0) 
{
super(x, y, width, height, zoom);
adjustBounds(0, 0, Lib.current.stage.stageWidth, Lib.current.stage.stageHeight);
adjustZoom(zoom);
}
/**
* Creates new camera from reference camera x, y, width, height,
* and zoom values.
* @param c Reference camera
* @return New camera
*/
public static inline function fromCamera(c : FlxCamera) : CustomCamera
{
return new CustomCamera(Std.int(c.x), Std.int(c.y), c.width, c.height, c.zoom);
}
/**
* Shorthand for adjusting all bounding box values.
* @param minX Furthest left point to scroll to
* @param minY Furthest top point to scroll to
* @param maxX Furthest right point to scroll to
* @param maxY Furthest bottom point to scroll to
*/
public inline function adjustBounds(minX : Null<Float>, minY : Null<Float>, maxX : Null<Float>, maxY : Null<Float>) : Void
{
this.minX = minX;
this.minY = minY;
this.maxX = maxX;
this.maxY = maxY;
}
/**
* Adjusts zoom and bounding box automatically.
* @param z Zoom amount
*/
public function adjustZoom(z : Float) : Void
{
if (z < 1)
{
z = 1;
}
var factor = (z - 1) / (z * 2);
dx = FlxG.width * factor;
dy = FlxG.height * factor;
var x1 = minX == null ? null : minX - dx;
var y1 = minY == null ? null : minY - dy;
var x2 = maxX == null ? null : maxX + dx;
var y2 = maxY == null ? null : maxY + dy;
zoom = z;
setScrollBounds(x1, x2, y1, y2);
}
/**
* Centers deadzone on target. Only works if target is set.
*/
public function deadzoneCenter() : Void
{
if (target != null)
{
this.deadzone.set(
(width - target.width) / 2,
(height - target.height) / 2,
target.width,
target.height
);
}
}
/**
* Horizontally centered deadzone with an offset from top.
* Only works if target is set.
* @param offset Offset (in pixels)
*/
public function deadzoneOffsetY(offset : Float = 0) : Void
{
if (target != null)
{
this.deadzone.set(
(width - target.width) / 2,
dy + offset,
target.width,
target.height
);
}
}
/**
* Vertically centered deadzone with an offset from left.
* Only works if target is set.
* @param offset Offset (in pixels)
*/
public function deadzoneOffsetX(offset : Float = 0) : Void
{
if (target != null)
{
this.deadzone.set(
dx + offset,
(height - target.height) / 2,
target.width,
target.height
);
}
}
}


The idea is to take into account the difference in size between the new viewport (post-zoom) and the old viewport (pre-zoom). These differences along the horizontal and vertical axis are stored in variables dx and dy, respectively. They are then used to automatically adjust the scroll bounds when adjusting the zoom value of the camera.

The camera is intended to be used as follows:

// When creating the state
playerCamera = CustomCamera.fromCamera(FlxG.camera);
playerCamera.follow(playerSprite);
playerCamera.adjustBounds(0, 0, FlxG.width, FlxG.height);
playerCamera.adjustZoom(2);
FlxG.cameras.reset(playerCamera);

Continue to use the playerCamera property when adjusting zoom and scroll bounds.

On the topic of HUDs, there are different ways to go about keeping them on screen. Some people prefer to set the scroll factor to zero, others prefer to give them their own camera. I belong to the latter camp because, well, it makes more sense to me.

A simple example of rendering the HUD in a separate, visible camera is as follows:

hudCamera = new FlxCamera(0, 0, hud.width, hud.height, 0);
hudCamera.follow(hud.background, FlxCameraFollowStyle.NO_DEAD_ZONE);
hudCamera.bgColor = flixel.util.FlxColor.TRANSPARENT;
FlxG.cameras.add(hudCamera);

Note the use of FlxCameraFollowStyle.NO_DEAD_ZONE to keep the entire HUD in view of the camera. Also note the use of FlxColor.TRANSPARENT to give the camera a transparent background to draw on, making it possible to overlay it over other cameras.

The HUD itself should be positioned somewhere outside of the player camera's view. If you want to prevent some objects from being rendered to that camera, you should adjust their cameras property. This is an array of cameras the object is drawn to, and is by default filled with all cameras in FlxG.cameras. For example, to prevent a backdrop from rendering on the HUD camera (i.e. to only have it render on the player camera), use:

backdrop.cameras = [playerCamera];

Now, to answer some of your questions:

So, I go study the FlxCamera demo.  It looks right when you run it, but how were these numbers figured out?

background = new FlxSprite(10000 -50, -175);
background.makeGraphic(300, 360, FlxColor.BLACK);

What?  10000...minus 50?  Why minus 50?  And how does -175 relate to anything?  (Let alone, why would you need to offset it at all?)  I guess it's the 360px-tall background graphic, minus 10 px...um...because that's how far the first text is offset from the top...divided by two...even though zoom = 1?
And why is it 300x360 when the hud cam is then 200x180?

The background sprite is created so that the HUD camera has something to follow. FlxCamera can only follow the FlxObject type and its subtypes. Since the HUD is a FlxGroup - a subclass of FlxBasica member of the group that is the size of the HUD must be exposed to be followed by the camera.

The background sprite's position is defined that way to keep it outside of the main camera's view. The third argument expects a FlxGraphicAsset, which is a typedef for three different types: FlxGraphic, BitmapData, and String. These can be instantiated in a manner of ways, one of which includes passing a hex colour value to create a rectangular sprite of the desired colour. And this is exactly what was passed to the constructor - an RGBA hex value of #FFFFFF51 corresponds to the decimal value of -175. In other words, this third argument creates a semi-transparent black rectangular sprite.

I can't comment on the HUD camera values because they seem to be set according to personal preference, much like the initial position of the HUD background sprite.

I hope you've found this helpful.

Regards,

Domagoj

Gimmicky Apps

unread,
Feb 27, 2016, 8:47:53 AM2/27/16
to HaxeFlixel
Hi Domagoj,

Thank you for sharing extensively your code and trying to answer my questions.

I think I probably didn't communicate them well enough in some cases, but I also think you're incorrect on at least one point.

1. I don't have the headspace to dive into your class code right now, but the fact that you need a class like this is an answer in itself to the overall problem, in the sense that you're saying "You can't do what you are trying to do with FlxCamera the way it currently is."  Fair enough.
2. On the part I'm still caught up on, my question "10000...minus 50?" was not about the 10000, of course that's personal preference and I realize well that it's off-screen content.  My question which I tried repeating for emphasis was "Why minus 50?" because elsewhere just "10000" is used, so where did this magic "- 50" come from that somehow makes it visually correct when you sic the camera on it?  Likewise for -175, and the relationship between 300x360 and 200x180, which aren't even the same aspect ratio!
3. The point I think you're incorrect on is -175 corresponding to a colour.  That may be an extremely astute observation of a coincidence, but the second parameter to FlxSprite's constructor isn't a colour, it's the Y position.  It may look like there are three parameters, but there are only two, the first one being "10000 -50" i.e. "9950", maybe you're seeing a comma where there isn't one.  If you try to pass a colour as the third parameter that will not make a sprite with that colour--how big would it be?  An RGBA value is none of the three types you mentioned, it's an Int.  All that, or maybe I'm completely misunderstanding your comment.  :)

Anyway thanks again for posting.  To me it's more and more obvious that FlxCamera was unfortunately designed without enough flexibility for a few of the random conceptualizations I have for zooming within various game contexts, and also that it's not something I'm often psyched to dig into.  (I had enough of a workout making FlxScrollbar and I'm trying to keep a good ratio of fun to "necessary unfun" during development so as to not lose heart.)

Best regards,
GA

Domagoj Štrekelj

unread,
Feb 28, 2016, 2:30:27 AM2/28/16
to HaxeFlixel
First off, I would like to apologize for the third point you've brought up. You are indeed correct about me seeing a comma where there isn't any! Because of this, at some point during my lengthy response I obviously mixed up the FlxSprite constructor with makeGraphic(). I'll make it a point to proofread lengthier replies multiple times to avoid this in the future. Again, sorry for any confusion this must have caused!

Regarding your second point, I believe some of these "offsetting" values (or magic numbers, really) could be there because the HUD follow-style. A LOCK_ON follow-style doesn't exactly centre the target on the camera, it defines a deadzone with a small vertical offset around the target for "breathing room" while moving. Then again, I'm only guessing much like you are.

I do agree with your sentiment - magic numbers in demos are confusing, especially if left undocumented. Not to mention that they are huge time wasters when you're trying to figure out the code they're used in.

Regards,

Domagoj
Reply all
Reply to author
Forward
0 new messages