mirror of
https://github.com/space-wizards/RobustToolbox.git
synced 2026-02-15 03:30:53 +01:00
Add support for tile variants. (#2577)
This commit is contained in:
BIN
Resources/Textures/noTile.png
Normal file
BIN
Resources/Textures/noTile.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 535 B |
@@ -1,7 +1,5 @@
|
||||
using System;
|
||||
using System.Buffers;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using OpenToolkit.Graphics.OpenGL4;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
@@ -101,13 +99,18 @@ namespace Robust.Client.Graphics.Clyde
|
||||
if (tile.IsEmpty)
|
||||
continue;
|
||||
|
||||
var regionMaybe = _tileDefinitionManager.TileAtlasRegion(tile.TypeId);
|
||||
if (regionMaybe == null)
|
||||
var regionMaybe = _tileDefinitionManager.TileAtlasRegion(tile);
|
||||
|
||||
Box2 region;
|
||||
if (regionMaybe == null || regionMaybe.Length <= tile.Variant)
|
||||
{
|
||||
continue;
|
||||
region = _tileDefinitionManager.ErrorTileRegion;
|
||||
}
|
||||
else
|
||||
{
|
||||
region = regionMaybe[tile.Variant];
|
||||
}
|
||||
|
||||
var region = regionMaybe.Value;
|
||||
var gx = x + cScaled.X;
|
||||
var gy = y + cScaled.Y;
|
||||
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.ResourceManagement;
|
||||
using Robust.Client.Utility;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Log;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Utility;
|
||||
@@ -22,16 +22,18 @@ namespace Robust.Client.Map
|
||||
|
||||
public Texture TileTextureAtlas => _tileTextureAtlas ?? Texture.Transparent;
|
||||
|
||||
private readonly Dictionary<ushort, Box2> _tileRegions = new();
|
||||
private readonly Dictionary<ushort, Box2[]> _tileRegions = new();
|
||||
|
||||
public Box2 ErrorTileRegion { get; private set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public Box2? TileAtlasRegion(Tile tile)
|
||||
public Box2[]? TileAtlasRegion(Tile tile)
|
||||
{
|
||||
return TileAtlasRegion(tile.TypeId);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Box2? TileAtlasRegion(ushort tileType)
|
||||
public Box2[]? TileAtlasRegion(ushort tileType)
|
||||
{
|
||||
if (_tileRegions.TryGetValue(tileType, out var region))
|
||||
{
|
||||
@@ -58,39 +60,79 @@ namespace Robust.Client.Map
|
||||
|
||||
const int tileSize = EyeManager.PixelsPerMeter;
|
||||
|
||||
var dimensionX = (int) Math.Ceiling(Math.Sqrt(defList.Count));
|
||||
var dimensionY = (int) Math.Ceiling((float) defList.Count / dimensionX);
|
||||
var tileCount = defList.Select(x => (int)x.Variants).Sum() + 1;
|
||||
|
||||
var sheet = new Image<Rgba32>(dimensionX * tileSize, dimensionY * tileSize);
|
||||
var dimensionX = (int) Math.Ceiling(Math.Sqrt(tileCount));
|
||||
var dimensionY = (int) Math.Ceiling((float) tileCount / dimensionX);
|
||||
|
||||
for (var i = 0; i < defList.Count; i++)
|
||||
var imgWidth = dimensionX * tileSize;
|
||||
var imgHeight = dimensionY * tileSize;
|
||||
var sheet = new Image<Rgba32>(imgWidth, imgHeight);
|
||||
|
||||
// Add in the missing tile texture sprite as tile texture 0.
|
||||
{
|
||||
var def = defList[i];
|
||||
var column = i % dimensionX;
|
||||
var row = i / dimensionX;
|
||||
var w = (float) sheet.Width;
|
||||
var h = (float) sheet.Height;
|
||||
ErrorTileRegion = Box2.FromDimensions(
|
||||
0, (h - EyeManager.PixelsPerMeter) / h,
|
||||
tileSize / w, tileSize / h);
|
||||
Image<Rgba32> image;
|
||||
using (var stream = _resourceCache.ContentFileRead("/Textures/noTile.png"))
|
||||
{
|
||||
image = Image.Load<Rgba32>(stream);
|
||||
}
|
||||
|
||||
image.Blit(new UIBox2i(0, 0, tileSize, tileSize), sheet, Vector2i.Zero);
|
||||
}
|
||||
|
||||
if (imgWidth >= 2048 || imgHeight >= 2048)
|
||||
{
|
||||
// Sanity warning, some machines don't have textures larger than this and need multiple atlases.
|
||||
Logger.WarningS("clyde",
|
||||
$"Tile texture atlas is ({imgWidth} x {imgHeight}), larger than 2048 x 2048. If you really need {tileCount} tiles, file an issue on RobustToolbox.");
|
||||
}
|
||||
|
||||
var column = 1;
|
||||
var row = 0;
|
||||
foreach (var def in defList)
|
||||
{
|
||||
Image<Rgba32> image;
|
||||
using (var stream = _resourceCache.ContentFileRead(new ResourcePath(def.Path) / $"{def.SpriteName}.png"))
|
||||
{
|
||||
image = Image.Load<Rgba32>(stream);
|
||||
}
|
||||
|
||||
if (image.Width != tileSize || image.Height != tileSize)
|
||||
if (image.Width != (tileSize * def.Variants) || image.Height != tileSize)
|
||||
{
|
||||
throw new NotSupportedException("Unable to use tiles with a dimension other than 32x32.");
|
||||
throw new NotSupportedException(
|
||||
$"Unable to load {new ResourcePath(def.Path) / $"{def.SpriteName}.png"}, due to being unable to use tile texture with a dimension other than {tileSize}x({tileSize} * Variants).");
|
||||
}
|
||||
|
||||
var point = new Vector2i(column * tileSize, row * tileSize);
|
||||
var regionList = new Box2[def.Variants];
|
||||
|
||||
image.Blit(new UIBox2i(0, 0, image.Width, image.Height), sheet, point);
|
||||
for (var j = 0; j < def.Variants; j++)
|
||||
{
|
||||
var point = new Vector2i(column * tileSize, row * tileSize);
|
||||
|
||||
var w = (float) sheet.Width;
|
||||
var h = (float) sheet.Height;
|
||||
var box = new UIBox2i(0, 0, tileSize, tileSize).Translated(new Vector2i(j * tileSize, 0));
|
||||
image.Blit(box, sheet, point);
|
||||
|
||||
_tileRegions.Add(def.TileId,
|
||||
Box2.FromDimensions(
|
||||
var w = (float) sheet.Width;
|
||||
var h = (float) sheet.Height;
|
||||
|
||||
regionList[j] = Box2.FromDimensions(
|
||||
point.X / w, (h - point.Y - EyeManager.PixelsPerMeter) / h,
|
||||
tileSize / w, tileSize / h));
|
||||
tileSize / w, tileSize / h);
|
||||
column++;
|
||||
|
||||
if (column >= dimensionX)
|
||||
{
|
||||
column = 0;
|
||||
row++;
|
||||
}
|
||||
}
|
||||
|
||||
_tileRegions.Add(def.TileId, regionList);
|
||||
}
|
||||
|
||||
_tileTextureAtlas = Texture.LoadFromImage(sheet, "Tile Atlas");
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using System.Collections.Generic;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Maths;
|
||||
@@ -9,6 +10,8 @@ namespace Robust.Client.Map
|
||||
/// </summary>
|
||||
internal interface IClydeTileDefinitionManager : ITileDefinitionManager
|
||||
{
|
||||
Box2 ErrorTileRegion { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The texture atlas containing all the tiles.
|
||||
/// </summary>
|
||||
@@ -18,12 +21,12 @@ namespace Robust.Client.Map
|
||||
/// Gets the region inside the texture atlas to use to draw a tile.
|
||||
/// </summary>
|
||||
/// <returns>If null, do not draw the tile at all.</returns>
|
||||
Box2? TileAtlasRegion(Tile tile);
|
||||
Box2[]? TileAtlasRegion(Tile tile);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the region inside the texture atlas to use to draw a tile type.
|
||||
/// </summary>
|
||||
/// <returns>If null, do not draw the tile at all.</returns>
|
||||
Box2? TileAtlasRegion(ushort tileType);
|
||||
Box2[]? TileAtlasRegion(ushort tileType);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -68,7 +68,8 @@ namespace Robust.Server.Maps
|
||||
{
|
||||
var tile = chunk.GetTile(x, y);
|
||||
writer.Write(tile.TypeId);
|
||||
writer.Write(tile.Data);
|
||||
writer.Write((byte)tile.Flags);
|
||||
writer.Write(tile.Variant);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -129,12 +130,13 @@ namespace Robust.Server.Maps
|
||||
for (ushort x = 0; x < grid.ChunkSize; x++)
|
||||
{
|
||||
var id = reader.ReadUInt16();
|
||||
var data = reader.ReadUInt16();
|
||||
var flags = (TileRenderFlag)reader.ReadByte();
|
||||
var variant = reader.ReadByte();
|
||||
|
||||
var defName = tileDefMapping[id];
|
||||
id = tileDefinitionManager[defName].TileId;
|
||||
|
||||
var tile = new Tile(id, data);
|
||||
var tile = new Tile(id, flags, variant);
|
||||
chunk.SetTile(x, y, tile);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,6 +27,8 @@
|
||||
|
||||
/// <summary>
|
||||
/// Path to the folder where the tile sprite is contained.
|
||||
/// The texture dimensions should be PixelsPerMeter x (PixelsPerMeter * Variants).
|
||||
/// This is likely 32 x (32 * variants) if you're working on Space Station 14.
|
||||
/// </summary>
|
||||
string Path { get; }
|
||||
|
||||
@@ -35,6 +37,11 @@
|
||||
/// </summary>
|
||||
float Friction { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Number of variants this tile has. ALSO DETERMINES THE EXPECTED INPUT TEXTURE SIZE.
|
||||
/// </summary>
|
||||
byte Variants { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Assign a new value to <see cref="TileId"/>, used when registering the tile definition.
|
||||
/// </summary>
|
||||
|
||||
@@ -101,6 +101,7 @@ namespace Robust.Shared.Map
|
||||
/// <exception cref="ArgumentOutOfRangeException">The index is less than or greater than the size of the chunk.</exception>
|
||||
public Tile GetTile(ushort xIndex, ushort yIndex)
|
||||
{
|
||||
|
||||
if (xIndex >= ChunkSize)
|
||||
throw new ArgumentOutOfRangeException(nameof(xIndex), "Tile indices out of bounds.");
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@ using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.Log;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Physics;
|
||||
using Robust.Shared.Timing;
|
||||
@@ -108,7 +109,6 @@ internal sealed class NetworkedMapManager : MapManager, INetworkedMapManager
|
||||
tileBuffer[x * grid.ChunkSize + y] = chunk.GetTile((ushort)x, (ushort)y);
|
||||
}
|
||||
}
|
||||
|
||||
chunkData.Add(new GameStateMapData.ChunkDatum(index, tileBuffer));
|
||||
}
|
||||
|
||||
|
||||
@@ -1,114 +1,126 @@
|
||||
using System;
|
||||
using JetBrains.Annotations;
|
||||
|
||||
namespace Robust.Shared.Map
|
||||
namespace Robust.Shared.Map;
|
||||
|
||||
/// <summary>
|
||||
/// This structure contains the data for an individual Tile in a <c>MapGrid</c>.
|
||||
/// </summary>
|
||||
[PublicAPI, Serializable]
|
||||
public readonly struct Tile : IEquatable<Tile>
|
||||
{
|
||||
/// <summary>
|
||||
/// This structure contains the data for an individual Tile in a <c>MapGrid</c>.
|
||||
/// Internal type ID of this tile.
|
||||
/// </summary>
|
||||
[PublicAPI, Serializable]
|
||||
public readonly struct Tile : IEquatable<Tile>
|
||||
public readonly ushort TypeId;
|
||||
|
||||
/// <summary>
|
||||
/// Rendering flags.
|
||||
/// </summary>
|
||||
public readonly TileRenderFlag Flags;
|
||||
|
||||
/// <summary>
|
||||
/// Variant of this tile to render.
|
||||
/// </summary>
|
||||
public readonly byte Variant;
|
||||
|
||||
/// <summary>
|
||||
/// An empty tile that can be compared against.
|
||||
/// </summary>
|
||||
public static readonly Tile Empty = new(0);
|
||||
|
||||
/// <summary>
|
||||
/// Is this tile space (empty)?
|
||||
/// </summary>
|
||||
public bool IsEmpty => TypeId == 0;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance of a grid tile.
|
||||
/// </summary>
|
||||
/// <param name="typeId">Internal type ID.</param>
|
||||
/// <param name="flags">Flags used by toolbox's rendering.</param>
|
||||
/// <param name="variant">The visual variant this tile is using.</param>
|
||||
public Tile(ushort typeId, TileRenderFlag flags = 0, byte variant = 0)
|
||||
{
|
||||
/// <summary>
|
||||
/// Internal type ID of this tile.
|
||||
/// </summary>
|
||||
public readonly ushort TypeId;
|
||||
TypeId = typeId;
|
||||
Flags = flags;
|
||||
Variant = variant;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Optional per-tile data of this tile.
|
||||
/// </summary>
|
||||
public readonly ushort Data;
|
||||
/// <summary>
|
||||
/// Explicit conversion of <c>Tile</c> to <c>uint</c> . This should only
|
||||
/// be used in special cases like serialization. Do NOT use this in
|
||||
/// content.
|
||||
/// </summary>
|
||||
public static explicit operator uint(Tile tile)
|
||||
{
|
||||
return ((uint)tile.TypeId << 16) | (uint)tile.Flags << 8 | tile.Variant;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// An empty tile that can be compared against.
|
||||
/// </summary>
|
||||
public static readonly Tile Empty = new(0);
|
||||
/// <summary>
|
||||
/// Explicit conversion of <c>uint</c> to <c>Tile</c> . This should only
|
||||
/// be used in special cases like serialization. Do NOT use this in
|
||||
/// content.
|
||||
/// </summary>
|
||||
public static explicit operator Tile(uint tile)
|
||||
{
|
||||
return new(
|
||||
(ushort)(tile >> 16),
|
||||
(TileRenderFlag)(tile >> 8),
|
||||
(byte)tile
|
||||
);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Is this tile space (empty)?
|
||||
/// </summary>
|
||||
public bool IsEmpty => TypeId == 0;
|
||||
/// <summary>
|
||||
/// Check for equality by value between two objects.
|
||||
/// </summary>
|
||||
public static bool operator ==(Tile a, Tile b)
|
||||
{
|
||||
return a.Equals(b);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance of a grid tile.
|
||||
/// </summary>
|
||||
/// <param name="typeId">Internal type ID.</param>
|
||||
/// <param name="data">Optional per-tile data.</param>
|
||||
public Tile(ushort typeId, ushort data = 0)
|
||||
/// <summary>
|
||||
/// Check for inequality by value between two objects.
|
||||
/// </summary>
|
||||
public static bool operator !=(Tile a, Tile b)
|
||||
{
|
||||
return !a.Equals(b);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generates String representation of this Tile.
|
||||
/// </summary>
|
||||
/// <returns>String representation of this Tile.</returns>
|
||||
public override string ToString()
|
||||
{
|
||||
return $"Tile {TypeId}, {Flags}, {Variant}";
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool Equals(Tile other)
|
||||
{
|
||||
return TypeId == other.TypeId && Flags == other.Flags && Variant == other.Variant;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override bool Equals(object? obj)
|
||||
{
|
||||
if (ReferenceEquals(null, obj))
|
||||
return false;
|
||||
return obj is Tile other && Equals(other);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override int GetHashCode()
|
||||
{
|
||||
unchecked
|
||||
{
|
||||
TypeId = typeId;
|
||||
Data = data;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Explicit conversion of <c>Tile</c> to <c>uint</c> . This should only
|
||||
/// be used in special cases like serialization. Do NOT use this in
|
||||
/// content.
|
||||
/// </summary>
|
||||
public static explicit operator uint(Tile tile)
|
||||
{
|
||||
return ((uint)tile.TypeId << 16) | tile.Data;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Explicit conversion of <c>uint</c> to <c>Tile</c> . This should only
|
||||
/// be used in special cases like serialization. Do NOT use this in
|
||||
/// content.
|
||||
/// </summary>
|
||||
public static explicit operator Tile(uint tile)
|
||||
{
|
||||
return new(
|
||||
(ushort)(tile >> 16),
|
||||
(ushort)tile
|
||||
);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check for equality by value between two objects.
|
||||
/// </summary>
|
||||
public static bool operator ==(Tile a, Tile b)
|
||||
{
|
||||
return a.Equals(b);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check for inequality by value between two objects.
|
||||
/// </summary>
|
||||
public static bool operator !=(Tile a, Tile b)
|
||||
{
|
||||
return !a.Equals(b);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generates String representation of this Tile.
|
||||
/// </summary>
|
||||
/// <returns>String representation of this Tile.</returns>
|
||||
public override string ToString()
|
||||
{
|
||||
return $"Tile {TypeId}, {Data}";
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool Equals(Tile other)
|
||||
{
|
||||
return TypeId == other.TypeId && Data == other.Data;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override bool Equals(object? obj)
|
||||
{
|
||||
if (ReferenceEquals(null, obj))
|
||||
return false;
|
||||
return obj is Tile other && Equals(other);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override int GetHashCode()
|
||||
{
|
||||
unchecked
|
||||
{
|
||||
return (TypeId.GetHashCode() * 397) ^ Data.GetHashCode();
|
||||
}
|
||||
return (TypeId.GetHashCode() * 397) ^ Flags.GetHashCode() ^ Variant.GetHashCode();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public enum TileRenderFlag : byte
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
@@ -32,13 +32,13 @@ namespace Robust.UnitTesting.Shared.Map
|
||||
var mapId = mapMan.CreateMap();
|
||||
var grid = (IMapGridInternal)mapMan.CreateGrid(mapId, null, 8);
|
||||
|
||||
grid.SetTile(new Vector2i(-9, -1), new Tile(1, 2));
|
||||
grid.SetTile(new Vector2i(-9, -1), new Tile(1, (TileRenderFlag)1, 1));
|
||||
|
||||
var result = grid.GetTileRef(new Vector2i(-9, -1));
|
||||
|
||||
Assert.That(grid.ChunkCount, Is.EqualTo(1));
|
||||
Assert.That(grid.GetMapChunks().Keys.ToList()[0], Is.EqualTo(new Vector2i(-2, -1)));
|
||||
Assert.That(result, Is.EqualTo(new TileRef(mapId, grid.Index, new Vector2i(-9,-1), new Tile(1, 2))));
|
||||
Assert.That(result, Is.EqualTo(new TileRef(mapId, grid.Index, new Vector2i(-9,-1), new Tile(1, (TileRenderFlag)1, 1))));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -144,14 +144,14 @@ namespace Robust.UnitTesting.Shared.Map
|
||||
var mapId = mapMan.CreateMap();
|
||||
var grid = (IMapGridInternal)mapMan.CreateGrid(mapId, null, 8);
|
||||
|
||||
grid.SetTile(new Vector2i(-9, -1), new Tile(1, 2));
|
||||
grid.SetTile(new Vector2i(-9, -1), new Tile(1, (TileRenderFlag)1, 1));
|
||||
|
||||
var foundTile = grid.TryGetTileRef(new Vector2i(-9, -1), out var tileRef);
|
||||
|
||||
Assert.That(foundTile, Is.True);
|
||||
Assert.That(grid.ChunkCount, Is.EqualTo(1));
|
||||
Assert.That(grid.GetMapChunks().Keys.ToList()[0], Is.EqualTo(new Vector2i(-2, -1)));
|
||||
Assert.That(tileRef, Is.EqualTo(new TileRef(mapId, grid.Index, new Vector2i(-9, -1), new Tile(1, 2))));
|
||||
Assert.That(tileRef, Is.EqualTo(new TileRef(mapId, grid.Index, new Vector2i(-9, -1), new Tile(1, (TileRenderFlag)1, 1))));
|
||||
}
|
||||
|
||||
[Test]
|
||||
|
||||
Reference in New Issue
Block a user