diff --git a/CHANGELOG.md b/CHANGELOG.md index 797ffd3c5d..57b2938bcb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,8 @@ +6.0.0 (TBD) +#### Changes and improvements: +- `FlxSpritegroup`: Setting `origin` now causes members to pivot around the same point ([#2981](https://github.com/HaxeFlixel/flixel/pull/2981)) +- `FlxCamera`: Smoother camera lerping, particularly with non-fixed timesteps ([#2922](https://github.com/HaxeFlixel/flixel/pull/2922)) + 5.6.0 (TBD) #### New features: diff --git a/flixel/FlxCamera.hx b/flixel/FlxCamera.hx index 416767d3ce..5d859aaeb2 100644 --- a/flixel/FlxCamera.hx +++ b/flixel/FlxCamera.hx @@ -126,12 +126,12 @@ class FlxCamera extends FlxBasic 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. + * The ratio of the distance to the follow `target` the camera moves per 1/60 sec. + * Valid values range from `0.0` to `1.0`. `1.0` means the camera always snaps to its target + * position. `0.5` means the camera always travels halfway to the target position, `0.0` means + * the camera does not move. Generally, the lower the value, the more smooth. */ - public var followLerp(default, set):Float = 60 / FlxG.updateFramerate; + public var followLerp:Float = 1.0; /** * You can assign a "dead zone" to the camera in order to better control its movement. @@ -1147,6 +1147,7 @@ class FlxCamera extends FlxBasic if (target != null) { updateFollow(); + updateLerp(elapsed); } updateScroll(); @@ -1194,7 +1195,7 @@ class FlxCamera extends FlxBasic * Updates camera's scroll. * Called every frame by camera's `update()` method (if camera's `target` isn't `null`). */ - public function updateFollow():Void + function updateFollow():Void { // Either follow the object closely, // or double check our deadzone and update accordingly. @@ -1271,15 +1272,21 @@ class FlxCamera extends FlxBasic _lastTargetPosition.y = target.y; } } - - if (followLerp >= 60 / FlxG.updateFramerate) + } + + function updateLerp(elapsed:Float) + { + final boundLerp = FlxMath.bound(followLerp, 0, 1); + // Adjust lerp based on the current frame rate so lerp is less framerate dependant + final adjustedLerp = 1.0 - Math.pow(1.0 - boundLerp, elapsed * 60); + if (adjustedLerp >= 1) { 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); + scroll.x += (_scrollTarget.x - scroll.x) * adjustedLerp; + scroll.y += (_scrollTarget.y - scroll.y) * adjustedLerp; } } @@ -1456,29 +1463,23 @@ class FlxCamera extends FlxBasic /** * 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`. + * @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). + * @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 + public function follow(target:FlxObject, style = LOCKON, lerp = 1.0):Void { - if (Style == null) - Style = LOCKON; - - if (Lerp == null) - Lerp = 60 / FlxG.updateFramerate; - - style = Style; - target = Target; - followLerp = Lerp; + this.style = style; + this.target = target; + followLerp = lerp; var helper:Float; var w:Float = 0; var h:Float = 0; _lastTargetPosition = null; - switch (Style) + switch (style) { case LOCKON: if (target != null) @@ -1925,11 +1926,6 @@ class FlxCamera extends FlxBasic 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) diff --git a/flixel/FlxG.hx b/flixel/FlxG.hx index 37e3b5e4d6..3292947bc1 100644 --- a/flixel/FlxG.hx +++ b/flixel/FlxG.hx @@ -377,34 +377,13 @@ class FlxG public static inline function switchState(nextState:NextState):Void { final stateOnCall = FlxG.state; - - if (!nextState.isInstance() || canSwitchTo(cast nextState)) + state.startOutro(function() { - state.startOutro(function() - { - if (FlxG.state == stateOnCall) - game._nextState = nextState; - else - FlxG.log.warn("`onOutroComplete` was called after the state was switched. This will be ignored"); - }); - } - } - - /** - * Calls state.switchTo(nextState) without a deprecation warning. - * This will be removed in Flixel 6.0.0 - * @since 5.6.0 - */ - @:noCompletion - @:haxe.warning("-WDeprecated") - static function canSwitchTo(nextState:FlxState) - { - #if (haxe < version("4.3.0")) - // Use reflection because @:haxe.warning("-WDeprecated") doesn't work until haxe 4.3 - return Reflect.callMethod(state, Reflect.field(state, 'switchTo'), [nextState]); - #else - return state.switchTo(nextState); - #end + if (FlxG.state == stateOnCall) + game._nextState = nextState; + else + FlxG.log.warn("`onOutroComplete` was called after the state was switched. This will be ignored"); + }); } /** @@ -413,13 +392,7 @@ class FlxG */ public static inline function resetState():Void { - if (state == null || state._constructor == null) - FlxG.log.error("FlxG.resetState was called while switching states"); - else if(!state._constructor.isInstance()) - switchState(state._constructor); - else - // create new instance here so that state.switchTo is called (for backwards compatibility) - switchState(Type.createInstance(Type.getClass(state), [])); + switchState(state._constructor); } /** @@ -455,9 +428,8 @@ class FlxG objectOrGroup2 = null; FlxQuadTree.divisions = worldDivisions; - final quadTree = FlxQuadTree.recycle(worldBounds.x, worldBounds.y, worldBounds.width, worldBounds.height); - quadTree.load(objectOrGroup1, objectOrGroup2, notifyCallback, processCallback); - final result:Bool = quadTree.execute(); + var quadTree = FlxQuadTree.recycle(worldBounds.x, worldBounds.y, worldBounds.width, worldBounds.height); + var result:Bool = quadTree.loadAndExecute(objectOrGroup1, objectOrGroup2, notifyCallback, processCallback); quadTree.destroy(); return result; } diff --git a/flixel/FlxGame.hx b/flixel/FlxGame.hx index 572958f885..d49f7640db 100644 --- a/flixel/FlxGame.hx +++ b/flixel/FlxGame.hx @@ -253,7 +253,8 @@ class FlxGame extends Sprite * [`scaleMode`](https://api.haxeflixel.com/flixel/system/scaleModes/index.html) * will determine the actual display size of the game. * @param initialState A constructor for the initial state, ex: `PlayState.new` or `()->new PlayState()`. - * Note: Also allows `Class` for backwards compatibility. + * Note: Before Flixel 6, this took a `Class`, this has been + * deprecated, but is still available, for backwards compatibility. * @param updateFramerate How frequently the game should update. Default is 60 fps. * @param drawFramerate Sets the actual display / draw framerate for the game. Default is 60 fps. * @param skipSplash Whether you want to skip the flixel splash screen with `FLX_NO_DEBUG`. @@ -628,7 +629,7 @@ class FlxGame extends Sprite // Finally assign and create the new state _state = _nextState.createInstance(); - _state._constructor = _nextState; + _state._constructor = _nextState.getConstructor(); _nextState = null; if (_gameJustStarted) @@ -647,8 +648,8 @@ class FlxGame extends Sprite FlxG.signals.postStateSwitch.dispatch(); } - - function gameStart():Void + + function gameStart() { FlxG.signals.postGameStart.dispatch(); _gameJustStarted = false; diff --git a/flixel/FlxState.hx b/flixel/FlxState.hx index 24051dad25..3691071d39 100644 --- a/flixel/FlxState.hx +++ b/flixel/FlxState.hx @@ -11,10 +11,6 @@ import flixel.util.typeLimit.NextState; * It is for all intents and purpose a fancy `FlxGroup`. And really, it's not even that fancy. */ @:keepSub // workaround for HaxeFoundation/haxe#3749 -#if FLX_NO_UNIT_TEST -@:autoBuild(flixel.system.macros.FlxMacroUtil.deprecateOverride("switchTo", "switchTo is deprecated, use startOutro")) -#end -// show deprecation warning when `switchTo` is overriden in dereived classes class FlxState extends FlxGroup { /** @@ -52,7 +48,7 @@ class FlxState extends FlxGroup */ @:allow(flixel.FlxGame) @:allow(flixel.FlxG) - var _constructor:NextState; + var _constructor:()->FlxState; /** * Current substate. Substates also can be nested. @@ -105,7 +101,7 @@ class FlxState extends FlxGroup */ public function create():Void {} - override public function draw():Void + override function draw():Void { if (persistentDraw || subState == null) super.draw(); @@ -186,18 +182,6 @@ class FlxState extends FlxGroup super.destroy(); } - /** - * Called from `FlxG.switchState()`. If `false` is returned, the state - * switch is cancelled - the default implementation returns `true`. - * - * Useful for customizing state switches, e.g. for transition effects. - */ - @:deprecated("switchTo is deprecated, use startOutro") - public function switchTo(nextState:FlxState):Bool - { - return true; - } - /** * Called from `FlxG.switchState()`, when `onOutroComplete` is called, the actual state * switching will happen. diff --git a/flixel/group/FlxSpriteGroup.hx b/flixel/group/FlxSpriteGroup.hx index 4f0c1b4955..26914d4d54 100644 --- a/flixel/group/FlxSpriteGroup.hx +++ b/flixel/group/FlxSpriteGroup.hx @@ -1057,7 +1057,7 @@ class FlxTypedSpriteGroup extends FlxSprite Sprite.offset.copyFrom(Offset); inline function originTransform(Sprite:FlxSprite, Origin:FlxPoint) - Sprite.origin.copyFrom(Origin); + Sprite.origin.set(x + origin.x - Sprite.x, y + origin.y - Sprite.y); inline function scaleTransform(Sprite:FlxSprite, Scale:FlxPoint) Sprite.scale.copyFrom(Scale); diff --git a/flixel/system/FlxQuadTree.hx b/flixel/system/FlxQuadTree.hx index 654a61379f..f7aed19a34 100644 --- a/flixel/system/FlxQuadTree.hx +++ b/flixel/system/FlxQuadTree.hx @@ -6,6 +6,9 @@ import flixel.group.FlxGroup.FlxTypedGroup; import flixel.math.FlxRect; import flixel.util.FlxDestroyUtil; +typedef ProcessCallback = (FlxObject,FlxObject)->Bool; +typedef NotifyCallback = (FlxObject,FlxObject)->Void; + /** * A fairly generic quad tree structure for rapid overlap checks. * FlxQuadTree is also configured for single or dual list operation. @@ -126,96 +129,6 @@ class FlxQuadTree extends FlxRect */ var _midpointY:Float; - /** - * Internal, used to reduce recursive method parameters during object placement and tree formation. - */ - static var _object:FlxObject; - - /** - * Internal, used to reduce recursive method parameters during object placement and tree formation. - */ - static var _objectLeftEdge:Float; - - /** - * Internal, used to reduce recursive method parameters during object placement and tree formation. - */ - static var _objectTopEdge:Float; - - /** - * Internal, used to reduce recursive method parameters during object placement and tree formation. - */ - static var _objectRightEdge:Float; - - /** - * Internal, used to reduce recursive method parameters during object placement and tree formation. - */ - static var _objectBottomEdge:Float; - - /** - * Internal, used during tree processing and overlap checks. - */ - static var _list:Int; - - /** - * Internal, used during tree processing and overlap checks. - */ - static var _useBothLists:Bool; - - /** - * Internal, used during tree processing and overlap checks. - */ - static var _processingCallback:FlxObject->FlxObject->Bool; - - /** - * Internal, used during tree processing and overlap checks. - */ - static var _notifyCallback:FlxObject->FlxObject->Void; - - /** - * Internal, used during tree processing and overlap checks. - */ - static var _iterator:FlxLinkedList; - - /** - * Internal, helpers for comparing actual object-to-object overlap - see overlapNode(). - */ - static var _objectHullX:Float; - - /** - * Internal, helpers for comparing actual object-to-object overlap - see overlapNode(). - */ - static var _objectHullY:Float; - - /** - * Internal, helpers for comparing actual object-to-object overlap - see overlapNode(). - */ - static var _objectHullWidth:Float; - - /** - * Internal, helpers for comparing actual object-to-object overlap - see overlapNode(). - */ - static var _objectHullHeight:Float; - - /** - * Internal, helpers for comparing actual object-to-object overlap - see overlapNode(). - */ - static var _checkObjectHullX:Float; - - /** - * Internal, helpers for comparing actual object-to-object overlap - see overlapNode(). - */ - static var _checkObjectHullY:Float; - - /** - * Internal, helpers for comparing actual object-to-object overlap - see overlapNode(). - */ - static var _checkObjectHullWidth:Float; - - /** - * Internal, helpers for comparing actual object-to-object overlap - see overlapNode(). - */ - static var _checkObjectHullHeight:Float; - /** * Pooling mechanism, turn FlxQuadTree into a linked list, when FlxQuadTrees are destroyed, they get added to the list, and when they get recycled they get removed. */ @@ -228,22 +141,22 @@ class FlxQuadTree extends FlxRect /** * Private, use recycle instead. */ - function new(X:Float, Y:Float, Width:Float, Height:Float, ?Parent:FlxQuadTree) + function new(x:Float, y:Float, width:Float, height:Float, ?parent:FlxQuadTree) { super(); - set(X, Y, Width, Height); - reset(X, Y, Width, Height, Parent); + set(x, y, width, height); + reset(x, y, width, height, parent); } /** * Recycle a cached Quad Tree node, or creates a new one if needed. - * @param X The X-coordinate of the point in space. - * @param Y The Y-coordinate of the point in space. - * @param Width Desired width of this node. - * @param Height Desired height of this node. - * @param Parent The parent branch or node. Pass null to create a root. + * @param x The X-coordinate of the point in space. + * @param y The Y-coordinate of the point in space. + * @param width Desired width of this node. + * @param height Desired height of this node. + * @param parent The parent branch or node. Pass null to create a root. */ - public static function recycle(X:Float, Y:Float, Width:Float, Height:Float, ?Parent:FlxQuadTree):FlxQuadTree + public static function recycle(x:Float, y:Float, width:Float, height:Float, ?parent:FlxQuadTree):FlxQuadTree { if (_cachedTreesHead != null) { @@ -251,11 +164,11 @@ class FlxQuadTree extends FlxRect _cachedTreesHead = _cachedTreesHead.next; _NUM_CACHED_QUAD_TREES--; - cachedTree.reset(X, Y, Width, Height, Parent); + cachedTree.reset(x, y, width, height, parent); return cachedTree; } else - return new FlxQuadTree(X, Y, Width, Height, Parent); + return new FlxQuadTree(x, y, width, height, parent); } /** @@ -273,23 +186,23 @@ class FlxQuadTree extends FlxRect _NUM_CACHED_QUAD_TREES = 0; } - public function reset(X:Float, Y:Float, Width:Float, Height:Float, ?Parent:FlxQuadTree):Void + public function reset(x:Float, y:Float, width:Float, height:Float, ?parent:FlxQuadTree):Void { exists = true; - set(X, Y, Width, Height); + set(x, y, width, height); _headA = _tailA = FlxLinkedList.recycle(); _headB = _tailB = FlxLinkedList.recycle(); // Copy the parent's children (if there are any) - if (Parent != null) + if (parent != null) { var iterator:FlxLinkedList; var ot:FlxLinkedList; - if (Parent._headA.object != null) + if (parent._headA.object != null) { - iterator = Parent._headA; + iterator = parent._headA; while (iterator != null) { if (_tailA.object != null) @@ -302,9 +215,9 @@ class FlxQuadTree extends FlxRect iterator = iterator.next; } } - if (Parent._headB.object != null) + if (parent._headB.object != null) { - iterator = Parent._headB; + iterator = parent._headB; while (iterator != null) { if (_tailB.object != null) @@ -356,10 +269,6 @@ class FlxQuadTree extends FlxRect _southWestTree = FlxDestroyUtil.destroy(_southWestTree); _southEastTree = FlxDestroyUtil.destroy(_southEastTree); - _object = null; - _processingCallback = null; - _notifyCallback = null; - exists = false; // Deposit this tree into the linked list for reusal. @@ -370,28 +279,11 @@ class FlxQuadTree extends FlxRect super.destroy(); } - /** - * Load objects and/or groups into the quad tree, and register notify and processing callbacks. - * @param ObjectOrGroup1 Any object that is or extends FlxObject or FlxGroup. - * @param ObjectOrGroup2 Any object that is or extends FlxObject or FlxGroup. If null, the first parameter will be checked against itself. - * @param NotifyCallback A function with the form myFunction(Object1:FlxObject,Object2:FlxObject):void that is called whenever two objects are found to overlap in world space, and either no ProcessCallback is specified, or the ProcessCallback returns true. - * @param ProcessCallback A function with the form myFunction(Object1:FlxObject,Object2:FlxObject):Boolean that is called whenever two objects are found to overlap in world space. The NotifyCallback is only called if this function returns true. See FlxObject.separate(). - */ - public function load(ObjectOrGroup1:FlxBasic, ?ObjectOrGroup2:FlxBasic, ?NotifyCallback:FlxObject->FlxObject->Void, - ?ProcessCallback:FlxObject->FlxObject->Bool):Void + function load(objectOrGroup1:FlxBasic, ?objectOrGroup2:FlxBasic):Void { - add(ObjectOrGroup1, A_LIST); - if (ObjectOrGroup2 != null) - { - add(ObjectOrGroup2, B_LIST); - _useBothLists = true; - } - else - { - _useBothLists = false; - } - _notifyCallback = NotifyCallback; - _processingCallback = ProcessCallback; + add(objectOrGroup1, A_LIST); + if (objectOrGroup2 != null) + add(objectOrGroup2, B_LIST); } /** @@ -402,52 +294,29 @@ class FlxQuadTree extends FlxRect * @param List A int flag indicating the list to which you want to add the objects. Options are A_LIST and B_LIST. */ @:access(flixel.group.FlxTypedGroup.resolveGroup) - public function add(ObjectOrGroup:FlxBasic, list:Int):Void + public function add(basic:FlxBasic, list:Int):Void { - _list = list; - - var group = FlxTypedGroup.resolveGroup(ObjectOrGroup); + final group = FlxTypedGroup.resolveGroup(basic); if (group != null) { - var i:Int = 0; - var basic:FlxBasic; - var members:Array = group.members; - var l:Int = group.length; - while (i < l) + for (member in group.members) { - basic = members[i++]; - if (basic != null && basic.exists) - { - group = FlxTypedGroup.resolveGroup(basic); - if (group != null) - { - add(group, list); - } - else - { - _object = cast basic; - if (_object.exists && _object.allowCollisions != NONE) - { - _objectLeftEdge = _object.x; - _objectTopEdge = _object.y; - _objectRightEdge = _object.x + _object.width; - _objectBottomEdge = _object.y + _object.height; - addObject(); - } - } - } + if (member != null && member.exists) + add(member, list); } } else { - _object = cast ObjectOrGroup; - if (_object.exists && _object.allowCollisions != NONE) + final object:FlxObject = cast basic; + if (object.exists && object.allowCollisions != NONE) { - _objectLeftEdge = _object.x; - _objectTopEdge = _object.y; - _objectRightEdge = _object.x + _object.width; - _objectBottomEdge = _object.y + _object.height; - addObject(); + final rect = FlxRect.get(); + rect.x = object.x; + rect.y = object.y; + rect.width = object.x + object.width; + rect.height = object.y + object.height; + addObject(object, rect, list); + rect.put(); } } } @@ -456,102 +325,108 @@ class FlxQuadTree extends FlxRect * Internal function for recursively navigating and creating the tree * while adding objects to the appropriate nodes. */ - function addObject():Void + function addObject(object:FlxObject, rect:FlxRect, list:Int):Void { // If this quad (not its children) lies entirely inside this object, add it here if (!_canSubdivide - || (_leftEdge >= _objectLeftEdge && _rightEdge <= _objectRightEdge && _topEdge >= _objectTopEdge && _bottomEdge <= _objectBottomEdge)) + || (_leftEdge >= rect.left && _rightEdge <= rect.right && _topEdge >= rect.top && _bottomEdge <= rect.bottom)) { - addToList(); + addToList(object, list); return; } // See if the selected object fits completely inside any of the quadrants - if ((_objectLeftEdge > _leftEdge) && (_objectRightEdge < _midpointX)) + if ((rect.left > _leftEdge) && (rect.right < _midpointX)) { - if ((_objectTopEdge > _topEdge) && (_objectBottomEdge < _midpointY)) + if ((rect.top > _topEdge) && (rect.bottom < _midpointY)) { if (_northWestTree == null) { _northWestTree = FlxQuadTree.recycle(_leftEdge, _topEdge, _halfWidth, _halfHeight, this); } - _northWestTree.addObject(); + _northWestTree.addObject(object, rect, list); return; } - if ((_objectTopEdge > _midpointY) && (_objectBottomEdge < _bottomEdge)) + + if ((rect.top > _midpointY) && (rect.bottom < _bottomEdge)) { if (_southWestTree == null) { _southWestTree = FlxQuadTree.recycle(_leftEdge, _midpointY, _halfWidth, _halfHeight, this); } - _southWestTree.addObject(); + _southWestTree.addObject(object, rect, list); return; } } - if ((_objectLeftEdge > _midpointX) && (_objectRightEdge < _rightEdge)) + + if ((rect.left > _midpointX) && (rect.right < _rightEdge)) { - if ((_objectTopEdge > _topEdge) && (_objectBottomEdge < _midpointY)) + if ((rect.top > _topEdge) && (rect.bottom < _midpointY)) { if (_northEastTree == null) { _northEastTree = FlxQuadTree.recycle(_midpointX, _topEdge, _halfWidth, _halfHeight, this); } - _northEastTree.addObject(); + _northEastTree.addObject(object, rect, list); return; } - if ((_objectTopEdge > _midpointY) && (_objectBottomEdge < _bottomEdge)) + + if ((rect.top > _midpointY) && (rect.bottom < _bottomEdge)) { if (_southEastTree == null) { _southEastTree = FlxQuadTree.recycle(_midpointX, _midpointY, _halfWidth, _halfHeight, this); } - _southEastTree.addObject(); + _southEastTree.addObject(object, rect, list); return; } } // If it wasn't completely contained we have to check out the partial overlaps - if ((_objectRightEdge > _leftEdge) && (_objectLeftEdge < _midpointX) && (_objectBottomEdge > _topEdge) && (_objectTopEdge < _midpointY)) + if ((rect.right > _leftEdge) && (rect.left < _midpointX) && (rect.bottom > _topEdge) && (rect.top < _midpointY)) { if (_northWestTree == null) { _northWestTree = FlxQuadTree.recycle(_leftEdge, _topEdge, _halfWidth, _halfHeight, this); } - _northWestTree.addObject(); + _northWestTree.addObject(object, rect, list); } - if ((_objectRightEdge > _midpointX) && (_objectLeftEdge < _rightEdge) && (_objectBottomEdge > _topEdge) && (_objectTopEdge < _midpointY)) + + if ((rect.right > _midpointX) && (rect.left < _rightEdge) && (rect.bottom > _topEdge) && (rect.top < _midpointY)) { if (_northEastTree == null) { _northEastTree = FlxQuadTree.recycle(_midpointX, _topEdge, _halfWidth, _halfHeight, this); } - _northEastTree.addObject(); + _northEastTree.addObject(object, rect, list); } - if ((_objectRightEdge > _midpointX) && (_objectLeftEdge < _rightEdge) && (_objectBottomEdge > _midpointY) && (_objectTopEdge < _bottomEdge)) + + if ((rect.right > _midpointX) && (rect.left < _rightEdge) && (rect.bottom > _midpointY) && (rect.top < _bottomEdge)) { if (_southEastTree == null) { _southEastTree = FlxQuadTree.recycle(_midpointX, _midpointY, _halfWidth, _halfHeight, this); } - _southEastTree.addObject(); + _southEastTree.addObject(object, rect, list); } - if ((_objectRightEdge > _leftEdge) && (_objectLeftEdge < _midpointX) && (_objectBottomEdge > _midpointY) && (_objectTopEdge < _bottomEdge)) + + if ((rect.right > _leftEdge) && (rect.left < _midpointX) && (rect.bottom > _midpointY) && (rect.top < _bottomEdge)) { if (_southWestTree == null) { _southWestTree = FlxQuadTree.recycle(_leftEdge, _midpointY, _halfWidth, _halfHeight, this); } - _southWestTree.addObject(); + _southWestTree.addObject(object, rect, list); } } /** * Internal function for recursively adding objects to leaf lists. */ - function addToList():Void + function addToList(object:FlxObject, list:Int):Void { var ot:FlxLinkedList; - if (_list == A_LIST) + if (list == A_LIST) { if (_tailA.object != null) { @@ -559,7 +434,7 @@ class FlxQuadTree extends FlxRect _tailA = FlxLinkedList.recycle(); ot.next = _tailA; } - _tailA.object = _object; + _tailA.object = object; } else { @@ -569,36 +444,46 @@ class FlxQuadTree extends FlxRect _tailB = FlxLinkedList.recycle(); ot.next = _tailB; } - _tailB.object = _object; + _tailB.object = object; } + if (!_canSubdivide) - { return; - } + if (_northWestTree != null) - { - _northWestTree.addToList(); - } + _northWestTree.addToList(object, list); + if (_northEastTree != null) - { - _northEastTree.addToList(); - } + _northEastTree.addToList(object, list); + if (_southEastTree != null) - { - _southEastTree.addToList(); - } + _southEastTree.addToList(object, list); + if (_southWestTree != null) - { - _southWestTree.addToList(); - } + _southWestTree.addToList(object, list); } /** - * FlxQuadTree's other main function. Call this after adding objects - * using FlxQuadTree.load() to compare the objects that you loaded. - * @return Whether or not any overlaps were found. - */ - public function execute():Bool + * Adds the objects or groups' members to the quadtree, searches for overlaps, + * processes them with the `processCallback`, calls the `notifyCallback` and eventually + * returns true if there were any overlaps. + * + * @param objectOrGroup1 Any object that is or extends FlxObject or FlxGroup. + * @param objectOrGroup2 Any object that is or extends FlxObject or FlxGroup. + * If null, the first parameter will be checked against itself. + * @param notifyCallback A function called whenever two overlapping objects are found, + * and the processCallback is `null` or returns `true`. + * @param processCallback A function called whenever two overlapping objects are found. + * This will return true if the notifyCallback should be called. + * @return Whether or not any overlaps were found. + */ + public function loadAndExecute(objectOrGroup1:FlxBasic, ?objectOrGroup2:FlxBasic, ?notifyCallback:NotifyCallback, ?processCallback:ProcessCallback):Bool + { + load(objectOrGroup1, objectOrGroup2); + return execute(objectOrGroup2 != null, notifyCallback, processCallback); + } + + function execute(useBothLists:Bool, notifyCallback:NotifyCallback, processCallback:ProcessCallback):Bool { var overlapProcessed:Bool = false; @@ -607,16 +492,12 @@ class FlxQuadTree extends FlxRect var iterator = _headA; while (iterator != null) { - _object = iterator.object; - if (_useBothLists) - { - _iterator = _headB; - } - else - { - _iterator = iterator.next; - } - if (_object != null && _object.exists && _object.allowCollisions > 0 && _iterator != null && _iterator.object != null && overlapNode()) + final object = iterator.object; + final next = useBothLists ? _headB : iterator.next; + + if (object != null && object.exists && object.allowCollisions > 0 + && next != null && next.object != null + && overlapNode(object, next, notifyCallback, processCallback)) { overlapProcessed = true; } @@ -625,23 +506,18 @@ class FlxQuadTree extends FlxRect } // Advance through the tree by calling overlap on each child - if ((_northWestTree != null) && _northWestTree.execute()) - { + if (_northWestTree != null && _northWestTree.execute(useBothLists, notifyCallback, processCallback)) overlapProcessed = true; - } - if ((_northEastTree != null) && _northEastTree.execute()) - { + + if (_northEastTree != null && _northEastTree.execute(useBothLists, notifyCallback, processCallback)) overlapProcessed = true; - } - if ((_southEastTree != null) && _southEastTree.execute()) - { + + if (_southEastTree != null && _southEastTree.execute(useBothLists, notifyCallback, processCallback)) overlapProcessed = true; - } - if ((_southWestTree != null) && _southWestTree.execute()) - { + + if (_southWestTree != null && _southWestTree.execute(useBothLists, notifyCallback, processCallback)) overlapProcessed = true; - } - + return overlapProcessed; } @@ -649,56 +525,57 @@ class FlxQuadTree extends FlxRect * An internal function for comparing an object against the contents of a node. * @return Whether or not any overlaps were found. */ - function overlapNode():Bool + function overlapNode(object:FlxObject, iterator:FlxLinkedList, notifyCallback:Null, processCallback:Null):Bool { - // Calculate bulk hull for _object - _objectHullX = (_object.x < _object.last.x) ? _object.x : _object.last.x; - _objectHullY = (_object.y < _object.last.y) ? _object.y : _object.last.y; - _objectHullWidth = _object.x - _object.last.x; - _objectHullWidth = _object.width + ((_objectHullWidth > 0) ? _objectHullWidth : -_objectHullWidth); - _objectHullHeight = _object.y - _object.last.y; - _objectHullHeight = _object.height + ((_objectHullHeight > 0) ? _objectHullHeight : -_objectHullHeight); + // Calculate bulk hull for the object + final objectHullX = (object.x < object.last.x) ? object.x : object.last.x; + final objectHullY = (object.y < object.last.y) ? object.y : object.last.y; + final objectHullWidth = object.x - object.last.x; + final objectHullWidth = object.width + ((objectHullWidth > 0) ? objectHullWidth : -objectHullWidth); + final objectHullHeight = object.y - object.last.y; + final objectHullHeight = object.height + ((objectHullHeight > 0) ? objectHullHeight : -objectHullHeight); // Walk the list and check for overlaps var overlapProcessed:Bool = false; var checkObject:FlxObject; - while (_iterator != null) + while (iterator != null) { - checkObject = _iterator.object; - if (_object == checkObject || !checkObject.exists || checkObject.allowCollisions <= 0) + checkObject = iterator.object; + if (object == checkObject || !checkObject.exists || checkObject.allowCollisions <= 0) { - _iterator = _iterator.next; + iterator = iterator.next; continue; } // Calculate bulk hull for checkObject - _checkObjectHullX = (checkObject.x < checkObject.last.x) ? checkObject.x : checkObject.last.x; - _checkObjectHullY = (checkObject.y < checkObject.last.y) ? checkObject.y : checkObject.last.y; - _checkObjectHullWidth = checkObject.x - checkObject.last.x; - _checkObjectHullWidth = checkObject.width + ((_checkObjectHullWidth > 0) ? _checkObjectHullWidth : -_checkObjectHullWidth); - _checkObjectHullHeight = checkObject.y - checkObject.last.y; - _checkObjectHullHeight = checkObject.height + ((_checkObjectHullHeight > 0) ? _checkObjectHullHeight : -_checkObjectHullHeight); + final checkObjectHullX = (checkObject.x < checkObject.last.x) ? checkObject.x : checkObject.last.x; + final checkObjectHullY = (checkObject.y < checkObject.last.y) ? checkObject.y : checkObject.last.y; + final checkObjectHullWidth = checkObject.x - checkObject.last.x; + final checkObjectHullWidth = checkObject.width + ((checkObjectHullWidth > 0) ? checkObjectHullWidth : -checkObjectHullWidth); + final checkObjectHullHeight = checkObject.y - checkObject.last.y; + final checkObjectHullHeight = checkObject.height + ((checkObjectHullHeight > 0) ? checkObjectHullHeight : -checkObjectHullHeight); // Check for intersection of the two hulls - if ((_objectHullX + _objectHullWidth > _checkObjectHullX) - && (_objectHullX < _checkObjectHullX + _checkObjectHullWidth) - && (_objectHullY + _objectHullHeight > _checkObjectHullY) - && (_objectHullY < _checkObjectHullY + _checkObjectHullHeight)) + if ((objectHullX + objectHullWidth > checkObjectHullX) + && (objectHullX < checkObjectHullX + checkObjectHullWidth) + && (objectHullY + objectHullHeight > checkObjectHullY) + && (objectHullY < checkObjectHullY + checkObjectHullHeight)) { // Execute callback functions if they exist - if (_processingCallback == null || _processingCallback(_object, checkObject)) + if (processCallback == null || processCallback(object, checkObject)) { overlapProcessed = true; - if (_notifyCallback != null) + if (notifyCallback != null) { - _notifyCallback(_object, checkObject); + notifyCallback(object, checkObject); } } } - if (_iterator != null) + + if (iterator != null) { - _iterator = _iterator.next; + iterator = iterator.next; } } diff --git a/flixel/util/typeLimit/NextState.hx b/flixel/util/typeLimit/NextState.hx index d563541526..7192878532 100644 --- a/flixel/util/typeLimit/NextState.hx +++ b/flixel/util/typeLimit/NextState.hx @@ -35,7 +35,7 @@ import flixel.FlxState; abstract NextState(Dynamic) { @:from - // @:deprecated("use `MyState.new` or `()->new MyState()` instead of `new MyState()`)") // wait until 6.0.0 + @:deprecated("use `MyState.new` or `()->new MyState()` instead of `new MyState()`)") public static function fromState(state:FlxState):NextState { return cast state; @@ -47,23 +47,11 @@ abstract NextState(Dynamic) return cast func; } - @:allow(flixel.FlxG) - inline function isInstance():Bool - { - return this is FlxState; - } - - @:allow(flixel.FlxG) - inline function isClass():Bool - { - return this is Class; - } - public function createInstance():FlxState { - if (isInstance()) + if (this is FlxState) return cast this; - else if (isClass()) + else if (this is Class) return Type.createInstance(this, []); else return cast this(); @@ -71,18 +59,13 @@ abstract NextState(Dynamic) public function getConstructor():()->FlxState { - if (isInstance()) + if (this is FlxState) { return function ():FlxState { return cast Type.createInstance(Type.getClass(this), []); } } - else if (isClass()) - return function ():FlxState - { - return cast Type.createInstance(this, []); - } else return cast this; } @@ -91,7 +74,7 @@ abstract NextState(Dynamic) /** * A utility type that allows methods to accept multiple types, when dealing with "future" `FlxStates`. * Prior to haxeFlixel 6, the `FlxGame` constructor took a `FlxState` class which meant initial - `FlxStates`could not have constructor args. In version 6.0.0 and higher, it now takes a function + * `FlxStates`could not have constructor args. In version 6.0.0 and higher, it now takes a function * that returns a newly created instance. * * ## examples: @@ -143,4 +126,4 @@ abstract InitialState(Dynamic) to NextState else return cast this; } -} +} \ No newline at end of file diff --git a/tests/unit/src/TestMain.hx b/tests/unit/src/TestMain.hx index bc092e7186..5a4936b3d2 100644 --- a/tests/unit/src/TestMain.hx +++ b/tests/unit/src/TestMain.hx @@ -21,7 +21,7 @@ class TestMain public function new() { // Flixel was not designed for unit testing so we can only have one instance for now. - Lib.current.stage.addChild(new FlxGame(640, 480, FlxState, 60, 60, true)); + Lib.current.stage.addChild(new FlxGame(640, 480, FlxState.new, 60, 60, true)); var suites = new Array>(); suites.push(TestSuite); diff --git a/tests/unit/src/flixel/FlxCameraTest.hx b/tests/unit/src/flixel/FlxCameraTest.hx index 62eeb63819..85459f6094 100644 --- a/tests/unit/src/flixel/FlxCameraTest.hx +++ b/tests/unit/src/flixel/FlxCameraTest.hx @@ -47,7 +47,7 @@ class FlxCameraTest extends FlxTest function testDefaultCamerasStateSwitch():Void { FlxCamera._defaultCameras = [FlxG.camera]; - switchState(new FlxState()); + switchState(FlxState.new); Assert.areEqual(FlxG.cameras.defaults, FlxCamera._defaultCameras); } diff --git a/tests/unit/src/flixel/FlxObjectTest.hx b/tests/unit/src/flixel/FlxObjectTest.hx index 292048bba0..69f3fafa70 100644 --- a/tests/unit/src/flixel/FlxObjectTest.hx +++ b/tests/unit/src/flixel/FlxObjectTest.hx @@ -159,7 +159,7 @@ class FlxObjectTest extends FlxTest function velocityCollidingWith(ground:FlxObject) { - switchState(new CollisionState()); + switchState(CollisionState.new); ground.setPosition(0, 10); object1.setSize(10, 10); diff --git a/tests/unit/src/flixel/FlxStateTest.hx b/tests/unit/src/flixel/FlxStateTest.hx index 0bf2d46a0d..5f81512a98 100644 --- a/tests/unit/src/flixel/FlxStateTest.hx +++ b/tests/unit/src/flixel/FlxStateTest.hx @@ -16,15 +16,6 @@ class FlxStateTest extends FlxTest @Ignore // TODO: investigate function testSwitchState() { - final state = new FlxState(); - - Assert.areNotEqual(state, FlxG.state); - switchState(state); - Assert.areEqual(state, FlxG.state); - - // Make sure this compiles - switchState(FlxState.new); - var nextState:FlxState = null; function createState() { @@ -37,19 +28,20 @@ class FlxStateTest extends FlxTest } @Test - function testResetStateInstance() + @:haxe.warning("-WDeprecated") + function testResetStateLegacy() { - var state = new TestState(); - switchState(state); + switchState(TestState.new); + var state = FlxG.state; Assert.areEqual(state, FlxG.state); resetState(); Assert.areNotEqual(state, FlxG.state); - Assert.isTrue((FlxG.state is TestState)); + Assert.isTrue(FlxG.state is TestState); } @Test - function testResetStateFunction() + function testResetState() { var nextState:TestState = null; function createState() @@ -68,28 +60,11 @@ class FlxStateTest extends FlxTest } @Test // #1676 - function testCancelStateSwitchInstance() - { - var finalState = new FinalStateLegacy(); - switchState(finalState); - Assert.areEqual(finalState, FlxG.state); - - switchState(new FlxState()); - Assert.areEqual(finalState, FlxG.state); - - resetState(); - Assert.areEqual(finalState, FlxG.state); - } - - @Test // #1676 - function testCancelStateSwitchFunction() + function testCancelStateSwitch() { switchState(FinalState.new); final finalState = FlxG.state; - switchState(new FlxState()); - Assert.areEqual(finalState, FlxG.state); - switchState(FlxState.new); Assert.areEqual(finalState, FlxG.state); @@ -100,27 +75,15 @@ class FlxStateTest extends FlxTest @Test function testOutro() { - var outroState = new OutroState(); - - FlxG.switchState(outroState); + FlxG.switchState(OutroState.new); step(); - Assert.areEqual(outroState, FlxG.state); + Assert.isType(FlxG.state, OutroState); - FlxG.switchState(new FlxState()); + FlxG.switchState(FlxState.new); step(); - Assert.areEqual(outroState, FlxG.state); + Assert.isType(FlxG.state, OutroState); step(); - Assert.areNotEqual(outroState, FlxG.state); - - } -} - -class FinalStateLegacy extends FlxState -{ - /* prevents state switches */ - override function switchTo(state) - { - return false; + Assert.isNotType(FlxG.state, OutroState); } } diff --git a/tests/unit/src/flixel/FlxSubStateTest.hx b/tests/unit/src/flixel/FlxSubStateTest.hx index af0389574a..acc26c9a96 100644 --- a/tests/unit/src/flixel/FlxSubStateTest.hx +++ b/tests/unit/src/flixel/FlxSubStateTest.hx @@ -41,28 +41,25 @@ class FlxSubStateTest extends FlxTest @Test // #1971 function testOpenPersistentSubStateFromNewParent() { - var state1 = new FlxState(); - var state2 = new FlxState(); - state1.destroySubStates = false; - FlxG.switchState(state1); + FlxG.switchState(FlxStateNoDestroySubState.new.bind(false)); step(); FlxG.state.openSubState(subState1); step(); - Assert.areEqual(state1.subState, subState1); + Assert.areEqual(FlxG.state.subState, subState1); subState1.close(); step(); - Assert.isNull(state1.subState); + Assert.isNull(FlxG.state.subState); - FlxG.switchState(state2); + FlxG.switchState(FlxStateNoDestroySubState.new.bind(true)); step(); FlxG.state.openSubState(subState1); step(); - Assert.areEqual(state2.subState, subState1); + Assert.areEqual(FlxG.state.subState, subState1); subState1.close(); step(); - Assert.isNull(state2.subState); + Assert.isNull(FlxG.state.subState); } @Test // #2023 @@ -87,3 +84,12 @@ class FlxSubStateTest extends FlxTest Assert.isTrue(closed); } } + +class FlxStateNoDestroySubState extends FlxState +{ + public function new (destroySubStates) + { + super(); + this.destroySubStates = destroySubStates; + } +} \ No newline at end of file diff --git a/tests/unit/src/flixel/group/FlxSpriteGroupTest.hx b/tests/unit/src/flixel/group/FlxSpriteGroupTest.hx index 9218668a9e..744a07baf4 100644 --- a/tests/unit/src/flixel/group/FlxSpriteGroupTest.hx +++ b/tests/unit/src/flixel/group/FlxSpriteGroupTest.hx @@ -137,6 +137,33 @@ class FlxSpriteGroupTest extends FlxTest Assert.isFalse(member2.revived); return group; } + @Test + /** + * Ensure that member origins are correctly set when + * the group origin is set. + */ + function testOriginTransform() + { + var f1 = new FlxSprite(-10, 100); + var f2 = new FlxSprite(50, 50); + group.add(f1); + group.add(f2); + + group.setPosition(280, 300); + group.origin.set(300, 400); + + // Verify positions are updated - absolute + Assert.areEqual(270, f1.x); + Assert.areEqual(400, f1.y); + Assert.areEqual(330, f2.x); + Assert.areEqual(350, f2.y); + + // Verify origins are correct - relative + Assert.areEqual(310, f1.origin.x); + Assert.areEqual(300, f1.origin.y); + Assert.areEqual(250, f2.origin.x); + Assert.areEqual(350, f2.origin.y); + } } class Member extends FlxSprite diff --git a/tests/unit/src/flixel/system/replay/FlxReplayTest.hx b/tests/unit/src/flixel/system/replay/FlxReplayTest.hx index 85a6d564a5..84212a11d1 100644 --- a/tests/unit/src/flixel/system/replay/FlxReplayTest.hx +++ b/tests/unit/src/flixel/system/replay/FlxReplayTest.hx @@ -90,12 +90,11 @@ class FlxReplayTest extends FlxTest createFrameRecord(3, JUST_RELEASED) ]; var recording = frames.map(function(r) return r.save()).join("\n"); - var state = new ReplayState(); - FlxG.vcr.loadReplay(recording, state); + FlxG.vcr.loadReplay(recording, ReplayState.new); step(10); - Assert.isTrue(state.called); + Assert.isTrue((cast FlxG.state:ReplayState).called); } function createFrameRecord(i:Int, mouseState:FlxInputState):FrameRecord