Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
bc54752
add various forEachInRay helpers
Geokureli Jan 10, 2025
877a893
move it all to the base class
Geokureli Jan 10, 2025
44f71d8
move more stuff to base, add getTileWidth/height
Geokureli Jan 10, 2025
4d51bd9
fix typo
Geokureli Jan 10, 2025
c929076
add more posAt helpers
Geokureli Jan 10, 2025
8702aa6
D'OH
Geokureli Jan 10, 2025
ff04f69
add FlxRayResult and many helpers
Geokureli Jan 13, 2025
bebe76f
Merge branch 'dev' into foreach-ray
Geokureli Dec 17, 2025
f7643b3
remove RayTools
Geokureli Dec 19, 2025
711412d
add rayAdvanced
Geokureli Dec 19, 2025
3114033
remove helper types
Geokureli Dec 19, 2025
6826897
fix bug with starting in tile
Geokureli Dec 20, 2025
d69ef4b
fix doc
Geokureli Dec 20, 2025
76328b1
remove RayResultHelper
Geokureli Dec 20, 2025
eea800c
doc + style fixes
Geokureli Dec 20, 2025
be78d69
remove temp test code
Geokureli Dec 20, 2025
ed8bbe1
add orient arg to getTileData mthods
Geokureli Dec 20, 2025
061c4bc
make getTileWidth abstract
Geokureli Dec 22, 2025
3ad9207
add findrow tools
Geokureli Dec 22, 2025
c0e9c05
fix doc
Geokureli Dec 22, 2025
68c32ac
fix doc
Geokureli Dec 23, 2025
9871ad6
prevent null tiles in (tile)->T callbacks
Geokureli Dec 23, 2025
0d82302
fix bug with straight up lines
Geokureli Dec 23, 2025
3b00494
Add result arg to getTilePos
Geokureli Dec 23, 2025
655aa3f
specify function types
Geokureli Dec 23, 2025
503587a
fix bug add checks
Geokureli Dec 23, 2025
40457e8
rename forEachIndexInRow/Column to forEachInRow/Column
Geokureli Dec 23, 2025
1d77ca0
forgot to rename args
Geokureli Dec 23, 2025
0a05334
add f(index) overloads
Geokureli Dec 23, 2025
464e97b
more doc
Geokureli Dec 23, 2025
595d80c
simplify overloads
Geokureli Dec 24, 2025
dba757f
add unit tests
Geokureli Dec 24, 2025
e37f31b
fic 4.2.5 CI
Geokureli Dec 24, 2025
0fd2cad
fix throw message
Geokureli Dec 24, 2025
b9b630b
simplify calcRayResult
Geokureli Dec 24, 2025
0791628
test result arg in invalid getTilePos
Geokureli Dec 24, 2025
9ce0967
remove unused return
Geokureli Dec 24, 2025
272ae57
more doc
Geokureli Dec 24, 2025
ab875c2
fix doc grammar
Geokureli Dec 24, 2025
8fb4b66
more grammar
Geokureli Dec 24, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
336 changes: 329 additions & 7 deletions flixel/tile/FlxBaseTilemap.hx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import flixel.math.FlxPoint;
import flixel.math.FlxRect;
import flixel.path.FlxPathfinder;
import flixel.system.FlxAssets;
import flixel.util.FlxDestroyUtil;
import flixel.util.FlxArrayUtil;
import flixel.util.FlxCollision;
import flixel.util.FlxColor;
Expand Down Expand Up @@ -207,20 +208,341 @@ class FlxBaseTilemap<Tile:FlxObject> extends FlxObject
* If/when it passes through a tile, it stores that point and returns false.
*
* **Note:** In flixel 5.0.0, this was redone, the old method is now `rayStep`
*
* @param start The world coordinates of the start of the ray.
* @param end The world coordinates of the end of the ray.
* @param result Optional result vector, to avoid creating a new instance to be returned.
* Only returned if the line enters the rect.
*
* @param start The world coordinates of the start of the ray
* @param end The world coordinates of the end of the ray
* @param result Optional result vector, indicating where the ray hit a wall
* @return Returns true if the ray made it from Start to End without hitting anything.
* Returns false and fills Result if a tile was hit.
* Returns false and fills `result` if a tile was hit.
*/
public function ray(start:FlxPoint, end:FlxPoint, ?result:FlxPoint):Bool
{
throw "ray must be implemented";
return false;
}

/**
* Shoots a ray from the start point to the end point.
* If/when it passes through a tile, it stores that point and returns false.
*
* **Note:** In flixel 5.0.0, this was redone, the old method is now `rayStep`
*
* @param start The world coordinates of the start of the ray
* @param end The world coordinates of the end of the ray
* @param func The condition, where `tile` is the tile instance for that location
*/
inline public function forEachInRay(start:FlxPoint, end:FlxPoint, ?func:(Tile)->Void):Void
{
findIndexInRayI(start, end, voidFindIgnoreIndex(func));
}

