mirror of
https://github.com/dcronqvist/DotTiled.git
synced 2025-05-08 15:36:02 +03:00
Add Raylib example project and helper methods for resolving tilesets and finding source rectangles of tiles
This commit is contained in:
parent
36c6f4dd12
commit
6191498618
8 changed files with 598 additions and 0 deletions
|
@ -0,0 +1,22 @@
|
||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<OutputType>Exe</OutputType>
|
||||||
|
<TargetFramework>net8.0</TargetFramework>
|
||||||
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
|
<Nullable>disable</Nullable>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="Raylib-cs" Version="7.0.1" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\..\DotTiled\DotTiled.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<Content Include="assets\**\*.*" CopyToOutputDirectory="PreserveNewest" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
203
src/DotTiled.Examples/DotTiled.Example.Raylib/Program.cs
Normal file
203
src/DotTiled.Examples/DotTiled.Example.Raylib/Program.cs
Normal file
|
@ -0,0 +1,203 @@
|
||||||
|
using System.Numerics;
|
||||||
|
using DotTiled.Serialization;
|
||||||
|
using Raylib_cs;
|
||||||
|
|
||||||
|
using RayColor = Raylib_cs.Color;
|
||||||
|
|
||||||
|
namespace DotTiled.Example
|
||||||
|
{
|
||||||
|
public class Program
|
||||||
|
{
|
||||||
|
public static void Main(string[] _)
|
||||||
|
{
|
||||||
|
// Initialize the Raylib window
|
||||||
|
Raylib.InitWindow(1280, 720, "DotTiled Example with Raylib");
|
||||||
|
Raylib.SetConfigFlags(ConfigFlags.VSyncHint);
|
||||||
|
|
||||||
|
// Load the Tiled map
|
||||||
|
var loader = Loader.Default();
|
||||||
|
var map = loader.LoadMap("assets/world.tmx");
|
||||||
|
|
||||||
|
// Load tileset textures
|
||||||
|
var tilesetTextures = LoadTilesetTextures(map);
|
||||||
|
|
||||||
|
// Extract layers from the map
|
||||||
|
var visualLayers = map.Layers.OfType<Group>().Single(l => l.Name == "Visuals").Layers.OfType<TileLayer>();
|
||||||
|
var collisionLayer = map.Layers.OfType<ObjectLayer>().Single(l => l.Name == "Collisions");
|
||||||
|
var pointsOfInterest = (ObjectLayer)map.Layers.Single(layer => layer.Name == "PointsOfInterest");
|
||||||
|
|
||||||
|
// Get the player's spawn point
|
||||||
|
var playerSpawnPoint = pointsOfInterest.Objects.Single(obj => obj.Name == "PlayerSpawn");
|
||||||
|
var playerPosition = new Vector2(playerSpawnPoint.X, playerSpawnPoint.Y);
|
||||||
|
|
||||||
|
// Set up the camera
|
||||||
|
var camera = new Camera2D
|
||||||
|
{
|
||||||
|
Target = playerPosition,
|
||||||
|
Offset = new Vector2(Raylib.GetScreenWidth() / 2, Raylib.GetScreenHeight() / 2),
|
||||||
|
Rotation = 0.0f,
|
||||||
|
Zoom = 1.0f
|
||||||
|
};
|
||||||
|
|
||||||
|
// Main game loop
|
||||||
|
while (!Raylib.WindowShouldClose())
|
||||||
|
{
|
||||||
|
// Update game logic
|
||||||
|
Update(ref playerPosition, collisionLayer, ref camera);
|
||||||
|
|
||||||
|
// Render the game
|
||||||
|
Render(map, visualLayers, tilesetTextures, playerPosition, camera);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clean up resources
|
||||||
|
Raylib.CloseWindow();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Loads tileset textures from the map.
|
||||||
|
/// </summary>
|
||||||
|
private static Dictionary<string, Texture2D> LoadTilesetTextures(Map map)
|
||||||
|
{
|
||||||
|
return map.Tilesets.ToDictionary(
|
||||||
|
tileset => tileset.Image.Value.Source.Value,
|
||||||
|
tileset => Raylib.LoadTexture(Path.Combine("assets", tileset.Image.Value.Source.Value))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Updates the player's position and camera.
|
||||||
|
/// </summary>
|
||||||
|
private static void Update(ref Vector2 playerPosition, ObjectLayer collisionLayer, ref Camera2D camera)
|
||||||
|
{
|
||||||
|
// Define the player's rectangle
|
||||||
|
var playerRect = new Rectangle(playerPosition.X, playerPosition.Y, 12, 12);
|
||||||
|
|
||||||
|
// Handle player movement
|
||||||
|
var move = HandlePlayerInput();
|
||||||
|
|
||||||
|
// Check for collisions
|
||||||
|
foreach (var obj in collisionLayer.Objects.OfType<RectangleObject>())
|
||||||
|
{
|
||||||
|
var objRect = new Rectangle(obj.X, obj.Y, obj.Width, obj.Height);
|
||||||
|
|
||||||
|
// Horizontal collision
|
||||||
|
var movePlayerHRect = new Rectangle(playerRect.X + move.X, playerRect.Y, playerRect.Width, playerRect.Height);
|
||||||
|
if (Raylib.CheckCollisionRecs(movePlayerHRect, objRect))
|
||||||
|
{
|
||||||
|
move.X = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Vertical collision
|
||||||
|
var movePlayerVRect = new Rectangle(playerRect.X, playerRect.Y + move.Y, playerRect.Width, playerRect.Height);
|
||||||
|
if (Raylib.CheckCollisionRecs(movePlayerVRect, objRect))
|
||||||
|
{
|
||||||
|
move.Y = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update player position
|
||||||
|
playerPosition += move;
|
||||||
|
|
||||||
|
// Smoothly update the camera target
|
||||||
|
var newCameraTarget = new Vector2(playerPosition.X, playerPosition.Y);
|
||||||
|
camera.Target += (newCameraTarget - camera.Target) * 15f * Raylib.GetFrameTime();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Handles player input for movement.
|
||||||
|
/// </summary>
|
||||||
|
private static Vector2 HandlePlayerInput()
|
||||||
|
{
|
||||||
|
var move = Vector2.Zero;
|
||||||
|
var playerSpeed = 150 * Raylib.GetFrameTime();
|
||||||
|
|
||||||
|
if (Raylib.IsKeyDown(KeyboardKey.W)) move.Y -= playerSpeed;
|
||||||
|
if (Raylib.IsKeyDown(KeyboardKey.S)) move.Y += playerSpeed;
|
||||||
|
if (Raylib.IsKeyDown(KeyboardKey.A)) move.X -= playerSpeed;
|
||||||
|
if (Raylib.IsKeyDown(KeyboardKey.D)) move.X += playerSpeed;
|
||||||
|
|
||||||
|
return move;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Renders the game, including layers and the player.
|
||||||
|
/// </summary>
|
||||||
|
private static void Render(Map map, IEnumerable<TileLayer> visualLayers, Dictionary<string, Texture2D> tilesetTextures, Vector2 playerPosition, Camera2D camera)
|
||||||
|
{
|
||||||
|
Raylib.BeginDrawing();
|
||||||
|
Raylib.ClearBackground(RayColor.Blank);
|
||||||
|
Raylib.BeginMode2D(camera);
|
||||||
|
|
||||||
|
// Render layers below the player
|
||||||
|
RenderLayers(map, visualLayers, tilesetTextures, ["Ground", "Ponds", "Paths", "HouseWalls", "HouseDoors", "FencesBushes"]);
|
||||||
|
|
||||||
|
// Draw the player
|
||||||
|
var playerVisualRect = new Rectangle(playerPosition.X, playerPosition.Y - 12, 12, 24);
|
||||||
|
Raylib.DrawRectangleRec(playerVisualRect, RayColor.Blue);
|
||||||
|
|
||||||
|
// Render layers above the player
|
||||||
|
RenderLayers(map, visualLayers, tilesetTextures, ["HouseRoofs"]);
|
||||||
|
|
||||||
|
Raylib.EndMode2D();
|
||||||
|
Raylib.EndDrawing();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Renders specific layers from the map.
|
||||||
|
/// </summary>
|
||||||
|
private static void RenderLayers(Map map, IEnumerable<TileLayer> visualLayers, Dictionary<string, Texture2D> tilesetTextures, string[] layerNames)
|
||||||
|
{
|
||||||
|
foreach (var layerName in layerNames)
|
||||||
|
{
|
||||||
|
var layer = visualLayers.OfType<TileLayer>().Single(l => l.Name == layerName);
|
||||||
|
RenderLayer(map, layer, tilesetTextures);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Renders a single layer from the map.
|
||||||
|
/// </summary>
|
||||||
|
private static void RenderLayer(Map map, TileLayer layer, Dictionary<string, Texture2D> tilesetTextures)
|
||||||
|
{
|
||||||
|
for (var y = 0; y < layer.Height; y++)
|
||||||
|
{
|
||||||
|
for (var x = 0; x < layer.Width; x++)
|
||||||
|
{
|
||||||
|
var tileGID = layer.GetGlobalTileIDAtCoord(x, y);
|
||||||
|
if (tileGID == 0) continue;
|
||||||
|
|
||||||
|
var tileset = map.ResolveTilesetForGlobalTileID(tileGID, out var localTileID);
|
||||||
|
var sourceRect = tileset.GetSourceRectangleForLocalTileID(localTileID);
|
||||||
|
|
||||||
|
// Source rec is shrunk by tiny amount to avoid ugly seams between tiles
|
||||||
|
// when the camera is at certain subpixel positions
|
||||||
|
var raylibSourceRect = ShrinkRectangle(new Rectangle(sourceRect.X, sourceRect.Y, sourceRect.Width, sourceRect.Height), 0.01f);
|
||||||
|
|
||||||
|
var destinationRect = new Rectangle(x * tileset.TileWidth, y * tileset.TileHeight, tileset.TileWidth, tileset.TileHeight);
|
||||||
|
|
||||||
|
Raylib.DrawTexturePro(
|
||||||
|
tilesetTextures[tileset.Image.Value.Source.Value],
|
||||||
|
raylibSourceRect,
|
||||||
|
destinationRect,
|
||||||
|
Vector2.Zero,
|
||||||
|
0,
|
||||||
|
RayColor.White
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Shrinks a rectangle by a specified amount.
|
||||||
|
/// </summary>
|
||||||
|
private static Rectangle ShrinkRectangle(Rectangle rect, float amount)
|
||||||
|
{
|
||||||
|
return new Rectangle(
|
||||||
|
rect.X + amount,
|
||||||
|
rect.Y + amount,
|
||||||
|
rect.Width - (2 * amount),
|
||||||
|
rect.Height - (2 * amount)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
132
src/DotTiled.Tests/UnitTests/MapTests.cs
Normal file
132
src/DotTiled.Tests/UnitTests/MapTests.cs
Normal file
|
@ -0,0 +1,132 @@
|
||||||
|
namespace DotTiled.Tests.UnitTests;
|
||||||
|
|
||||||
|
public class MapTests
|
||||||
|
{
|
||||||
|
[Fact]
|
||||||
|
public void ResolveTilesetForGlobalTileID_NoTilesets_ThrowsException()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var map = new Map
|
||||||
|
{
|
||||||
|
Version = "version",
|
||||||
|
Orientation = MapOrientation.Orthogonal,
|
||||||
|
Width = 10,
|
||||||
|
Height = 10,
|
||||||
|
TileWidth = 16,
|
||||||
|
TileHeight = 16,
|
||||||
|
NextLayerID = 1,
|
||||||
|
NextObjectID = 1
|
||||||
|
};
|
||||||
|
|
||||||
|
// Act & Assert
|
||||||
|
Assert.Throws<ArgumentException>(() => map.ResolveTilesetForGlobalTileID(1, out var _));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void ResolveTilesetForGlobalTileID_GlobalTileIDOutOfRange_ThrowsException()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var map = new Map
|
||||||
|
{
|
||||||
|
Version = "version",
|
||||||
|
Orientation = MapOrientation.Orthogonal,
|
||||||
|
Width = 10,
|
||||||
|
Height = 10,
|
||||||
|
TileWidth = 16,
|
||||||
|
TileHeight = 16,
|
||||||
|
NextLayerID = 1,
|
||||||
|
NextObjectID = 1,
|
||||||
|
Tilesets = [
|
||||||
|
new Tileset
|
||||||
|
{
|
||||||
|
FirstGID = 1,
|
||||||
|
Name = "Tileset1",
|
||||||
|
TileWidth = 16,
|
||||||
|
TileHeight = 16,
|
||||||
|
TileCount = 5,
|
||||||
|
Columns = 5
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
|
||||||
|
// Act & Assert
|
||||||
|
Assert.Throws<ArgumentException>(() => map.ResolveTilesetForGlobalTileID(6, out var _));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void ResolveTilesetForGlobalTileID_GlobalTileIDInRangeOfOnlyTileset_ReturnsTileset()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var tileset = new Tileset
|
||||||
|
{
|
||||||
|
FirstGID = 1,
|
||||||
|
Name = "Tileset1",
|
||||||
|
TileWidth = 16,
|
||||||
|
TileHeight = 16,
|
||||||
|
TileCount = 5,
|
||||||
|
Columns = 5
|
||||||
|
};
|
||||||
|
var map = new Map
|
||||||
|
{
|
||||||
|
Version = "version",
|
||||||
|
Orientation = MapOrientation.Orthogonal,
|
||||||
|
Width = 10,
|
||||||
|
Height = 10,
|
||||||
|
TileWidth = 16,
|
||||||
|
TileHeight = 16,
|
||||||
|
NextLayerID = 1,
|
||||||
|
NextObjectID = 1,
|
||||||
|
Tilesets = [tileset]
|
||||||
|
};
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = map.ResolveTilesetForGlobalTileID(3, out var localTileID);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal(tileset, result);
|
||||||
|
Assert.Equal(2, (int)localTileID); // 3 - 1 = 2 (local tile ID)
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void ResolveTilesetForGlobalTileID_GlobalTileIDInRangeOfMultipleTilesets_ReturnsCorrectTileset()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var tileset1 = new Tileset
|
||||||
|
{
|
||||||
|
FirstGID = 1,
|
||||||
|
Name = "Tileset1",
|
||||||
|
TileWidth = 16,
|
||||||
|
TileHeight = 16,
|
||||||
|
TileCount = 5,
|
||||||
|
Columns = 5
|
||||||
|
};
|
||||||
|
var tileset2 = new Tileset
|
||||||
|
{
|
||||||
|
FirstGID = 6,
|
||||||
|
Name = "Tileset2",
|
||||||
|
TileWidth = 16,
|
||||||
|
TileHeight = 16,
|
||||||
|
TileCount = 5,
|
||||||
|
Columns = 5
|
||||||
|
};
|
||||||
|
var map = new Map
|
||||||
|
{
|
||||||
|
Version = "version",
|
||||||
|
Orientation = MapOrientation.Orthogonal,
|
||||||
|
Width = 10,
|
||||||
|
Height = 10,
|
||||||
|
TileWidth = 16,
|
||||||
|
TileHeight = 16,
|
||||||
|
NextLayerID = 1,
|
||||||
|
NextObjectID = 1,
|
||||||
|
Tilesets = [tileset1, tileset2]
|
||||||
|
};
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = map.ResolveTilesetForGlobalTileID(8, out var localTileID);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal(tileset2, result);
|
||||||
|
Assert.Equal(2, (int)localTileID); // 8 - 6 = 2 (local tile ID)
|
||||||
|
}
|
||||||
|
}
|
114
src/DotTiled.Tests/UnitTests/Tilesets/TilesetTests.cs
Normal file
114
src/DotTiled.Tests/UnitTests/Tilesets/TilesetTests.cs
Normal file
|
@ -0,0 +1,114 @@
|
||||||
|
namespace DotTiled.Tests.UnitTests;
|
||||||
|
|
||||||
|
public class TilesetTests
|
||||||
|
{
|
||||||
|
[Fact]
|
||||||
|
public void GetSourceRectangleForLocalTileID_TileIDOutOfRange_ThrowsException()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var tileset = new Tileset
|
||||||
|
{
|
||||||
|
FirstGID = 0,
|
||||||
|
Name = "Tileset1",
|
||||||
|
TileWidth = 16,
|
||||||
|
TileHeight = 16,
|
||||||
|
TileCount = 5,
|
||||||
|
Columns = 5
|
||||||
|
};
|
||||||
|
|
||||||
|
// Act & Assert
|
||||||
|
Assert.Throws<ArgumentException>(() => tileset.GetSourceRectangleForLocalTileID(6));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void GetSourceRectangleForLocalTileID_ValidTileIDIsInTilesList_ReturnsCorrectRectangle()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var tileset = new Tileset
|
||||||
|
{
|
||||||
|
FirstGID = 0,
|
||||||
|
Name = "Tileset1",
|
||||||
|
TileWidth = 16,
|
||||||
|
TileHeight = 16,
|
||||||
|
TileCount = 2,
|
||||||
|
Columns = 2,
|
||||||
|
Tiles = [
|
||||||
|
new Tile
|
||||||
|
{
|
||||||
|
ID = 0,
|
||||||
|
X = 0,
|
||||||
|
Y = 0,
|
||||||
|
Width = 16,
|
||||||
|
Height = 16,
|
||||||
|
},
|
||||||
|
new Tile
|
||||||
|
{
|
||||||
|
ID = 1,
|
||||||
|
X = 16,
|
||||||
|
Y = 0,
|
||||||
|
Width = 16,
|
||||||
|
Height = 16,
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var rectangle = tileset.GetSourceRectangleForLocalTileID(1);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal(16, rectangle.X);
|
||||||
|
Assert.Equal(0, rectangle.Y);
|
||||||
|
Assert.Equal(16, rectangle.Width);
|
||||||
|
Assert.Equal(16, rectangle.Height);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void GetSourceRectangleForLocalTileID_ValidTileIDIsNotInTilesListNoMarginNoSpacing_ReturnsCorrectRectangle()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var tileset = new Tileset
|
||||||
|
{
|
||||||
|
FirstGID = 0,
|
||||||
|
Name = "Tileset1",
|
||||||
|
TileWidth = 16,
|
||||||
|
TileHeight = 16,
|
||||||
|
TileCount = 5,
|
||||||
|
Columns = 5,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var rectangle = tileset.GetSourceRectangleForLocalTileID(3);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal(48, rectangle.X);
|
||||||
|
Assert.Equal(0, rectangle.Y);
|
||||||
|
Assert.Equal(16, rectangle.Width);
|
||||||
|
Assert.Equal(16, rectangle.Height);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void GetSourceRectangleForLocalTileID_ValidTileIDIsNotInTilesListWithMarginAndSpacing_ReturnsCorrectRectangle()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var tileset = new Tileset
|
||||||
|
{
|
||||||
|
FirstGID = 0,
|
||||||
|
Name = "Tileset1",
|
||||||
|
TileWidth = 16,
|
||||||
|
TileHeight = 16,
|
||||||
|
TileCount = 5,
|
||||||
|
Columns = 5,
|
||||||
|
Margin = 3,
|
||||||
|
Spacing = 1
|
||||||
|
};
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var rectangle = tileset.GetSourceRectangleForLocalTileID(3);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal(54, rectangle.X);
|
||||||
|
Assert.Equal(3, rectangle.Y);
|
||||||
|
Assert.Equal(16, rectangle.Width);
|
||||||
|
Assert.Equal(16, rectangle.Height);
|
||||||
|
}
|
||||||
|
}
|
|
@ -15,6 +15,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DotTiled.Example.Console",
|
||||||
EndProject
|
EndProject
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DotTiled.Example.Godot", "DotTiled.Examples\DotTiled.Example.Godot\DotTiled.Example.Godot.csproj", "{7541A9B3-43A5-45A7-939E-6F542319D990}"
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DotTiled.Example.Godot", "DotTiled.Examples\DotTiled.Example.Godot\DotTiled.Example.Godot.csproj", "{7541A9B3-43A5-45A7-939E-6F542319D990}"
|
||||||
EndProject
|
EndProject
|
||||||
|
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "DotTiled.Examples", "DotTiled.Examples", "{F3D6E648-AF8F-4EC9-A810-8C348DBB9924}"
|
||||||
|
EndProject
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DotTiled.Example.Raylib", "DotTiled.Examples\DotTiled.Example.Raylib\DotTiled.Example.Raylib.csproj", "{53585FB8-6E94-46F0-87E2-9692874E1714}"
|
||||||
|
EndProject
|
||||||
Global
|
Global
|
||||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
Debug|Any CPU = Debug|Any CPU
|
Debug|Any CPU = Debug|Any CPU
|
||||||
|
@ -44,9 +48,14 @@ Global
|
||||||
{7541A9B3-43A5-45A7-939E-6F542319D990}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
{7541A9B3-43A5-45A7-939E-6F542319D990}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
{7541A9B3-43A5-45A7-939E-6F542319D990}.Release|Any CPU.ActiveCfg = Debug|Any CPU
|
{7541A9B3-43A5-45A7-939E-6F542319D990}.Release|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
{7541A9B3-43A5-45A7-939E-6F542319D990}.Release|Any CPU.Build.0 = Debug|Any CPU
|
{7541A9B3-43A5-45A7-939E-6F542319D990}.Release|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{53585FB8-6E94-46F0-87E2-9692874E1714}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{53585FB8-6E94-46F0-87E2-9692874E1714}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{53585FB8-6E94-46F0-87E2-9692874E1714}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{53585FB8-6E94-46F0-87E2-9692874E1714}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
GlobalSection(NestedProjects) = preSolution
|
GlobalSection(NestedProjects) = preSolution
|
||||||
{F9892295-6C2C-4ABD-9D6F-2AC81D2C6E67} = {8C54542E-3C2C-486C-9BEF-4C510391AFDA}
|
{F9892295-6C2C-4ABD-9D6F-2AC81D2C6E67} = {8C54542E-3C2C-486C-9BEF-4C510391AFDA}
|
||||||
{7541A9B3-43A5-45A7-939E-6F542319D990} = {8C54542E-3C2C-486C-9BEF-4C510391AFDA}
|
{7541A9B3-43A5-45A7-939E-6F542319D990} = {8C54542E-3C2C-486C-9BEF-4C510391AFDA}
|
||||||
|
{53585FB8-6E94-46F0-87E2-9692874E1714} = {F3D6E648-AF8F-4EC9-A810-8C348DBB9924}
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
EndGlobal
|
EndGlobal
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
using System;
|
||||||
|
|
||||||
namespace DotTiled;
|
namespace DotTiled;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -29,4 +31,26 @@ public class TileLayer : BaseLayer
|
||||||
/// The tile layer data.
|
/// The tile layer data.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public Optional<Data> Data { get; set; } = Optional<Data>.Empty;
|
public Optional<Data> Data { get; set; } = Optional<Data>.Empty;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Helper method to retrieve the Global Tile ID at a given coordinate in the layer.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="x">The X coordinate in the layer</param>
|
||||||
|
/// <param name="y">The Y coordinate in the layer</param>
|
||||||
|
/// <returns>The Global Tile ID at the given coordinate.</returns>
|
||||||
|
/// <exception cref="InvalidOperationException">Thrown when either <see cref="Data"/> or <see cref="Data.GlobalTileIDs"/> are missing values.</exception>
|
||||||
|
/// <exception cref="ArgumentException">Thrown when the given coordinate is not within bounds of the layer.</exception>
|
||||||
|
public uint GetGlobalTileIDAtCoord(int x, int y)
|
||||||
|
{
|
||||||
|
if (!Data.HasValue)
|
||||||
|
throw new InvalidOperationException("Data is not set.");
|
||||||
|
|
||||||
|
if (x < 0 || x >= Width || y < 0 || y >= Height)
|
||||||
|
throw new ArgumentException("Coordinates are out of bounds.");
|
||||||
|
|
||||||
|
if (!Data.Value.GlobalTileIDs.HasValue)
|
||||||
|
throw new InvalidOperationException("GlobalTileIDs is not set.");
|
||||||
|
|
||||||
|
return Data.Value.GlobalTileIDs.Value[(y * Width) + x];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
|
|
||||||
|
@ -205,4 +206,31 @@ public class Map : HasPropertiesBase
|
||||||
/// Hierarchical list of layers. <see cref="Group"/> is a layer type which can contain sub-layers to create a hierarchy.
|
/// Hierarchical list of layers. <see cref="Group"/> is a layer type which can contain sub-layers to create a hierarchy.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public List<BaseLayer> Layers { get; set; } = [];
|
public List<BaseLayer> Layers { get; set; } = [];
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Resolves which tileset a global tile ID belongs to, and returns the corresponding local tile ID.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="globalTileID">The global tile ID to resolve.</param>
|
||||||
|
/// <param name="localTileID">The local tile ID within the tileset.</param>
|
||||||
|
/// <returns>The tileset that contains the tile with the specified global tile ID.</returns>
|
||||||
|
/// <exception cref="ArgumentException">Thrown when no tileset is found for the specified global tile ID.</exception>
|
||||||
|
public Tileset ResolveTilesetForGlobalTileID(uint globalTileID, out uint localTileID)
|
||||||
|
{
|
||||||
|
for (int i = Tilesets.Count - 1; i >= 0; i--)
|
||||||
|
{
|
||||||
|
var tileset = Tilesets[i];
|
||||||
|
|
||||||
|
if (globalTileID >= tileset.FirstGID.Value
|
||||||
|
&& globalTileID < tileset.FirstGID.Value + tileset.TileCount)
|
||||||
|
{
|
||||||
|
localTileID = globalTileID - tileset.FirstGID.Value;
|
||||||
|
return tileset;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new ArgumentException(
|
||||||
|
$"No tileset found for global tile ID {globalTileID}.",
|
||||||
|
nameof(globalTileID)
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,6 @@
|
||||||
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
|
||||||
namespace DotTiled;
|
namespace DotTiled;
|
||||||
|
|
||||||
|
@ -90,6 +92,32 @@ public enum FillMode
|
||||||
PreserveAspectFit
|
PreserveAspectFit
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A helper class to specify where in a tileset image a tile is located.
|
||||||
|
/// </summary>
|
||||||
|
public class SourceRectangle
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The X coordinate of the tile in the tileset image.
|
||||||
|
/// </summary>
|
||||||
|
public int X { get; set; } = 0;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The Y coordinate of the tile in the tileset image.
|
||||||
|
/// </summary>
|
||||||
|
public int Y { get; set; } = 0;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The width of the tile in the tileset image.
|
||||||
|
/// </summary>
|
||||||
|
public int Width { get; set; } = 0;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The height of the tile in the tileset image.
|
||||||
|
/// </summary>
|
||||||
|
public int Height { get; set; } = 0;
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// A tileset is a collection of tiles that can be used in a tile layer, or by tile objects.
|
/// A tileset is a collection of tiles that can be used in a tile layer, or by tile objects.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -209,4 +237,42 @@ public class Tileset : HasPropertiesBase
|
||||||
/// If this tileset is based on a collection of images, then this list of tiles will contain the individual images that make up the tileset.
|
/// If this tileset is based on a collection of images, then this list of tiles will contain the individual images that make up the tileset.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public List<Tile> Tiles { get; set; } = [];
|
public List<Tile> Tiles { get; set; } = [];
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns the source rectangle for a tile in this tileset given its local tile ID.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="localTileID">The local tile ID of the tile.</param>
|
||||||
|
/// <returns>A source rectangle describing the tile's position in the tileset's </returns>
|
||||||
|
/// <exception cref="ArgumentOutOfRangeException">Thrown when the local tile ID is out of range.</exception>
|
||||||
|
public SourceRectangle GetSourceRectangleForLocalTileID(uint localTileID)
|
||||||
|
{
|
||||||
|
if (localTileID >= TileCount)
|
||||||
|
throw new ArgumentException("The local tile ID is out of range.", nameof(localTileID));
|
||||||
|
|
||||||
|
var tileInTiles = Tiles.FirstOrDefault(t => t.ID == localTileID);
|
||||||
|
if (tileInTiles != null)
|
||||||
|
{
|
||||||
|
return new SourceRectangle
|
||||||
|
{
|
||||||
|
X = tileInTiles.X,
|
||||||
|
Y = tileInTiles.Y,
|
||||||
|
Width = tileInTiles.Width,
|
||||||
|
Height = tileInTiles.Height
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
var column = (int)(localTileID % Columns);
|
||||||
|
var row = (int)(localTileID / Columns);
|
||||||
|
|
||||||
|
var x = Margin + ((TileWidth + Spacing) * column);
|
||||||
|
var y = Margin + ((TileHeight + Spacing) * row);
|
||||||
|
|
||||||
|
return new SourceRectangle
|
||||||
|
{
|
||||||
|
X = x,
|
||||||
|
Y = y,
|
||||||
|
Width = TileWidth,
|
||||||
|
Height = TileHeight
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue