flixel/FlxCamera.hx
package flixel;
import openfl.display.Bitmap;
import openfl.display.BitmapData;
import openfl.display.DisplayObject;
import openfl.display.Graphics;
import openfl.display.Sprite;
import openfl.geom.ColorTransform;
import openfl.geom.Point;
import openfl.geom.Rectangle;
import flixel.graphics.FlxGraphic;
import flixel.graphics.frames.FlxFrame;
import flixel.graphics.tile.FlxDrawBaseItem;
import flixel.graphics.tile.FlxDrawTrianglesItem;
import flixel.math.FlxMath;
import flixel.math.FlxMatrix;
import flixel.math.FlxPoint;
import flixel.math.FlxRect;
import flixel.system.FlxAssets.FlxShader;
import flixel.util.FlxAxes;
import flixel.util.FlxColor;
import flixel.util.FlxDestroyUtil;
import flixel.util.FlxSpriteUtil;
import openfl.Vector;
import openfl.display.BlendMode;
import openfl.filters.BitmapFilter;
using flixel.util.FlxColorTransformUtil;
private typedef FlxDrawItem = flixel.graphics.tile.FlxDrawQuadsItem;
/**
* The camera class is used to display the game's visuals.
* By default one camera is created automatically, that is the same size as window.
* You can add more cameras or even replace the main camera using utilities in `FlxG.cameras`.
*
* Every camera has following display list:
* `flashSprite:Sprite` (which is a container for everything else in the camera, it's added to FlxG.game sprite)
* |-> `_scrollRect:Sprite` (which is used for cropping camera's graphic, mostly in tile render mode)
* |-> `_flashBitmap:Bitmap` (its bitmapData property is buffer BitmapData, this var is used in blit render mode.
* Everything is rendered on buffer in blit render mode)
* |-> `canvas:Sprite` (its graphics is used for rendering objects in tile render mode)
* |-> `debugLayer:Sprite` (this sprite is used in tile render mode for rendering debug info, like bounding boxes)
*/
class FlxCamera extends FlxBasic
{
/**
* Any `FlxCamera` with a zoom of 0 (the default value) will have this zoom value.
*/
public static var defaultZoom:Float = 1.0;
/**
* Used behind-the-scenes during the draw phase so that members use the same default
* cameras as their parent.
*
* Prior to 4.9.0 it was useful to change this value, but that feature is deprecated.
* Instead use the `defaultDrawTarget` argument in `FlxG.cameras.add `.
* or`FlxG.cameras.setDefaultDrawTarget` .
* @see FlxG.cameras.setDefaultDrawTarget
*/
@:deprecated("`FlxCamera.defaultCameras` is deprecated, use `FlxG.cameras.setDefaultDrawTarget` instead")
public static var defaultCameras(get, set):Array<FlxCamera>;
/**
* Used behind-the-scenes during the draw phase so that members use the same default
* cameras as their parent.
*
* This is the non-deprecated list that the public `defaultCameras` proxies. Allows flixel classes
* to use it without warning.
*/
@:allow(flixel.FlxBasic.get_cameras)
@:allow(flixel.FlxBasic.get_camera)
@:allow(flixel.system.frontEnds.CameraFrontEnd)
@:allow(flixel.group.FlxTypedGroup.draw)
static var _defaultCameras:Array<FlxCamera>;
/**
* The X position of this camera's display. `zoom` does NOT affect this number.
* Measured in pixels from the left side of the window.
* You might be interested in using camera's `scroll.x` instead.
*/
public var x(default, set):Float = 0;
/**
* The Y position of this camera's display. `zoom` does NOT affect this number.
* Measured in pixels from the top of the window.
* You might be interested in using camera's `scroll.y` instead.
*/
public var y(default, set):Float = 0;
/**
* The scaling on horizontal axis for this camera.
* Setting `scaleX` changes `scaleX` and x coordinate of camera's internal display objects.
*/
public var scaleX(default, null):Float = 0;
/**
* The scaling on vertical axis for this camera.
* Setting `scaleY` changes `scaleY` and y coordinate of camera's internal display objects.
*/
public var scaleY(default, null):Float = 0;
/**
* Product of camera's `scaleX` and game's scale mode `scale.x` multiplication.
*/
public var totalScaleX(default, null):Float;
/**
* Product of camera's scaleY and game's scale mode scale.y multiplication.
*/
public var totalScaleY(default, null):Float;
/**
* Tells the camera to use this following style.
*/
public var style:FlxCameraFollowStyle;
/**
* Tells the camera to follow this FlxObject object around.
*/
public var target:FlxObject;
/**
* Offset the camera target.
*/
public var targetOffset(default, null):FlxPoint = FlxPoint.get();
/**
* Used to smoothly track the camera as it follows:
* The percent of the distance to the follow `target` the camera moves per 1/60 sec.
* Values are bounded between `0.0` and `60 / FlxG.updateFramerate` for consistency across framerates.
* The maximum value means no camera easing. A value of `0` means the camera does not move.
*/
public var followLerp(default, set):Float = 60 / FlxG.updateFramerate;
/**
* You can assign a "dead zone" to the camera in order to better control its movement.
* The camera will always keep the focus object inside the dead zone, unless it is bumping up against
* the camera bounds. The `deadzone`'s coordinates are measured from the camera's upper left corner in game pixels.
* For rapid prototyping, you can use the preset deadzones (e.g. `PLATFORMER`) with `follow()`.
*/
public var deadzone:FlxRect;
/**
* Lower bound of the camera's `scroll` on the x axis.
*/
public var minScrollX:Null<Float>;
/**
* Upper bound of the camera's `scroll` on the x axis.
*/
public var maxScrollX:Null<Float>;
/**
* Lower bound of the camera's `scroll` on the y axis.
*/
public var minScrollY:Null<Float>;
/**
* Upper bound of the camera's `scroll` on the y axis.
*/
public var maxScrollY:Null<Float>;
/**
* Stores the basic parallax scrolling values.
* This is basically the camera's top-left corner position in world coordinates.
* There is also `focusOn(point:FlxPoint)` which you can use to
* make the camera look at specified point in world coordinates.
*/
public var scroll:FlxPoint = FlxPoint.get();
/**
* The actual `BitmapData` of the camera display itself.
* Used in blit render mode, where you can manipulate its pixels for achieving some visual effects.
*/
public var buffer:BitmapData;
/**
* The natural background color of the camera, in `AARRGGBB` format. Defaults to `FlxG.cameras.bgColor`.
* On Flash, transparent backgrounds can be used in conjunction with `useBgAlphaBlending`.
*/
public var bgColor:FlxColor;
/**
* Sometimes it's easier to just work with a `FlxSprite`, than it is to work directly with the `BitmapData` buffer.
* This sprite reference will allow you to do exactly that.
* Basically, this sprite's `pixels` property is the camera's `BitmapData` buffer.
*
* **NOTE:** This field is only used in blit render mode.
*/
public var screen:FlxSprite;
/**
* Whether to use alpha blending for the camera's background fill or not.
* If `true`, then the previously drawn graphics won't be erased,
* and if the camera's `bgColor` is transparent/semitransparent, then you
* will be able to see the graphics of the previous frame.
*
* This is Useful for blit render mode (and only works in this mode).
* Default value is `false`.
*/
public var useBgAlphaBlending:Bool = false;
/**
* Used to render buffer to screen space.
* NOTE: We don't recommend modifying this directly unless you are fairly experienced.
* Uses include 3D projection, advanced display list modification, and more.
* This is container for everything else that is used by camera and rendered to the camera.
*
* Its position is modified by `updateFlashSpritePosition()` which is called every frame.
*/
public var flashSprite:Sprite = new Sprite();
/**
* Whether the positions of the objects rendered on this camera are rounded.
* If set on individual objects, they ignore the global camera setting.
* Defaults to `false` with `FlxG.renderTile` and to `true` with `FlxG.renderBlit`.
* WARNING: setting this to `false` on blitting targets is very expensive.
*/
public var pixelPerfectRender:Bool;
/**
* If true, screen shake will be rounded to game pixels. If null, pixelPerfectRender is used.
* @since 5.4.0
*/
public var pixelPerfectShake:Null<Bool> = null;
/**
* How wide the camera display is, in game pixels.
*/
public var width(default, set):Int = 0;
/**
* How tall the camera display is, in game pixels.
*/
public var height(default, set):Int = 0;
/**
* The zoom level of this camera. `1` = 1:1, `2` = 2x zoom, etc.
* Indicates how far the camera is zoomed in.
* Note: Changing this property from it's initial value will change properties like:
* `viewX`, `viewY`, `viewWidth`, `viewHeight` and many others. Cameras always zoom in to
* their center, meaning as you zoom in, the view is cut off on all sides.
*/
public var zoom(default, set):Float;
/**
* The margin cut off on the left and right by the camera zooming in (or out), in world space.
* @since 5.2.0
*/
public var viewMarginX(default, null):Float;
/**
* The margin cut off on the top and bottom by the camera zooming in (or out), in world space.
* @since 5.2.0
*/
public var viewMarginY(default, null):Float;
// deprecated vars
@:deprecated("use viewMarginLeft or viewMarginX")
var viewOffsetX(get, set):Float;
@:deprecated("use viewMarginTop or viewMarginY")
var viewOffsetY(get, set):Float;
@:deprecated("use viewMarginLeft or viewMarginX")
var viewOffsetWidth(get, never):Float;
@:deprecated("use viewMarginTop or viewMarginY")
var viewOffsetHeight(get, never):Float;
// delegates
/**
* The margin cut off on the left by the camera zooming in (or out), in world space.
* @since 5.2.0
*/
public var viewMarginLeft(get, never):Float;
/**
* The margin cut off on the top by the camera zooming in (or out), in world space
* @since 5.2.0
*/
public var viewMarginTop(get, never):Float;
/**
* The margin cut off on the right by the camera zooming in (or out), in world space
* @since 5.2.0
*/
public var viewMarginRight(get, never):Float;
/**
* The margin cut off on the bottom by the camera zooming in (or out), in world space
* @since 5.2.0
*/
public var viewMarginBottom(get, never):Float;
/**
* The size of the camera's view, in world space.
* @since 5.2.0
*/
public var viewWidth(get, never):Float;
/**
* The size of the camera's view, in world space.
* @since 5.2.0
*/
public var viewHeight(get, never):Float;
/**
* The left of the camera's view, in world space.
* @since 5.2.0
*/
public var viewX(get, never):Float;
/**
* The top of the camera's view, in world space.
* @since 5.2.0
*/
public var viewY(get, never):Float;
/**
* The left of the camera's view, in world space.
* @since 5.2.0
*/
public var viewLeft(get, never):Float;
/**
* The top of the camera's view, in world space.
* @since 5.2.0
*/
public var viewTop(get, never):Float;
/**
* The right side of the camera's view, in world space.
* @since 5.2.0
*/
public var viewRight(get, never):Float;
/**
* The bottom side of the camera's view, in world space.
* @since 5.2.0
*/
public var viewBottom(get, never):Float;
/**
* Helper matrix object. Used in blit render mode when camera's zoom is less than initialZoom
* (it is applied to all objects rendered on the camera at such circumstances).
*/
var _blitMatrix:FlxMatrix = new FlxMatrix();
/**
* Logical flag for tracking whether to apply _blitMatrix transformation to objects or not.
*/
var _useBlitMatrix:Bool = false;
/**
* The alpha value of this camera display (a number between `0.0` and `1.0`).
*/
public var alpha(default, set):Float = 1;
/**
* The angle of the camera display (in degrees).
*/
public var angle(default, set):Float = 0;
/**
* The color tint of the camera display.
*/
public var color(default, set):FlxColor = FlxColor.WHITE;
/**
* Whether the camera display is smooth and filtered, or chunky and pixelated.
* Default behavior is chunky-style.
*/
public var antialiasing(default, set):Bool = false;
/**
* Used to force the camera to look ahead of the target.
*/
public var followLead(default, null):FlxPoint = FlxPoint.get();
/**
* Enables or disables the filters set via `setFilters()`.
*/
public var filtersEnabled:Bool = true;
/**
* Internal, used in blit render mode in camera's `fill()` method for less garbage creation.
* It represents the size of buffer `BitmapData`
* (the area of camera's buffer which should be filled with `bgColor`).
* Do not modify it unless you know what are you doing.
*/
var _flashRect:Rectangle;
/**
* Internal, used in blit render mode in camera's `fill()` method for less garbage creation:
* Its coordinates are always `(0,0)`, where camera's buffer filling should start.
* Do not modify it unless you know what are you doing.
*/
var _flashPoint:Point = new Point();
/**
* Internal, used for positioning camera's `flashSprite` on screen.
* Basically it represents position of camera's center point in game sprite.
* It's recalculated every time you resize game or camera.
* Its value depends on camera's size (`width` and `height`), game's `scale` and camera's initial zoom factor.
* Do not modify it unless you know what are you doing.
*/
var _flashOffset:FlxPoint = FlxPoint.get();
/**
* Internal, represents the color of `flash()` special effect.
*/
var _fxFlashColor:FlxColor = FlxColor.TRANSPARENT;
/**
* Internal, stores `flash()` special effect duration.
*/
var _fxFlashDuration:Float = 0;
/**
* Internal, camera's `flash()` complete callback.
*/
var _fxFlashComplete:Void->Void = null;
/**
* Internal, used to control the `flash()` special effect.
*/
var _fxFlashAlpha:Float = 0;
/**
* Internal, color of fading special effect.
*/
var _fxFadeColor:FlxColor = FlxColor.TRANSPARENT;
/**
* Used to calculate the following target current velocity.
*/
var _lastTargetPosition:FlxPoint;
/**
* Helper to calculate follow target current scroll.
*/
var _scrollTarget:FlxPoint = FlxPoint.get();
/**
* Internal, `fade()` special effect duration.
*/
var _fxFadeDuration:Float = 0;
/**
* Internal, "direction" of the `fade()` effect.
* `true` means that camera fades from a color, `false` - camera fades to it.
*/
var _fxFadeIn:Bool = false;
/**
* Internal, used to control the `fade()` special effect complete callback.
*/
var _fxFadeComplete:Void->Void = null;
/**
* Internal, alpha component of fade color.
* Changes from 0 to 1 or from 1 to 0 as the effect continues.
*/
var _fxFadeAlpha:Float = 0;
/**
* Internal, percentage of screen size representing the maximum distance that the screen can move while shaking.
*/
var _fxShakeIntensity:Float = 0;
/**
* Internal, duration of the `shake()` effect.
*/
var _fxShakeDuration:Float = 0;
/**
* Internal, `shake()` effect complete callback.
*/
var _fxShakeComplete:Void->Void;
/**
* Internal, defines on what axes to `shake()`. Default value is `XY` / both.
*/
var _fxShakeAxes:FlxAxes = XY;
/**
* Internal, used for repetitive calculations and added to help avoid costly allocations.
*/
var _point:FlxPoint = FlxPoint.get();
/**
* The filters array to be applied to the camera.
*/
public var filters:Null<Array<BitmapFilter>> = null;
@:deprecated("_filters is deprecated, use filters instead")
var _filters(get, set):Null<Array<BitmapFilter>>;
/**
* Camera's initial zoom value. Used for camera's scale handling.
*/
public var initialZoom(default, null):Float = 1;
/**
* Internal helper variable for doing better wipes/fills between renders.
* Used it blit render mode only (in `fill()` method).
*/
var _fill:BitmapData;
/**
* Internal, used to render buffer to screen space. Used it blit render mode only.
* This Bitmap used for rendering camera's buffer (`_flashBitmap.bitmapData = buffer;`)
* Its position is modified by `updateInternalSpritePositions()`, which is called on camera's resize and scale events.
* It is a child of the `_scrollRect` `Sprite`.
*/
var _flashBitmap:Bitmap;
/**
* Internal sprite, used for correct trimming of camera viewport.
* It is a child of `flashSprite`.
* Its position is modified by `updateScrollRect()` method, which is called on camera's resize and scale events.
*/
var _scrollRect:Sprite = new Sprite();
/**
* Helper rect for `drawTriangles()` visibility checks
*/
var _bounds:FlxRect = FlxRect.get();
/**
* Sprite used for actual rendering in tile render mode (instead of `_flashBitmap` for blitting).
* Its graphics is used as a drawing surface for `drawTriangles()` and `drawTiles()` methods.
* It is a child of `_scrollRect` `Sprite` (which trims graphics that should be invisible).
* Its position is modified by `updateInternalSpritePositions()`, which is called on camera's resize and scale events.
*/
public var canvas:Sprite;
#if FLX_DEBUG
/**
* Sprite for visual effects (flash and fade) and drawDebug information
* (bounding boxes are drawn on it) for tile render mode.
* It is a child of `_scrollRect` `Sprite` (which trims graphics that should be invisible).
* Its position is modified by `updateInternalSpritePositions()`, which is called on camera's resize and scale events.
*/
public var debugLayer:Sprite;
#end
var _helperMatrix:FlxMatrix = new FlxMatrix();
var _helperPoint:Point = new Point();
/**
* Currently used draw stack item
*/
var _currentDrawItem:FlxDrawBaseItem<Dynamic>;
/**
* Pointer to head of stack with draw items
*/
var _headOfDrawStack:FlxDrawBaseItem<Dynamic>;
/**
* Last draw tiles item
*/
var _headTiles:FlxDrawItem;
/**
* Last draw triangles item
*/
var _headTriangles:FlxDrawTrianglesItem;
/**
* Draw tiles stack items that can be reused
*/
static var _storageTilesHead:FlxDrawItem;
/**
* Draw triangles stack items that can be reused
*/
static var _storageTrianglesHead:FlxDrawTrianglesItem;
/**
* Internal variable, used for visibility checks to minimize `drawTriangles()` calls.
*/
static var drawVertices:Vector<Float> = new Vector<Float>();
/**
* Internal variable, used in blit render mode to render triangles (`drawTriangles()`) on camera's buffer.
*/
static var trianglesSprite:Sprite = new Sprite();
/**
* Internal variables, used in blit render mode to draw trianglesSprite on camera's buffer.
* Added for less garbage creation.
*/
static var renderPoint:FlxPoint = FlxPoint.get();
static var renderRect:FlxRect = FlxRect.get();
@:noCompletion
public function startQuadBatch(graphic:FlxGraphic, colored:Bool, hasColorOffsets:Bool = false, ?blend:BlendMode, smooth:Bool = false, ?shader:FlxShader)
{
#if FLX_RENDER_TRIANGLE
return startTrianglesBatch(graphic, smooth, colored, blend);
#else
var itemToReturn = null;
var blendInt:Int = FlxDrawBaseItem.blendToInt(blend);
if (_currentDrawItem != null
&& _currentDrawItem.type == FlxDrawItemType.TILES
&& _headTiles.graphics == graphic
&& _headTiles.colored == colored
&& _headTiles.hasColorOffsets == hasColorOffsets
&& _headTiles.blending == blendInt
&& _headTiles.blend == blend
&& _headTiles.antialiasing == smooth
&& _headTiles.shader == shader)
{
return _headTiles;
}
if (_storageTilesHead != null)
{
itemToReturn = _storageTilesHead;
var newHead = _storageTilesHead.nextTyped;
itemToReturn.reset();
_storageTilesHead = newHead;
}
else
{
itemToReturn = new FlxDrawItem();
}
// TODO: catch this error when the dev actually messes up, not in the draw phase
if (graphic.isDestroyed)
throw 'Attempted to queue an invalid FlxDrawItem, did you destroy a cached sprite?';
itemToReturn.graphics = graphic;
itemToReturn.antialiasing = smooth;
itemToReturn.colored = colored;
itemToReturn.hasColorOffsets = hasColorOffsets;
itemToReturn.blending = blendInt;
itemToReturn.blend = blend;
itemToReturn.shader = shader;
itemToReturn.nextTyped = _headTiles;
_headTiles = itemToReturn;
if (_headOfDrawStack == null)
{
_headOfDrawStack = itemToReturn;
}
if (_currentDrawItem != null)
{
_currentDrawItem.next = itemToReturn;
}
_currentDrawItem = itemToReturn;
return itemToReturn;
#end
}
@:noCompletion
public function startTrianglesBatch(graphic:FlxGraphic, smoothing:Bool = false, isColored:Bool = false, ?blend:BlendMode, ?hasColorOffsets:Bool, ?shader:FlxShader):FlxDrawTrianglesItem
{
var blendInt:Int = FlxDrawBaseItem.blendToInt(blend);
if (_currentDrawItem != null
&& _currentDrawItem.type == FlxDrawItemType.TRIANGLES
&& _headTriangles.graphics == graphic
&& _headTriangles.antialiasing == smoothing
&& _headTriangles.colored == isColored
&& _headTriangles.blending == blendInt
#if !flash
&& _headTriangles.hasColorOffsets == hasColorOffsets
&& _headTriangles.shader == shader
#end
)
{
return _headTriangles;
}
return getNewDrawTrianglesItem(graphic, smoothing, isColored, blend, hasColorOffsets, shader);
}
@:noCompletion
public function getNewDrawTrianglesItem(graphic:FlxGraphic, smoothing:Bool = false, isColored:Bool = false, ?blend:BlendMode, ?hasColorOffsets:Bool, ?shader:FlxShader):FlxDrawTrianglesItem
{
var itemToReturn:FlxDrawTrianglesItem = null;
var blendInt:Int = FlxDrawBaseItem.blendToInt(blend);
if (_storageTrianglesHead != null)
{
itemToReturn = _storageTrianglesHead;
var newHead:FlxDrawTrianglesItem = _storageTrianglesHead.nextTyped;
itemToReturn.reset();
_storageTrianglesHead = newHead;
}
else
{
itemToReturn = new FlxDrawTrianglesItem();
}
itemToReturn.graphics = graphic;
itemToReturn.antialiasing = smoothing;
itemToReturn.colored = isColored;
itemToReturn.blending = blendInt;
#if !flash
itemToReturn.hasColorOffsets = hasColorOffsets;
itemToReturn.shader = shader;
#end
itemToReturn.nextTyped = _headTriangles;
_headTriangles = itemToReturn;
if (_headOfDrawStack == null)
{
_headOfDrawStack = itemToReturn;
}
if (_currentDrawItem != null)
{
_currentDrawItem.next = itemToReturn;
}
_currentDrawItem = itemToReturn;
return itemToReturn;
}
@:allow(flixel.system.frontEnds.CameraFrontEnd)
function clearDrawStack():Void
{
var currTiles = _headTiles;
var newTilesHead;
while (currTiles != null)
{
newTilesHead = currTiles.nextTyped;
currTiles.reset();
currTiles.nextTyped = _storageTilesHead;
_storageTilesHead = currTiles;
currTiles = newTilesHead;
}
var currTriangles:FlxDrawTrianglesItem = _headTriangles;
var newTrianglesHead:FlxDrawTrianglesItem;
while (currTriangles != null)
{
newTrianglesHead = currTriangles.nextTyped;
currTriangles.reset();
currTriangles.nextTyped = _storageTrianglesHead;
_storageTrianglesHead = currTriangles;
currTriangles = newTrianglesHead;
}
_currentDrawItem = null;
_headOfDrawStack = null;
_headTiles = null;
_headTriangles = null;
}
@:allow(flixel.system.frontEnds.CameraFrontEnd)
function render():Void
{
var currItem:FlxDrawBaseItem<Dynamic> = _headOfDrawStack;
while (currItem != null)
{
currItem.render(this);
currItem = currItem.next;
}
}
public function drawPixels(?frame:FlxFrame, ?pixels:BitmapData, matrix:FlxMatrix, ?transform:ColorTransform, ?blend:BlendMode, ?smoothing:Bool = false,
?shader:FlxShader):Void
{
if (FlxG.renderBlit)
{
_helperMatrix.copyFrom(matrix);
if (_useBlitMatrix)
{
_helperMatrix.concat(_blitMatrix);
buffer.draw(pixels, _helperMatrix, null, null, null, (smoothing || antialiasing));
}
else
{
_helperMatrix.translate(-viewMarginLeft, -viewMarginTop);
buffer.draw(pixels, _helperMatrix, null, blend, null, (smoothing || antialiasing));
}
}
else
{
var isColored = (transform != null && transform.hasRGBMultipliers());
var hasColorOffsets:Bool = (transform != null && transform.hasRGBAOffsets());
#if FLX_RENDER_TRIANGLE
var drawItem:FlxDrawTrianglesItem = startTrianglesBatch(frame.parent, smoothing, isColored, blend);
#else
var drawItem = startQuadBatch(frame.parent, isColored, hasColorOffsets, blend, smoothing, shader);
#end
drawItem.addQuad(frame, matrix, transform);
}
}
public function copyPixels(?frame:FlxFrame, ?pixels:BitmapData, ?sourceRect:Rectangle, destPoint:Point, ?transform:ColorTransform, ?blend:BlendMode,
?smoothing:Bool = false, ?shader:FlxShader):Void
{
if (FlxG.renderBlit)
{
if (pixels != null)
{
if (_useBlitMatrix)
{
_helperMatrix.identity();
_helperMatrix.translate(destPoint.x, destPoint.y);
_helperMatrix.concat(_blitMatrix);
buffer.draw(pixels, _helperMatrix, null, null, null, (smoothing || antialiasing));
}
else
{
_helperPoint.x = destPoint.x - Std.int(viewMarginLeft);
_helperPoint.y = destPoint.y - Std.int(viewMarginTop);
buffer.copyPixels(pixels, sourceRect, _helperPoint, null, null, true);
}
}
else if (frame != null)
{
// TODO: fix this case for zoom less than initial zoom...
frame.paint(buffer, destPoint, true);
}
}
else
{
_helperMatrix.identity();
_helperMatrix.translate(destPoint.x + frame.offset.x, destPoint.y + frame.offset.y);
var isColored = (transform != null && transform.hasRGBMultipliers());
var hasColorOffsets:Bool = (transform != null && transform.hasRGBAOffsets());
#if !FLX_RENDER_TRIANGLE
var drawItem = startQuadBatch(frame.parent, isColored, hasColorOffsets, blend, smoothing, shader);
#else
var drawItem:FlxDrawTrianglesItem = startTrianglesBatch(frame.parent, smoothing, isColored, blend);
#end
drawItem.addQuad(frame, _helperMatrix, transform);
}
}
public function drawTriangles(graphic:FlxGraphic, vertices:DrawData<Float>, indices:DrawData<Int>, uvtData:DrawData<Float>, ?colors:DrawData<Int>,
?position:FlxPoint, ?blend:BlendMode, repeat:Bool = false, smoothing:Bool = false, ?transform:ColorTransform, ?shader:FlxShader):Void
{
if (FlxG.renderBlit)
{
if (position == null)
position = renderPoint.set();
_bounds.set(0, 0, width, height);
var verticesLength:Int = vertices.length;
var currentVertexPosition:Int = 0;
var tempX:Float, tempY:Float;
var i:Int = 0;
var bounds = renderRect.set();
drawVertices.splice(0, drawVertices.length);
while (i < verticesLength)
{
tempX = position.x + vertices[i];
tempY = position.y + vertices[i + 1];
drawVertices[currentVertexPosition++] = tempX;
drawVertices[currentVertexPosition++] = tempY;
if (i == 0)
{
bounds.set(tempX, tempY, 0, 0);
}
else
{
FlxDrawTrianglesItem.inflateBounds(bounds, tempX, tempY);
}
i += 2;
}
position.putWeak();
if (!_bounds.overlaps(bounds))
{
drawVertices.splice(drawVertices.length - verticesLength, verticesLength);
}
else
{
trianglesSprite.graphics.clear();
trianglesSprite.graphics.beginBitmapFill(graphic.bitmap, null, repeat, smoothing);
trianglesSprite.graphics.drawTriangles(drawVertices, indices, uvtData);
trianglesSprite.graphics.endFill();
// TODO: check this block of code for cases, when zoom < 1 (or initial zoom?)...
if (_useBlitMatrix)
_helperMatrix.copyFrom(_blitMatrix);
else
{
_helperMatrix.identity();
_helperMatrix.translate(-viewMarginLeft, -viewMarginTop);
}
buffer.draw(trianglesSprite, _helperMatrix);
#if FLX_DEBUG
if (FlxG.debugger.drawDebug)
{
var gfx:Graphics = FlxSpriteUtil.flashGfx;
gfx.clear();
gfx.lineStyle(1, FlxColor.BLUE, 0.5);
gfx.drawTriangles(drawVertices, indices);
buffer.draw(FlxSpriteUtil.flashGfxSprite, _helperMatrix);
}
#end
// End of TODO...
}
bounds.put();
}
else
{
_bounds.set(0, 0, width, height);
var isColored:Bool = (colors != null && colors.length != 0);
#if !flash
var hasColorOffsets:Bool = (transform != null && transform.hasRGBAOffsets());
isColored = isColored || (transform != null && transform.hasRGBMultipliers());
var drawItem:FlxDrawTrianglesItem = startTrianglesBatch(graphic, smoothing, isColored, blend, hasColorOffsets, shader);
drawItem.addTriangles(vertices, indices, uvtData, colors, position, _bounds, transform);
#else
var drawItem:FlxDrawTrianglesItem = startTrianglesBatch(graphic, smoothing, isColored, blend);
drawItem.addTriangles(vertices, indices, uvtData, colors, position, _bounds);
#end
}
}
/**
* Helper method preparing debug rectangle for rendering in blit render mode
* @param rect rectangle to prepare for rendering
* @return transformed rectangle with respect to camera's zoom factor
*/
function transformRect(rect:FlxRect):FlxRect
{
if (FlxG.renderBlit)
{
rect.offset(-viewMarginLeft, -viewMarginTop);
if (_useBlitMatrix)
{
rect.x *= zoom;
rect.y *= zoom;
rect.width *= zoom;
rect.height *= zoom;
}
}
return rect;
}
/**
* Helper method preparing debug point for rendering in blit render mode (for debug path rendering, for example)
* @param point point to prepare for rendering
* @return transformed point with respect to camera's zoom factor
*/
function transformPoint(point:FlxPoint):FlxPoint
{
if (FlxG.renderBlit)
{
point.subtract(viewMarginLeft, viewMarginTop);
if (_useBlitMatrix)
point.scale(zoom);
}
return point;
}
/**
* Helper method preparing debug vectors (relative positions) for rendering in blit render mode
* @param vector relative position to prepare for rendering
* @return transformed vector with respect to camera's zoom factor
*/
inline function transformVector(vector:FlxPoint):FlxPoint
{
if (FlxG.renderBlit && _useBlitMatrix)
vector.scale(zoom);
return vector;
}
/**
* Helper method for applying transformations (scaling and offsets)
* to specified display objects which has been added to the camera display list.
* For example, debug sprite for nape debug rendering.
* @param object display object to apply transformations to.
* @return transformed object.
*/
function transformObject(object:DisplayObject):DisplayObject
{
object.scaleX *= totalScaleX;
object.scaleY *= totalScaleY;
object.x -= scroll.x * totalScaleX;
object.y -= scroll.y * totalScaleY;
object.x -= 0.5 * width * (scaleX - initialZoom) * FlxG.scaleMode.scale.x;
object.y -= 0.5 * height * (scaleY - initialZoom) * FlxG.scaleMode.scale.y;
return object;
}
/**
* Instantiates a new camera at the specified location, with the specified size and zoom level.
*
* @param X X location of the camera's display in pixels. Uses native, 1:1 resolution, ignores zoom.
* @param Y Y location of the camera's display in pixels. Uses native, 1:1 resolution, ignores zoom.
* @param Width The width of the camera display in pixels.
* @param Height The height of the camera display in pixels.
* @param Zoom The initial zoom level of the camera.
* A zoom level of 2 will make all pixels display at 2x resolution.
*/
public function new(X:Float = 0, Y:Float = 0, Width:Int = 0, Height:Int = 0, Zoom:Float = 0)
{
super();
x = X;
y = Y;
// Use the game dimensions if width / height are <= 0
width = (Width <= 0) ? FlxG.width : Width;
height = (Height <= 0) ? FlxG.height : Height;
_flashRect = new Rectangle(0, 0, width, height);
flashSprite.addChild(_scrollRect);
_scrollRect.scrollRect = new Rectangle();
pixelPerfectRender = FlxG.renderBlit;
if (FlxG.renderBlit)
{
screen = new FlxSprite();
buffer = new BitmapData(width, height, true, 0);
screen.pixels = buffer;
screen.origin.set();
_flashBitmap = new Bitmap(buffer);
_scrollRect.addChild(_flashBitmap);
_fill = new BitmapData(width, height, true, FlxColor.TRANSPARENT);
}
else
{
canvas = new Sprite();
_scrollRect.addChild(canvas);
#if FLX_DEBUG
debugLayer = new Sprite();
_scrollRect.addChild(debugLayer);
#end
}
set_color(FlxColor.WHITE);
initialZoom = (Zoom == 0) ? defaultZoom : Zoom;
zoom = Zoom; // sets the scale of flash sprite, which in turn loads flashOffset values
updateScrollRect();
updateFlashOffset();
updateFlashSpritePosition();
updateInternalSpritePositions();
bgColor = FlxG.cameras.bgColor;
}
/**
* Clean up memory.
*/
override public function destroy():Void
{
FlxDestroyUtil.removeChild(flashSprite, _scrollRect);
if (FlxG.renderBlit)
{
FlxDestroyUtil.removeChild(_scrollRect, _flashBitmap);
screen = FlxDestroyUtil.destroy(screen);
buffer = null;
_flashBitmap = null;
_fill = FlxDestroyUtil.dispose(_fill);
}
else
{
#if FLX_DEBUG
FlxDestroyUtil.removeChild(_scrollRect, debugLayer);
debugLayer = null;
#end
FlxDestroyUtil.removeChild(_scrollRect, canvas);
if (canvas != null)
{
for (i in 0...canvas.numChildren)
{
canvas.removeChildAt(0);
}
canvas = null;
}
if (_headOfDrawStack != null)
{
clearDrawStack();
}
_blitMatrix = null;
_helperMatrix = null;
_helperPoint = null;
}
_bounds = null;
scroll = FlxDestroyUtil.put(scroll);
targetOffset = FlxDestroyUtil.put(targetOffset);
deadzone = FlxDestroyUtil.put(deadzone);
target = null;
flashSprite = null;
_scrollRect = null;
_flashRect = null;
_flashPoint = null;
_fxFlashComplete = null;
_fxFadeComplete = null;
_fxShakeComplete = null;
super.destroy();
}
/**
* Updates the camera scroll as well as special effects like screen-shake or fades.
*/
override public function update(elapsed:Float):Void
{
// follow the target, if there is one
if (target != null)
{
updateFollow();
}
updateScroll();
updateFlash(elapsed);
updateFade(elapsed);
flashSprite.filters = filtersEnabled ? filters : null;
updateFlashSpritePosition();
updateShake(elapsed);
}
/**
* Updates (bounds) the camera scroll.
* Called every frame by camera's `update()` method.
*/
public function updateScroll():Void
{
// Make sure we didn't go outside the camera's bounds
bindScrollPos(scroll);
}
/**
* Takes the desired scroll position and restricts it to the camera's min/max scroll properties.
* This modifies the given point.
*
* @param scrollPos The scroll position
* @return The same point passed in, moved within the scroll bounds
* @since 5.4.0
*/
public function bindScrollPos(scrollPos:FlxPoint)
{
var minX:Null<Float> = minScrollX == null ? null : minScrollX - (zoom - 1) * width / (2 * zoom);
var maxX:Null<Float> = maxScrollX == null ? null : maxScrollX + (zoom - 1) * width / (2 * zoom);
var minY:Null<Float> = minScrollY == null ? null : minScrollY - (zoom - 1) * height / (2 * zoom);
var maxY:Null<Float> = maxScrollY == null ? null : maxScrollY + (zoom - 1) * height / (2 * zoom);
// keep point with bounds
scrollPos.x = FlxMath.bound(scrollPos.x, minX, (maxX != null) ? maxX - width : null);
scrollPos.y = FlxMath.bound(scrollPos.y, minY, (maxY != null) ? maxY - height : null);
return scrollPos;
}
/**
* Updates camera's scroll.
* Called every frame by camera's `update()` method (if camera's `target` isn't `null`).
*/
public function updateFollow():Void
{
// Either follow the object closely,
// or double check our deadzone and update accordingly.
if (deadzone == null)
{
target.getMidpoint(_point);
_point.addPoint(targetOffset);
_scrollTarget.set(_point.x - width * 0.5, _point.y - height * 0.5);
}
else
{
var edge:Float;
var targetX:Float = target.x + targetOffset.x;
var targetY:Float = target.y + targetOffset.y;
if (style == SCREEN_BY_SCREEN)
{
if (targetX >= viewRight)
{
_scrollTarget.x += viewWidth;
}
else if (targetX + target.width < viewLeft)
{
_scrollTarget.x -= viewWidth;
}
if (targetY >= viewBottom)
{
_scrollTarget.y += viewHeight;
}
else if (targetY + target.height < viewTop)
{
_scrollTarget.y -= viewHeight;
}
// without this we see weird behavior when switching to SCREEN_BY_SCREEN at arbitrary scroll positions
bindScrollPos(_scrollTarget);
}
else
{
edge = targetX - deadzone.x;
if (_scrollTarget.x > edge)
{
_scrollTarget.x = edge;
}
edge = targetX + target.width - deadzone.x - deadzone.width;
if (_scrollTarget.x < edge)
{
_scrollTarget.x = edge;
}
edge = targetY - deadzone.y;
if (_scrollTarget.y > edge)
{
_scrollTarget.y = edge;
}
edge = targetY + target.height - deadzone.y - deadzone.height;
if (_scrollTarget.y < edge)
{
_scrollTarget.y = edge;
}
}
if ((target is FlxSprite))
{
if (_lastTargetPosition == null)
{
_lastTargetPosition = FlxPoint.get(target.x, target.y); // Creates this point.
}
_scrollTarget.x += (target.x - _lastTargetPosition.x) * followLead.x;
_scrollTarget.y += (target.y - _lastTargetPosition.y) * followLead.y;
_lastTargetPosition.x = target.x;
_lastTargetPosition.y = target.y;
}
}
if (followLerp >= 60 / FlxG.updateFramerate)
{
scroll.copyFrom(_scrollTarget); // no easing
}
else
{
scroll.x += (_scrollTarget.x - scroll.x) * followLerp * (60 / FlxG.updateFramerate);
scroll.y += (_scrollTarget.y - scroll.y) * followLerp * (60 / FlxG.updateFramerate);
}
}
function updateFlash(elapsed:Float):Void
{
// Update the "flash" special effect
if (_fxFlashAlpha > 0.0)
{
_fxFlashAlpha -= elapsed / _fxFlashDuration;
if ((_fxFlashAlpha <= 0) && (_fxFlashComplete != null))
{
_fxFlashComplete();
}
}
}
function updateFade(elapsed:Float):Void
{
if (_fxFadeDuration == 0.0)
return;
if (_fxFadeIn)
{
_fxFadeAlpha -= elapsed / _fxFadeDuration;
if (_fxFadeAlpha <= 0.0)
{
_fxFadeAlpha = 0.0;
completeFade();
}
}
else
{
_fxFadeAlpha += elapsed / _fxFadeDuration;
if (_fxFadeAlpha >= 1.0)
{
_fxFadeAlpha = 1.0;
completeFade();
}
}
}
function completeFade()
{
_fxFadeDuration = 0.0;
if (_fxFadeComplete != null)
_fxFadeComplete();
}
function updateShake(elapsed:Float):Void
{
if (_fxShakeDuration > 0)
{
_fxShakeDuration -= elapsed;
if (_fxShakeDuration <= 0)
{
if (_fxShakeComplete != null)
{
_fxShakeComplete();
}
}
else
{
final pixelPerfect = pixelPerfectShake == null ? pixelPerfectRender : pixelPerfectShake;
if (_fxShakeAxes.x)
{
var shakePixels = FlxG.random.float(-1, 1) * _fxShakeIntensity * width;
if (pixelPerfect)
shakePixels = Math.round(shakePixels);
flashSprite.x += shakePixels * zoom * FlxG.scaleMode.scale.x;
}
if (_fxShakeAxes.y)
{
var shakePixels = FlxG.random.float(-1, 1) * _fxShakeIntensity * height;
if (pixelPerfect)
shakePixels = Math.round(shakePixels);
flashSprite.y += shakePixels * zoom * FlxG.scaleMode.scale.y;
}
}
}
}
/**
* Recalculates `flashSprite` position.
* Called every frame by camera's `update()` method and every time you change camera's position.
*/
function updateFlashSpritePosition():Void
{
if (flashSprite != null)
{
flashSprite.x = x * FlxG.scaleMode.scale.x + _flashOffset.x;
flashSprite.y = y * FlxG.scaleMode.scale.y + _flashOffset.y;
}
}
/**
* Recalculates `_flashOffset` point, which is used for positioning flashSprite in the game.
* It's called every time you resize the camera or the game.
*/
function updateFlashOffset():Void
{
_flashOffset.x = width * 0.5 * FlxG.scaleMode.scale.x * initialZoom;
_flashOffset.y = height * 0.5 * FlxG.scaleMode.scale.y * initialZoom;
}
/**
* Updates `_scrollRect` sprite to crop graphics of the camera:
* 1) `scrollRect` property of this sprite
* 2) position of this sprite inside `flashSprite`
*
* It takes camera's size and game's scale into account.
* It's called every time you resize the camera or the game.
*/
function updateScrollRect():Void
{
var rect:Rectangle = (_scrollRect != null) ? _scrollRect.scrollRect : null;
if (rect != null)
{
rect.x = rect.y = 0;
rect.width = width * initialZoom * FlxG.scaleMode.scale.x;
rect.height = height * initialZoom * FlxG.scaleMode.scale.y;
_scrollRect.scrollRect = rect;
_scrollRect.x = -0.5 * rect.width;
_scrollRect.y = -0.5 * rect.height;
}
}
/**
* Modifies position of `_flashBitmap` in blit render mode and `canvas` and `debugSprite`
* in tile render mode (these objects are children of `_scrollRect` sprite).
* It takes camera's size and game's scale into account.
* It's called every time you resize the camera or the game.
*/
function updateInternalSpritePositions():Void
{
if (FlxG.renderBlit)
{
if (_flashBitmap != null)
{
_flashBitmap.x = 0;
_flashBitmap.y = 0;
}
}
else
{
if (canvas != null)
{
canvas.x = -0.5 * width * (scaleX - initialZoom) * FlxG.scaleMode.scale.x;
canvas.y = -0.5 * height * (scaleY - initialZoom) * FlxG.scaleMode.scale.y;
canvas.scaleX = totalScaleX;
canvas.scaleY = totalScaleY;
#if FLX_DEBUG
if (debugLayer != null)
{
debugLayer.x = canvas.x;
debugLayer.y = canvas.y;
debugLayer.scaleX = totalScaleX;
debugLayer.scaleY = totalScaleY;
}
#end
}
}
}
/**
* Tells this camera object what `FlxObject` to track.
*
* @param Target The object you want the camera to track. Set to `null` to not follow anything.
* @param Style Leverage one of the existing "deadzone" presets. Default is `LOCKON`.
* If you use a custom deadzone, ignore this parameter and
* manually specify the deadzone after calling `follow()`.
* @param Lerp How much lag the camera should have (can help smooth out the camera movement).
*/
public function follow(Target:FlxObject, ?Style:FlxCameraFollowStyle, ?Lerp:Float):Void
{
if (Style == null)
Style = LOCKON;
if (Lerp == null)
Lerp = 60 / FlxG.updateFramerate;
style = Style;
target = Target;
followLerp = Lerp;
_lastTargetPosition = FlxDestroyUtil.put(_lastTargetPosition);
deadzone = FlxDestroyUtil.put(deadzone);
switch (Style)
{
case LOCKON:
var w:Float = 0;
var h:Float = 0;
if (target != null)
{
w = target.width;
h = target.height;
}
deadzone = FlxRect.get((width - w) / 2, (height - h) / 2 - h * 0.25, w, h);
case PLATFORMER:
final w:Float = (width / 8);
final h:Float = (height / 3);
deadzone = FlxRect.get((width - w) / 2, (height - h) / 2 - h * 0.25, w, h);
case TOPDOWN:
final helper = Math.max(width, height) / 4;
deadzone = FlxRect.get((width - helper) / 2, (height - helper) / 2, helper, helper);
case TOPDOWN_TIGHT:
final helper = Math.max(width, height) / 8;
deadzone = FlxRect.get((width - helper) / 2, (height - helper) / 2, helper, helper);
case SCREEN_BY_SCREEN:
deadzone = FlxRect.get(0, 0, width, height);
case NO_DEAD_ZONE:
deadzone = null;
}
}
/**
* Snaps the camera to the current `target`. Useful to move the camera without
* any easing when the `target` position changes and there is a `followLerp`.
*/
public function snapToTarget():Void
{
updateFollow();
scroll.copyFrom(_scrollTarget);
}
/**
* Move the camera focus to this location instantly.
*
* @param Point Where you want the camera to focus.
*/
public inline function focusOn(point:FlxPoint):Void
{
scroll.set(point.x - width * 0.5, point.y - height * 0.5);
point.putWeak();
}
/**
* The screen is filled with this color and gradually returns to normal.
*
* @param Color The color you want to use.
* @param Duration How long it takes for the flash to fade.
* @param OnComplete A function you want to run when the flash finishes.
* @param Force Force the effect to reset.
*/
public function flash(Color:FlxColor = FlxColor.WHITE, Duration:Float = 1, ?OnComplete:Void->Void, Force:Bool = false):Void
{
if (!Force && (_fxFlashAlpha > 0.0))
return;
_fxFlashColor = Color;
if (Duration <= 0)
Duration = 0.000001;
_fxFlashDuration = Duration;
_fxFlashComplete = OnComplete;
_fxFlashAlpha = 1.0;
}
/**
* The screen is gradually filled with this color.
*
* @param Color The color you want to use.
* @param Duration How long it takes for the fade to finish.
* @param FadeIn `true` fades from a color, `false` fades to it.
* @param OnComplete A function you want to run when the fade finishes.
* @param Force Force the effect to reset.
*/
public function fade(Color:FlxColor = FlxColor.BLACK, Duration:Float = 1, FadeIn:Bool = false, ?OnComplete:Void->Void, Force:Bool = false):Void
{
if (_fxFadeDuration > 0 && !Force)
return;
_fxFadeColor = Color;
if (Duration <= 0)
Duration = 0.000001;
_fxFadeIn = FadeIn;
_fxFadeDuration = Duration;
_fxFadeComplete = OnComplete;
_fxFadeAlpha = _fxFadeIn ? 0.999999 : 0.000001;
}
/**
* A simple screen-shake effect.
*
* @param Intensity Percentage of screen size representing the maximum distance
* that the screen can move while shaking.
* @param Duration The length in seconds that the shaking effect should last.
* @param OnComplete A function you want to run when the shake effect finishes.
* @param Force Force the effect to reset (default = `true`, unlike `flash()` and `fade()`!).
* @param Axes On what axes to shake. Default value is `FlxAxes.XY` / both.
*/
public function shake(Intensity:Float = 0.05, Duration:Float = 0.5, ?OnComplete:Void->Void, Force:Bool = true, ?Axes:FlxAxes):Void
{
if (Axes == null)
Axes = XY;
if (!Force && _fxShakeDuration > 0)
return;
_fxShakeIntensity = Intensity;
_fxShakeDuration = Duration;
_fxShakeComplete = OnComplete;
_fxShakeAxes = Axes;
}
/**
* Stops the fade effect on `this` camera.
*/
public function stopFade():Void
{
_fxFadeAlpha = 0.0;
_fxFadeDuration = 0.0;
}
/**
* Stops the flash effect on `this` camera.
*/
public function stopFlash():Void
{
_fxFlashAlpha = 0.0;
updateFlashSpritePosition();
}
/**
* Stops the shake effect on `this` camera.
*/
public function stopShake():Void
{
_fxShakeDuration = 0.0;
}
/**
* Stops all effects on `this` camera.
*/
public function stopFX():Void
{
_fxFadeAlpha = 0.0;
_fxFadeDuration = 0.0;
_fxFlashAlpha = 0.0;
updateFlashSpritePosition();
_fxShakeDuration = 0.0;
}
/**
* Sets the filter array to be applied to the camera.
*/
@:deprecated("setFilters() is deprecated, use the filters array instead")
public function setFilters(filters:Array<BitmapFilter>):Void
{
this.filters = filters;
}
/**
* Copy the bounds, focus object, and `deadzone` info from an existing camera.
*
* @param Camera The camera you want to copy from.
* @return A reference to this `FlxCamera` object.
*/
public function copyFrom(Camera:FlxCamera):FlxCamera
{
setScrollBounds(Camera.minScrollX, Camera.maxScrollX, Camera.minScrollY, Camera.maxScrollY);
target = Camera.target;
if (target != null)
{
if (Camera.deadzone == null)
{
deadzone = null;
}
else
{
if (deadzone == null)
{
deadzone = FlxRect.get();
}
deadzone.copyFrom(Camera.deadzone);
}
}
return this;
}
/**
* Fill the camera with the specified color.
*
* @param Color The color to fill with in `0xAARRGGBB` hex format.
* @param BlendAlpha Whether to blend the alpha value or just wipe the previous contents. Default is `true`.
*/
public function fill(Color:FlxColor, BlendAlpha:Bool = true, FxAlpha:Float = 1.0, ?graphics:Graphics):Void
{
if (FlxG.renderBlit)
{
if (BlendAlpha)
{
_fill.fillRect(_flashRect, Color);
buffer.copyPixels(_fill, _flashRect, _flashPoint, null, null, BlendAlpha);
}
else
{
buffer.fillRect(_flashRect, Color);
}
}
else
{
if (FxAlpha == 0)
return;
var targetGraphics:Graphics = (graphics == null) ? canvas.graphics : graphics;
targetGraphics.beginFill(Color, FxAlpha);
// i'm drawing rect with these parameters to avoid light lines at the top and left of the camera,
// which could appear while cameras fading
targetGraphics.drawRect(viewMarginLeft - 1, viewMarginTop - 1, viewWidth + 2, viewHeight + 2);
targetGraphics.endFill();
}
}
/**
* Internal helper function, handles the actual drawing of all the special effects.
*/
@:allow(flixel.system.frontEnds.CameraFrontEnd)
function drawFX():Void
{
var alphaComponent:Float;
// Draw the "flash" special effect onto the buffer
if (_fxFlashAlpha > 0.0)
{
alphaComponent = _fxFlashColor.alpha;
if (FlxG.renderBlit)
{
fill((Std.int(((alphaComponent <= 0) ? 0xff : alphaComponent) * _fxFlashAlpha) << 24) + (_fxFlashColor & 0x00ffffff));
}
else
{
fill((_fxFlashColor & 0x00ffffff), true, ((alphaComponent <= 0) ? 0xff : alphaComponent) * _fxFlashAlpha / 255, canvas.graphics);
}
}
// Draw the "fade" special effect onto the buffer
if (_fxFadeAlpha > 0.0)
{
alphaComponent = _fxFadeColor.alpha;
if (FlxG.renderBlit)
{
fill((Std.int(((alphaComponent <= 0) ? 0xff : alphaComponent) * _fxFadeAlpha) << 24) + (_fxFadeColor & 0x00ffffff));
}
else
{
fill((_fxFadeColor & 0x00ffffff), true, ((alphaComponent <= 0) ? 0xff : alphaComponent) * _fxFadeAlpha / 255, canvas.graphics);
}
}
}
@:allow(flixel.system.frontEnds.CameraFrontEnd)
function checkResize():Void
{
if (FlxG.renderBlit)
{
if (width != buffer.width || height != buffer.height)
{
var oldBuffer:FlxGraphic = screen.graphic;
buffer = new BitmapData(width, height, true, 0);
screen.pixels = buffer;
screen.origin.set();
_flashBitmap.bitmapData = buffer;
_flashRect.width = width;
_flashRect.height = height;
_fill = FlxDestroyUtil.dispose(_fill);
_fill = new BitmapData(width, height, true, FlxColor.TRANSPARENT);
FlxG.bitmap.removeIfNoUse(oldBuffer);
}
updateBlitMatrix();
}
}
inline function updateBlitMatrix():Void
{
_blitMatrix.identity();
_blitMatrix.translate(-viewMarginLeft, -viewMarginTop);
_blitMatrix.scale(scaleX, scaleY);
_useBlitMatrix = (scaleX < initialZoom) || (scaleY < initialZoom);
}
/**
* Shortcut for setting both `width` and `height`.
*
* @param Width The new camera width.
* @param Height The new camera height.
*/
public inline function setSize(Width:Int, Height:Int)
{
width = Width;
height = Height;
}
/**
* Helper function to set the coordinates of this camera.
* Handy since it only requires one line of code.
*
* @param X The new x position.
* @param Y The new y position.
*/
public inline function setPosition(X:Float = 0, Y:Float = 0):Void
{
x = X;
y = Y;
}
/**
* Specify the bounding rectangle of where the camera is allowed to move.
*
* @param X The smallest X value of your level (usually `0`).
* @param Y The smallest Y value of your level (usually `0`).
* @param Width The largest X value of your level (usually the level width).
* @param Height The largest Y value of your level (usually the level height).
* @param UpdateWorld Whether the global quad-tree's dimensions should be updated to match (default: `false`).
*/
public function setScrollBoundsRect(X:Float = 0, Y:Float = 0, Width:Float = 0, Height:Float = 0, UpdateWorld:Bool = false):Void
{
if (UpdateWorld)
{
FlxG.worldBounds.set(X, Y, Width, Height);
}
setScrollBounds(X, X + Width, Y, Y + Height);
}
/**
* Specify the bounds of where the camera is allowed to move.
* Set the boundary of a side to `null` to leave that side unbounded.
*
* @param MinX The minimum X value the camera can scroll to
* @param MaxX The maximum X value the camera can scroll to
* @param MinY The minimum Y value the camera can scroll to
* @param MaxY The maximum Y value the camera can scroll to
*/
public function setScrollBounds(MinX:Null<Float>, MaxX:Null<Float>, MinY:Null<Float>, MaxY:Null<Float>):Void
{
minScrollX = MinX;
maxScrollX = MaxX;
minScrollY = MinY;
maxScrollY = MaxY;
updateScroll();
}
/**
* Helper function to set the scale of this camera.
* Handy since it only requires one line of code.
*
* @param X The new scale on x axis
* @param Y The new scale of y axis
*/
public function setScale(X:Float, Y:Float):Void
{
scaleX = X;
scaleY = Y;
totalScaleX = scaleX * FlxG.scaleMode.scale.x;
totalScaleY = scaleY * FlxG.scaleMode.scale.y;
if (FlxG.renderBlit)
{
updateBlitMatrix();
if (_useBlitMatrix)
{
_flashBitmap.scaleX = initialZoom * FlxG.scaleMode.scale.x;
_flashBitmap.scaleY = initialZoom * FlxG.scaleMode.scale.y;
}
else
{
_flashBitmap.scaleX = totalScaleX;
_flashBitmap.scaleY = totalScaleY;
}
}
calcMarginX();
calcMarginY();
updateScrollRect();
updateInternalSpritePositions();
FlxG.cameras.cameraResized.dispatch(this);
}
/**
* Called by camera front end every time you resize the game.
* It triggers reposition of camera's internal display objects.
*/
public function onResize():Void
{
updateFlashOffset();
setScale(scaleX, scaleY);
}
/**
* The size and position of this camera's margins, via `viewMarginLeft`, `viewMarginTop`, `viewWidth`
* and `viewHeight`.
*
* Notes: Deprecated, in 4.11.0 this was made public, but the wording is confusing.
* In flixel 6.0.0 this will be changed to use `viewX`, `viewY`, `viewWidth` and `viewHeight`,
* meaning, this will return the world coordinates of the camera.
* @since 4.11.0
*/
@deprecated("getViewMarginRect")
public function getViewRect(?rect:FlxRect)
{
if (rect == null)
rect = FlxRect.get();
return rect.set(viewMarginLeft, viewMarginTop, viewWidth, viewHeight);
}
/**
* The size and position of this camera's margins, via `viewMarginLeft`, `viewMarginTop`, `viewWidth`
* and `viewHeight`.
* @since 5.2.0
*/
public function getViewMarginRect(?rect:FlxRect)
{
if (rect == null)
rect = FlxRect.get();
return rect.set(viewMarginLeft, viewMarginTop, viewWidth, viewHeight);
}
/**
* Checks whether this camera contains a given point or rectangle, in
* screen coordinates.
* @since 4.3.0
*/
public inline function containsPoint(point:FlxPoint, width:Float = 0, height:Float = 0):Bool
{
var contained = (point.x + width > viewMarginLeft) && (point.x < viewMarginRight)
&& (point.y + height > viewMarginTop) && (point.y < viewMarginBottom);
point.putWeak();
return contained;
}
/**
* Checks whether this camera contains a given rectangle, in screen coordinates.
* @since 4.11.0
*/
public inline function containsRect(rect:FlxRect):Bool
{
var contained = (rect.right > viewMarginLeft) && (rect.x < viewMarginRight)
&& (rect.bottom > viewMarginTop) && (rect.y < viewMarginBottom);
rect.putWeak();
return contained;
}
function set_followLerp(Value:Float):Float
{
return followLerp = FlxMath.bound(Value, 0, 60 / FlxG.updateFramerate);
}
function set_width(Value:Int):Int
{
if (width != Value && Value > 0)
{
width = Value;
calcMarginX();
updateFlashOffset();
updateScrollRect();
updateInternalSpritePositions();
FlxG.cameras.cameraResized.dispatch(this);
}
return Value;
}
function set_height(Value:Int):Int
{
if (height != Value && Value > 0)
{
height = Value;
calcMarginY();
updateFlashOffset();
updateScrollRect();
updateInternalSpritePositions();
FlxG.cameras.cameraResized.dispatch(this);
}
return Value;
}
function set_zoom(Zoom:Float):Float
{
zoom = (Zoom == 0) ? defaultZoom : Zoom;
setScale(zoom, zoom);
return zoom;
}
function set_alpha(Alpha:Float):Float
{
alpha = FlxMath.bound(Alpha, 0, 1);
if (FlxG.renderBlit)
{
_flashBitmap.alpha = Alpha;
}
else
{
canvas.alpha = Alpha;
}
return Alpha;
}
function set_angle(Angle:Float):Float
{
angle = Angle;
flashSprite.rotation = Angle;
return Angle;
}
function set_color(Color:FlxColor):FlxColor
{
color = Color;
var colorTransform:ColorTransform;
if (FlxG.renderBlit)
{
if (_flashBitmap == null)
{
return Color;
}
colorTransform = _flashBitmap.transform.colorTransform;
}
else
{
colorTransform = canvas.transform.colorTransform;
}
colorTransform.redMultiplier = color.redFloat;
colorTransform.greenMultiplier = color.greenFloat;
colorTransform.blueMultiplier = color.blueFloat;
if (FlxG.renderBlit)
{
_flashBitmap.transform.colorTransform = colorTransform;
}
else
{
canvas.transform.colorTransform = colorTransform;
}
return Color;
}
function set_antialiasing(Antialiasing:Bool):Bool
{
antialiasing = Antialiasing;
if (FlxG.renderBlit)
{
_flashBitmap.smoothing = Antialiasing;
}
return Antialiasing;
}
function set_x(x:Float):Float
{
this.x = x;
updateFlashSpritePosition();
return x;
}
function set_y(y:Float):Float
{
this.y = y;
updateFlashSpritePosition();
return y;
}
override function set_visible(visible:Bool):Bool
{
if (flashSprite != null)
{
flashSprite.visible = visible;
}
return this.visible = visible;
}
@:deprecated("Use calcMarginX")
inline function calcOffsetX():Void calcMarginX();
@:deprecated("Use calcMarginY")
inline function calcOffsetY():Void calcMarginY();
inline function calcMarginX():Void
{
viewMarginX = 0.5 * width * (scaleX - initialZoom) / scaleX;
}
inline function calcMarginY():Void
{
viewMarginY = 0.5 * height * (scaleY - initialZoom) / scaleY;
}
static inline function get_defaultCameras():Array<FlxCamera>
{
return _defaultCameras;
}
static inline function set_defaultCameras(value:Array<FlxCamera>):Array<FlxCamera>
{
return _defaultCameras = value;
}
inline function get_viewMarginLeft():Float
{
return viewMarginX;
}
inline function get_viewMarginTop():Float
{
return viewMarginY;
}
inline function get_viewMarginRight():Float
{
return width - viewMarginX;
}
inline function get_viewMarginBottom():Float
{
return height - viewMarginY;
}
inline function get_viewWidth():Float
{
return width - viewMarginX * 2;
}
inline function get_viewHeight():Float
{
return height - viewMarginY * 2;
}
inline function get_viewX():Float
{
return scroll.x + viewMarginX;
}
inline function get_viewY():Float
{
return scroll.y + viewMarginY;
}
inline function get_viewLeft():Float
{
return viewX;
}
inline function get_viewTop():Float
{
return viewY;
}
inline function get_viewRight():Float
{
return scroll.x + viewMarginRight;
}
inline function get_viewBottom():Float
{
return scroll.y + viewMarginBottom;
}
// deprecated vars
inline function get_viewOffsetX():Float
{
return viewMarginX;
}
inline function set_viewOffsetX(value:Float):Float
{
return viewMarginX = value;
}
inline function get_viewOffsetY():Float
{
return viewMarginY;
}
inline function set_viewOffsetY(value:Float):Float
{
return viewMarginY = value;
}
inline function get_viewOffsetWidth():Float
{
return viewMarginRight;
}
inline function get_viewOffsetHeight():Float
{
return viewMarginBottom;
}
inline function get__filters():Array<BitmapFilter>
{
return filters;
}
inline function set__filters(Value:Array<BitmapFilter>):Array<BitmapFilter>
{
return filters = Value;
}
/**
* Do not use the following fields! They only exists because FlxCamera extends FlxBasic,
* we're hiding them because they've only caused confusion.
*/
@:deprecated("don't reference camera.camera")
@:noCompletion
override function get_camera():FlxCamera throw "don't reference camera.camera";
@:deprecated("don't reference camera.camera")
@:noCompletion
override function set_camera(value:FlxCamera):FlxCamera throw "don't reference camera.camera";
@:deprecated("don't reference camera.cameras")
@:noCompletion
override function get_cameras():Array<FlxCamera> throw "don't reference camera.cameras";
@:deprecated("don't reference camera.cameras")
@:noCompletion
override function set_cameras(value:Array<FlxCamera>):Array<FlxCamera> throw "don't reference camera.cameras";
}
enum FlxCameraFollowStyle
{
/**
* Camera has no deadzone, just tracks the focus object directly.
*/
LOCKON;
/**
* Camera's deadzone is narrow but tall.
*/
PLATFORMER;
/**
* Camera's deadzone is a medium-size square around the focus object.
*/
TOPDOWN;
/**
* Camera's deadzone is a small square around the focus object.
*/
TOPDOWN_TIGHT;
/**
* Camera will move screenwise.
*/
SCREEN_BY_SCREEN;
/**
* Camera has no deadzone, just tracks the focus object directly and centers it.
*/
NO_DEAD_ZONE;
}