/**
* Calls `func` on all tiles overlapping a ray from `start` to `end`
*
* @param start The world coordinates of the start of the ray
* @param end The world coordinates of the end of the ray
* @param func The condition, where `index` is the tile's map index, and `tile` is
* the tile instance for that location, or `null` if there is no instance
*/
inline public function forEachInRayI(start:FlxPoint, end:FlxPoint, func:(index:Int, tile:Null<Tile>) -> Void)
{
findIndexInRayI(start, end, voidFind(func));
}

/**
* Checks all tiles overlapping a ray from `start` to `end`,
* finds the first tile that satisfies to condition of `func` and returns its index
*
* **Note:** This skips any tiles with no instance
*
* @param start The world coordinates of the start of the ray
* @param end The world coordinates of the end of the ray
* @param func The condition, where `tile` is the tile instance for that location
* @param result Optional result vector, indicating where the ray hit the found tile
* @return The index of the found tile
*/
inline public function findIndexInRay(start:FlxPoint, end:FlxPoint, func:(Tile) -> Bool, ?result:FlxPoint):Int
{
return findIndexInRayI(start, end, ignoreIndex(func), result);
}

/**
* Checks all tile indices overlapping a ray from `start` to `end`,
* finds the first tile that satisfies to condition of `func` and returns its index
*
* @param start The world coordinates of the start of the ray
* @param end The world coordinates of the end of the ray
* @param func The condition, where `index` is the tile's map index, and `tile` is
* the tile instance for that location, or `null` if there is no instance
* @param result Optional result vector, indicating where the ray hit the found tile
* @return The index of the found tile
*/
public function findIndexInRayI(start:FlxPoint, end:FlxPoint, func:(index:Int, tile:Null<Tile>) -> Bool, ?result:FlxPoint):Int
{
// trim the line to the parts inside the map
final trimmedStart = calcRayEntry(start, end);
final trimmedEnd = calcRayExit(start, end);

start.putWeak();
end.putWeak();

if (trimmedStart == null || trimmedEnd == null)
{
FlxDestroyUtil.put(trimmedStart);
FlxDestroyUtil.put(trimmedEnd);
return -1;
}

start = trimmedStart;
end = trimmedEnd;

inline function clearRefs()
{
trimmedStart.put();
trimmedEnd.put();
}

final startIndex = getMapIndex(start);
final endIndex = getMapIndex(end);

// If the starting tile is solid, return the starting position
final tile = getTileData(startIndex);
if (tile != null && tile.solid)
{
if (result != null)
result.copyFrom(start);

clearRefs();
return startIndex;
}

final startTileX = getColumn(startIndex);
final startTileY = getRow(startIndex);
final endTileX = getColumn(endIndex);
final endTileY = getRow(endIndex);

final scaledTileWidth = getColumnPos(1) - getColumnPos(0);
final scaledTileHeight = getRowPos(1) - getRowPos(0);
var hitIndex = -1;

if (start.x == end.x)
{
hitIndex = findIndexInColumnI(startTileX, startTileY, endTileY, func);
if (hitIndex != -1 && result != null)
{
// check the bottom
result.copyFrom(getTilePos(hitIndex));
result.x = start.x;
if (start.y > end.y)
result.y += scaledTileHeight;
}
}
else
{
// Use y = mx + b formula
final m = (start.y - end.y) / (start.x - end.x);
// y - mx = b
final b = start.y - m * start.x;

final movesRight = start.x < end.x;
final inc = movesRight ? 1 : -1;
final offset = movesRight ? 1 : 0;
var tileX = startTileX;
var lastTileY = startTileY;

while (tileX != endTileX)
{
final xPos = getColumnPos(tileX + offset);
final yPos = m * getColumnPos(tileX + offset) + b;
final tileY = getRowAt(yPos);
hitIndex = findIndexInColumnI(tileX, lastTileY, tileY, func);
if (hitIndex != -1)
break;
lastTileY = tileY;
tileX += inc;
}

if (hitIndex == -1)
hitIndex = findIndexInColumnI(endTileX, lastTileY, endTileY, func);

if (hitIndex != -1 && result != null)
{
result.copyFrom(getTilePos(hitIndex));
if (Std.int(hitIndex / widthInTiles) == lastTileY)
{
if (start.x > end.x)
result.x += scaledTileWidth;

// set result to left side
result.y = m * result.x + b; // mx + b
}
else
{
// if ascending
if (start.y > end.y)
{
// change result to bottom
result.y += scaledTileHeight;
}
// otherwise result is top

// x = (y - b)/m
result.x = (result.y - b) / m;
}
}
}

clearRefs();
return hitIndex;
}

