351 lines
11 KiB
C#
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
|
|
}
|
|
}
|