From bde1ede816ab0c6f34505d489d83a70b05d42690 Mon Sep 17 00:00:00 2001
From: stallratte <48615771+stallratte@users.noreply.github.com>
Date: Fri, 10 May 2024 18:43:10 +0200
Subject: [PATCH] Tile layer memory consumption (#797)
* tile layers now need less memory
* reducing loading time when using base64 encoded layers
---
.../Assets/Tiled/Runtime/Layer.Runtime.cs | 29 ++-------
.../Assets/Tiled/Runtime/TiledMapLoader.cs | 62 ++++++++-----------
Nez.Portable/Assets/Tiled/TiledTypes/Layer.cs | 22 +++++--
.../ECS/Components/Physics/TiledMapMover.cs | 33 ++++++++--
4 files changed, 76 insertions(+), 70 deletions(-)
diff --git a/Nez.Portable/Assets/Tiled/Runtime/Layer.Runtime.cs b/Nez.Portable/Assets/Tiled/Runtime/Layer.Runtime.cs
index 5d23d2516..8932034ae 100644
--- a/Nez.Portable/Assets/Tiled/Runtime/Layer.Runtime.cs
+++ b/Nez.Portable/Assets/Tiled/Runtime/Layer.Runtime.cs
@@ -1,4 +1,3 @@
-using System;
using System.Collections.Generic;
using Microsoft.Xna.Framework;
@@ -15,7 +14,9 @@ public partial class TmxLayer : ITmxLayer
/// The y coordinate.
public TmxLayerTile GetTile(int x, int y)
{
- return GetTile(x + y * Width);
+ Tiles.TryGetValue(Grid[x + y * Width], out var tmxLayerTile);
+
+ return tmxLayerTile;
}
public TmxLayerTile GetTile(int index)
@@ -146,29 +147,11 @@ public List GetTilesIntersectingBounds(Rectangle bounds)
/// The x coordinate.
/// The y coordinate.
/// Tile.
- [Obsolete("Please use SetTile with gid instead.")]
public TmxLayerTile SetTile(int x, int y, TmxLayerTile tile)
{
- return SetTile(x, y, tile.Gid, tile.HorizontalFlip, tile.VerticalFlip, tile.DiagonalFlip);
- }
-
- ///
- /// Sets the tile at position x, y.
- ///
- /// The x coordinate.
- /// The y coordinate.
- /// Global Tile ID (without the flip flags)
- /// Should the tile be flipped horizontally?
- /// Should the tile be flipped vertically?
- /// Should the tile be flipped diagonally?
- /// The tile.
- public TmxLayerTile SetTile(int x, int y, int gid, bool flipHorizontally = false, bool flipVertically = false, bool flipDiagonally = false)
- {
- if (gid == 0) return null;
-
- uint rawGid = TmxLayerTile.GetRawGid(gid, flipHorizontally, flipVertically, flipDiagonally);
-
- Grid[x + y * Width] = rawGid;
+ Grid[x + y * Width] = tile.RawGid;
+ Tiles.Add(tile.RawGid, tile);
+ tile.Tileset = Map.GetTilesetForTileGid(tile.Gid);
TmxLayerTile tileToSet;
diff --git a/Nez.Portable/Assets/Tiled/Runtime/TiledMapLoader.cs b/Nez.Portable/Assets/Tiled/Runtime/TiledMapLoader.cs
index 40b2cdc13..ed811a75a 100644
--- a/Nez.Portable/Assets/Tiled/Runtime/TiledMapLoader.cs
+++ b/Nez.Portable/Assets/Tiled/Runtime/TiledMapLoader.cs
@@ -323,30 +323,26 @@ public static TmxLayer LoadTmxLayer(this TmxLayer layer, TmxMap map, XElement xL
var xData = xLayer.Element("data");
var encoding = (string)xData.Attribute("encoding");
-
+
layer.Grid = new uint[width * height];
layer.Tiles = new Dictionary();
-
+
if (encoding == "base64")
{
var decodedStream = new TmxBase64Data(xData);
-
+
var index = 0;
- using (var stream = decodedStream.Data)
- {
- using (var br = new BinaryReader(stream))
- {
+ using (var stream = decodedStream.Data) {
+ using (var br = new BinaryReader(stream)) {
const int uintSizeInBytes = sizeof(uint);
-
+
var buffer = new byte[uintSizeInBytes * 1024];
int bytesRead;
- while ((bytesRead = br.Read(buffer, 0, buffer.Length)) > 0)
- {
+ while ((bytesRead = br.Read(buffer, 0, buffer.Length)) > 0) {
var numberOfUIntValuesRead = bytesRead / uintSizeInBytes;
-
- for (var i = 0; i < numberOfUIntValuesRead; i++)
- {
+
+ for (var i = 0; i < numberOfUIntValuesRead; i++) {
var gid = BitConverter.ToUInt32(buffer, i * uintSizeInBytes);
AddTile(layer, map, gid);
layer.Grid[index++] = gid;
@@ -358,28 +354,21 @@ public static TmxLayer LoadTmxLayer(this TmxLayer layer, TmxMap map, XElement xL
else if (encoding == "csv")
{
var csvData = xData.Value;
- var k = 0;
- var startIndex = 0;
- uint gid;
-
- for (var i = 0; i < csvData.Length; i++)
+ int k = 0;
+
+ int startIndex = 0;
+ for (var i = 0; i < csvData.Length; i++)
{
if (csvData[i] == ',')
{
- gid = ParseString(csvData, startIndex, i - startIndex);
-
+ var gid = ParseString(csvData, startIndex, i - startIndex);
+
AddTile(layer, map, gid);
- layer.Grid[k++] = gid;
-
+ layer.Grid[k++] = gid;
+
startIndex = i + 1;
}
}
-
- // Add remaining uid
- gid = ParseString(csvData, startIndex, csvData.Length - 1 - startIndex);
-
- AddTile(layer, map, gid);
- layer.Grid[k] = gid;
}
else if (encoding == null)
{
@@ -387,9 +376,9 @@ public static TmxLayer LoadTmxLayer(this TmxLayer layer, TmxMap map, XElement xL
foreach (var e in xData.Elements("tile"))
{
var gid = (uint?)e.Attribute("gid") ?? 0;
-
+
AddTile(layer, map, gid);
- layer.Grid[k++] = gid;
+ layer.Grid[k++] = gid;
}
}
else throw new Exception("TmxLayer: Unknown encoding.");
@@ -399,25 +388,24 @@ public static TmxLayer LoadTmxLayer(this TmxLayer layer, TmxMap map, XElement xL
return layer;
}
- private static void AddTile(TmxLayer layer, TmxMap map, uint gid)
- {
- if (gid != 0 && !layer.Tiles.ContainsKey(gid))
+ private static void AddTile(TmxLayer layer, TmxMap map, uint gid) {
+ if (gid != 0 && !layer.Tiles.ContainsKey(gid))
{
layer.Tiles.Add(gid, new TmxLayerTile(map, gid));
}
}
- private static uint ParseString(string str, int startIndex, int length)
+ private static uint ParseString(string str, int startIndex, int length)
{
uint result = 0;
for (int i = startIndex; i < startIndex + length; i++)
{
- if (char.IsDigit(str[i]))
+ if(char.IsDigit(str[i]))
result = result * 10u + (uint)(str[i] - '0');
}
return result;
}
-
+
public static TmxObjectGroup LoadTmxObjectGroup(this TmxObjectGroup group, TmxMap map, XElement xObjectGroup)
{
group.Map = map;
@@ -488,6 +476,7 @@ public static TmxObject LoadTmxObject(this TmxObject obj, TmxMap map, XElement x
if (xGid != null)
{
+ obj.Tile = new TmxLayerTile(map, (uint)xGid);
obj.Tile = new TmxLayerTile(map, (uint)xGid);
obj.ObjectType = TmxObjectType.Tile;
}
@@ -542,6 +531,7 @@ public static TmxObject LoadTmxObjectFromTemplate(this TmxObject obj, TmxMap map
if (xGid != null)
{
+ obj.Tile = new TmxLayerTile(map, (uint)xGid);
obj.Tile = new TmxLayerTile(map, (uint)xGid);
obj.ObjectType = TmxObjectType.Tile;
}
diff --git a/Nez.Portable/Assets/Tiled/TiledTypes/Layer.cs b/Nez.Portable/Assets/Tiled/TiledTypes/Layer.cs
index 5b0a6af94..7e6d08ee3 100644
--- a/Nez.Portable/Assets/Tiled/TiledTypes/Layer.cs
+++ b/Nez.Portable/Assets/Tiled/TiledTypes/Layer.cs
@@ -30,11 +30,17 @@ public partial class TmxLayer : ITmxLayer
public uint[] Grid;
public Dictionary Tiles;
+ public uint[] Grid;
+ public Dictionary Tiles;
+
///
/// returns the TmxLayerTile with gid. This is a slow lookup so cache it!
///
///
///
+ public TmxLayerTile GetTileWithGid(uint gid) {
+ Tiles.TryGetValue(gid, out var result);
+ return result;
public TmxLayerTile GetTileWithGid(uint gid) {
Tiles.TryGetValue(gid, out var result);
return result;
@@ -47,11 +53,13 @@ public class TmxLayerTile
const uint FLIPPED_VERTICALLY_FLAG = 0x40000000;
const uint FLIPPED_DIAGONALLY_FLAG = 0x20000000;
- public readonly TmxTileset Tileset;
- public readonly int Gid;
- public readonly bool HorizontalFlip;
- public readonly bool VerticalFlip;
- public readonly bool DiagonalFlip;
+ public TmxTileset Tileset;
+ // GID which still contains the flip flags.
+ public uint RawGid;
+ public int Gid;
+ public bool HorizontalFlip;
+ public bool VerticalFlip;
+ public bool DiagonalFlip;
int? _tilesetTileIndex;
@@ -82,8 +90,12 @@ public TmxTilesetTile TilesetTile
}
}
+ public TmxLayerTile(TmxMap map, uint rawGid)
+
public TmxLayerTile(TmxMap map, uint rawGid)
{
+ RawGid = rawGid;
+
// Scan for tile flip bit flags
bool flip;
flip = (rawGid & FLIPPED_HORIZONTALLY_FLAG) != 0;
diff --git a/Nez.Portable/ECS/Components/Physics/TiledMapMover.cs b/Nez.Portable/ECS/Components/Physics/TiledMapMover.cs
index 4b8c32d58..9072e0e79 100644
--- a/Nez.Portable/ECS/Components/Physics/TiledMapMover.cs
+++ b/Nez.Portable/ECS/Components/Physics/TiledMapMover.cs
@@ -114,8 +114,10 @@ public override string ToString()
///
/// temporary storage for all the coordinates of tiles that intersect the bounds being checked
+ /// temporary storage for all the coordinates of tiles that intersect the bounds being checked
///
List _collidingTilesCoordinates = new List();
+ List _collidingTilesCoordinates = new List();
///
/// temporary storage to avoid having to pass it around
@@ -253,25 +255,28 @@ bool TestMapCollision(Rectangle collisionRect, Edge direction, CollisionState co
var shouldTestSlopes = side.IsVertical();
PopulateCollidingTiles(collisionRect, direction);
+ for (var i = 0; i < _collidingTilesCoordinates.Count; i++)
for (var i = 0; i < _collidingTilesCoordinates.Count; i++)
{
var collidingTile = CollisionLayer.GetTile(_collidingTilesCoordinates[i].X, _collidingTilesCoordinates[i].Y);
-
+
if (collidingTile == null)
continue;
// disregard horizontal collisions with tiles on the same row as a slope if the last tile we were grounded on was a slope.
// the y collision response will push us up on the slope.
if (direction.IsHorizontal() && collisionState._lastGroundTile != null &&
+ collisionState._lastGroundTile.IsSlope() && IsSlopeCollisionRow(_collidingTilesCoordinates[i].Y))
collisionState._lastGroundTile.IsSlope() && IsSlopeCollisionRow(_collidingTilesCoordinates[i].Y))
continue;
- if (TestTileCollision(collidingTile, _collidingTilesCoordinates[i].X, _collidingTilesCoordinates[i].Y, side, perpindicularPosition, leadingPosition,
+ if (TestTileCollision(collidingTile, _collidingTilesCoordinates[i].X, _collidingTilesCoordinates[i].Y,side, perpindicularPosition, leadingPosition,
shouldTestSlopes, out collisionResponse))
{
// store off our last ground tile if we collided below
if (direction == Edge.Bottom)
{
+ collisionState._lastGroundTile = collidingTile;
collisionState._lastGroundTile = collidingTile;
collisionState.IsGroundedOnOneWayPlatform = collisionState._lastGroundTile.IsOneWayPlatform();
}
@@ -285,13 +290,14 @@ bool TestMapCollision(Rectangle collisionRect, Edge direction, CollisionState co
// if grounded on a slope and intersecting a slope or if grounded on a wall and intersecting a tall slope we go sticky.
// tall slope here means one where the the slopeTopLeft/Right is 0, i.e. it connects to a wall
var isHighSlopeNearest = collidingTile.IsSlope() &&
- collidingTile.GetNearestEdge(_collidingTilesCoordinates[i].X, perpindicularPosition) ==
- collidingTile.GetHighestSlopeEdge();
+ collidingTile.GetNearestEdge(perpindicularPosition, _collidingTilesCoordinates[i].X) ==
+ collidingTile.GetHighestSlopeEdge();
if ((collisionState._lastGroundTile.IsSlope() && collidingTile.IsSlope()) ||
(!collisionState._lastGroundTile.IsSlope() && isHighSlopeNearest))
{
// store off our last ground tile if we collided below
collisionState._lastGroundTile = collidingTile;
+ collisionState._lastGroundTile = collidingTile;
return true;
}
}
@@ -307,10 +313,11 @@ bool TestMapCollision(Rectangle collisionRect, Edge direction, CollisionState co
/// the row to check
bool IsSlopeCollisionRow(int rowY)
{
+ for (var i = 0; i < _collidingTilesCoordinates.Count; i++)
for (var i = 0; i < _collidingTilesCoordinates.Count; i++)
{
var collidingTile = CollisionLayer.GetTile(_collidingTilesCoordinates[i].X, _collidingTilesCoordinates[i].Y);
-
+
if (collidingTile != null && collidingTile.IsSlope() && _collidingTilesCoordinates[i].Y == rowY)
return true;
}
@@ -325,11 +332,14 @@ bool IsSlopeCollisionRow(int rowY)
/// Tile.
/// /// x position of the tile.
/// /// y position of the tile..
+ /// /// x position of the tile.
+ /// /// y position of the tile..
/// the opposite side of movement, the side the leading edge will collide with
/// Perpindicular position.
/// Leading position.
/// Should test slopes.
/// Collision response.
+ bool TestTileCollision(TmxLayerTile tile, int x, int y, Edge edgeToTest, int perpindicularPosition, int leadingPosition,
bool TestTileCollision(TmxLayerTile tile, int x, int y, Edge edgeToTest, int perpindicularPosition, int leadingPosition,
bool shouldTestSlopes, out int collisionResponse)
{
@@ -344,6 +354,7 @@ bool TestTileCollision(TmxLayerTile tile, int x, int y, Edge edgeToTest, int per
// our response should be the top of the platform
collisionResponse = TiledMap.TileToWorldPositionX(y);
+ collisionResponse = TiledMap.TileToWorldPositionX(y);
return _boxColliderBounds.Bottom <= collisionResponse;
}
@@ -353,7 +364,7 @@ bool TestTileCollision(TmxLayerTile tile, int x, int y, Edge edgeToTest, int per
// and we were not intesecting the tile before moving.
// this prevents clipping through a tile when hitting its edge: -> |\
if (edgeToTest.IsHorizontal() && tile.IsSlope() &&
- tile.GetNearestEdge(x, leadingPosition) == tile.GetHighestSlopeEdge())
+ tile.GetNearestEdge(leadingPosition, x) == tile.GetHighestSlopeEdge())
{
var moveDir = edgeToTest.OppositeEdge();
var leadingPositionPreMovement = _boxColliderBounds.GetSide(moveDir);
@@ -362,6 +373,8 @@ bool TestTileCollision(TmxLayerTile tile, int x, int y, Edge edgeToTest, int per
var tileX = moveDir == Edge.Right
? TiledMap.TileToWorldPositionX(x)
: TiledMap.TileToWorldPositionX(x + 1);
+ ? TiledMap.TileToWorldPositionX(x)
+ : TiledMap.TileToWorldPositionX(x + 1);
// using the edge before movement, we see if we were colliding before moving.
var wasCollidingBeforeMove = moveDir == Edge.Right
@@ -378,15 +391,19 @@ bool TestTileCollision(TmxLayerTile tile, int x, int y, Edge edgeToTest, int per
switch (edgeToTest)
{
case Edge.Top:
+ collisionResponse = TiledMap.TileToWorldPositionY(y);
collisionResponse = TiledMap.TileToWorldPositionY(y);
break;
case Edge.Bottom:
+ collisionResponse = TiledMap.TileToWorldPositionY(y + 1);
collisionResponse = TiledMap.TileToWorldPositionY(y + 1);
break;
case Edge.Left:
+ collisionResponse = TiledMap.TileToWorldPositionX(x);
collisionResponse = TiledMap.TileToWorldPositionX(x);
break;
case Edge.Right:
+ collisionResponse = TiledMap.TileToWorldPositionX(x + 1);
collisionResponse = TiledMap.TileToWorldPositionX(x + 1);
break;
}
@@ -396,6 +413,8 @@ bool TestTileCollision(TmxLayerTile tile, int x, int y, Edge edgeToTest, int per
if (shouldTestSlopes)
{
+ var tileWorldX = TiledMap.TileToWorldPositionX(x);
+ var tileWorldY = TiledMap.TileToWorldPositionX(y);
var tileWorldX = TiledMap.TileToWorldPositionX(x);
var tileWorldY = TiledMap.TileToWorldPositionX(y);
var slope = tile.GetSlope();
@@ -430,6 +449,7 @@ bool TestTileCollision(TmxLayerTile tile, int x, int y, Edge edgeToTest, int per
/// Direction.
void PopulateCollidingTiles(Rectangle bounds, Edge direction)
{
+ _collidingTilesCoordinates.Clear();
_collidingTilesCoordinates.Clear();
var isHorizontal = direction.IsHorizontal();
var primaryAxis = isHorizontal ? Axis.X : Axis.Y;
@@ -458,6 +478,7 @@ void PopulateCollidingTiles(Rectangle bounds, Edge direction)
var col = isHorizontal ? primary : secondary;
var row = !isHorizontal ? primary : secondary;
_collidingTilesCoordinates.Add(new Point(col, row));
+ _collidingTilesCoordinates.Add(new Point(col, row));
#if DEBUG_MOVER
if(direction.IsHorizontal())