phaser/Phaser/system/Camera.ts

627 lines
20 KiB
TypeScript
Raw Normal View History

2013-04-18 13:16:18 +00:00
/// <reference path="../gameobjects/Sprite.ts" />
/// <reference path="../Game.ts" />
2013-04-18 13:16:18 +00:00
/**
2013-04-18 15:49:08 +00:00
* Phaser - Camera
*
* A Camera is your view into the game world. It has a position, size, scale and rotation and renders only those objects
* within its field of view. The game automatically creates a single Stage sized camera on boot, but it can be changed and
* additional cameras created via the CameraManager.
2013-04-18 13:16:18 +00:00
*/
module Phaser {
export class Camera {
/**
*Sprite constructor
* Instantiates a new camera at the specified location, with the specified size and zoom level.
*
* @param game {Phaser.Game} Current game instance.
* @param id {number} Unique identity.
* @param x {number} X location of the camera's display in pixels. Uses native, 1:1 resolution, ignores zoom.
* @param y {number} Y location of the camera's display in pixels. Uses native, 1:1 resolution, ignores zoom.
* @param width {number} The width of the camera display in pixels.
* @param height {number} The height of the camera display in pixels.
*/
2013-04-18 13:16:18 +00:00
constructor(game: Game, id: number, x: number, y: number, width: number, height: number) {
this._game = game;
this.ID = id;
this._stageX = x;
this._stageY = y;
this.fx = new FXManager(this._game, this);
2013-04-18 13:16:18 +00:00
// The view into the world canvas we wish to render
this.worldView = new Rectangle(0, 0, width, height);
this.checkClip();
}
/**
* Local private reference to Game.
*/
2013-04-18 13:16:18 +00:00
private _game: Game;
private _clip: bool = false;
private _stageX: number;
private _stageY: number;
private _rotation: number = 0;
private _target: Sprite = null;
private _sx: number = 0;
private _sy: number = 0;
/**
* Camera "follow" style preset: camera has no deadzone, just tracks the focus object directly.
* @type {number}
*/
2013-04-18 13:16:18 +00:00
public static STYLE_LOCKON: number = 0;
/**
* Camera "follow" style preset: camera deadzone is narrow but tall.
* @type {number}
*/
2013-04-18 13:16:18 +00:00
public static STYLE_PLATFORMER: number = 1;
/**
* Camera "follow" style preset: camera deadzone is a medium-size square around the focus object.
* @type {number}
*/
2013-04-18 13:16:18 +00:00
public static STYLE_TOPDOWN: number = 2;
/**
* Camera "follow" style preset: camera deadzone is a small square around the focus object.
* @type {number}
*/
2013-04-18 13:16:18 +00:00
public static STYLE_TOPDOWN_TIGHT: number = 3;
/**
* Identity of this camera.
*/
2013-04-18 13:16:18 +00:00
public ID: number;
/**
* Camera view rectangle in world coordinate.
* @type {Rectangle}
*/
2013-04-18 13:16:18 +00:00
public worldView: Rectangle;
/**
* How many sprites will be rendered by this camera.
* @type {number}
*/
2013-04-18 13:16:18 +00:00
public totalSpritesRendered: number;
/**
* Scale factor of the camera.
* @type {MicroPoint}
*/
public scale: MicroPoint = new MicroPoint(1, 1);
/**
* Scrolling factor.
* @type {MicroPoint}
*/
public scroll: MicroPoint = new MicroPoint(0, 0);
/**
* Camera bounds.
* @type {Rectangle}
*/
2013-04-18 13:16:18 +00:00
public bounds: Rectangle = null;
/**
* Sprite moving inside this rectangle will not cause camera moving.
* @type {Rectangle}
*/
2013-04-18 13:16:18 +00:00
public deadzone: Rectangle = null;
// Camera Border
2013-05-02 00:02:06 +00:00
public disableClipping: bool = false;
/**
* Whether render border of this camera or not. (default is false)
* @type {boolean}
*/
2013-04-18 13:16:18 +00:00
public showBorder: bool = false;
/**
* Color of border of this camera. (in css color string)
* @type {string}
*/
2013-04-18 13:16:18 +00:00
public borderColor: string = 'rgb(255,255,255)';
// Camera Background Color
/**
* Whethor camera background invisible or not.
* @type {boolean}
*/
2013-04-18 13:16:18 +00:00
public opaque: bool = true;
/**
* Background color in css color string.
* @type {string}
*/
2013-04-18 13:16:18 +00:00
private _bgColor: string = 'rgb(0,0,0)';
/**
* Background texture to be rendered if background is visible.
*/
2013-04-18 13:16:18 +00:00
private _bgTexture;
/**
* Background texture repeat style. (default is 'repeat')
* @type {string}
*/
2013-04-18 13:16:18 +00:00
private _bgTextureRepeat: string = 'repeat';
// Camera Shadow
/**
* Render camera shadow or not. (default is false)
* @type {boolean}
*/
2013-04-18 13:16:18 +00:00
public showShadow: bool = false;
/**
* Color of shadow, in css color string.
* @type {string}
*/
2013-04-18 13:16:18 +00:00
public shadowColor: string = 'rgb(0,0,0)';
/**
* Blur factor of shadow.
* @type {number}
*/
2013-04-18 13:16:18 +00:00
public shadowBlur: number = 10;
/**
* Offset of the shadow from camera's position.
* @type {MicroPoint}
*/
public shadowOffset: MicroPoint = new MicroPoint(4, 4);
2013-04-18 13:16:18 +00:00
/**
* Whether this camera visible or not. (default is true)
* @type {boolean}
*/
2013-04-18 13:16:18 +00:00
public visible: bool = true;
/**
* Alpha of the camera. (everything rendered to this camera will be affected)
* @type {number}
*/
2013-04-18 13:16:18 +00:00
public alpha: number = 1;
/**
* The x position of the current input event in world coordinates.
* @type {number}
*/
2013-04-18 13:16:18 +00:00
public inputX: number = 0;
/**
* The y position of the current input event in world coordinates.
* @type {number}
*/
2013-04-18 13:16:18 +00:00
public inputY: number = 0;
/**
* Effects manager.
* @type {FXManager}
*/
public fx: FXManager;
2013-04-12 16:19:56 +00:00
/**
* Tells this camera object what sprite to track.
* @param target {Sprite} The object you want the camera to track. Set to null to not follow anything.
* @param style {number} Optional, Leverage one of the existing "deadzone" presets. If you use a custom deadzone, ignore this parameter and manually specify the deadzone after calling follow().
*/
2013-04-18 13:16:18 +00:00
public follow(target: Sprite, style?: number = Camera.STYLE_LOCKON) {
2013-04-12 16:19:56 +00:00
2013-04-18 13:16:18 +00:00
this._target = target;
2013-04-18 13:16:18 +00:00
var helper: number;
2013-04-12 16:19:56 +00:00
2013-04-18 13:16:18 +00:00
switch (style)
{
case Camera.STYLE_PLATFORMER:
var w: number = this.width / 8;
var h: number = this.height / 3;
this.deadzone = new Rectangle((this.width - w) / 2, (this.height - h) / 2 - h * 0.25, w, h);
break;
case Camera.STYLE_TOPDOWN:
helper = Math.max(this.width, this.height) / 4;
this.deadzone = new Rectangle((this.width - helper) / 2, (this.height - helper) / 2, helper, helper);
break;
case Camera.STYLE_TOPDOWN_TIGHT:
helper = Math.max(this.width, this.height) / 8;
this.deadzone = new Rectangle((this.width - helper) / 2, (this.height - helper) / 2, helper, helper);
break;
case Camera.STYLE_LOCKON:
default:
this.deadzone = null;
break;
}
2013-04-12 16:19:56 +00:00
2013-04-18 13:16:18 +00:00
}
2013-04-12 16:19:56 +00:00
/**
* Move the camera focus to this location instantly.
* @param x {number} X position.
* @param y {number} Y position.
*/
2013-04-18 13:16:18 +00:00
public focusOnXY(x: number, y: number) {
2013-04-12 16:19:56 +00:00
2013-04-18 13:16:18 +00:00
x += (x > 0) ? 0.0000001 : -0.0000001;
y += (y > 0) ? 0.0000001 : -0.0000001;
2013-04-12 16:19:56 +00:00
2013-04-18 13:16:18 +00:00
this.scroll.x = Math.round(x - this.worldView.halfWidth);
this.scroll.y = Math.round(y - this.worldView.halfHeight);
2013-04-12 16:19:56 +00:00
}
/**
* Move the camera focus to this location instantly.
* @param point {any} Point you want to focus.
*/
public focusOn(point) {
2013-04-12 16:19:56 +00:00
2013-04-18 13:16:18 +00:00
point.x += (point.x > 0) ? 0.0000001 : -0.0000001;
point.y += (point.y > 0) ? 0.0000001 : -0.0000001;
2013-04-12 16:19:56 +00:00
2013-04-18 13:16:18 +00:00
this.scroll.x = Math.round(point.x - this.worldView.halfWidth);
this.scroll.y = Math.round(point.y - this.worldView.halfHeight);
2013-04-12 16:19:56 +00:00
2013-04-18 13:16:18 +00:00
}
2013-04-12 16:19:56 +00:00
2013-04-18 13:16:18 +00:00
/**
* Specify the boundaries of the world or where the camera is allowed to move.
*
* @param x {number} The smallest X value of your world (usually 0).
* @param y {number} The smallest Y value of your world (usually 0).
* @param width {number} The largest X value of your world (usually the world width).
* @param height {number} The largest Y value of your world (usually the world height).
2013-04-18 13:16:18 +00:00
*/
public setBounds(x: number = 0, y: number = 0, width: number = 0, height: number = 0) {
2013-04-12 16:19:56 +00:00
2013-04-18 13:16:18 +00:00
if (this.bounds == null)
{
this.bounds = new Rectangle();
}
2013-04-12 16:19:56 +00:00
this.bounds.setTo(x, y, width, height);
this.scroll.setTo(0, 0);
2013-04-12 16:19:56 +00:00
2013-04-18 13:16:18 +00:00
this.update();
2013-04-12 16:19:56 +00:00
}
/**
* Update focusing and scrolling.
*/
2013-04-18 13:16:18 +00:00
public update() {
this.fx.preUpdate();
2013-04-18 13:16:18 +00:00
if (this._target !== null)
{
if (this.deadzone == null)
{
this.focusOnXY(this._target.x + this._target.origin.x, this._target.y + this._target.origin.y);
}
else
{
var edge: number;
var targetX: number = this._target.x + ((this._target.x > 0) ? 0.0000001 : -0.0000001);
var targetY: number = this._target.y + ((this._target.y > 0) ? 0.0000001 : -0.0000001);
edge = targetX - this.deadzone.x;
2013-04-12 16:19:56 +00:00
2013-04-18 13:16:18 +00:00
if (this.scroll.x > edge)
{
this.scroll.x = edge;
}
2013-04-12 16:19:56 +00:00
2013-04-18 13:16:18 +00:00
edge = targetX + this._target.width - this.deadzone.x - this.deadzone.width;
2013-04-12 16:19:56 +00:00
2013-04-18 13:16:18 +00:00
if (this.scroll.x < edge)
{
this.scroll.x = edge;
}
2013-04-12 16:19:56 +00:00
2013-04-18 13:16:18 +00:00
edge = targetY - this.deadzone.y;
2013-04-12 16:19:56 +00:00
2013-04-18 13:16:18 +00:00
if (this.scroll.y > edge)
{
this.scroll.y = edge;
}
2013-04-12 16:19:56 +00:00
2013-04-18 13:16:18 +00:00
edge = targetY + this._target.height - this.deadzone.y - this.deadzone.height;
if (this.scroll.y < edge)
{
this.scroll.y = edge;
}
2013-04-12 16:19:56 +00:00
}
2013-04-18 13:16:18 +00:00
}
2013-04-12 16:19:56 +00:00
// Make sure we didn't go outside the cameras bounds
2013-04-18 13:16:18 +00:00
if (this.bounds !== null)
{
if (this.scroll.x < this.bounds.left)
2013-04-12 16:19:56 +00:00
{
2013-04-18 13:16:18 +00:00
this.scroll.x = this.bounds.left;
2013-04-12 16:19:56 +00:00
}
2013-04-18 13:16:18 +00:00
if (this.scroll.x > this.bounds.right - this.width)
2013-04-12 16:19:56 +00:00
{
this.scroll.x = (this.bounds.right - this.width) + 1;
2013-04-12 16:19:56 +00:00
}
2013-04-18 13:16:18 +00:00
if (this.scroll.y < this.bounds.top)
{
this.scroll.y = this.bounds.top;
}
2013-04-12 16:19:56 +00:00
2013-04-18 13:16:18 +00:00
if (this.scroll.y > this.bounds.bottom - this.height)
2013-04-12 16:19:56 +00:00
{
this.scroll.y = (this.bounds.bottom - this.height) + 1;
2013-04-12 16:19:56 +00:00
}
}
2013-04-18 13:16:18 +00:00
this.worldView.x = this.scroll.x;
this.worldView.y = this.scroll.y;
2013-04-12 16:19:56 +00:00
2013-04-18 13:16:18 +00:00
// Input values
this.inputX = this.worldView.x + this._game.input.x;
this.inputY = this.worldView.y + this._game.input.y;
2013-04-12 16:19:56 +00:00
this.fx.postUpdate();
2013-04-18 13:16:18 +00:00
2013-04-12 16:19:56 +00:00
}
/**
* Draw background, shadow, effects, and objects if this is visible.
*/
2013-04-18 13:16:18 +00:00
public render() {
2013-04-12 16:19:56 +00:00
if (this.visible === false || this.alpha < 0.1)
2013-04-12 16:19:56 +00:00
{
2013-04-18 13:16:18 +00:00
return;
2013-04-12 16:19:56 +00:00
}
2013-04-18 13:16:18 +00:00
//if (this._rotation !== 0 || this._clip || this.scale.x !== 1 || this.scale.y !== 1)
//{
//this._game.stage.context.save();
//}
2013-04-12 16:19:56 +00:00
// It may be safer/quicker to just save the context every frame regardless (needs testing on mobile)
2013-04-18 13:16:18 +00:00
this._game.stage.context.save();
2013-04-12 16:19:56 +00:00
this.fx.preRender(this, this._stageX, this._stageY, this.worldView.width, this.worldView.height);
2013-04-18 13:16:18 +00:00
if (this.alpha !== 1)
{
this._game.stage.context.globalAlpha = this.alpha;
}
2013-04-12 16:19:56 +00:00
2013-04-18 13:16:18 +00:00
this._sx = this._stageX;
this._sy = this._stageY;
2013-04-12 16:19:56 +00:00
2013-04-18 13:16:18 +00:00
// Shadow
if (this.showShadow)
{
this._game.stage.context.shadowColor = this.shadowColor;
this._game.stage.context.shadowBlur = this.shadowBlur;
this._game.stage.context.shadowOffsetX = this.shadowOffset.x;
this._game.stage.context.shadowOffsetY = this.shadowOffset.y;
}
2013-04-12 16:19:56 +00:00
2013-04-18 13:16:18 +00:00
// Scale on
if (this.scale.x !== 1 || this.scale.y !== 1)
{
this._game.stage.context.scale(this.scale.x, this.scale.y);
this._sx = this._sx / this.scale.x;
this._sy = this._sy / this.scale.y;
}
2013-04-12 16:19:56 +00:00
2013-04-18 13:16:18 +00:00
// Rotation - translate to the mid-point of the camera
if (this._rotation !== 0)
{
this._game.stage.context.translate(this._sx + this.worldView.halfWidth, this._sy + this.worldView.halfHeight);
this._game.stage.context.rotate(this._rotation * (Math.PI / 180));
2013-04-12 16:19:56 +00:00
2013-04-18 13:16:18 +00:00
// now shift back to where that should actually render
this._game.stage.context.translate(-(this._sx + this.worldView.halfWidth), -(this._sy + this.worldView.halfHeight));
}
2013-04-12 16:19:56 +00:00
2013-04-18 13:16:18 +00:00
// Background
if (this.opaque == true)
{
if (this._bgTexture)
{
this._game.stage.context.fillStyle = this._bgTexture;
this._game.stage.context.fillRect(this._sx, this._sy, this.worldView.width, this.worldView.height);
}
else
{
this._game.stage.context.fillStyle = this._bgColor;
this._game.stage.context.fillRect(this._sx, this._sy, this.worldView.width, this.worldView.height);
}
}
2013-04-12 16:19:56 +00:00
2013-04-18 13:16:18 +00:00
// Shadow off
if (this.showShadow)
{
this._game.stage.context.shadowBlur = 0;
this._game.stage.context.shadowOffsetX = 0;
this._game.stage.context.shadowOffsetY = 0;
}
2013-04-12 16:19:56 +00:00
this.fx.render(this, this._stageX, this._stageY, this.worldView.width, this.worldView.height);
2013-04-18 13:16:18 +00:00
// Clip the camera so we don't get sprites appearing outside the edges
2013-05-02 00:02:06 +00:00
if (this._clip && this.disableClipping == false)
2013-04-18 13:16:18 +00:00
{
this._game.stage.context.beginPath();
this._game.stage.context.rect(this._sx, this._sy, this.worldView.width, this.worldView.height);
this._game.stage.context.closePath();
this._game.stage.context.clip();
}
2013-04-12 16:19:56 +00:00
2013-04-18 13:16:18 +00:00
this._game.world.group.render(this, this._sx, this._sy);
2013-04-12 16:19:56 +00:00
2013-04-18 13:16:18 +00:00
if (this.showBorder)
{
this._game.stage.context.strokeStyle = this.borderColor;
this._game.stage.context.lineWidth = 1;
this._game.stage.context.rect(this._sx, this._sy, this.worldView.width, this.worldView.height);
this._game.stage.context.stroke();
}
2013-04-12 16:19:56 +00:00
2013-04-18 13:16:18 +00:00
// Scale off
if (this.scale.x !== 1 || this.scale.y !== 1)
{
this._game.stage.context.scale(1, 1);
}
2013-04-12 16:19:56 +00:00
this.fx.postRender(this, this._sx, this._sy, this.worldView.width, this.worldView.height);
2013-05-02 00:02:06 +00:00
if (this._rotation !== 0 || (this._clip && this.disableClipping == false))
2013-04-18 13:16:18 +00:00
{
this._game.stage.context.translate(0, 0);
}
2013-04-12 16:19:56 +00:00
2013-04-18 13:16:18 +00:00
this._game.stage.context.restore();
2013-04-12 16:19:56 +00:00
2013-04-18 13:16:18 +00:00
if (this.alpha !== 1)
{
this._game.stage.context.globalAlpha = 1;
}
2013-04-12 16:19:56 +00:00
}
2013-04-18 13:16:18 +00:00
public set backgroundColor(color: string) {
2013-04-12 16:19:56 +00:00
2013-04-18 13:16:18 +00:00
this._bgColor = color;
2013-04-12 16:19:56 +00:00
}
2013-04-18 13:16:18 +00:00
public get backgroundColor(): string {
return this._bgColor;
2013-04-12 16:19:56 +00:00
}
/**
* Set camera background texture.
* @param key {string} Asset key of the texture.
* @param repeat {string} Optional, what kind of repeat will this texture used for background.
*/
2013-04-18 13:16:18 +00:00
public setTexture(key: string, repeat?: string = 'repeat') {
2013-04-12 16:19:56 +00:00
2013-04-18 13:16:18 +00:00
this._bgTexture = this._game.stage.context.createPattern(this._game.cache.getImage(key), repeat);
this._bgTextureRepeat = repeat;
2013-04-12 16:19:56 +00:00
2013-04-18 13:16:18 +00:00
}
2013-04-12 16:19:56 +00:00
/**
* Set position of this camera.
* @param x {number} X position.
* @param y {number} Y position.
*/
2013-04-18 13:16:18 +00:00
public setPosition(x: number, y: number) {
2013-04-12 16:19:56 +00:00
2013-04-18 13:16:18 +00:00
this._stageX = x;
this._stageY = y;
2013-04-12 16:19:56 +00:00
2013-04-18 13:16:18 +00:00
this.checkClip();
2013-04-12 16:19:56 +00:00
2013-04-18 13:16:18 +00:00
}
2013-04-12 16:19:56 +00:00
/**
* Give this camera a new size.
* @param width {number} Width of new size.
* @param height {number} Height of new size.
*/
2013-04-18 13:16:18 +00:00
public setSize(width: number, height: number) {
2013-04-12 16:19:56 +00:00
2013-04-18 13:16:18 +00:00
this.worldView.width = width;
this.worldView.height = height;
this.checkClip();
2013-04-12 16:19:56 +00:00
2013-04-18 13:16:18 +00:00
}
2013-04-12 16:19:56 +00:00
/**
* Render debug infos. (including id, position, rotation, scrolling factor, bounds and some other properties)
* @param x {number} X position of the debug info to be rendered.
* @param y {number} Y position of the debug info to be rendered.
* @param color {number} Optional, color of the debug info to be rendered. (format is css color string)
*/
2013-04-18 13:16:18 +00:00
public renderDebugInfo(x: number, y: number, color?: string = 'rgb(255,255,255)') {
2013-04-12 16:19:56 +00:00
2013-04-18 13:16:18 +00:00
this._game.stage.context.fillStyle = color;
this._game.stage.context.fillText('Camera ID: ' + this.ID + ' (' + this.worldView.width + ' x ' + this.worldView.height + ')', x, y);
this._game.stage.context.fillText('X: ' + this._stageX + ' Y: ' + this._stageY + ' Rotation: ' + this._rotation, x, y + 14);
this._game.stage.context.fillText('World X: ' + this.scroll.x.toFixed(1) + ' World Y: ' + this.scroll.y.toFixed(1), x, y + 28);
2013-04-12 16:19:56 +00:00
2013-04-18 13:16:18 +00:00
if (this.bounds)
{
this._game.stage.context.fillText('Bounds: ' + this.bounds.width + ' x ' + this.bounds.height, x, y + 56);
}
2013-04-12 16:19:56 +00:00
}
2013-04-18 13:16:18 +00:00
public get x(): number {
return this._stageX;
}
2013-04-12 16:19:56 +00:00
2013-04-18 13:16:18 +00:00
public set x(value: number) {
this._stageX = value;
this.checkClip();
}
2013-04-12 16:19:56 +00:00
2013-04-18 13:16:18 +00:00
public get y(): number {
return this._stageY;
}
2013-04-12 16:19:56 +00:00
2013-04-18 13:16:18 +00:00
public set y(value: number) {
this._stageY = value;
this.checkClip();
}
2013-04-12 16:19:56 +00:00
2013-04-18 13:16:18 +00:00
public get width(): number {
return this.worldView.width;
}
2013-04-12 16:19:56 +00:00
2013-04-18 13:16:18 +00:00
public set width(value: number) {
if (value > this._game.stage.width)
{
value = this._game.stage.width;
}
2013-04-18 13:16:18 +00:00
this.worldView.width = value;
this.checkClip();
}
2013-04-12 16:19:56 +00:00
2013-04-18 13:16:18 +00:00
public get height(): number {
return this.worldView.height;
}
2013-04-12 16:19:56 +00:00
2013-04-18 13:16:18 +00:00
public set height(value: number) {
if (value > this._game.stage.height)
{
value = this._game.stage.height;
}
2013-04-18 13:16:18 +00:00
this.worldView.height = value;
this.checkClip();
}
2013-04-12 16:19:56 +00:00
2013-04-18 13:16:18 +00:00
public get rotation(): number {
return this._rotation;
}
2013-04-12 16:19:56 +00:00
2013-04-18 13:16:18 +00:00
public set rotation(value: number) {
this._rotation = this._game.math.wrap(value, 360, 0);
}
2013-04-12 16:19:56 +00:00
2013-04-18 13:16:18 +00:00
private checkClip() {
2013-04-12 16:19:56 +00:00
2013-04-18 13:16:18 +00:00
if (this._stageX !== 0 || this._stageY !== 0 || this.worldView.width < this._game.stage.width || this.worldView.height < this._game.stage.height)
{
this._clip = true;
}
else
{
this._clip = false;
}
2013-04-12 16:19:56 +00:00
}
}
}