Add MrGameEng.Tilemaps: code-built tile grids rendered through the batcher
CI / build-test (push) Failing after 1m4s
CI / build-test (push) Failing after 1m4s
TileSet maps ids to texture regions with tints (0 = empty), TileGrid is a bounds-checked dense ushort grid, and the Tilemap component places a grid in the world per render layer. UseTilemaps() inserts TilemapRenderSystem before the flush; it submits only the camera-visible cell range (TilemapMath.VisibleCells), so frame cost scales with the screen, not the grid. Demonstrated in the sample as a checkerboard floor; Tiled loading stays on the roadmap on top of this API.
This commit is contained in:
@@ -0,0 +1,19 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<OutputType>Exe</OutputType>
|
||||
<IsPackable>false</IsPackable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" />
|
||||
<PackageReference Include="xunit.v3" />
|
||||
<PackageReference Include="xunit.runner.visualstudio" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\src\MrGameEng.Tilemaps\MrGameEng.Tilemaps.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -0,0 +1,152 @@
|
||||
using Microsoft.Xna.Framework;
|
||||
using Microsoft.Xna.Framework.Graphics;
|
||||
using MrGameEng.Graphics;
|
||||
using Xunit;
|
||||
|
||||
namespace MrGameEng.Tilemaps.Tests;
|
||||
|
||||
public class TileGridTests
|
||||
{
|
||||
[Fact]
|
||||
public void NewGrid_IsEmpty()
|
||||
{
|
||||
var grid = new TileGrid(4, 3);
|
||||
|
||||
Assert.Equal(4, grid.Width);
|
||||
Assert.Equal(3, grid.Height);
|
||||
for (var y = 0; y < 3; y++)
|
||||
{
|
||||
for (var x = 0; x < 4; x++)
|
||||
{
|
||||
Assert.Equal(0, grid[x, y]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SetGet_RoundtripsPerCell()
|
||||
{
|
||||
var grid = new TileGrid(3, 3);
|
||||
|
||||
grid[2, 1] = 7;
|
||||
|
||||
Assert.Equal(7, grid[2, 1]);
|
||||
Assert.Equal(0, grid[1, 2]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Fill_SetsEveryCell()
|
||||
{
|
||||
var grid = new TileGrid(5, 5);
|
||||
|
||||
grid.Fill(3);
|
||||
|
||||
Assert.Equal(3, grid[0, 0]);
|
||||
Assert.Equal(3, grid[4, 4]);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(-1, 0)]
|
||||
[InlineData(0, -1)]
|
||||
[InlineData(3, 0)]
|
||||
[InlineData(0, 2)]
|
||||
public void OutOfBounds_Throws(int x, int y)
|
||||
{
|
||||
var grid = new TileGrid(3, 2);
|
||||
|
||||
Assert.False(grid.Contains(x, y));
|
||||
Assert.Throws<ArgumentOutOfRangeException>(() => grid[x, y]);
|
||||
Assert.Throws<ArgumentOutOfRangeException>(() => grid[x, y] = 1);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(0, 0)]
|
||||
[InlineData(-1, 1)]
|
||||
public void InvalidSize_Throws(int width, int height)
|
||||
{
|
||||
Assert.Throws<ArgumentOutOfRangeException>(() => new TileGrid(width, height));
|
||||
}
|
||||
}
|
||||
|
||||
public class TileSetTests
|
||||
{
|
||||
// Texture2D == null допустим в headless-тестах (см. Texture2DRegion).
|
||||
private static Texture2DRegion Region(int size = 16) =>
|
||||
new(null!, new Rectangle(0, 0, size, size));
|
||||
|
||||
[Fact]
|
||||
public void Add_ReturnsSequentialIds_StartingAtOne()
|
||||
{
|
||||
var tiles = new TileSet();
|
||||
|
||||
var first = tiles.Add(Region());
|
||||
var second = tiles.Add(Region(), Color.Red);
|
||||
|
||||
Assert.Equal(1, first);
|
||||
Assert.Equal(2, second);
|
||||
Assert.Equal(3, tiles.Count);
|
||||
Assert.Equal(Color.White, tiles[first].Color);
|
||||
Assert.Equal(Color.Red, tiles[second].Color);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void EmptyTileZero_HasNoRegion()
|
||||
{
|
||||
var tiles = new TileSet();
|
||||
|
||||
Assert.Null(tiles[0].Region);
|
||||
}
|
||||
}
|
||||
|
||||
public class TilemapMathTests
|
||||
{
|
||||
[Fact]
|
||||
public void CameraInsideMap_ReturnsClampedRange()
|
||||
{
|
||||
var cull = new RectF(35f, 18f, 40f, 30f); // правый край 75, нижний 48
|
||||
|
||||
var visible = TilemapMath.VisibleCells(in cull, Vector2.Zero, 16f, 10, 10,
|
||||
out var x0, out var y0, out var x1, out var y1);
|
||||
|
||||
Assert.True(visible);
|
||||
Assert.Equal((2, 1, 4, 3), (x0, y0, x1, y1));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void MapOffsetByOrigin_ShiftsRange()
|
||||
{
|
||||
var cull = new RectF(0f, 0f, 64f, 64f);
|
||||
|
||||
var visible = TilemapMath.VisibleCells(in cull, new Vector2(-32f, -32f), 16f, 100, 100,
|
||||
out var x0, out var y0, out var x1, out var y1);
|
||||
|
||||
Assert.True(visible);
|
||||
Assert.Equal((2, 2, 6, 6), (x0, y0, x1, y1));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CameraLargerThanMap_ClampsToWholeGrid()
|
||||
{
|
||||
var cull = new RectF(-1000f, -1000f, 5000f, 5000f);
|
||||
|
||||
var visible = TilemapMath.VisibleCells(in cull, Vector2.Zero, 16f, 8, 6,
|
||||
out var x0, out var y0, out var x1, out var y1);
|
||||
|
||||
Assert.True(visible);
|
||||
Assert.Equal((0, 0, 7, 5), (x0, y0, x1, y1));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(200f, 0f)] // справа от карты
|
||||
[InlineData(-200f, 0f)] // слева
|
||||
[InlineData(0f, 200f)] // ниже
|
||||
public void CameraOutsideMap_ReturnsFalse(float offsetX, float offsetY)
|
||||
{
|
||||
var cull = new RectF(offsetX, offsetY, 100f, 100f);
|
||||
|
||||
var visible = TilemapMath.VisibleCells(in cull, new Vector2(-150f, -150f), 16f, 8, 8,
|
||||
out _, out _, out _, out _);
|
||||
|
||||
Assert.False(visible);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user