/**
* Calls `func` on all tiles in the `column` between the specified `startRow` and `endRow`
*
* **Note:** This skips any tiles with no instance
*
* @param column The column to check
* @param startRow The row to check from
* @param endRow The row to check to
* @param func The function, where `tile` is the tile instance for that location
*/
inline public function forEachIndexInColumn(column:Int, startRow:Int, endRow:Int, func:(Tile)->Void)
{
findIndexInColumnI(column, startRow, endRow, voidFindIgnoreIndex(func));
}

/**
* Calls `func` on all tiles in the `column` between the specified `startRow` and `endRow`
*
* @param column The column to check
* @param startRow The row to check from
* @param endRow The row to check to
* @param func The function, where `index` is the tile's map index, and `tile` is
* the tile instance for that location, or `null` if there is no instance
*/
inline public function forEachIndexInColumnI(column:Int, startRow:Int, endRow:Int, func:(index:Int, tile:Null<Tile>)->Bool)
{
findIndexInColumnI(column, startRow, endRow, voidFind(func));
}

/**
* Helper to convert `(Int, Null<Tile>)->Bool` to `(Tile>)->Void`.
* Checks null, ignores index, always returns false
*/
inline function voidFindIgnoreIndex(func:(Tile)->Void):(Int, Null<Tile>)->Bool
{
return function (_, t)
{
if (t != null)
func(t);

return false;
};
}

/**
* Helper to convert `(Int, Null<Tile>)->Bool` to `(Int, Null<Tile>)->Void`.
* Always returns false
*/
inline function voidFind(func:(Int, Null<Tile>)->Void):(index:Int, tile:Null<Tile>)->Bool
{
return (_, t)->{ func(_, t); return false; };
}


/**
* Checks all tiles in the `column` between the specified `startRow` and `endRow`,
* Retrieves the first tile that satisfies to condition of `func` and returns it
*
* **Note:** This skips any tiles with no instance
*
* @param column The column to check
* @param startRow The row to check from
* @param endRow The row to check to
* @param func The condition, where `tile` is the tile instance for that location
* @return The found tile
*/
public function findInColumn(column:Int, startRow:Int, endRow:Int, func:(Tile)->Bool):Tile
{
final index = findIndexInColumnI(column, startRow, endRow, ignoreIndex(func));
if (index < 0)
return null;

final data = getTileData(index);
if (data == null)
throw 'Unexpected null tile at $index'; // internal error

return data;
}

/**
* Checks all tiles in the `column` between the specified `startRow` and `endRow`,
* finds the first tile that satisfies to condition of `func` and returns its index
*
* **Note:** This skips any tiles with no instance
*
* @param column The column to check
* @param startRow The row to check from
* @param endRow The row to check to
* @param func The condition, where `tile` is the tile instance for that location
* @return The index of the found tile
*/
inline public function findIndexInColumn(column:Int, startRow:Int, endRow:Int, func:(Tile)->Bool):Int
{
return findIndexInColumnI(column, startRow, endRow, ignoreIndex(func));
}

/**
* Helper to convert `(Int, Null<Tile>)->Bool` to `(Tile)->Bool`.
* Checks null, ignores index
*/
inline function ignoreIndex(func:(Tile)->Bool):(Int, Null<Tile>)->Bool
{
return (_, t)->t != null && func(t);
}

/**
* Checks all tile indices in the `column` between the specified `startRow` and `endRow`,
* finds the first tile that satisfies to condition of `func` and returns its index
*
* @param column The column to check
* @param startRow The row to check from
* @param endRow The row to check to
* @param func The condition, where `index` is the tile's map index, and `tile` is
* the tile instance for that location, or `null` if there is no instance
* @return The index of the found tile
*/
public function findIndexInColumnI(column:Int, startRow:Int, endRow:Int, func:(index:Int, tile:Null<Tile>)->Bool):Int
{
if (startRow < 0)
startRow = 0;

if (endRow < 0)
endRow = 0;

if (startRow > heightInTiles - 1)
startRow = heightInTiles - 1;

if (endRow > heightInTiles - 1)
endRow = heightInTiles - 1;

var row = startRow;
final step = startRow <= endRow ? 1 : -1;
while (true)
{
final index = getMapIndex(column, row);
final tile = getTileData(index);
if (func(index, tile))
return index;

if (row == endRow)
break;

row += step;
}

return -1;
}

/**
* Shoots a ray from the start point to the end point.
* If/when it passes through a tile, it stores that point and returns false.
Expand Down
Loading
Loading