Camera

The camera system is simple, but allows for zooming control, camera rotation, target tracking and bounding the movement of the camera within a space when required.

The camera is always required when your game is bigger than the screen its running in, though some apps don't require it so its not setup by default. In this article we'll learn how to set the camera up and manipulate it.

as the camera can track any object with x and y position, you can even use a fake target object such as a flash Point to create a more dynamic camera movement or even set up some cinematic intro scenes. A camera sequencer is not available however, you can use your prefered tweening library to move the target around and sequence changes in zoom or rotation to do so.

warning some of the camera features are not available when using Citrus with blitting or away3D yet. blitting because of its general restrictions , and away3D because the camera was mostly created for 2D and there are no 3D features to it yet unfortunately.

For this article, we will be using the starling citrus engine, however the same can be done with the normal flash display list.

Setting up the camera

A camera is already created in any state. it is accessible through view.camera - you do not have to initialize or create it, it is created and disabled when the state is created.

you then only have to either enable it by hand with

view.camera.enabled = true;

and set its individual properties. or use view.camera.setUp which enables it and lets you decide on the most used features of the camera in one line.

here's an example of a camera setup. We'll be tracking a point (camTarget) but that can be any citrusObject such as your hero.

protected var camTarget:Point = new Point();
protected var _camera:StarlingCamera;
 
_camera = view.camera as StarlingCamera; // a reference for future use
 
_camera.setUp(camTarget,null,new Point(0.25,0.6));
 
_camera.bounds = new Rectangle(-10000,-10000,20000,20000);
_camera.allowRotation = true;
_camera.allowZoom = true;
_camera.easing.setTo(1, 1);
_camera.rotationEasing = 1;
_camera.zoomEasing = 1;	
_camera.zoomFit(400, 400, true);
_camera.reset();

we grab a reference to the camera returned by view.camera.setUp and cast it as a StarlingCamera (you would cast it as a SpriteCamera if in a flash only citrus engine context) since ACitrusCamera doesn't contain all features used here.

the first argument to setUp is the target. again this can be a hero, a crate, a point, anything with a x and y property. the coordinates of the target need to be in the state's space as camera will consider them to be so.

also remember, that if the camera follows a hero, hero.x/hero.y will not return its position on screen but in the game's coordinates, or the state's coordinates, we'll see later how to project camera coordinates to game coordinates or vice versa.

  • the third argument, is the camera “center”, which is basically where the target will appear to be on screen, consider it like the camera's center, but this center can actually be moved.

if the center point is (0.5,0.5) - which is the default point - then the target will appear at the center of the screen.

If you were to create an infinite scroller where you run from left to right, you may not enjoy that the target appears at the center of the screen, you might want to set it more on the left to see more incoming obstacles, as well as more down if you need more space for ui or flying obstacles who knows… in fact here the target will appear at 1/4 of the width of the screen horizontally, and 6/10 of the height vertically from the top.

  • the second argument is the bounds but we define it later on.

which is basically going to restrict the camera's movement in this rectangle, if the bounds are null, the camera will not be restricted. A note here, is that this rectangle should be in “game coordinates” as well.

  • then we allow rotation and zoom (they are not allowed by default, to prevent the calculations they require).
  • easing is basically the acceleration factor of the camera's movement to follow the target. if set to 1,1, on each update the camera will be exactly where the target is on the x and y axis. you want that to be lower for a nicer easing effect, or leave the default values.
  • rotation and camera easing is the same principle. we set them all to one so they are exactly as the value we will set on each update.
  • camera.zoomFit() will zoom the camera so that a region of the game is “fitted” into the screen.

camera.zoomFit(400,400,true); will zoom the camera so that a 400×400 area of your game would fit exactly into the screen. the 3rd argument , if set to true, will set the camera's baseZoom to that new zoom factor calculated by zoomFit() and set the camera zoom value back to 1.

So if the third argument is true, from now on, if you set zoom to be 1, you'll always be seeing 400×400 of your game and all next zoom values you will apply will then be relative to that factor.

  • calling camera.reset(); will basically update the camera without considering all easing values and put the camera straight at the target.

When you start a game and you have easing values and the target is far away, you would see the camera move from 0,0 to the target which is unpleasant… with reset() it just goes where its supposed to be and you don't get that effect when the state starts.

