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.
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.
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.
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.
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.
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.
To know exactly where that center is on screen, you can read the _camera.offset point which is the center in absolute values.
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
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.
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.
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.
These are advanced features you might need to set.
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.
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).
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.
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
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.
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.
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() )