/// <summary> /// Resolves the <see cref="Index"/> values of the specified <see cref="Tile"/> grid area, given the grid's raw data block. /// </summary> /// <param name="tileGridData"></param> /// <param name="beginX"></param> /// <param name="beginY"></param> /// <param name="width"></param> /// <param name="height"></param> /// <param name="stride"></param> /// <param name="tilesetRes"></param> public static void ResolveIndices(Tile[] tileGridData, int stride, int beginX, int beginY, int width, int height, ContentRef <Tileset> tileset) { if (tileset.Res == null) { throw new ArgumentNullException("tileset"); } if (!tileset.Res.Compiled) { throw new InvalidOperationException("The specified Tileset needs to be compiled first."); } Tileset tilesetRes = tileset.Res; TileInfo[] tileData = tilesetRes.TileData.Data; int tileCount = tilesetRes.TileCount; for (int y = beginY; y < beginY + height; y++) { for (int x = beginX; x < beginX + width; x++) { int i = y * stride + x; int baseIndex = MathF.Clamp(tileGridData[i].BaseIndex, 0, tileCount - 1); int autoTileIndex = tileData[baseIndex].AutoTileLayer - 1; TilesetAutoTileInfo autoTile = autoTileIndex > -1 ? tilesetRes.AutoTileData[autoTileIndex] : null; tileGridData[i].ResolveIndex(autoTile); } } }
/// <summary> /// Initializes a new tile with the specified <see cref="BaseIndex"/> and derives all other values /// from their defaults as specified in the provided <see cref="Tileset"/>. /// /// This guarantees that, even for AutoTiles, the specified <see cref="BaseIndex"/> will be used /// directly as-is, without the resolve step adjusting it. When possible, prefer other methods of /// initializing tiles, as this one is more expensive than the others. /// </summary> /// <param name="baseIndex"></param> /// <param name="defaultLookup"></param> public Tile(int baseIndex, ContentRef <Tileset> defaultLookup) { Tileset tileset = defaultLookup.Res; if (tileset == null) { throw new ArgumentNullException("defaultLookup"); } // As long as not resolved otherwise, use the base index directly. this.Index = baseIndex; this.BaseIndex = baseIndex; this.DepthOffset = 0; // By default, pre-initialize the tile with the AutoTile connectivity state that is // specified for it in the Tileset. This way, when painting the tile unaltered, // resolving it using base index and connectivity state won't replace it with a // different tile. TileInfo tileInfo = tileset.TileData[baseIndex]; int autoTileLayer = tileInfo.AutoTileLayer; if (autoTileLayer != 0) { TilesetAutoTileInfo autoTile = tileset.AutoTileData[autoTileLayer - 1]; IReadOnlyList <TilesetAutoTileItem> autoTileInfo = autoTile.TileInfo; this.AutoTileCon = autoTileInfo[baseIndex].Neighbours; this.BaseIndex = autoTile.BaseTileIndex; } else { this.AutoTileCon = TileConnection.None; } }
/// <summary> /// Resolves the <see cref="Index"/> of the <see cref="Tile"/> based on /// its <see cref="BaseIndex"/> and <see cref="AutoTileCon"/>. /// </summary> /// <param name="tileset"></param> public void ResolveIndex(ContentRef <Tileset> tileset) { if (tileset.Res == null) { throw new ArgumentNullException("tileset"); } if (!tileset.Res.Compiled) { throw new InvalidOperationException("The specified Tileset needs to be compiled first."); } Tileset tilesetRes = tileset.Res; int autoTileIndex = tilesetRes.TileData[this.BaseIndex].AutoTileLayer - 1; TilesetAutoTileInfo autoTile = autoTileIndex >= 0 ? tilesetRes.AutoTileData[autoTileIndex] : null; this.ResolveIndex(autoTile); }
/// <summary> /// Resolves the <see cref="Index"/> of the <see cref="Tile"/> based on /// its <see cref="BaseIndex"/> and <see cref="AutoTileCon"/>. /// </summary> /// <param name="autoTile"></param> private void ResolveIndex(TilesetAutoTileInfo autoTile) { // Non-AutoTiles always use their base index directly. if (autoTile == null) { this.Index = this.BaseIndex; } // AutoTiles require a dynamic lookup with their connectivity state, because // they might use generated tiles that do not have a consistent index across // different Tileset configs. else { // If there is no connectivity info and a non-matching base index, this tile // was likely painted before it was configured to be an AutoTile. In that case, // derive the appropriate connectivity from the specs and adjust the base index // to match. if (this.BaseIndex != autoTile.BaseTileIndex && this.AutoTileCon == TileConnection.None) { this.AutoTileCon = autoTile.TileInfo[this.BaseIndex].Neighbours; this.BaseIndex = autoTile.BaseTileIndex; } int targetIndex = autoTile.StateToTile[(int)this.AutoTileCon]; // If the AutoTile connectivity state already matches the one we'd get with the default // resolved tile index, use the current one directly and don't change it. // This will allow scenarios where users specify multiple tiles for a certain connectivity // state, without forcing them back to a single one during resolve. if (autoTile.TileInfo[this.BaseIndex].Neighbours == autoTile.TileInfo[targetIndex].Neighbours) { this.Index = this.BaseIndex; } // Otherwise, lookup the expected tile using base index and connectivity. This // will retrieve the proper generated tile, which has an index that may change // between multiple compilations. else { this.Index = targetIndex; } } }
/// <summary> /// Updates the <see cref="AutoTileCon"/> state of an arbitrary region on the specified tile grid /// based on its connectivity state with neighbouring tiles. /// </summary> /// <param name="tileGrid"></param> /// <param name="updateMask"></param> /// <param name="beginX"></param> /// <param name="beginY"></param> /// <param name="width"></param> /// <param name="height"></param> /// <param name="tilesetRes"></param> public static void UpdateAutoTileCon(Grid <Tile> tileGrid, Grid <bool> updateMask, int beginX, int beginY, int width, int height, ContentRef <Tileset> tileset) { if (tileset.Res == null) { throw new ArgumentNullException("tileset"); } if (tileGrid == null) { throw new ArgumentNullException("tileGrid"); } Tileset tilesetRes = tileset.Res; TileInfo[] tileData = tilesetRes.TileData.Data; Tile[] tiles = tileGrid.RawData; bool[] maskData = updateMask != null ? updateMask.RawData : null; int tileStride = tileGrid.Width; int maskStride = updateMask.Width; int maxTileX = tileGrid.Width - 1; int maxTileY = tileGrid.Height - 1; for (int y = 0; y < height; y++) { for (int x = 0; x < width; x++) { // Skip tiles that have been masked away int m = x + maskStride * y; if (maskData != null && !maskData[m]) { continue; } // Determine tilemap coordinates and index int tileX = x + beginX; int tileY = y + beginY; int i = tileX + tileStride * tileY; // Skip non-AutoTiles int autoTileIndex = tileData[tiles[i].BaseIndex].AutoTileLayer - 1; if (autoTileIndex == -1) { continue; } // Lookup AutoTile data TilesetAutoTileInfo autoTile = tilesetRes.AutoTileData[autoTileIndex]; IReadOnlyList <TilesetAutoTileItem> autoTileInfo = autoTile.TileInfo; // Check neighbour connectivity bool topLeft = (tileX <= 0 || tileY <= 0) || autoTileInfo[tiles[i - 1 - tileStride].Index].ConnectsToAutoTile; bool top = (tileY <= 0) || autoTileInfo[tiles[i - tileStride].Index].ConnectsToAutoTile; bool topRight = (tileX >= maxTileX || tileY <= 0) || autoTileInfo[tiles[i + 1 - tileStride].Index].ConnectsToAutoTile; bool left = (tileX <= 0) || autoTileInfo[tiles[i - 1].Index].ConnectsToAutoTile; bool right = (tileX >= maxTileX) || autoTileInfo[tiles[i + 1].Index].ConnectsToAutoTile; bool bottomLeft = (tileX <= 0 || tileY >= maxTileY) || autoTileInfo[tiles[i - 1 + tileStride].Index].ConnectsToAutoTile; bool bottom = (tileY >= maxTileY) || autoTileInfo[tiles[i + tileStride].Index].ConnectsToAutoTile; bool bottomRight = (tileX >= maxTileX || tileY >= maxTileY) || autoTileInfo[tiles[i + 1 + tileStride].Index].ConnectsToAutoTile; // Create connectivity bitmask TileConnection autoTileCon = TileConnection.None; if (topLeft) { autoTileCon |= TileConnection.TopLeft; } if (top) { autoTileCon |= TileConnection.Top; } if (topRight) { autoTileCon |= TileConnection.TopRight; } if (left) { autoTileCon |= TileConnection.Left; } if (right) { autoTileCon |= TileConnection.Right; } if (bottomLeft) { autoTileCon |= TileConnection.BottomLeft; } if (bottom) { autoTileCon |= TileConnection.Bottom; } if (bottomRight) { autoTileCon |= TileConnection.BottomRight; } // Update connectivity and re-resolve index tiles[i].AutoTileCon = autoTileCon; tiles[i].ResolveIndex(autoTile); } } }
/// <summary> /// Resolves the <see cref="Index"/> of the <see cref="Tile"/> based on /// its <see cref="BaseIndex"/> and <see cref="AutoTileCon"/>. /// </summary> /// <param name="autoTile"></param> private void ResolveIndex(TilesetAutoTileInfo autoTile) { // Non-AutoTiles always use their base index directly. if (autoTile == null) { this.Index = this.BaseIndex; } // AutoTiles require a dynamic lookup with their connectivity state, because // they might use generated tiles that do not have a consistent index across // different Tileset configs. else { // If there is no connectivity info and a non-matching base index, this tile // was likely painted before it was configured to be an AutoTile. In that case, // derive the appropriate connectivity from the specs and adjust the base index // to match. if (this.BaseIndex != autoTile.BaseTileIndex && this.AutoTileCon == TileConnection.None) { this.AutoTileCon = autoTile.TileInfo[this.BaseIndex].Neighbours; this.BaseIndex = autoTile.BaseTileIndex; } int targetIndex = autoTile.StateToTile[(int)this.AutoTileCon]; // If the AutoTile connectivity state already matches the one we'd get with the default // resolved tile index, use the current one directly and don't change it. // This will allow scenarios where users specify multiple tiles for a certain connectivity // state, without forcing them back to a single one during resolve. if (autoTile.TileInfo[this.BaseIndex].Neighbours == autoTile.TileInfo[targetIndex].Neighbours) { this.Index = this.BaseIndex; } // Otherwise, lookup the expected tile using base index and connectivity. This // will retrieve the proper generated tile, which has an index that may change // between multiple compilations. else { this.Index = targetIndex; } } }
/// <summary> /// Compiles a <see cref="Tileset"/> using its specified source data, in order to /// generate optimized target data for rendering and collision detection. /// </summary> public TilesetCompilerOutput Compile(TilesetCompilerInput input) { TilesetCompilerOutput output = input.ExistingOutput; output.TileData = output.TileData ?? new RawList <TileInfo>(input.TileInput.Count); output.RenderData = output.RenderData ?? new List <Texture>(); output.AutoTileData = output.AutoTileData ?? new List <TilesetAutoTileInfo>(); // Clear existing data, but keep the sufficiently big data structures output.TileData.Clear(); output.RenderData.Clear(); output.AutoTileData.Clear(); // Determine how many source tiles we have int sourceTileCount = int.MaxValue; for (int renderInputIndex = 0; renderInputIndex < input.RenderConfig.Count; renderInputIndex++) { TilesetRenderInput renderInput = input.RenderConfig[renderInputIndex] ?? DefaultRenderInput; PixelData sourceLayerData = (renderInput.SourceData.Res ?? Pixmap.Checkerboard.Res).MainLayer; LayerGeometry layerGeometry = this.CalculateLayerGeometry(renderInput, sourceLayerData); sourceTileCount = Math.Min(sourceTileCount, layerGeometry.SourceTileCount); } if (input.RenderConfig.Count == 0) { sourceTileCount = 0; } // Transform AutoTile data for (int autoTileIndex = 0; autoTileIndex < input.AutoTileConfig.Count; autoTileIndex++) { TilesetAutoTileInput autoTileInput = input.AutoTileConfig[autoTileIndex]; TilesetAutoTileInfo autoTileInfo = this.TransformAutoTileData( autoTileIndex, autoTileInput, output.TileData, sourceTileCount); output.AutoTileData.Add(autoTileInfo); } // Initialize all tiles to being visually empty. They will be subtractively updated // during output pixel data generation in the next step. { int tileDataCount = output.TileData.Count; TileInfo[] tileData = output.TileData.Data; for (int i = 0; i < tileDataCount; i++) { tileData[i].IsVisuallyEmpty = true; } } // Generate output pixel data for (int renderInputIndex = 0; renderInputIndex < input.RenderConfig.Count; renderInputIndex++) { TilesetRenderInput renderInput = input.RenderConfig[renderInputIndex] ?? DefaultRenderInput; PixelData sourceLayerData = (renderInput.SourceData.Res ?? Pixmap.Checkerboard.Res).MainLayer; // Determine overal geometry values for this layer, such as tile bounds and texture sizes LayerGeometry layerGeometry = this.CalculateLayerGeometry(renderInput, sourceLayerData); // Generate pixel data and atlas values for this layer's texture LayerPixelData targetLayerData = this.GenerateLayerPixelData( renderInput, sourceLayerData, layerGeometry, output.TileData); // Create the texture to be used for this rendering input using (Pixmap targetPixmap = new Pixmap(targetLayerData.PixelData)) { targetPixmap.Atlas = targetLayerData.Atlas; Texture targetTexture = new Texture( targetPixmap, TextureSizeMode.Enlarge, renderInput.TargetMagFilter, renderInput.TargetMinFilter, TextureWrapMode.Clamp, TextureWrapMode.Clamp, renderInput.TargetFormat); output.RenderData.Add(targetTexture); } } // Generate additional per-tile data this.TransformTileData(input.TileInput, output.TileData, output.RenderData); // Apply global tileset stats output.TileCount = sourceTileCount; return(output); }