On resize (going fullscreen from a web page or Air runtime, rotating a mobile device) the camera will use the newest stage sizes to update its position. It will use the center x and y factors to calculate the new camera center as well.

To know exactly where that center is on screen, you can read the _camera.offset point which is the center in absolute values.

Using the camera

Zoom/rotate

you can zoom and/or rotate relatively to the current zoom/rotate values the camera has (useful in updates):

camera.zoom(0.9);
camera.rotate(0.02); // in radians

or set the value directly

camera.setZoom(0.9);
camera.setRotation(0.02); // in radians

Knowing the camera position

This is a bit trickier, specially while taking into account rotation.

But anyway you can get the camera rectangle, in state/game coordinates with

camera.getRect();

this rectangle actually represents the Axis Aligned Bounding Box of the camera rectangle in local state space. As such, it is exactly like the “real” camera rectangle when the rotation of the camera is 0. when its not however, its slightly larger or wider , if you're using rotation and checking if a local point in the game is inside that rectangle, it could be contained even when you actually don't see it at all.

the position of the camera center in state coordinates is

var cameraOffset:Point = camera.camPos;

you would think that you don't need this cause if easing is [1,1] then that point is exactly where target is, but with slow easing or other things in consideration, you are safer using camPos than the target's position.

Projecting coordinates to/from the camera/state

coordinates local to the camera, can be considered as the same as screen or global coordinates.

So you could use flash or starling's globalToLocal or localToGlobal to project a point from state to screen or a point on screen to the state if you want to…

We have “utility functions” though to help with that cause you would need to use localToGlobal/globalToLocal on the viewroot of the state… and if you don't know what I'm talking about, that's it , use these functions !!!

var topLeftCamera:Point = camera.pointFromLocal(0,0);
 
var heroPosition:Point = new Point(_hero.x,_hero.y);
var heroPositionOnScreen:Point = camera.pointToLocal(heroPosition);

Now, confusing names, but pointFromLocal gives us a point from camera coordinates (global) to state coordinates. pointToLocal gives us the opposite of course.

and here we know where the topLeftCamera corner is situated in our game, and where the hero appears on screen.

Checking if a point/object is inside the camera

Using the camera's getRect() would allow you to use the Rectangle.contains() function to check whether a point is inside the camera or not.

But since getRect() is the axis aligned bounding box for the camera, if the camera is rotated, then the result is not accurate.

camera.contains(xa,ya); makes sure if the given coordinates are within the camera rectangle (defined by cameraLensWidth/cameraLensHeight) by considering its transformation (zoom/rotation/translation).

the following can be done to check if a certain object's center is on screen :

if(camera.contains(_hero.x,_hero.y))
   trace("HERO IS ON SCREEN :D");

this doesn't say if the full object is actually contained, only its position.

So really what you want is to see if the graphical representation of your object is contained within the camera.

luckily starling and flash do provide a way to know the “bounds” of a display object through respectively getBounds() and getRect().

The following code in a state, will kill the hero only if its fully outside of the camera :

var heroArt:StarlingArt = view.getArt(_hero) as StarlingArt;
 
if(heroArt.content is DisplayObject)
	if (!camera.intersectsRect( (heroArt.content as DisplayObject).getBounds(heroArt.stage) ))
		_hero.kill = true;

You can even set a different area than the camera rectangle to use intersectsRect, for example if you want to see if the object is within closer range inside the screen, or further away using the second argument to intersectsRect().

var heroArt:StarlingArt = view.getArt(_hero) as StarlingArt;
 
var range:int = 100;
var area:Rectangle = new Rectangle(-range,-range,camera.cameraLensWidth + range, camera.cameraLensHeight + range);
 
if(heroArt.content is DisplayObject)
	if (!camera.containsRect( (heroArt.content as DisplayObject).getBounds(heroArt.stage) , area ))
		trace("Hero is within a",range,"pixel range to the camera.");

intersectsRect returns true if the object is partially on screen, containsRect returns true if it is completely on screen. Both have different uses and both are available.

After the release of 3.1.9, in the github version of CE, issues with containsRect and intersectsRect were brought to our attention, the code here should work for the latest version only. The difference here is the target space used for getBounds(), with 3.1.9 and before, the target space would've been heroArt.parent instead of heroArt.stage .
Hint : getBounds or getRect (the flash display list equivalent) are costly operations, if you are not rotating your object, you can get its bounds once, store it somewhere and move it along with the object as the bounds' width/height will not change , only its position should. you would therefore save performance by doing so.

