From 9e8036deb230e957dfb29f01825934b70792a16b Mon Sep 17 00:00:00 2001 From: Nat Quayle Nelson Date: Tue, 2 Aug 2022 02:24:22 +0000 Subject: [PATCH] CameraTools/GroupTools bugfixing and sprite scale support --- .../source/HabitState.kiss | 272 ++++++++++-------- .../source/Main.hx | 5 +- .../src/kiss_flixel/CameraTools.hx | 1 + .../src/kiss_flixel/CameraTools.kiss | 38 ++- .../kiss-flixel/src/kiss_flixel/GroupTools.hx | 2 + .../src/kiss_flixel/GroupTools.kiss | 26 +- 6 files changed, 192 insertions(+), 152 deletions(-) diff --git a/projects/flixel-desktop-habit-puzzle-game/source/HabitState.kiss b/projects/flixel-desktop-habit-puzzle-game/source/HabitState.kiss index 17a611ce..44cb8df5 100644 --- a/projects/flixel-desktop-habit-puzzle-game/source/HabitState.kiss +++ b/projects/flixel-desktop-habit-puzzle-game/source/HabitState.kiss @@ -5,9 +5,13 @@ (prop &mut :FlxCamera uiCamera) (method &override :Void create [] + (#when debug + (add cameraBounds)) + (add logTexts) (set Prelude.printStr log) (set pieceCamera FlxG.camera) + (set cameraBounds.cameras [pieceCamera]) (set uiCamera (new FlxCamera)) (set uiCamera.bgColor FlxColor.TRANSPARENT) (pieceCamera.copyFrom FlxG.camera) @@ -23,10 +27,19 @@ (prop &mut :EntryType typeAdding Todo) (prop &mut :FlxInputText entryNameText) +(prop :FlxSprite cameraBounds (new FlxSprite)) + (method &override :Void update [:Float elapsed] (super.update elapsed) + + (#when debug + (let [b (pieceCamera.getScrollBounds)] + (set cameraBounds.x b.x) + (set cameraBounds.y b.y) + (cameraBounds.makeGraphic (Std.int b.width) (Std.int b.height) FlxColor.TRANSPARENT true) + (cameraBounds.drawRect 0 0 b.width b.height FlxColor.TRANSPARENT (object color FlxColor.LIME thickness 5)))) - (pieceCamera.updateScrollWheelZoom elapsed 1) + (pieceCamera.updateScrollWheelZoom elapsed 5) (pieceCamera.updateMouseBorderControl elapsed KEYBOARD_SCROLL_SPEED 0.002 uiCamera) // Hold left-click to hide the habit text and see the image clearly: @@ -42,7 +55,7 @@ (set save.data.backgroundIndex #{(save.data.backgroundIndex + 1) % backgroundOptions.length;}#) (save.flush) // setModel so the entry text gets remade in inverted/lightened colors when necessary - (setModel model (nth model.rewardFiles rewardFileIndex)))) + (refreshModel))) (method startAdding [:EntryType type] (set typeAdding type) @@ -54,8 +67,9 @@ (when FlxG.keys.justPressed.ENTER (cond (entryNameText + // addEntry() calls save() (model.addEntry typeAdding [entryNameText.text]) - (setModel model (nth model.rewardFiles rewardFileIndex)) + (refreshModel) (entryNameText.kill) (set entryNameText null)) (true @@ -79,11 +93,11 @@ (when FlxG.keys.justPressed.LEFT (unless (= rewardFileIndex 0) (-= rewardFileIndex 1) - (setModel model (nth model.rewardFiles rewardFileIndex)))) + (refreshModel))) (when FlxG.keys.justPressed.RIGHT (unless (= rewardFileIndex maxRewardFile) (+= rewardFileIndex 1) - (setModel model (nth model.rewardFiles rewardFileIndex))))) + (refreshModel)))) // Handle keyboard input: (when (and shortcutHandler !entryNameText) (shortcutHandler.update))) @@ -110,6 +124,7 @@ (prop &mut :Map indexMap (new Map)) (prop &mut :Map spriteMap (new Map)) // Because rewardSprites will be re-ordered in depth handling, this is required +(prop &mut lastRewardFileIndex -1) (prop &mut rewardFileIndex 0) (prop &mut maxRewardFile 0) @@ -144,129 +159,130 @@ (set save.data.backgroundIndex 0)) (set pieceCamera.bgColor (nth backgroundOptions save.data.backgroundIndex)) - (let [rewardSprite - (new FlxSprite 0 0 - (BitmapData.fromFile - currentRewardFile.path))] - (when rewardSprites - (remove rewardSprites)) - (rewardSprite.setGraphicSize FlxG.width 0) - (rewardSprite.updateHitbox) - (when (> rewardSprite.height FlxG.height) - (rewardSprite.setGraphicSize 0 FlxG.height)) - (rewardSprite.updateHitbox) - (rewardSprite.screenCenter) + (unless (= lastRewardFileIndex rewardFileIndex) + (let [rewardSprite + (new FlxSprite 0 0 + (BitmapData.fromFile + currentRewardFile.path))] + (when rewardSprites + (remove rewardSprites)) + (rewardSprite.setGraphicSize FlxG.width 0) + (rewardSprite.updateHitbox) + (when (> rewardSprite.height FlxG.height) + (rewardSprite.setGraphicSize 0 FlxG.height)) + (rewardSprite.updateHitbox) + (rewardSprite.screenCenter) - (set pieceCamera.zoom rewardSprite.scale.x) + (set pieceCamera.zoom rewardSprite.scale.x) - (set rewardSprites (new FlxTypedGroup)) - - (doFor map [matchingPiecesLeft matchingPiecesRight matchingPiecesUp matchingPiecesDown] - (map.clear)) - (connectedPieces.clear) - (doFor i (range TOTAL_PIECES) (dictSet connectedPieces i [])) - (indexMap.clear) + (set rewardSprites (new FlxTypedGroup)) + + (doFor map [matchingPiecesLeft matchingPiecesRight matchingPiecesUp matchingPiecesDown] + (map.clear)) + (connectedPieces.clear) + (doFor i (range TOTAL_PIECES) (dictSet connectedPieces i [])) + (indexMap.clear) - (let [r (new FlxRandom (Strings.hashCode currentRewardFile.path)) - graphicWidth rewardSprite.pixels.width - graphicHeight rewardSprite.pixels.height - pieceAssetWidth (Std.int (/ graphicWidth PUZZLE_WIDTH)) - pieceAssetHeight (Std.int (/ graphicHeight PUZZLE_HEIGHT)) - j (new Jigsawx pieceAssetWidth pieceAssetHeight EDGE_LEEWAY BUBBLE_SIZE PUZZLE_HEIGHT PUZZLE_WIDTH r) - PIECE_WIDTH - (/ rewardSprite.width PUZZLE_WIDTH) - PIECE_HEIGHT - (/ rewardSprite.height PUZZLE_HEIGHT) - :Array startingPoints []] - (let [&mut i 0] - (doFor y (range PUZZLE_HEIGHT) - (doFor x (range PUZZLE_WIDTH) - (startingPoints.push - (new FlxPoint (+ rewardSprite.x (* x PIECE_WIDTH)) (+ rewardSprite.y (* y PIECE_HEIGHT)))) - (+= i 1)))) - (r.shuffle startingPoints) - (set jigsaw j) - (r.shuffle jigsaw.jigs) - (localVar spriteGrid (for y (range PUZZLE_HEIGHT) (for x (range PUZZLE_WIDTH) null))) - (localVar indexGrid (for y (range PUZZLE_HEIGHT) (for x (range PUZZLE_WIDTH) 0))) - (doFor i (range (min TOTAL_PIECES (- p currentRewardFile.startingPoints))) - (let [jig (nth jigsaw.jigs i) - pos (ifLet [point (dictGet (the Map save.data.storedPositions) i)] - point - (nth startingPoints i)) - s (new FlxExtendedSprite pos.x pos.y) - source (new FlxSprite) - mask (new FlxSprite) - sourceRect (new Rectangle jig.xy.x jig.xy.y jig.wh.x jig.wh.y)] - (set s.priorityID i) - (setNth spriteGrid jig.row jig.col s) - (setNth indexGrid jig.row jig.col i) - (dictSet pieceData i jig) - (dictSet indexMap s i) - (dictSet spriteMap i s) - (set s.draggable true) - (s.enableMouseDrag false true) - (set s.mouseStartDragCallback - ->:Void [s x y] - { - // Bring currently held pieces to the front: - (rewardSprites.bringToFront s) - (set s.priorityID (+ 1 .priorityID (last (the kiss.List rewardSprites.members)))) - (doFor connected (recursivelyConnectedPieces s) - (rewardSprites.bringToFront connected)) - - (set draggingSprite s) - (set draggingLastPos (new FlxPoint s.x s.y)) - }) - (set s.mouseStopDragCallback - ->:Void [s x y] - { - (set draggingSprite null) - (checkMatches i) - (dictSet (the Map save.data.storedPositions) i (new FlxPoint s.x s.y)) - (doFor connected (recursivelyConnectedPieces s) - (checkMatches (dictGet indexMap connected)) - (log "uh $(dictGet indexMap connected)") - (dictSet (the Map save.data.storedPositions) (dictGet indexMap connected) (new FlxPoint connected.x connected.y))) + (let [r (new FlxRandom (Strings.hashCode currentRewardFile.path)) + graphicWidth rewardSprite.pixels.width + graphicHeight rewardSprite.pixels.height + pieceAssetWidth (Std.int (/ graphicWidth PUZZLE_WIDTH)) + pieceAssetHeight (Std.int (/ graphicHeight PUZZLE_HEIGHT)) + j (new Jigsawx pieceAssetWidth pieceAssetHeight EDGE_LEEWAY BUBBLE_SIZE PUZZLE_HEIGHT PUZZLE_WIDTH r) + PIECE_WIDTH + (/ rewardSprite.width PUZZLE_WIDTH) + PIECE_HEIGHT + (/ rewardSprite.height PUZZLE_HEIGHT) + :Array startingPoints []] + (let [&mut i 0] + (doFor y (range PUZZLE_HEIGHT) + (doFor x (range PUZZLE_WIDTH) + (startingPoints.push + (new FlxPoint (+ rewardSprite.x (* x PIECE_WIDTH)) (+ rewardSprite.y (* y PIECE_HEIGHT)))) + (+= i 1)))) + (r.shuffle startingPoints) + (set jigsaw j) + (r.shuffle jigsaw.jigs) + (localVar spriteGrid (for y (range PUZZLE_HEIGHT) (for x (range PUZZLE_WIDTH) null))) + (localVar indexGrid (for y (range PUZZLE_HEIGHT) (for x (range PUZZLE_WIDTH) 0))) + (doFor i (range (min TOTAL_PIECES (- p currentRewardFile.startingPoints))) + (let [jig (nth jigsaw.jigs i) + pos (ifLet [point (dictGet (the Map save.data.storedPositions) i)] + point + (nth startingPoints i)) + s (new FlxExtendedSprite pos.x pos.y) + source (new FlxSprite) + mask (new FlxSprite) + sourceRect (new Rectangle jig.xy.x jig.xy.y jig.wh.x jig.wh.y)] + (set s.priorityID i) + (setNth spriteGrid jig.row jig.col s) + (setNth indexGrid jig.row jig.col i) + (dictSet pieceData i jig) + (dictSet indexMap s i) + (dictSet spriteMap i s) + (set s.draggable true) + (s.enableMouseDrag false true) + (set s.mouseStartDragCallback + ->:Void [s x y] + { + // Bring currently held pieces to the front: + (rewardSprites.bringToFront s) + (set s.priorityID (+ 1 .priorityID (last (the kiss.List rewardSprites.members)))) + (doFor connected (recursivelyConnectedPieces s) + (rewardSprites.bringToFront connected)) - (pieceCamera.calculateScrollBounds rewardSprites SCROLL_BOUND_MARGIN) - (save.flush) - }) - - (source.makeGraphic (Std.int sourceRect.width) (Std.int sourceRect.height) FlxColor.TRANSPARENT true) - (source.pixels.copyPixels rewardSprite.pixels sourceRect (new Point 0 0)) - - (mask.makeGraphic (Std.int sourceRect.width) (Std.int sourceRect.height) FlxColor.TRANSPARENT true) - (drawPieceShape mask jig FlxColor.BLACK) - (FlxSpriteUtil.alphaMask s source.pixels mask.pixels) + (set draggingSprite s) + (set draggingLastPos (new FlxPoint s.x s.y)) + }) + (set s.mouseStopDragCallback + ->:Void [s x y] + { + (set draggingSprite null) + (checkMatches i) + (dictSet (the Map save.data.storedPositions) i (new FlxPoint s.x s.y)) + (doFor connected (recursivelyConnectedPieces s) + (checkMatches (dictGet indexMap connected)) + (dictSet (the Map save.data.storedPositions) (dictGet indexMap connected) (new FlxPoint connected.x connected.y))) + + (pieceCamera.calculateScrollBounds rewardSprites uiCamera SCROLL_BOUND_MARGIN) + (save.flush) + }) + + (source.makeGraphic (Std.int sourceRect.width) (Std.int sourceRect.height) FlxColor.TRANSPARENT true) + (source.pixels.copyPixels rewardSprite.pixels sourceRect (new Point 0 0)) + + (mask.makeGraphic (Std.int sourceRect.width) (Std.int sourceRect.height) FlxColor.TRANSPARENT true) + (drawPieceShape mask jig FlxColor.BLACK) + (FlxSpriteUtil.alphaMask s source.pixels mask.pixels) - (set s.cameras [pieceCamera]) + (set s.cameras [pieceCamera]) - (#when debug - (let [matchZones [(matchZoneLeft s) (matchZoneRight s)(matchZoneUp s)(matchZoneDown s)]] - (doFor z matchZones - (FlxSpriteUtil.drawRect s (- z.x s.x) (- z.y s.y) z.width z.height FlxColor.TRANSPARENT (object thickness 1 color FlxColor.RED))))) + (#when debug + (let [matchZones [(matchZoneLeft s) (matchZoneRight s)(matchZoneUp s)(matchZoneDown s)]] + (doFor z matchZones + (FlxSpriteUtil.drawRect s (- z.x s.x) (- z.y s.y) z.width z.height FlxColor.TRANSPARENT (object thickness 1 color FlxColor.RED))))) - (rewardSprites.add s))) - (doFor row (range PUZZLE_HEIGHT) - (doFor col (range PUZZLE_WIDTH) - (let [id (nth indexGrid row col)] - // combination of try/whenLet should cover target languages - // where out-of-bounds nth throws an error AND languages - // where it returns null - (try (whenLet [toLeft (nth spriteGrid row (- col 1))] - (dictSet matchingPiecesLeft id toLeft)) (catch [e] null)) - (try (whenLet [toRight (nth spriteGrid row (+ col 1))] - (dictSet matchingPiecesRight id toRight)) (catch [e] null)) - (try (whenLet [toUp (nth spriteGrid (- row 1) col)] - (dictSet matchingPiecesUp id toUp)) (catch [e] null)) - (try (whenLet [toDown (nth spriteGrid (+ row 1) col)] - (dictSet matchingPiecesDown id toDown)) (catch [e] null))))) - (add rewardSprites) - (doFor i (range TOTAL_PIECES) - (checkMatches i)))) + (rewardSprites.add s))) + (doFor row (range PUZZLE_HEIGHT) + (doFor col (range PUZZLE_WIDTH) + (let [id (nth indexGrid row col)] + // combination of try/whenLet should cover target languages + // where out-of-bounds nth throws an error AND languages + // where it returns null + (try (whenLet [toLeft (nth spriteGrid row (- col 1))] + (dictSet matchingPiecesLeft id toLeft)) (catch [e] null)) + (try (whenLet [toRight (nth spriteGrid row (+ col 1))] + (dictSet matchingPiecesRight id toRight)) (catch [e] null)) + (try (whenLet [toUp (nth spriteGrid (- row 1) col)] + (dictSet matchingPiecesUp id toUp)) (catch [e] null)) + (try (whenLet [toDown (nth spriteGrid (+ row 1) col)] + (dictSet matchingPiecesDown id toDown)) (catch [e] null))))) + (add rewardSprites) + (doFor i (range TOTAL_PIECES) + (checkMatches i))))) + (set lastRewardFileIndex rewardFileIndex) - (pieceCamera.calculateScrollBounds rewardSprites SCROLL_BOUND_MARGIN) + (pieceCamera.calculateScrollBounds rewardSprites uiCamera SCROLL_BOUND_MARGIN) (when entryTexts (remove entryTexts)) (set entryTexts (new FlxTypedGroup)) @@ -307,6 +323,10 @@ (shortcutHandler.start))) (shortcutHandler.start)) +(method refreshModel [&opt m] + (let [m (or m model)] + (setModel m (nth m.rewardFiles rewardFileIndex)))) + (prop &mut textY 0) // Color currently used for making text, may be inverted or lightened to contrast with background: (prop &mut :FlxColor _color FlxColor.BLACK) @@ -361,8 +381,8 @@ (prop &mut c 0) (method :Void connectPiece [id self toSprite] - (let [thisConnectedPieces (dictGet connectedPieces ~id) - toConnectedPieces (dictGet connectedPieces ~(dictGet indexMap toSprite))] + (let [thisConnectedPieces (dictGet connectedPieces id) + toConnectedPieces (dictGet connectedPieces (dictGet indexMap toSprite))] (+= c 1) // Don't add duplicates (thisConnectedPieces.remove toSprite) @@ -388,13 +408,13 @@ (unless .isEmpty (mzl.intersection mzr) (connectPiece id s toRight))) (whenLet [toUp (dictGet matchingPiecesUp id) - mzu ~(matchZoneUp s) - mzd ~(matchZoneDown toUp)] + mzu (matchZoneUp s) + mzd (matchZoneDown toUp)] (unless .isEmpty (mzu.intersection mzd) (connectPiece id s toUp))) (whenLet [toDown (dictGet matchingPiecesDown id) - mzd ~(matchZoneDown s) - mzu ~(matchZoneUp toDown)] + mzd (matchZoneDown s) + mzu (matchZoneUp toDown)] (unless .isEmpty (mzu.intersection mzd) (connectPiece id s toDown))))) diff --git a/projects/flixel-desktop-habit-puzzle-game/source/Main.hx b/projects/flixel-desktop-habit-puzzle-game/source/Main.hx index c0288dc9..de5b6981 100644 --- a/projects/flixel-desktop-habit-puzzle-game/source/Main.hx +++ b/projects/flixel-desktop-habit-puzzle-game/source/Main.hx @@ -19,12 +19,11 @@ class Main extends Sprite }; function reloadModel(_) { if (t.draggingSprite == null) { - // TODO don't change camera position and zoom when this happens: - t.setModel(new HabitModel(habitFile)); + t.refreshModel(new HabitModel(habitFile)); t.model.save(); } } - reloadModel(null); + t.setModel(new HabitModel(habitFile)); new FlxTimer().start(30, reloadModel, 0); } diff --git a/projects/kiss-flixel/src/kiss_flixel/CameraTools.hx b/projects/kiss-flixel/src/kiss_flixel/CameraTools.hx index b7c140f9..0f7221ef 100644 --- a/projects/kiss-flixel/src/kiss_flixel/CameraTools.hx +++ b/projects/kiss-flixel/src/kiss_flixel/CameraTools.hx @@ -7,6 +7,7 @@ import flash.display.BitmapData; import flixel.FlxCamera; import flixel.math.FlxVector; import flixel.math.FlxPoint; +import flixel.math.FlxRect; import flixel.FlxSprite; import flixel.FlxG; import flixel.util.FlxColor; diff --git a/projects/kiss-flixel/src/kiss_flixel/CameraTools.kiss b/projects/kiss-flixel/src/kiss_flixel/CameraTools.kiss index aa035889..6673d4eb 100644 --- a/projects/kiss-flixel/src/kiss_flixel/CameraTools.kiss +++ b/projects/kiss-flixel/src/kiss_flixel/CameraTools.kiss @@ -76,20 +76,36 @@ (<= (- bottom margin) mPos.y) (<= (- bottom margin) mPos.y bottom))))) -(function updateScrollWheelZoom [:FlxCamera camera :Float elapsed :Float speed] - #{ - if (FlxG.mouse.wheel != 0) { - camera.zoom += (FlxG.mouse.wheel * elapsed * speed); - } - }#) +// GOTCHA: if you change FlxG.camera to a moving camera, you MUST provide a default camera for FlxG.mouse.getScreenPosition() +(function updateScrollWheelZoom [:FlxCamera camera :Float elapsed :Float speed &opt :FlxCamera screenCamera] + (case FlxG.mouse.wheel + (0 null) + (v + (let [deltaZoom (* camera.zoom v elapsed speed) + scrollPosition (camera.scroll.copyTo) + mouseWorldPosition (FlxG.mouse.getWorldPosition camera)] + (+= camera.zoom deltaZoom) + (let [newMouseWorldPosition (FlxG.mouse.getWorldPosition camera) + deltaMousePosition (newMouseWorldPosition.subtractPoint mouseWorldPosition)] + (camera.scroll.subtractPoint deltaMousePosition)) + // Undo any scrolling that expands the viewport past its bounds + **(unless (.containsPoint (getScrollBounds camera) camera.scroll) + (-= camera.zoom deltaZoom) + (set camera.scroll scrollPosition)))) + (otherwise null))) -(function calculateScrollBounds <>[:FlxObject T] [:FlxCamera camera :FlxTypedGroup group &opt :Float margin] - (let [r (GroupTools.calculateBounds group margin)] - (camera.setScrollBoundsRect r.x r.y r.width r.height))) +(function getScrollBounds [:FlxCamera camera] + (.fromTwoPoints (new FlxRect) (new FlxPoint camera.minScrollX camera.minScrollY) (new FlxPoint camera.maxScrollX camera.maxScrollY))) -(function extendScrollBounds [:FlxCamera camera :FlxObject object &opt :Float margin] +// GOTCHA: if you change FlxG.camera to a moving camera, you MUST provide a default camera for FlxG.mouse.getScreenPosition() +(function calculateScrollBounds <>[:FlxSprite T] [:FlxCamera camera :FlxTypedGroup group &opt :FlxCamera screenCamera :Float margin] + (let [r (GroupTools.calculateScreenBounds group screenCamera margin)] + (camera.setScrollBoundsRect r.x r.y ~r.width ~r.height))) + +// GOTCHA: if you change FlxG.camera to a moving camera, you MUST provide a default camera for FlxG.mouse.getScreenPosition() +(function extendScrollBounds [:FlxCamera camera :FlxSprite sprite &opt :FlxCamera screenCamera :Float margin] // if the given object is out of bounds, extend the bounds - (let [r (object.getRotatedBounds)] + (let [r (sprite.getScreenBounds camera)] (setMin camera.minScrollX (- r.left margin)) (setMin camera.minScrollY (- r.top margin)) (setMax camera.maxScrollX (+ r.right margin)) diff --git a/projects/kiss-flixel/src/kiss_flixel/GroupTools.hx b/projects/kiss-flixel/src/kiss_flixel/GroupTools.hx index 91cfd69e..a1f5ff42 100644 --- a/projects/kiss-flixel/src/kiss_flixel/GroupTools.hx +++ b/projects/kiss-flixel/src/kiss_flixel/GroupTools.hx @@ -6,7 +6,9 @@ import flixel.FlxObject; import flixel.FlxState; import flixel.math.FlxRect; import flixel.math.FlxPoint; +import flixel.FlxCamera; import flixel.group.FlxGroup; +import flixel.FlxSprite; @:build(kiss.Kiss.build()) class GroupTools {} diff --git a/projects/kiss-flixel/src/kiss_flixel/GroupTools.kiss b/projects/kiss-flixel/src/kiss_flixel/GroupTools.kiss index 22be101f..6726081b 100644 --- a/projects/kiss-flixel/src/kiss_flixel/GroupTools.kiss +++ b/projects/kiss-flixel/src/kiss_flixel/GroupTools.kiss @@ -8,20 +8,22 @@ (group.remove obj) (group.insert 0 obj))) -(function :FlxRect calculateBounds <>[:FlxObject T] [:FlxTypedGroup group &opt :Float margin] +(function :FlxRect calculateScreenBounds <>[:FlxSprite T] [:FlxTypedGroup group &opt :FlxCamera camera :Float margin] (unless margin (set margin 0)) - (let [&mut minX 0 - &mut maxX 0 - &mut minY 0 - &mut maxY 0 - r (new FlxRect)] + (let [s (group.getFirstAlive) + r (new FlxRect) + bounds (s.getScreenBounds r camera) + &mut minX bounds.left + &mut maxX bounds.right + &mut minY bounds.top + &mut maxY bounds.bottom] - (group.forEach ->object - (let [bounds (object.getRotatedBounds r)] - (set minX (min minX bounds.left)) - (set minY (min minY bounds.top)) - (set maxX (max maxX bounds.right)) - (set maxY (max maxY bounds.bottom)))) + (group.forEach ->sprite + (let [bounds (sprite.getScreenBounds r camera)] + (setMin minX bounds.left) + (setMin minY bounds.top) + (setMax maxX bounds.right) + (setMax maxY bounds.bottom))) (r.fromTwoPoints (new FlxPoint (- minX margin) (- minY margin))