using System.Collections.Generic; using UnityEngine; using UnityEngine.Tilemaps; namespace InfiniteWorld { public static class ProceduralWorldArt { private const int TextureSize = 32; private static readonly Dictionary SpriteCache = new Dictionary(); private static readonly Dictionary TileCache = new Dictionary(); public static Sprite CreatePlayerSprite() { const string key = "player_sprite"; if (SpriteCache.TryGetValue(key, out Sprite cached)) { return cached; } Color transparent = new Color(0f, 0f, 0f, 0f); Texture2D texture = CreateTexture(key, transparent); Color cloak = new Color(0.13f, 0.33f, 0.79f, 1f); Color trim = new Color(0.85f, 0.92f, 1f, 1f); Color face = new Color(0.99f, 0.89f, 0.72f, 1f); FillRect(texture, 10, 4, 12, 16, cloak); FillRect(texture, 9, 18, 14, 7, cloak); FillRect(texture, 12, 24, 8, 5, face); FillRect(texture, 11, 23, 10, 1, trim); FillRect(texture, 8, 12, 3, 10, trim); FillRect(texture, 21, 12, 3, 10, trim); FillRect(texture, 12, 0, 3, 4, cloak); FillRect(texture, 17, 0, 3, 4, cloak); texture.Apply(); Sprite sprite = CreateSprite(texture); SpriteCache[key] = sprite; return sprite; } public static TileBase CreateSolidTile(string key, Color color, float noiseStrength = 0.04f) { if (TileCache.TryGetValue(key, out Tile cached)) { return cached; } Texture2D texture = CreateTexture(key, color); ApplyNoise(texture, noiseStrength, 31); texture.Apply(); Tile tile = CreateTile(texture, key); TileCache[key] = tile; return tile; } public static TileBase CreateFeatureTile(string key, Color fill, Color border, bool top, bool right, bool bottom, bool left, InnerCornerMask innerCornerMask = InnerCornerMask.None, bool solidCollider = false) { if (TileCache.TryGetValue(key, out Tile cached)) { return cached; } Texture2D texture = CreateTexture(key, fill); ApplyNoise(texture, 0.05f, 71); DrawBorder(texture, top, right, bottom, left, border, 4); CarveInnerCorner(texture, innerCornerMask); texture.Apply(); Tile tile = CreateTile(texture, key); if (solidCollider) { tile.colliderType = Tile.ColliderType.Grid; } TileCache[key] = tile; return tile; } public static TileBase CreateDecorationTile(string key, Color baseColor, Color accentColor, DecorationPattern pattern) { if (TileCache.TryGetValue(key, out Tile cached)) { return cached; } Color transparent = new Color(0f, 0f, 0f, 0f); Texture2D texture = CreateTexture(key, transparent); switch (pattern) { case DecorationPattern.Bush: DrawDisc(texture, 16, 15, 8, baseColor); DrawDisc(texture, 10, 13, 6, baseColor); DrawDisc(texture, 22, 13, 6, baseColor); DrawDisc(texture, 14, 17, 3, accentColor); DrawDisc(texture, 21, 17, 3, accentColor); break; case DecorationPattern.Flower: DrawDisc(texture, 16, 12, 2, baseColor); DrawDisc(texture, 12, 16, 2, baseColor); DrawDisc(texture, 20, 16, 2, baseColor); DrawDisc(texture, 16, 20, 2, baseColor); DrawDisc(texture, 16, 16, 2, accentColor); DrawLine(texture, 16, 0, 16, 12, new Color(0.2f, 0.5f, 0.2f, 1f)); break; default: DrawDisc(texture, 12, 12, 4, baseColor); DrawDisc(texture, 20, 18, 3, accentColor); DrawDisc(texture, 9, 20, 2, accentColor); break; } texture.Apply(); Tile tile = CreateTile(texture, key); TileCache[key] = tile; return tile; } private static Texture2D CreateTexture(string key, Color fillColor) { Texture2D texture = new Texture2D(TextureSize, TextureSize, TextureFormat.RGBA32, false) { filterMode = FilterMode.Point, wrapMode = TextureWrapMode.Clamp, name = key }; Color[] pixels = new Color[TextureSize * TextureSize]; for (int i = 0; i < pixels.Length; i++) { pixels[i] = fillColor; } texture.SetPixels(pixels); return texture; } private static Tile CreateTile(Texture2D texture, string key) { Sprite sprite = CreateSprite(texture); Tile tile = ScriptableObject.CreateInstance(); tile.sprite = sprite; tile.name = key; tile.hideFlags = HideFlags.HideAndDontSave; return tile; } private static Sprite CreateSprite(Texture2D texture) { Sprite sprite = Sprite.Create(texture, new Rect(0f, 0f, texture.width, texture.height), new Vector2(0.5f, 0.5f), TextureSize); sprite.name = texture.name; return sprite; } private static void ApplyNoise(Texture2D texture, float strength, int offset) { if (strength <= 0f) { return; } for (int y = 0; y < texture.height; y++) { for (int x = 0; x < texture.width; x++) { Color pixel = texture.GetPixel(x, y); if (pixel.a <= 0f) { continue; } float noise = Mathf.PerlinNoise((x + offset) * 0.21f, (y + offset) * 0.21f) - 0.5f; texture.SetPixel(x, y, pixel * (1f + noise * strength * 2f)); } } } private static void DrawBorder(Texture2D texture, bool top, bool right, bool bottom, bool left, Color color, int thickness) { if (top) { FillRect(texture, 0, TextureSize - thickness, TextureSize, thickness, color); } if (bottom) { FillRect(texture, 0, 0, TextureSize, thickness, color); } if (left) { FillRect(texture, 0, 0, thickness, TextureSize, color); } if (right) { FillRect(texture, TextureSize - thickness, 0, thickness, TextureSize, color); } } private static void CarveInnerCorner(Texture2D texture, InnerCornerMask innerCornerMask) { if (innerCornerMask == InnerCornerMask.None) { return; } Color transparent = new Color(0f, 0f, 0f, 0f); const int radius = 8; if ((innerCornerMask & InnerCornerMask.TopLeft) != 0) { CutCorner(texture, 0, TextureSize - 1, radius, transparent, 1, -1); } if ((innerCornerMask & InnerCornerMask.TopRight) != 0) { CutCorner(texture, TextureSize - 1, TextureSize - 1, radius, transparent, -1, -1); } if ((innerCornerMask & InnerCornerMask.BottomRight) != 0) { CutCorner(texture, TextureSize - 1, 0, radius, transparent, -1, 1); } if ((innerCornerMask & InnerCornerMask.BottomLeft) != 0) { CutCorner(texture, 0, 0, radius, transparent, 1, 1); } } private static void CutCorner(Texture2D texture, int originX, int originY, int radius, Color transparent, int dirX, int dirY) { for (int x = 0; x < radius; x++) { for (int y = 0; y < radius; y++) { float dx = x / (float)radius; float dy = y / (float)radius; if (dx * dx + dy * dy > 1f) { continue; } int px = originX + x * dirX; int py = originY + y * dirY; if (px >= 0 && px < TextureSize && py >= 0 && py < TextureSize) { texture.SetPixel(px, py, transparent); } } } } private static void DrawDisc(Texture2D texture, int centerX, int centerY, int radius, Color color) { int sqrRadius = radius * radius; for (int y = -radius; y <= radius; y++) { for (int x = -radius; x <= radius; x++) { if (x * x + y * y > sqrRadius) { continue; } int px = centerX + x; int py = centerY + y; if (px >= 0 && px < TextureSize && py >= 0 && py < TextureSize) { texture.SetPixel(px, py, color); } } } } private static void DrawLine(Texture2D texture, int x0, int y0, int x1, int y1, Color color) { int dx = Mathf.Abs(x1 - x0); int sx = x0 < x1 ? 1 : -1; int dy = -Mathf.Abs(y1 - y0); int sy = y0 < y1 ? 1 : -1; int error = dx + dy; while (true) { if (x0 >= 0 && x0 < TextureSize && y0 >= 0 && y0 < TextureSize) { texture.SetPixel(x0, y0, color); } if (x0 == x1 && y0 == y1) { break; } int twiceError = 2 * error; if (twiceError >= dy) { error += dy; x0 += sx; } if (twiceError <= dx) { error += dx; y0 += sy; } } } private static void FillRect(Texture2D texture, int x, int y, int width, int height, Color color) { for (int py = y; py < y + height; py++) { if (py < 0 || py >= TextureSize) { continue; } for (int px = x; px < x + width; px++) { if (px < 0 || px >= TextureSize) { continue; } texture.SetPixel(px, py, color); } } } } public enum DecorationPattern { Bush, Flower, Rocks } [System.Flags] public enum InnerCornerMask { None = 0, TopLeft = 1, TopRight = 2, BottomRight = 4, BottomLeft = 8 } }