Parrallax and Bounds modes

These are advanced features you might need to set.

boundsMode

camera.boundsMode by default is set to

ACitrusCamera.BOUNDS_MODE_AABB

this means, the camera will be restricted by its AABB rect, this makes sure you'd see nothing out of the bounds even when rotated.

When this mode is just too horrible because you hate seeing the camera boucing on the corners when rotating, there is a more rotation friendly mode :

ACitrusCamera.BOUNDS_MODE_ADVANCED

This basically considers the camera as a circle and restricts that circle within the defined bounds. when rotating and touching a corner , the camera will not bounce away, it will stay within that circle and freely rotate.

the disadvantage here is you will never see a corner fully (being a circle).

ACitrusCamera.BOUNDS_MODE_OFFSET

this restricts only the offset point of the camera. so no bouncing off corners when rotating, but only the center of the camera is restricted and so you can see outside of the bounds.

parallaxMode

there is still a lot of discussion on how parallax should be handled, the parallaxMode were introduced basically to let room for other approaches to parallax :

default is :

ACitrusCamera.PARALLAX_MODE_TOPLEFT

this mode displaces objects by the parallax factor according to the camera position, so its distance to the state's 0,0 (top left) point , which was the default behavior since parallax exist in CitrusEngine.

ACitrus.PARALLAX_MODE_DEPTH

this mode is interesting but not for everyone. Now it was called “depth” because if you wanted to create a “fake depth” situation where scale of visual objects depended on the parallax values, you would use this mode.

Instead of objects being displaced from the 0,0 point, this has a more natural/real effect :

if the camera is at its target, whatever that object's parallax is, the object will be visible at the center of the camera as well… like if you were to flatten everything you see in a 2D plane, the thing that was straight in front of you would be there no matter what its depth was. This doesn't happen with the default mode.

Basically the objects are displaced according to their position to the camera. this mode made sense to integrate, but is harder to use as far as editing a level in flash is concerned because flash does not simulate that parallax mode (in fact it simulates none of them that's why parallax is hard to work with unless we had a specific editor).

Switching to another target

Basic

Changing the target is as simple as setting the target property to some other object that has x/y properties. the camera's easing will still be used to move to the new target. The camera in fact, is constantly trying to move to the current target position unless target = null.

switchToTarget

You might however prefer to set the speed of this movement. The switchToTarget method allows you to switch to a new target with a defined speed and also lets you define a callback to be used when the new target is reached. When that movement is done and the new target is reached, the camera's target property will be the new property. During the movement however, the target property of the camera will be a temporary target used to move the camera “manually”.

_camera.switchToTarget(newTarget,5,function():void //speed of 5 "pixels" per update.
	{
		trace("camera is now following",newTarget);
	});

A more advanced example of switchToTarget exists in the cameramovement package in the examples where we switch from one hero to another both visually and in control so when target is reached we are able to control the second hero and vice versa (this requires a bit of input system magic): https://github.com/alamboley/Citrus-Engine-Examples/blob/master/src/cameramovement/CameraMovement.as#L220

tweenSwitchToTarget

You can also use tweenSwitchToTarget which is using an EazeTween to tween the camera's movement and returns the EazeTween instance. unlike switchToTarget, if the new target moves during the tween, the target position of the tween is not updated.

camera sequencing, cinematics...

The features are not made available however using the principles within the functions described above, or combining several of them - or even making your own - you can easily set up a camera sequencing system (which might come to CE one day who knows) to have nice in game cinematics or complex camera movement which, lets admit it, adds to the visual and cinematical quality of a game.

The core principle here anyway is “overriding” the camera movement by turning off easing and setting a custom target temporarily.

Notes

The camera creates a transform matrix and applies it to the state's “viewRoot” and the debug views of physics engines.

that transform matrix is accessible via

var matrix:Matrix = camera.transformMatrix;

And you can apply it to any other sprite and have a secondary layer to starling that will move along with the camera for example, or apply it to a flash display object to create your own debug display for anything you need. Make sure to not modify it or issues may occur (if you need to do calculations on it, you are safer cloning it with clone() )

  citrus/camera.txt · Last modified: 2018/08/01 15:18 by 85.54.194.249
 
Powered by DokuWiki