diff --git a/Features/FlxCamera/assets/BorderSlice.png b/Features/FlxCamera/assets/BorderSlice.png
new file mode 100644
index 000000000..1ff3f8345
Binary files /dev/null and b/Features/FlxCamera/assets/BorderSlice.png differ
diff --git a/Features/FlxCamera/source/HUD.hx b/Features/FlxCamera/source/HUD.hx
deleted file mode 100644
index 6f5bdcced..000000000
--- a/Features/FlxCamera/source/HUD.hx
+++ /dev/null
@@ -1,75 +0,0 @@
-package;
-
-import flixel.FlxSprite;
-import flixel.group.FlxGroup;
-import flixel.text.FlxText;
-import flixel.util.FlxColor;
-
-/**
- * @author TiagoLr ( ~~~ProG4mr~~~ )
- */
-class HUD extends FlxGroup
-{
- public var width:Int = 200;
- public var height:Int = 180;
- public var background:FlxSprite;
-
- var txtStyle:FlxText;
- var txtLerp:FlxText;
- var txtLead:FlxText;
- var txtZoom:FlxText;
-
- public function new()
- {
- super();
-
- var x:Int = 10000;
-
- background = new FlxSprite(x, 0);
- background.makeGraphic(width, height, FlxColor.BLACK);
- add(background);
-
- x += 6;
- var startY:Int = 10;
-
- add(new FlxText(x, startY, width, "[W,A,S,D] or arrows to control the orb."));
-
- add(new FlxText(x, startY + 20, 300, "[H] to change follow style."));
- addGreenText(txtStyle = new FlxText(x, startY + 33, width, "LOCKON"));
-
- add(new FlxText(x, startY + 55, width, "[U] or [J] to change lerp."));
- addGreenText(txtLerp = new FlxText(x, startY + 68, width, "Camera lerp: 1"));
-
- add(new FlxText(x, startY + 95, width, "[I] or [K] to change lead."));
- addGreenText(txtLead = new FlxText(x, startY + 108, width, "Camera lead: 0"));
-
- add(new FlxText(x, startY + 135, width, "[O] or [L] to change zoom."));
- addGreenText(txtZoom = new FlxText(x, startY + 148, width, "Camera zoom: 1"));
- }
-
- function addGreenText(text:FlxText)
- {
- text.setFormat(null, 11, 0x55FF55);
- add(text);
- }
-
- public function updateStyle(string:String)
- {
- txtStyle.text = string;
- }
-
- public function updateCamLerp(lerp:Float)
- {
- txtLerp.text = "Camera lerp: " + lerp;
- }
-
- public function updateCamLead(lead:Float)
- {
- txtLead.text = "Camera lead: " + lead;
- }
-
- public function updateZoom(zoom:Float)
- {
- txtZoom.text = "Camera Zoom: " + Math.floor(zoom * 10) / 10;
- }
-}
diff --git a/Features/FlxCamera/source/Main.hx b/Features/FlxCamera/source/Main.hx
index 9ba757312..443fac7e1 100644
--- a/Features/FlxCamera/source/Main.hx
+++ b/Features/FlxCamera/source/Main.hx
@@ -1,6 +1,7 @@
package;
import flixel.FlxGame;
+import flixel.FlxSprite;
import openfl.display.Sprite;
class Main extends Sprite
@@ -8,6 +9,8 @@ class Main extends Sprite
public function new()
{
super();
+
+ FlxSprite.defaultAntialiasing = true;
addChild(new FlxGame(640, 480, PlayState));
}
}
diff --git a/Features/FlxCamera/source/Orb.hx b/Features/FlxCamera/source/Orb.hx
deleted file mode 100644
index dd019b068..000000000
--- a/Features/FlxCamera/source/Orb.hx
+++ /dev/null
@@ -1,36 +0,0 @@
-package;
-
-import flixel.FlxG;
-import flixel.FlxSprite;
-import flixel.addons.nape.FlxNapeSprite;
-import openfl.Assets;
-
-/**
- * @author TiagoLr ( ~~~ProG4mr~~~ )
- */
-class Orb extends FlxNapeSprite
-{
- public var shadow:FlxSprite;
-
- public function new()
- {
- super(FlxG.width / 2, FlxG.height / 2, "assets/Orb.png");
- createCircularBody(18);
- body.allowRotation = false;
- setDrag(0.98, 1);
- }
-
- override public function update(elapsed:Float):Void
- {
- super.update(elapsed);
-
- if (FlxG.camera.target != null && FlxG.camera.followLead.x == 0) // target check is used for debug purposes.
- {
- x = Math.round(x); // Smooths camera and orb shadow following. Does not work well with camera lead.
- y = Math.round(y); // Smooths camera and orb shadow following. Does not work well with camera lead.
- }
-
- shadow.x = Math.round(x);
- shadow.y = Math.round(y);
- }
-}
diff --git a/Features/FlxCamera/source/PlayState.hx b/Features/FlxCamera/source/PlayState.hx
index c8ce551e7..12edeb639 100644
--- a/Features/FlxCamera/source/PlayState.hx
+++ b/Features/FlxCamera/source/PlayState.hx
@@ -1,17 +1,20 @@
package;
-import flash.Lib;
-import flash.display.BlendMode;
+import haxe.EnumTools;
import flixel.FlxCamera;
import flixel.FlxG;
-import flixel.FlxSprite;
import flixel.FlxState;
+import flixel.FlxSprite;
+import flixel.addons.display.FlxBackdrop;
import flixel.addons.nape.FlxNapeSpace;
import flixel.math.FlxMath;
-import flixel.math.FlxRect;
-import flixel.util.FlxColor;
-import nape.geom.Vec2;
-import openfl.Assets;
+import nape.phys.Material;
+import props.BorderSlice;
+import props.Orb;
+import props.OtherOrb;
+import props.PlayerOrb;
+import ui.DeadzoneOverlay;
+import ui.HUD;
using flixel.util.FlxSpriteUtil;
@@ -20,248 +23,139 @@ using flixel.util.FlxSpriteUtil;
*/
class PlayState extends FlxState
{
- // Demo arena boundaries
- static var LEVEL_MIN_X:Float;
- static var LEVEL_MAX_X:Float;
- static var LEVEL_MIN_Y:Float;
- static var LEVEL_MAX_Y:Float;
-
- var orb:Orb;
- var orbShadow:FlxSprite;
+ static var followStyles = EnumTools.createAll(FlxCameraFollowStyle);
+
+ var player:PlayerOrb;
var hud:HUD;
- var hudCam:FlxCamera;
- var overlayCamera:FlxCamera;
- var deadzoneOverlay:FlxSprite;
+ var deadzoneOverlay:DeadzoneOverlay;
override public function create():Void
{
FlxNapeSpace.init();
- LEVEL_MIN_X = -FlxG.stage.stageWidth / 2;
- LEVEL_MAX_X = FlxG.stage.stageWidth * 1.5;
- LEVEL_MIN_Y = -FlxG.stage.stageHeight / 2;
- LEVEL_MAX_Y = FlxG.stage.stageHeight * 1.5;
+ // final levelMinX = -FlxG.stage.stageWidth;
+ // final levelMaxX = FlxG.stage.stageWidth;
+ // final levelMinY = -FlxG.stage.stageHeight;
+ // final levelMaxY = FlxG.stage.stageHeight;
+ final levelMinX = 0;
+ final levelMaxX = FlxG.stage.stageWidth * 2;
+ final levelMinY = 0;
+ final levelMaxY = FlxG.stage.stageHeight * 2;
+ final levelWidth = levelMaxX - levelMinX;
+ final levelHeight = levelMaxY - levelMinY;
super.create();
- FlxG.mouse.visible = false;
-
FlxNapeSpace.velocityIterations = 5;
FlxNapeSpace.positionIterations = 5;
- createFloorTiles();
- FlxNapeSpace.createWalls(LEVEL_MIN_X, LEVEL_MIN_Y, LEVEL_MAX_X, LEVEL_MAX_Y);
- // Walls border.
- add(new FlxSprite(-FlxG.width / 2, -FlxG.height / 2, "assets/Border.png"));
+ // repeating backdrop
+ final backdrop = new FlxBackdrop("assets/FloorTexture.png");
+ #if debug
+ backdrop.ignoreDrawDebug = true;
+ #end
+ add(backdrop);
+
+ // create nape wall colliders
+ final border = 10;
+ FlxNapeSpace.createWalls(levelMinX + border, levelMinY + border, levelMaxX - border, levelMaxY - border, border, new Material(1.0, 0.0, 0.0, 1));
+
+ // Walls border sprite
+ final borderSprite = new BorderSlice(levelMinX, levelMinY, levelWidth, levelHeight);
+ add(borderSprite);
// Player orb
- orbShadow = new FlxSprite(FlxG.width / 2, FlxG.height / 2, "assets/OrbShadow.png");
- orbShadow.centerOffsets();
- orbShadow.blend = BlendMode.MULTIPLY;
-
- orb = new Orb();
-
- add(orbShadow);
- add(orb);
-
- orb.shadow = orbShadow;
-
+ player = new PlayerOrb(levelMinX + levelWidth / 2, levelMinY + levelHeight / 2);
+ add(player);
+ // if the player is using a virtual pad, add it to the state
+ if (player.controls.virtualPad != null)
+ add(player.controls.virtualPad);
+
// Other orbs
for (i in 0...5)
{
- var otherOrbShadow = new FlxSprite(100, 100, "assets/OtherOrbShadow.png");
- otherOrbShadow.centerOffsets();
- otherOrbShadow.blend = BlendMode.MULTIPLY;
-
- var otherOrb = new Orb();
- otherOrb.loadGraphic("assets/OtherOrb.png", true, 140, 140);
- otherOrb.createCircularBody(50);
- otherOrb.setBodyMaterial(1, 0.2, 0.4, 0.5);
- otherOrb.antialiasing = true;
- otherOrb.setDrag(1, 1);
-
- add(otherOrbShadow);
- add(otherOrb);
-
- otherOrb.shadow = otherOrbShadow;
-
- switch (i)
- {
- case 0:
- otherOrb.body.position.setxy(320 - 400, 240 - 400);
- otherOrb.animation.frameIndex = 0;
- case 1:
- otherOrb.body.position.setxy(320 + 400, 240 - 400);
- otherOrb.animation.frameIndex = 4;
- case 2:
- otherOrb.body.position.setxy(320 + 400, 240 + 400);
- otherOrb.animation.frameIndex = 3;
- case 3:
- otherOrb.body.position.setxy(-300, 240);
- otherOrb.animation.frameIndex = 2;
- case 4:
- otherOrb.body.position.setxy(0, 240 + 400);
- otherOrb.animation.frameIndex = 1;
- }
- otherOrb.body.velocity.setxy(FlxG.random.int(75, 150), FlxG.random.int(75, 150));
+ final orb = new OtherOrb(0, 0, i);
+ add(orb);
+ orb.randomizeVelocity();
+
+ // randomize spawn position until it's far enough from the player, up to 20 times
+ var tries = 20;
+ do orb.randomizePosition(levelMinX, levelMaxX, levelMinY, levelMaxY)
+ while (Math.abs(orb.x - player.x) < 200 && Math.abs(orb.y - player.y) < 200 && tries-- > 0);
}
-
+
hud = new HUD();
add(hud);
-
+
// Camera Overlay
- deadzoneOverlay = new FlxSprite(-10000, -10000);
- deadzoneOverlay.makeGraphic(FlxG.width, FlxG.height, FlxColor.TRANSPARENT, true);
- deadzoneOverlay.antialiasing = true;
-
- overlayCamera = new FlxCamera(0, 0, 640, 720);
- overlayCamera.bgColor = FlxColor.TRANSPARENT;
- overlayCamera.follow(deadzoneOverlay);
- FlxG.cameras.add(overlayCamera);
+ deadzoneOverlay = new DeadzoneOverlay();
add(deadzoneOverlay);
-
- FlxG.camera.setScrollBoundsRect(LEVEL_MIN_X, LEVEL_MIN_Y, LEVEL_MAX_X + Math.abs(LEVEL_MIN_X), LEVEL_MAX_Y + Math.abs(LEVEL_MIN_Y), true);
- FlxG.camera.follow(orb, LOCKON, 1);
- drawDeadzone(); // now that deadzone is present
-
- hudCam = new FlxCamera(440, 0, hud.width, hud.height);
- hudCam.zoom = 1; // For 1/2 zoom out.
- hudCam.follow(hud.background, FlxCameraFollowStyle.NO_DEAD_ZONE);
- hudCam.alpha = .5;
- FlxG.cameras.add(hudCam);
+
+ FlxG.camera.pixelPerfectRender = false;
+ FlxG.camera.setScrollBounds(levelMinX, levelMaxX, levelMinY, levelMaxY);
+ FlxG.worldBounds.set(levelMinX, levelMinY, levelWidth, levelHeight);
+ FlxG.camera.follow(player, followStyles[0], 1);
+ deadzoneOverlay.redraw(FlxG.camera); // now that deadzone is present
}
-
- function drawDeadzone()
+
+ override public function update(elapsed:Float):Void
{
- deadzoneOverlay.fill(FlxColor.TRANSPARENT);
- var dz:FlxRect = FlxG.camera.deadzone;
- if (dz == null)
- return;
-
- var lineLength:Int = 20;
- var lineStyle:LineStyle = {color: FlxColor.WHITE, thickness: 3};
-
- // adjust points slightly so lines will be visible when at screen edges
- dz.x += lineStyle.thickness / 2;
- dz.width -= lineStyle.thickness;
- dz.y += lineStyle.thickness / 2;
- dz.height -= lineStyle.thickness;
-
- // Left Up Corner
- deadzoneOverlay.drawLine(dz.left, dz.top, dz.left + lineLength, dz.top, lineStyle);
- deadzoneOverlay.drawLine(dz.left, dz.top, dz.left, dz.top + lineLength, lineStyle);
- // Right Up Corner
- deadzoneOverlay.drawLine(dz.right, dz.top, dz.right - lineLength, dz.top, lineStyle);
- deadzoneOverlay.drawLine(dz.right, dz.top, dz.right, dz.top + lineLength, lineStyle);
- // Bottom Left Corner
- deadzoneOverlay.drawLine(dz.left, dz.bottom, dz.left + lineLength, dz.bottom, lineStyle);
- deadzoneOverlay.drawLine(dz.left, dz.bottom, dz.left, dz.bottom - lineLength, lineStyle);
- // Bottom Right Corner
- deadzoneOverlay.drawLine(dz.right, dz.bottom, dz.right - lineLength, dz.bottom, lineStyle);
- deadzoneOverlay.drawLine(dz.right, dz.bottom, dz.right, dz.bottom - lineLength, lineStyle);
+ super.update(elapsed);
+
+ final justPressed = FlxG.keys.justPressed;
+
+ if (justPressed.Y) setStyle(1);
+ if (justPressed.H) setStyle(-1);
+
+ if (justPressed.U) setLerp(.1);
+ if (justPressed.J) setLerp(-.1);
+
+ if (justPressed.I) setLead(.5);
+ if (justPressed.K) setLead(-.5);
+
+ if (justPressed.O) setZoom(.1);
+ if (justPressed.L) setZoom(-.1);
+
+ if (justPressed.M) FlxG.camera.shake();
}
-
- public function setZoom(zoom:Float)
+
+ public function setZoom(delta:Float)
{
- FlxG.camera.zoom = FlxMath.bound(zoom, 0.5, 4);
+ final newZoom = FlxG.camera.zoom + delta;
+ FlxG.camera.zoom = FlxMath.bound(Math.round(newZoom * 10) / 10, 0.5, 4);
hud.updateZoom(FlxG.camera.zoom);
}
-
- function createFloorTiles()
- {
- var floorImg = Assets.getBitmapData("assets/FloorTexture.png");
- var imgWidth = floorImg.width;
- var imgHeight = floorImg.height;
- var i = LEVEL_MIN_X;
- var j = LEVEL_MIN_Y;
-
- while (i <= LEVEL_MAX_X)
- {
- while (j <= LEVEL_MAX_Y)
- {
- add(new FlxSprite(i, j, floorImg));
- j += imgHeight;
- }
- i += imgWidth;
- j = LEVEL_MIN_Y;
- }
- }
-
- override public function update(elapsed:Float):Void
- {
- super.update(elapsed);
-
- var speed = 20;
- if (FlxG.keys.anyPressed([A, LEFT]))
- orb.body.applyImpulse(new Vec2(-speed, 0));
- if (FlxG.keys.anyPressed([S, DOWN]))
- orb.body.applyImpulse(new Vec2(0, speed));
- if (FlxG.keys.anyPressed([D, RIGHT]))
- orb.body.applyImpulse(new Vec2(speed, 0));
- if (FlxG.keys.anyPressed([W, UP]))
- orb.body.applyImpulse(new Vec2(0, -speed));
-
- if (FlxG.keys.justPressed.Y)
- setStyle(1);
- if (FlxG.keys.justPressed.H)
- setStyle(-1);
-
- if (FlxG.keys.justPressed.U)
- setLerp(.1);
- if (FlxG.keys.justPressed.J)
- setLerp(-.1);
-
- if (FlxG.keys.justPressed.I)
- setLead(.5);
- if (FlxG.keys.justPressed.K)
- setLead(-.5);
-
- if (FlxG.keys.justPressed.O)
- setZoom(FlxG.camera.zoom + .1);
- if (FlxG.keys.justPressed.L)
- setZoom(FlxG.camera.zoom - .1);
-
- if (FlxG.keys.justPressed.M)
- FlxG.camera.shake();
- }
-
- function setLead(lead:Float)
+
+ function setLead(delta:Float)
{
var cam = FlxG.camera;
- cam.followLead.x += lead;
- cam.followLead.y += lead;
-
+ cam.followLead.x += delta;
+ cam.followLead.y += delta;
+
if (cam.followLead.x < 0)
{
cam.followLead.x = 0;
cam.followLead.y = 0;
}
-
+
hud.updateCamLead(cam.followLead.x);
}
-
- function setLerp(lerp:Float)
+
+ function setLerp(delta:Float)
{
var cam = FlxG.camera;
- cam.followLerp += lerp;
+ cam.followLerp += delta;
cam.followLerp = Math.round(10 * cam.followLerp) / 10; // adding or subtracting .1 causes roundoff errors
hud.updateCamLerp(cam.followLerp);
}
-
- function setStyle(i:Int)
+
+ function setStyle(delta:Int)
{
- var newCamStyleIndex:Int = Type.enumIndex(FlxG.camera.style) + i;
- newCamStyleIndex < 0 ? newCamStyleIndex += 6 : newCamStyleIndex %= 6;
-
- var newCamStyle = Type.createEnumIndex(FlxCameraFollowStyle, newCamStyleIndex);
- FlxG.camera.follow(orb, newCamStyle, FlxG.camera.followLerp);
- drawDeadzone();
-
- hud.updateStyle(Std.string(FlxG.camera.style));
-
- if (FlxG.camera.style == SCREEN_BY_SCREEN)
- {
- setZoom(1);
- }
+ final nextStyleIndex = (followStyles.indexOf(FlxG.camera.style) + delta) % followStyles.length;
+ FlxG.camera.follow(player, followStyles[nextStyleIndex], FlxG.camera.followLerp);
+
+ deadzoneOverlay.redraw(FlxG.camera);
+
+ hud.updateStyle(FlxG.camera.style);
}
}
diff --git a/Features/FlxCamera/source/input/PlayerControls.hx b/Features/FlxCamera/source/input/PlayerControls.hx
new file mode 100644
index 000000000..beb0696f8
--- /dev/null
+++ b/Features/FlxCamera/source/input/PlayerControls.hx
@@ -0,0 +1,123 @@
+package input;
+
+import flixel.FlxG;
+import flixel.input.gamepad.FlxGamepadInputID;
+import flixel.input.keyboard.FlxKey;
+import flixel.ui.FlxVirtualPad;
+
+class PlayerControls
+{
+ /**
+ * Maps input types to their corresponding keyboard button
+ */
+ static public var keyMap:Map> =
+ [
+ Input.LEFT => [FlxKey.A, FlxKey.LEFT ],
+ Input.DOWN => [FlxKey.S, FlxKey.DOWN ],
+ Input.RIGHT => [FlxKey.D, FlxKey.RIGHT],
+ Input.UP => [FlxKey.W, FlxKey.UP ]
+ ];
+
+ #if FLX_GAMEPAD
+ /**
+ * Maps input types to their corresponding gamepad dpad button
+ */
+ static public var buttonMap:Map =
+ [
+ Input.LEFT => { dpad:DPAD_LEFT , analog:LEFT_STICK_DIGITAL_LEFT },
+ Input.DOWN => { dpad:DPAD_DOWN , analog:LEFT_STICK_DIGITAL_DOWN },
+ Input.RIGHT => { dpad:DPAD_RIGHT, analog:LEFT_STICK_DIGITAL_RIGHT },
+ Input.UP => { dpad:DPAD_UP , analog:LEFT_STICK_DIGITAL_UP }
+ ];
+ #end
+
+ /**
+ * Reference to the gamepad controlling this orb
+ */
+ public var virtualPad:VirtualPad = null;
+
+ public function new()
+ {
+ // create a virtual pad to play on mobile devices
+ final useVirtualPad = #if html5 FlxG.html5.onMobile #elseif mobile true #else false #end;
+ if (useVirtualPad)
+ virtualPad = new VirtualPad();
+ }
+
+ public function isGamepadConnected()
+ {
+ #if FLX_GAMEPAD
+ return FlxG.gamepads.numActiveGamepads > 0;
+ #else
+ return false;
+ #end
+ }
+
+ /**
+ * Helper to detect keyboard or virtual pad presses
+ */
+ inline public function inputPressed(input:Input)
+ {
+ return keyPressed(input) || virtualPadPressed(input) || gamePadPressed(input);
+ }
+
+ /**
+ * Helper to detect keyboard presses
+ */
+ inline function keyPressed(input:Input)
+ {
+ return FlxG.keys.anyPressed(keyMap[input]);
+ }
+
+ /**
+ * Helper to detect virtual pad presses
+ */
+ inline function virtualPadPressed(input:Input)
+ {
+ return virtualPad != null && virtualPad.pressed(input);
+ }
+
+ /**
+ * Helper to detect gamepad presses
+ */
+ inline function gamePadPressed(input:Input)
+ {
+ #if FLX_GAMEPAD
+ final buttons = buttonMap[input];
+ return FlxG.gamepads.anyPressed(buttons.dpad) || FlxG.gamepads.anyPressed(buttons.analog);
+ #else
+ return false;
+ #end
+ }
+}
+
+/**
+ * Simplified virtual pad that takes an Input and returns whether the corresponding button is pressed
+ */
+abstract VirtualPad(FlxVirtualPad) from FlxVirtualPad to FlxVirtualPad
+{
+ inline public function new()
+ {
+ this = new FlxVirtualPad(FULL, NONE);
+ }
+
+ public function pressed(input:Input)
+ {
+ return switch(input)
+ {
+ case Input.LEFT : this.buttonLeft.pressed;
+ case Input.RIGHT: this.buttonRight.pressed;
+ case Input.UP : this.buttonUp.pressed;
+ case Input.DOWN : this.buttonDown.pressed;
+ default: false;
+ }
+ }
+}
+
+enum Input
+{
+ LEFT;
+ RIGHT;
+ UP;
+ DOWN;
+}
\ No newline at end of file
diff --git a/Features/FlxCamera/source/props/BorderSlice.hx b/Features/FlxCamera/source/props/BorderSlice.hx
new file mode 100644
index 000000000..5c59e4503
--- /dev/null
+++ b/Features/FlxCamera/source/props/BorderSlice.hx
@@ -0,0 +1,25 @@
+package props;
+
+import flixel.math.FlxRect;
+import flixel.addons.display.FlxSliceSprite;
+
+@:forward
+abstract BorderSlice(FlxSliceSprite) from FlxSliceSprite to FlxSliceSprite
+{
+ inline public function new (x = 0.0, y = 0.0, width:Float, height:Float)
+ {
+ this = new FlxSliceSprite("assets/BorderSlice.png", new FlxRect(15, 15, 20, 20), width, height);
+ this.x = x;
+ this.y = y;
+ // reduce vertice counts by stretching rather than tiling
+ this.fillCenter = false;
+ this.stretchBottom = true;
+ this.stretchTop = true;
+ this.stretchLeft = true;
+ this.stretchRight = true;
+ this.stretchCenter = true;
+ #if debug
+ this.ignoreDrawDebug = true;
+ #end
+ }
+}
\ No newline at end of file
diff --git a/Features/FlxCamera/source/props/Orb.hx b/Features/FlxCamera/source/props/Orb.hx
new file mode 100644
index 000000000..3f501c844
--- /dev/null
+++ b/Features/FlxCamera/source/props/Orb.hx
@@ -0,0 +1,77 @@
+package props;
+
+import flixel.FlxG;
+import flixel.FlxSprite;
+import flixel.addons.nape.FlxNapeSprite;
+import flixel.util.FlxDestroyUtil;
+
+/**
+ * @author TiagoLr ( ~~~ProG4mr~~~ )
+ */
+class Orb extends FlxNapeSprite
+{
+ var shadow:FlxSprite;
+
+ public function new(x = 0.0, y = 0.0, radius:Int, ?graphic, ?shadowGraphic)
+ {
+ super(x, y, null, false);
+ createCircularBody(radius);
+ body.allowRotation = false;
+ pixelPerfectPosition = false;
+ pixelPerfectRender = false;
+
+ // create a shadow that follows the sprite around
+ shadow = new FlxSprite(x, y, shadowGraphic);
+ shadow.blend = MULTIPLY;
+ shadow.pixelPerfectPosition = false;
+ shadow.pixelPerfectRender = false;
+ #if debug
+ shadow.ignoreDrawDebug = true;
+ #end
+
+ // call loadGraphic after the body is created so it adjusts the hitbox
+ if (graphic != null)
+ loadGraphic(graphic);
+ }
+
+ override function loadGraphic(graphic, animated = false, frameWidth = 0, frameHeight = 0, unique = false, ?key:String)
+ {
+ super.loadGraphic(graphic, animated, frameWidth, frameHeight, unique, key);
+
+ // adjust flixel hitbox to match the radius for tighter camera following
+ if (body != null && body.shapes != null)
+ {
+ width = body.bounds.width;
+ height = body.bounds.height;
+ centerOffsets(false);
+ origin.set(width / 2, height / 2);
+
+ // same offset for shadowP
+ shadow.offset.copyFrom(offset);
+ }
+
+ return this;
+ }
+
+ override public function update(elapsed:Float):Void
+ {
+ super.update(elapsed);
+
+ shadow.update(elapsed);
+ shadow.x = x;
+ shadow.y = y;
+ }
+
+ override function draw()
+ {
+ // draw shadow first, so it's underneath
+ shadow.draw();
+ super.draw();
+ }
+
+ override function destroy()
+ {
+ super.destroy();
+ shadow = FlxDestroyUtil.destroy(shadow);
+ }
+}
diff --git a/Features/FlxCamera/source/props/OtherOrb.hx b/Features/FlxCamera/source/props/OtherOrb.hx
new file mode 100644
index 000000000..22459afa3
--- /dev/null
+++ b/Features/FlxCamera/source/props/OtherOrb.hx
@@ -0,0 +1,39 @@
+package props;
+
+import flixel.FlxG;
+import flixel.FlxSprite;
+// import flixel.math.FlxMath;
+import flixel.util.FlxDestroyUtil;
+// import nape.geom.Vec2;
+
+class OtherOrb extends Orb
+{
+ public function new(x = 0.0, y = 0.0, colorIndex:Int)
+ {
+ super(x, y, 50, null, "assets/OtherOrbShadow.png");
+
+ loadGraphic("assets/OtherOrb.png", true, 140, 140);
+ animation.frameIndex = colorIndex;
+ setBodyMaterial(1, 0.0, 0.0, 0.5);
+ }
+
+ /**
+ * Randomizes the position of this orb within the given bounds
+ */
+ public function randomizePosition(minX = 0, maxX = 0, minY = 0, maxY = 0)
+ {
+ x = body.position.x = FlxG.random.int(minX, maxX - Math.ceil(width));
+ y = body.position.y = FlxG.random.int(minY, maxY - Math.ceil(height));
+ return this;
+ }
+
+ /**
+ * Randomizes the velocity of this orb with the given absolute speed range and a random angle
+ */
+ public function randomizeVelocity(min = 100, max = 200)
+ {
+ body.velocity.setxy(FlxG.random.float(min, max), 0);
+ body.velocity.angle = FlxG.random.float(0, 2 * Math.PI);
+ return this;
+ }
+}
\ No newline at end of file
diff --git a/Features/FlxCamera/source/props/PlayerOrb.hx b/Features/FlxCamera/source/props/PlayerOrb.hx
new file mode 100644
index 000000000..a2ae435a9
--- /dev/null
+++ b/Features/FlxCamera/source/props/PlayerOrb.hx
@@ -0,0 +1,59 @@
+package props;
+
+import flixel.FlxG;
+import input.PlayerControls;
+import nape.geom.Vec2;
+
+/**
+ * User controlled orb
+ */
+class PlayerOrb extends Orb
+{
+ /**
+ * The impulse applied each frame when pressing the corresponding key
+ */
+ static inline var IMPULSE = 20;
+
+ /**
+ * Used Internally to avoid creating new instances each frame
+ */
+ static final impulseHelper = new Vec2();
+
+ public var controls:PlayerControls;
+
+ public function new(x = 0.0, y = 0.0)
+ {
+ super(x, y, 18, "assets/Orb.png", "assets/OrbShadow.png");
+ // small amount of drag
+ setDrag(0.98);
+
+ controls = new PlayerControls();
+ }
+
+ override function update(elapsed:Float)
+ {
+ super.update(elapsed);
+
+ // apply impusles to the body based on key presses
+
+ if (controls.inputPressed(LEFT))
+ applyImpulseXY(-IMPULSE, 0);
+
+ if (controls.inputPressed(DOWN))
+ applyImpulseXY(0, IMPULSE);
+
+ if (controls.inputPressed(RIGHT))
+ applyImpulseXY(IMPULSE, 0);
+
+ if (controls.inputPressed(UP))
+ applyImpulseXY(0, -IMPULSE);
+ }
+
+ /**
+ * Helper to apply impulse via x and y floats to avoid creating new Vec2 instances each frame
+ */
+ inline function applyImpulseXY(x:Float, y:Float)
+ {
+ body.applyImpulse(impulseHelper.setxy(x, y));
+ }
+}
\ No newline at end of file
diff --git a/Features/FlxCamera/source/ui/DeadzoneOverlay.hx b/Features/FlxCamera/source/ui/DeadzoneOverlay.hx
new file mode 100644
index 000000000..909738dd9
--- /dev/null
+++ b/Features/FlxCamera/source/ui/DeadzoneOverlay.hx
@@ -0,0 +1,81 @@
+package ui;
+
+import flixel.FlxCamera;
+import flixel.FlxG;
+import flixel.FlxSprite;
+import flixel.math.FlxRect;
+import flixel.util.FlxColor;
+
+using flixel.util.FlxSpriteUtil;
+
+/**
+ * Sprite used to draw a representation of the current follow styles.
+ * For most styles, it draws the deadzone.
+ */
+class DeadzoneOverlay extends FlxSprite
+{
+ public function new ()
+ {
+ super();
+
+ scrollFactor.set(0, 0);// move with the camera
+
+ #if debug
+ ignoreDrawDebug = true;
+ #end
+ }
+
+ public function redraw(targetCamera:FlxCamera)
+ {
+ if (targetCamera.style == SCREEN_BY_SCREEN)
+ {
+ // just hide it, otherwise we'd need to redraw it with zoom changes
+ visible = false;
+ return;
+ }
+ visible = true;
+
+ final lineLength = 12;
+ final padding = 2;
+ final thickness = 3 + padding;
+ final halfThickness = thickness / 2;
+ final lineStyle:LineStyle = {color: FlxColor.WHITE, thickness: thickness - padding};
+
+ if (targetCamera.style == NO_DEAD_ZONE)
+ {
+ // No deadzone, draw a simple crosshair in the center of the camera's view
+ final reticalSize = 20;
+ // pad the graphic a little, for thick lines
+ makeGraphic(reticalSize + thickness, reticalSize + thickness, FlxColor.TRANSPARENT, true);
+ x = (camera.width - frameWidth) / 2;
+ y = (camera.height - frameHeight) / 2;
+
+ final centerX = frameWidth / 2;
+ final centerY = frameHeight / 2;
+ final reticalHalfSize = reticalSize / 2;
+ this.drawLine(centerX, centerY - reticalHalfSize, centerX, centerY + reticalHalfSize, lineStyle);
+ this.drawLine(centerX - reticalHalfSize, centerY, centerX + reticalHalfSize, centerY, lineStyle);
+ return;
+ }
+
+ // draw the deadzone's corners
+ final dz:FlxRect = targetCamera.deadzone;
+ x = dz.x - halfThickness;
+ y = dz.y - halfThickness;
+ // pad the graphic a little, for thick lines
+ makeGraphic(Std.int(dz.width + thickness), Std.int(dz.height + thickness), FlxColor.TRANSPARENT, true);
+
+ // Top-Left
+ this.drawLine(dz.left - x, dz.top - y, dz.left - x + lineLength, dz.top - y, lineStyle);
+ this.drawLine(dz.left - x, dz.top - y, dz.left - x, dz.top + lineLength - y, lineStyle);
+ // Top-Right
+ this.drawLine(dz.right - x, dz.top - y, dz.right - x - lineLength, dz.top - y, lineStyle);
+ this.drawLine(dz.right - x, dz.top - y, dz.right - x, dz.top + lineLength - y, lineStyle);
+ // Bottom-Left
+ this.drawLine(dz.left - x, dz.bottom - y, dz.left - x + lineLength, dz.bottom - y, lineStyle);
+ this.drawLine(dz.left - x, dz.bottom - y, dz.left - x, dz.bottom - lineLength - y, lineStyle);
+ // Bottom-Right
+ this.drawLine(dz.right - x, dz.bottom - y, dz.right - x - lineLength, dz.bottom - y, lineStyle);
+ this.drawLine(dz.right - x, dz.bottom - y, dz.right - x, dz.bottom - lineLength - y, lineStyle);
+ }
+}
\ No newline at end of file
diff --git a/Features/FlxCamera/source/ui/HUD.hx b/Features/FlxCamera/source/ui/HUD.hx
new file mode 100644
index 000000000..6e9862c39
--- /dev/null
+++ b/Features/FlxCamera/source/ui/HUD.hx
@@ -0,0 +1,97 @@
+package ui;
+
+import flixel.FlxCamera;
+import flixel.FlxG;
+import flixel.FlxSprite;
+import flixel.group.FlxGroup;
+import flixel.text.FlxText;
+
+inline var WIDTH = 200;
+inline var HEIGHT = 180;
+
+/**
+ * @author TiagoLr ( ~~~ProG4mr~~~ )
+ */
+class HUD extends FlxGroup
+{
+ var txtStyle:Text;
+ var txtLerp:Text;
+ var txtLead:Text;
+ var txtZoom:Text;
+
+ public function new()
+ {
+ super();
+
+ var left = 6;
+ var startY = 10;
+
+ add(new Text(left, startY, "[W,A,S,D] or arrows to control the orb."));
+
+ add(new Text(left, startY + 20, "[H] or [Y] to change follow style."));
+ add(txtStyle = new GreenText(left, startY + 33, "LOCKON"));
+
+ add(new Text(left, startY + 55, "[U] or [J] to change lerp."));
+ add(txtLerp = new GreenText(left, startY + 68, "Camera lerp: 1"));
+
+ add(new Text(left, startY + 95, "[I] or [K] to change lead."));
+ add(txtLead = new GreenText(left, startY + 108, "Camera lead: 0"));
+
+ add(new Text(left, startY + 135, "[O] or [L] to change zoom."));
+ add(txtZoom = new GreenText(left, startY + 148, "Camera zoom: 1"));
+
+ // create new camera in the top-right corner that only draws this
+ camera = new FlxCamera(440, 0, WIDTH, HEIGHT, 1.0);
+ camera.alpha = .5;
+ camera.bgColor = 0x80000000;
+ FlxG.cameras.add(camera, false);
+ }
+
+ public function updateStyle(style:FlxCameraFollowStyle)
+ {
+ txtStyle.text = Std.string(style);
+ }
+
+ public function updateCamLerp(lerp:Float)
+ {
+ txtLerp.text = "Camera lerp: " + lerp;
+ }
+
+ public function updateCamLead(lead:Float)
+ {
+ txtLead.text = "Camera lead: " + lead;
+ }
+
+ public function updateZoom(zoom:Float)
+ {
+ txtZoom.text = "Camera Zoom: " + Math.floor(zoom * 10) / 10;
+ }
+}
+
+/**
+ * A simplified, specialized FlxText instance, mainly used to omit the fieldWidth arg
+ */
+@:forward
+abstract Text(FlxText) from FlxText to FlxText
+{
+ inline public function new (x = 0.0, y = 0.0, ?text:String, size = 8)
+ {
+ this = new FlxText(x, y, WIDTH, text, size);
+ #if debug
+ this.ignoreDrawDebug = true;
+ #end
+ }
+}
+
+/**
+ * An even more specialized version of Text used to highlight the changing camera values
+ */
+@:forward
+abstract GreenText(Text) from Text to Text
+{
+ inline public function new (x = 0.0, y = 0.0, ?text:String)
+ {
+ this = new Text(x, y, text);
+ this.setFormat(null, 11, 0x55FF55);
+ }
+}