/**
* FlxExtendedSprite
* -- Part of the Flixel Power Tools set
*
* v1.4 Added MouseSpring, plugin checks and all the missing documentation
* v1.3 Added Gravity, Friction and Tolerance support
* v1.2 Now works fully with FlxMouseControl to be completely clickable and draggable!
* v1.1 Added "setMouseDrag" and "mouse over" states
* v1.0 Updated for the Flixel 2.5 Plugin system
*
* @version 1.4 - July 29th 2011
* @link http://www.photonstorm.com
* @author Richard Davey / Photon Storm
*/
package org.flixel.plugin.photonstorm
{
import org.flixel.*;
import org.flixel.plugin.photonstorm.BaseTypes.MouseSpring;
/**
* An enhanced FlxSprite that is capable of receiving mouse clicks, being dragged and thrown, mouse springs, gravity and other useful things
*/
public class FlxExtendedSprite extends FlxSprite
{
/**
* Used by FlxMouseControl when multiple sprites overlap and register clicks, and you need to determine which sprite has priority
*/
public var priorityID:uint;
/**
* If the mouse currently pressed down on this sprite?
* @default false
*/
public var isPressed:Boolean = false;
/**
* Is this sprite allowed to be clicked?
* @default false
*/
public var clickable:Boolean = false;
private var clickOnRelease:Boolean = false;
private var clickPixelPerfect:Boolean = false;
private var clickPixelPerfectAlpha:uint;
private var clickCounter:uint;
/**
* Function called when the mouse is pressed down on this sprite. Function is passed these parameters: obj:FlxExtendedSprite, x:int, y:int
* @default null
*/
public var mousePressedCallback:Function;
/**
* Function called when the mouse is released from this sprite. Function is passed these parameters: obj:FlxExtendedSprite, x:int, y:int
* @default null
*/
public var mouseReleasedCallback:Function;
/**
* Is this sprite allowed to be thrown?
* @default false
*/
public var throwable:Boolean = false;
private var throwXFactor:int;
private var throwYFactor:int;
/**
* Does this sprite have gravity applied to it?
* @default false
*/
public var hasGravity:Boolean = false;
/**
* The x axis gravity influence
*/
public var gravityX:int;
/**
* The y axis gravity influence
*/
public var gravityY:int;
/**
* Determines how quickly the Sprite come to rest on the walls if the sprite has x gravity enabled
* @default 500
*/
public var frictionX:Number;
/**
* Determines how quickly the Sprite come to rest on the ground if the sprite has y gravity enabled
* @default 500
*/
public var frictionY:Number;
/**
* If the velocity.x of this sprite falls between zero and this amount, then the sprite will come to a halt (have velocity.x set to zero)
*/
public var toleranceX:Number;
/**
* If the velocity.y of this sprite falls between zero and this amount, then the sprite will come to a halt (have velocity.y set to zero)
*/
public var toleranceY:Number;
/**
* Is this sprite being dragged by the mouse or not?
* @default false
*/
public var isDragged:Boolean = false;
/**
* Is this sprite allowed to be dragged by the mouse? true = yes, false = no
* @default false
*/
public var draggable:Boolean = false;
private var dragPixelPerfect:Boolean = false;
private var dragPixelPerfectAlpha:uint;
private var dragOffsetX:int;
private var dragOffsetY:int;
private var dragFromPoint:Boolean;
private var allowHorizontalDrag:Boolean = true;
private var allowVerticalDrag:Boolean = true;
/**
* Function called when the mouse starts to drag this sprite. Function is passed these parameters: obj:FlxExtendedSprite, x:int, y:int
* @default null
*/
public var mouseStartDragCallback:Function;
/**
* Function called when the mouse stops dragging this sprite. Function is passed these parameters: obj:FlxExtendedSprite, x:int, y:int
* @default null
*/
public var mouseStopDragCallback:Function;
/**
* An FlxRect region of the game world within which the sprite is restricted during mouse drag
* @default null
*/
public var boundsRect:FlxRect = null;
/**
* An FlxSprite the bounds of which this sprite is restricted during mouse drag
* @default null
*/
public var boundsSprite:FlxSprite = null;
private var snapOnDrag:Boolean = false;
private var snapOnRelease:Boolean = false;
private var snapX:int;
private var snapY:int;
/**
* Is this sprite using a mouse spring?
* @default false
*/
public var hasMouseSpring:Boolean = false;
/**
* Will the Mouse Spring be active always (false) or only when pressed (true)
* @default true
*/
public var springOnPressed:Boolean = true;
/**
* The MouseSpring object which is used to tie this sprite to the mouse
*/
public var mouseSpring:MouseSpring;
/**
* By default the spring attaches to the top left of the sprite. To change this location provide an x offset (in pixels)
*/
public var springOffsetX:int;
/**
* By default the spring attaches to the top left of the sprite. To change this location provide a y offset (in pixels)
*/
public var springOffsetY:int;
/**
* Creates a white 8x8 square FlxExtendedSprite
at the specified position.
* Optionally can load a simple, one-frame graphic instead.
*
* @param X The initial X position of the sprite.
* @param Y The initial Y position of the sprite.
* @param SimpleGraphic The graphic you want to display (OPTIONAL - for simple stuff only, do NOT use for animated images!).
*/
public function FlxExtendedSprite(X:Number = 0, Y:Number = 0, SimpleGraphic:Class = null)
{
super(X, Y, SimpleGraphic);
}
/**
* Allow this Sprite to receive mouse clicks, the total number of times this sprite is clicked is stored in this.clicks
* You can add callbacks via mousePressedCallback and mouseReleasedCallback
*
* @param onRelease Register the click when the mouse is pressed down (false) or when it's released (true). Note that callbacks still fire regardless of this setting.
* @param pixelPerfect If true it will use a pixel perfect test to see if you clicked the Sprite. False uses the bounding box.
* @param alphaThreshold If using pixel perfect collision this specifies the alpha level from 0 to 255 above which a collision is processed (default 255)
*/
public function enableMouseClicks(onRelease:Boolean, pixelPerfect:Boolean = false, alphaThreshold:uint = 255):void
{
if (FlxG.getPlugin(FlxMouseControl) == null)
{
throw Error("FlxExtendedSprite.enableMouseClicks called but FlxMouseControl plugin not activated");
}
clickable = true;
clickOnRelease = onRelease;
clickPixelPerfect = pixelPerfect;
clickPixelPerfectAlpha = alphaThreshold;
clickCounter = 0;
}
/**
* Stops this sprite from checking for mouse clicks and clears any set callbacks
*/
public function disableMouseClicks():void
{
clickable = false;
mousePressedCallback = null;
mouseReleasedCallback = null;
}
/**
* Returns the number of times this sprite has been clicked (can be reset by setting clicks to zero)
*/
public function get clicks():uint
{
return clickCounter;
}
/**
* Sets the number of clicks this item has received. Usually you'd only set it to zero.
*/
public function set clicks(i:uint):void
{
clickCounter = i;
}
/**
* Make this Sprite draggable by the mouse. You can also optionally set mouseStartDragCallback and mouseStopDragCallback
*
* @param lockCenter If false the Sprite will drag from where you click it. If true it will center itself to the tip of the mouse pointer.
* @param pixelPerfect If true it will use a pixel perfect test to see if you clicked the Sprite. False uses the bounding box.
* @param alphaThreshold If using pixel perfect collision this specifies the alpha level from 0 to 255 above which a collision is processed (default 255)
* @param boundsRect If you want to restrict the drag of this sprite to a specific FlxRect, pass the FlxRect here, otherwise it's free to drag anywhere
* @param boundsSprite If you want to restrict the drag of this sprite to within the bounding box of another sprite, pass it here
*/
public function enableMouseDrag(lockCenter:Boolean = false, pixelPerfect:Boolean = false, alphaThreshold:uint = 255, boundsRect:FlxRect = null, boundsSprite:FlxSprite = null):void
{
if (FlxG.getPlugin(FlxMouseControl) == null)
{
throw Error("FlxExtendedSprite.enableMouseDrag called but FlxMouseControl plugin not activated");
}
draggable = true;
dragFromPoint = lockCenter;
dragPixelPerfect = pixelPerfect;
dragPixelPerfectAlpha = alphaThreshold;
if (boundsRect)
{
this.boundsRect = boundsRect;
}
if (boundsSprite)
{
this.boundsSprite = boundsSprite;
}
}
/**
* Stops this sprite from being able to be dragged. If it is currently the target of an active drag it will be stopped immediately. Also disables any set callbacks.
*/
public function disableMouseDrag():void
{
if (isDragged)
{
FlxMouseControl.dragTarget = null;
FlxMouseControl.isDragging = false;
}
isDragged = false;
draggable = false;
mouseStartDragCallback = null;
mouseStopDragCallback = null;
}
/**
* Restricts this sprite to drag movement only on the given axis. Note: If both are set to false the sprite will never move!
*
* @param allowHorizontal To enable the sprite to be dragged horizontally set to true, otherwise false
* @param allowVertical To enable the sprite to be dragged vertically set to true, otherwise false
*/
public function setDragLock(allowHorizontal:Boolean = true, allowVertical:Boolean = true):void
{
allowHorizontalDrag = allowHorizontal;
allowVerticalDrag = allowVertical;
}
/**
* Make this Sprite throwable by the mouse. The sprite is thrown only when the mouse button is released.
*
* @param xFactor The sprites velocity is set to FlxMouseControl.speedX * xFactor. Try a value around 50+
* @param yFactor The sprites velocity is set to FlxMouseControl.speedY * yFactor. Try a value around 50+
*/
public function enableMouseThrow(xFactor:int, yFactor:int):void
{
if (FlxG.getPlugin(FlxMouseControl) == null)
{
throw Error("FlxExtendedSprite.enableMouseThrow called but FlxMouseControl plugin not activated");
}
throwable = true;
throwXFactor = xFactor;
throwYFactor = yFactor;
if (clickable == false && draggable == false)
{
clickable = true;
}
}
/**
* Stops this sprite from being able to be thrown. If it currently has velocity this is NOT removed from it.
*/
public function disableMouseThrow():void
{
throwable = false;
}
/**
* Make this Sprite snap to the given grid either during drag or when it's released.
* For example 16x16 as the snapX and snapY would make the sprite snap to every 16 pixels.
*
* @param snapX The width of the grid cell in pixels
* @param snapY The height of the grid cell in pixels
* @param onDrag If true the sprite will snap to the grid while being dragged
* @param onRelease If true the sprite will snap to the grid when released
*/
public function enableMouseSnap(snapX:int, snapY:int, onDrag:Boolean = true, onRelease:Boolean = false):void
{
snapOnDrag = onDrag;
snapOnRelease = onRelease;
this.snapX = snapX;
this.snapY = snapY;
}
/**
* Stops the sprite from snapping to a grid during drag or release.
*/
public function disableMouseSnap():void
{
snapOnDrag = false;
snapOnRelease = false;
}
/**
* Adds a simple spring between the mouse and this Sprite. The spring can be activated either when the mouse is pressed (default), or enabled all the time.
* Note that nearly always the Spring will over-ride any other motion setting the sprite has (like velocity or gravity)
*
* @param onPressed true if the spring should only be active when the mouse is pressed down on this sprite
* @param retainVelocity true to retain the velocity of the spring when the mouse is released, or false to clear it
* @param tension The tension of the spring, smaller numbers create springs closer to the mouse pointer
* @param friction The friction applied to the spring as it moves
* @param gravity The gravity controls how far "down" the spring hangs (use a negative value for it to hang up!)
*
* @return The MouseSpring object if you wish to perform further chaining on it. Also available via FlxExtendedSprite.mouseSpring
*/
public function enableMouseSpring(onPressed:Boolean = true, retainVelocity:Boolean = false, tension:Number = 0.1, friction:Number = 0.95, gravity:Number = 0):MouseSpring
{
if (FlxG.getPlugin(FlxMouseControl) == null)
{
throw Error("FlxExtendedSprite.enableMouseSpring called but FlxMouseControl plugin not activated");
}
hasMouseSpring = true;
springOnPressed = onPressed;
if (mouseSpring == null)
{
mouseSpring = new MouseSpring(this, retainVelocity, tension, friction, gravity);
}
else
{
mouseSpring.tension = tension;
mouseSpring.friction = friction;
mouseSpring.gravity = gravity;
}
if (clickable == false && draggable == false)
{
clickable = true;
}
return mouseSpring;
}
/**
* Stops the sprite to mouse spring from being active
*/
public function disableMouseSpring():void
{
hasMouseSpring = false;
mouseSpring = null;
}
/**
* The spring x coordinate in game world space. Consists of sprite.x + springOffsetX
*/
public function get springX():int
{
return x + springOffsetX;
}
/**
* The spring y coordinate in game world space. Consists of sprite.y + springOffsetY
*/
public function get springY():int
{
return y + springOffsetY;
}
/**
* Core update loop
*/
override public function update():void
{
if (draggable && isDragged)
{
updateDrag();
}
if (isPressed == false && FlxG.mouse.justPressed())
{
checkForClick();
}
if (hasGravity)
{
updateGravity();
}
if (hasMouseSpring)
{
if (springOnPressed == false)
{
mouseSpring.update();
}
else
{
if (isPressed == true)
{
mouseSpring.update();
}
else
{
mouseSpring.reset();
}
}
}
super.update();
}
/**
* Called by update, applies friction if the sprite has gravity to stop jittery motion when slowing down
*/
private function updateGravity():void
{
// A sprite can have horizontal and/or vertical gravity in each direction (positiive / negative)
// First let's check the x movement
if (velocity.x != 0)
{
if (acceleration.x < 0)
{
// Gravity is pulling them left
if (touching & WALL)
{
drag.y = frictionY;
if ((wasTouching & WALL) == false)
{
if (velocity.x < toleranceX)
{
//trace("(left) velocity.x", velocity.x, "stopped via tolerance break", toleranceX);
velocity.x = 0;
}
}
}
else
{
drag.y = 0;
}
}
else if (acceleration.x > 0)
{
// Gravity is pulling them right
if (touching & WALL)
{
// Stop them sliding like on ice
drag.y = frictionY;
if ((wasTouching & WALL) == false)
{
if (velocity.x > -toleranceX)
{
//trace("(right) velocity.x", velocity.x, "stopped via tolerance break", toleranceX);
velocity.x = 0;
}
}
}
else
{
drag.y = 0;
}
}
}
// Now check the y movement
if (velocity.y != 0)
{
if (acceleration.y < 0)
{
// Gravity is pulling them up (velocity is negative)
if (touching & CEILING)
{
drag.x = frictionX;
if ((wasTouching & CEILING) == false)
{
if (velocity.y < toleranceY)
{
//trace("(down) velocity.y", velocity.y, "stopped via tolerance break", toleranceY);
velocity.y = 0;
}
}
}
else
{
drag.x = 0;
}
}
else if (acceleration.y > 0)
{
// Gravity is pulling them down (velocity is positive)
if (touching & FLOOR)
{
// Stop them sliding like on ice
drag.x = frictionX;
if ((wasTouching & FLOOR) == false)
{
if (velocity.y > -toleranceY)
{
//trace("(down) velocity.y", velocity.y, "stopped via tolerance break", toleranceY);
velocity.y = 0;
}
}
}
else
{
drag.x = 0;
}
}
}
}
/**
* Updates the Mouse Drag on this Sprite.
*/
private function updateDrag():void
{
//FlxG.mouse.getWorldPosition(null, tempPoint);
if (allowHorizontalDrag)
{
x = int(FlxG.mouse.x) - dragOffsetX;
}
if (allowVerticalDrag)
{
y = int(FlxG.mouse.y) - dragOffsetY;
}
if (boundsRect)
{
checkBoundsRect();
}
if (boundsSprite)
{
checkBoundsSprite();
}
if (snapOnDrag)
{
x = int(Math.floor(x / snapX) * snapX);
y = int(Math.floor(y / snapY) * snapY);
}
}
/**
* Checks if the mouse is over this sprite and pressed, then does a pixel perfect check if needed and adds it to the FlxMouseControl check stack
*/
private function checkForClick():void
{
if (mouseOver && FlxG.mouse.justPressed())
{
// If we don't need a pixel perfect check, then don't bother running one! By this point we know the mouse is over the sprite already
if (clickPixelPerfect == false && dragPixelPerfect == false)
{
FlxMouseControl.addToStack(this);
return;
}
if (clickPixelPerfect && FlxCollision.pixelPerfectPointCheck(FlxG.mouse.x, FlxG.mouse.y, this, clickPixelPerfectAlpha))
{
FlxMouseControl.addToStack(this);
return;
}
if (dragPixelPerfect && FlxCollision.pixelPerfectPointCheck(FlxG.mouse.x, FlxG.mouse.y, this, dragPixelPerfectAlpha))
{
FlxMouseControl.addToStack(this);
return;
}
}
}
/**
* Called by FlxMouseControl when this sprite is clicked. Should not usually be called directly.
*/
public function mousePressedHandler():void
{
isPressed = true;
if (clickable && clickOnRelease == false)
{
clickCounter++;
}
if (mousePressedCallback is Function)
{
mousePressedCallback.apply(null, [ this, mouseX, mouseY ] );
}
}
/**
* Called by FlxMouseControl when this sprite is released from a click. Should not usually be called directly.
*/
public function mouseReleasedHandler():void
{
isPressed = false;
if (isDragged)
{
stopDrag();
}
if (clickable && clickOnRelease == true)
{
clickCounter++;
}
if (throwable)
{
velocity.x = FlxMouseControl.speedX * throwXFactor;
velocity.y = FlxMouseControl.speedY * throwYFactor;
}
if (mouseReleasedCallback is Function)
{
mouseReleasedCallback.apply(null, [ this, mouseX, mouseY ] );
}
}
/**
* Called by FlxMouseControl when Mouse Drag starts on this Sprite. Should not usually be called directly.
*/
public function startDrag():void
{
isDragged = true;
if (dragFromPoint == false)
{
dragOffsetX = int(FlxG.mouse.x) - x;
dragOffsetY = int(FlxG.mouse.y) - y;
}
else
{
// Move the sprite to the middle of the mouse
dragOffsetX = (frameWidth / 2);
dragOffsetY = (frameHeight / 2);
}
}
/**
* Bounds Rect check for the sprite drag
*/
private function checkBoundsRect():void
{
if (x < boundsRect.left)
{
x = boundsRect.x;
}
else if ((x + width) > boundsRect.right)
{
x = boundsRect.right - width;
}
if (y < boundsRect.top)
{
y = boundsRect.top;
}
else if ((y + height) > boundsRect.bottom)
{
y = boundsRect.bottom - height;
}
}
/**
* Parent Sprite Bounds check for the sprite drag
*/
private function checkBoundsSprite():void
{
if (x < boundsSprite.x)
{
x = boundsSprite.x;
}
else if ((x + width) > (boundsSprite.x + boundsSprite.width))
{
x = (boundsSprite.x + boundsSprite.width) - width;
}
if (y < boundsSprite.y)
{
y = boundsSprite.y;
}
else if ((y + height) > (boundsSprite.y + boundsSprite.height))
{
y = (boundsSprite.y + boundsSprite.height) - height;
}
}
/**
* Called by FlxMouseControl when Mouse Drag is stopped on this Sprite. Should not usually be called directly.
*/
public function stopDrag():void
{
isDragged = false;
if (snapOnRelease)
{
x = int(Math.floor(x / snapX) * snapX);
y = int(Math.floor(y / snapY) * snapY);
}
}
/**
* Gravity can be applied to the sprite, pulling it in any direction. Gravity is given in pixels per second and is applied as acceleration.
* If you don't want gravity for a specific direction pass a value of zero. To cancel it entirely pass both values as zero.
*
* @param gravityX A positive value applies gravity dragging the sprite to the right. A negative value drags the sprite to the left. Zero disables horizontal gravity.
* @param gravityY A positive value applies gravity dragging the sprite down. A negative value drags the sprite up. Zero disables vertical gravity.
* @param frictionX The amount of friction applied to the sprite if it hits a wall. Allows it to come to a stop without constantly jittering.
* @param frictionY The amount of friction applied to the sprite if it hits the floor/roof. Allows it to come to a stop without constantly jittering.
* @param toleranceX If the velocity.x of the sprite falls between 0 and +- this value, it is set to stop (velocity.x = 0)
* @param toleranceY If the velocity.y of the sprite falls between 0 and +- this value, it is set to stop (velocity.y = 0)
*/
public function setGravity(gravityX:int, gravityY:int, frictionX:Number = 500, frictionY:Number = 500, toleranceX:Number = 10, toleranceY:Number = 10):void
{
hasGravity = true;
this.gravityX = gravityX;
this.gravityY = gravityY;
this.frictionX = frictionX;
this.frictionY = frictionY;
this.toleranceX = toleranceX;
this.toleranceY = toleranceY;
if (gravityX == 0 && gravityY == 0)
{
hasGravity = false;
}
acceleration.x = gravityX;
acceleration.y = gravityY;
}
/**
* Switches the gravity applied to the sprite. If gravity was +400 Y (pulling them down) this will swap it to -400 Y (pulling them up)
* To reset call flipGravity again
*/
public function flipGravity():void
{
if (gravityX && gravityX != 0)
{
gravityX = -gravityX;
acceleration.x = gravityX;
}
if (gravityY && gravityY != 0)
{
gravityY = -gravityY;
acceleration.y = gravityY;
}
}
/**
* Returns an FlxPoint consisting of this sprites world x/y coordinates
*/
public function get point():FlxPoint
{
return _point;
}
public function set point(p:FlxPoint):void
{
_point = p;
}
/**
* Return true if the mouse is over this Sprite, otherwise false. Only takes the Sprites bounding box into consideration and does not check if there
* are other sprites potentially on-top of this one. Check the value of this.isPressed if you need to know if the mouse is currently clicked on this sprite.
*/
public function get mouseOver():Boolean
{
return FlxMath.pointInCoordinates(FlxG.mouse.x, FlxG.mouse.y, x, y, width, height);
}
/**
* Returns how many horizontal pixels the mouse pointer is inside this sprite from the top left corner. Returns -1 if outside.
*/
public function get mouseX():int
{
if (mouseOver)
{
return FlxG.mouse.x - x;
}
return -1;
}
/**
* Returns how many vertical pixels the mouse pointer is inside this sprite from the top left corner. Returns -1 if outside.
*/
public function get mouseY():int
{
if (mouseOver)
{
return FlxG.mouse.y - y;
}
return -1;
}
/**
* Returns an FlxRect consisting of the bounds of this Sprite.
*/
public function get rect():FlxRect
{
_rect.x = x;
_rect.y = y;
_rect.width = width;
_rect.height = height;
return _rect;
}
}
}