diff --git a/flixel/FlxObject.hx b/flixel/FlxObject.hx index fa268062cb..05394e45d9 100644 --- a/flixel/FlxObject.hx +++ b/flixel/FlxObject.hx @@ -650,16 +650,23 @@ class FlxObject extends FlxBasic */ public var acceleration(default, null):FlxPoint; + public var maxSpeedMode = FlxMaxSpeedMode.NONE; + + public var dragMode = FlxDragMode.NONE; + public var angularDragApplyMode:FlxDragApplyMode = INERTIAL; + /** * This isn't drag exactly, more like deceleration that is only applied * when `acceleration` is not affecting the sprite. */ + // @:deprecated("drag is deprecated, use dragMode instead") public var drag(default, null):FlxPoint; /** * If you are using `acceleration`, you can use `maxVelocity` with it * to cap the speed automatically (very useful!). */ + // @:deprecated("maxVelocity is deprecated, use maxSpeedMode instead") public var maxVelocity(default, null):FlxPoint; /** @@ -819,6 +826,7 @@ class FlxObject extends FlxBasic * Internal function for initialization of some object's variables. */ @:noCompletion + @:haxe.warning("-WDeprecated") function initVars():Void { flixelType = OBJECT; @@ -833,12 +841,15 @@ class FlxObject extends FlxBasic * Internal function for initialization of some variables that are used in `updateMotion()`. */ @:noCompletion + @:haxe.warning("-WDeprecated") inline function initMotionVars():Void { velocity = FlxPoint.get(); acceleration = FlxPoint.get(); - drag = FlxPoint.get(); - maxVelocity = FlxPoint.get(10000, 10000); + function setDrag(p:FlxPoint) dragMode = XY(p.x, p.y, INERTIAL, INERTIAL); + function setMaxSpeed(p:FlxPoint) maxSpeedMode = XY(p.x, p.y); + drag = new FlxCallbackPoint(setDrag, setDrag, setDrag); + maxVelocity = new FlxCallbackPoint(setMaxSpeed, setMaxSpeed, setMaxSpeed); } /** @@ -851,14 +862,15 @@ class FlxObject extends FlxBasic * Override this function to `null` out variables manually or call `destroy()` on class members if necessary. * Don't forget to call `super.destroy()`! */ + @:haxe.warning("-WDeprecated") override public function destroy():Void { super.destroy(); velocity = FlxDestroyUtil.put(velocity); acceleration = FlxDestroyUtil.put(acceleration); - drag = FlxDestroyUtil.put(drag); - maxVelocity = FlxDestroyUtil.put(maxVelocity); + drag = FlxDestroyUtil.destroy(drag); + maxVelocity = FlxDestroyUtil.destroy(maxVelocity); scrollFactor = FlxDestroyUtil.put(scrollFactor); last = FlxDestroyUtil.put(last); _point = FlxDestroyUtil.put(_point); @@ -900,18 +912,20 @@ class FlxObject extends FlxBasic angularVelocity += velocityDelta; angle += angularVelocity * elapsed; angularVelocity += velocityDelta; - - velocityDelta = 0.5 * (FlxVelocity.computeVelocity(velocity.x, acceleration.x, drag.x, maxVelocity.x, elapsed) - velocity.x); - velocity.x += velocityDelta; - var delta = velocity.x * elapsed; - velocity.x += velocityDelta; - x += delta; - - velocityDelta = 0.5 * (FlxVelocity.computeVelocity(velocity.y, acceleration.y, drag.y, maxVelocity.y, elapsed) - velocity.y); - velocity.y += velocityDelta; - delta = velocity.y * elapsed; - velocity.y += velocityDelta; - y += delta; + + final newVelocity = FlxPoint.get().copyFrom(velocity); + FlxVelocity.computeSpeed2D(elapsed, newVelocity, acceleration, maxSpeedMode, dragMode); + + final velocityDeltaX = 0.5 * (newVelocity.x - velocity.x); + final velocityDeltaY = 0.5 * (newVelocity.y - velocity.y); + velocity.x += velocityDeltaX; + velocity.y += velocityDeltaY; + x += velocity.x * elapsed; + y += velocity.y * elapsed; + velocity.x += velocityDeltaX; + velocity.y += velocityDeltaY; + + newVelocity.put(); } /** @@ -1542,3 +1556,44 @@ enum abstract CollisionDragType(Int) /** Drags when colliding with heavier objects. Immovable objects have infinite mass. */ var HEAVIER = 3; } + +enum FlxMaxSpeedMode +{ + /** The magnitude of velocity is capped */ + LINEAR(value:Float); + + /** Each axis is capped independantly */ + XY(x:Float, y:Float); + + /** No max speed */ + NONE; +} + +enum FlxDragMode +{ + /** Along one axis, usually in the direction of movement */ + UNIFORM(value:Float, applyMode:FlxDragApplyMode); + + /** Drag is applied to each axes separately */ + XY(x:Float, y:Float, xApplyMode:FlxDragApplyMode, ?yApplyMode:FlxDragApplyMode); + + /** No drag */ + NONE; +} + +enum FlxDragApplyMode +{ + /** Drag is always applied to the object's velocity */ + ALWAYS; + + /** Drag is applied to objects in an "inertial" state (not accelerating) */ + INERTIAL; + + /** + * Drag is to objects not accelerating in the direction they are moving, including + * objects in an inertial state. For a `dragmode` of `XY` this applies to both + * axes separately, for `LINEAR`, drag is applied if the object is not accelerating + * less than 90 degrees from the object's current movement direction + */ + SKID; +} \ No newline at end of file diff --git a/flixel/math/FlxPoint.hx b/flixel/math/FlxPoint.hx index 1943a95de3..4d0555ef92 100644 --- a/flixel/math/FlxPoint.hx +++ b/flixel/math/FlxPoint.hx @@ -794,7 +794,17 @@ import openfl.geom.Point; p.putWeak(); return dotProductWeak(normalized); } - + + /** + * Check if the angle between 2 vectors are less than 90 degrees + * + * @param p point to check + */ + public inline function areSameFacing(p:FlxPoint):Bool + { + return dotProduct(p) > 0; + } + /** * Check the perpendicularity of two points. * diff --git a/flixel/math/FlxVelocity.hx b/flixel/math/FlxVelocity.hx index b5b331a4de..e11edab252 100644 --- a/flixel/math/FlxVelocity.hx +++ b/flixel/math/FlxVelocity.hx @@ -1,5 +1,6 @@ package flixel.math; +import flixel.FlxObject; import flixel.FlxSprite; #if FLX_TOUCH import flixel.input.touch.FlxTouch; @@ -222,49 +223,172 @@ class FlxVelocity /** * A tween-like function that takes a starting velocity and some other factors and returns an altered velocity. * - * @param Velocity Any component of velocity (e.g. 20). - * @param Acceleration Rate at which the velocity is changing. - * @param Drag Really kind of a deceleration, this is how much the velocity changes if Acceleration is not set. - * @param Max An absolute value cap for the velocity (0 for no cap). - * @param Elapsed The amount of time passed in to the latest update cycle - * @return The altered Velocity value. + * @param speed Any component of velocity (e.g. 20). + * @param acceleration Rate at which the velocity is changing. + * @param drag Really kind of a deceleration, this is how much the velocity changes if Acceleration is not set. + * @param max An absolute value cap for the velocity (0 for no cap). + * @param elapsed The amount of time passed in to the latest update cycle + * @return The altered Velocity value. */ - public static function computeVelocity(Velocity:Float, Acceleration:Float, Drag:Float, Max:Float, Elapsed:Float):Float + public static function computeVelocity(speed:Float, acceleration:Float, drag:Float, max:Float, elapsed:Float):Float { - if (Acceleration != 0) - { - Velocity += Acceleration * Elapsed; - } - else if (Drag != 0) + speed = computeSpeed1D(elapsed, speed, acceleration, drag, FlxDragApplyMode.INERTIAL); + + return capSpeed1D(speed, max); + } + + public static function capSpeed1D(speed:Float, max:Float):Float + { + if (speed != 0 && max > 0) { - var drag:Float = Drag * Elapsed; - if (Velocity - drag > 0) - { - Velocity -= drag; - } - else if (Velocity + drag < 0) + if (speed > max) { - Velocity += drag; + speed = max; } - else + else if (speed < -max) { - Velocity = 0; + speed = -max; } } - if ((Velocity != 0) && (Max != 0)) + + return speed; + } + + /** + * A tween-like function that takes a starting velocity and some other factors and returns an altered velocity. + * + * @param elapsed The amount of time passed in to the latest update cycle + * @param speed Any component of velocity (e.g. 20). + * @param acceleration Rate at which the velocity is changing. + * @param drag Really kind of a deceleration, this is how much the velocity changes if Acceleration is not set. + * @return The altered Velocity value. + */ + public static function computeSpeed1D(elapsed:Float, speed:Float, acceleration:Float, + drag:Float, dragApply:FlxDragApplyMode):Float + { + final applyDrag = drag > 0 && switch(dragApply) { - if (Velocity > Max) - { - Velocity = Max; - } - else if (Velocity < -Max) - { - Velocity = -Max; - } + case ALWAYS: + true; + case INERTIAL: + acceleration == 0; + case SKID: + // Apply drag if accelerating the opposite direction of current movement + acceleration == 0 || ((acceleration < 0) == (speed < 0)); + } - return Velocity; + + if (acceleration != 0) + { + speed += acceleration * elapsed; + } + + if (applyDrag) + { + speed = applyDrag1D(elapsed, speed, drag); + } + + return speed; } - + + public static function applyDrag1D(elapsed:Float, speed:Float, drag:Float):Float + { + final frameDrag = drag * elapsed; + if (speed - frameDrag > 0) + { + speed -= frameDrag; + } + else if (speed + frameDrag < 0) + { + speed += frameDrag; + } + else + { + speed = 0; + } + + return speed; + } + + /** + * A tween-like function that takes a starting velocity and some other factors and returns an altered velocity. + * + * @param elapsed The amount of time passed in to the latest update cycle + * @param velocity Any component of velocity (e.g. 20). + * @param acceleration Rate at which the velocity is changing. + * @param drag Really kind of a deceleration, this is how much the velocity changes if Acceleration is not set. + * @return The altered Velocity value. + */ + public static function computeSpeed2D(elapsed:Float, velocity:FlxPoint, acceleration:FlxPoint, + max:FlxMaxSpeedMode, drag:FlxDragMode) + { + switch(drag) + { + case NONE: + + velocity.x += elapsed * acceleration.x; + velocity.y += elapsed * acceleration.y; + + case XY(dragX, dragY, applyX, applyY): + + velocity.x = computeSpeed1D(elapsed, velocity.x, acceleration.x, dragX, applyX); + velocity.y = computeSpeed1D(elapsed, velocity.y, acceleration.y, dragY, applyY != null ? applyY : applyX); + + case UNIFORM(linearDrag, dragApply): + + final applyDrag = linearDrag > 0 && switch(dragApply) + { + case ALWAYS: + true; + case INERTIAL: + acceleration.isZero(); + case SKID: + // Apply drag unless if accelerating in the direction of movement (under ±90 degrees) + acceleration.isZero() || !acceleration.areSameFacing(velocity); + + } + + if (!acceleration.isZero()) + { + velocity.x += acceleration.x * elapsed; + velocity.y += acceleration.y * elapsed; + } + + if (applyDrag) + { + final speed = velocity.length; + final scale = applyDrag1D(elapsed, speed, linearDrag) / speed; + velocity.x *= scale; + velocity.y *= scale; + } + } + + return capSpeed2D(velocity, max); + } + + public static function capSpeed2D(velocity:FlxPoint, max:FlxMaxSpeedMode) + { + switch(max) + { + case NONE: + case XY(maxX, maxY): + + velocity.x = capSpeed1D(velocity.x, maxX); + velocity.y = capSpeed1D(velocity.y, maxY); + + case LINEAR(max): + + final speed = velocity.length; + if (speed > max) + { + velocity.x *= max / speed; + velocity.y *= max / speed; + } + } + + return velocity; + } + /** * Sets the x/y acceleration on the source FlxSprite so it will accelerate in the direction of the specified angle. * You must give a maximum speed value (in pixels per second), beyond which the FlxSprite won't go any faster.