Files
TheDeclineOfWarriors/Assets/Scripts/WorldGen/ProceduralWorldArt.cs
T
2026-03-29 02:26:31 +07:00

351 lines
11 KiB
C#

using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Tilemaps;
namespace InfiniteWorld
{
public static class ProceduralWorldArt
{
private const int TextureSize = 32;
private static readonly Dictionary<string, Sprite> SpriteCache = new Dictionary<string, Sprite>();
private static readonly Dictionary<string, Tile> TileCache = new Dictionary<string, Tile>();
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>();
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
}
}