Project_WL/AgentScripts/WL816e_Build.cs

324 lines
18 KiB
C#
Raw Permalink Normal View History

// WL-816e 빌드 — ① 섬 윗면 텍스처·머티리얼(데모 톤) ② 설정 SO ③ 윗면/길 마스크 굽기
// 원본(FarmingIsland · 3DPixelArtEnvironment)은 읽기만 한다.
public static class WL816e_Build
{
const string kDir = "Assets/WL/Look/Farm/";
const string kTexPath = kDir + "Textures/WL_IslandTop_Demo.png";
const string kMatPath = kDir + "Materials/Farm_IslandTop_Demo.mat";
const string kSoPath = kDir + "Resources/WL/WLIslandLookSettings.asset";
// 🔴 「데모 지면과 **화면에서 같은 색**이 되는」 값 — 실측으로 역산했다.
// 데모 ToonTerrain 의 화면 초록(구름 그림자 없는 p95) = #B1E196 = 선형(0.4397, 0.7529, 0.3050)
// 우리 Shader Graphs/Toon 은 텍스처를 (1.031, 0.889, 0.717) 배로 화면에 낸다(실측)
// → 텍스처 초록 = 0.4397/1.031, 0.7529/0.889, 0.3050/0.717 = (0.4265, 0.8469, 0.4254) = #AFEDAE
static readonly UnityEngine.Color kGrassLin = new UnityEngine.Color(0.4265f, 0.8469f, 0.4254f, 1f);
// 모래·흙은 데모 Terrain 2층(_GreenHighlight / _GreenShadow)에 같은 감쇠 0.717 을 적용
static readonly UnityEngine.Color kSandLin = new UnityEngine.Color(0.6020f, 0.5954f, 0.4629f, 1f);
static readonly UnityEngine.Color kDirtLin = new UnityEngine.Color(0.2165f, 0.1632f, 0.0949f, 1f);
// 그림자비 = Terrain 의 _RedShadow / _RedHighlight
static readonly UnityEngine.Color kShadowRatio = new UnityEngine.Color(0.3248f, 0.3416f, 0.3936f, 1f);
public static void RunTex()
{
var sb = new System.Text.StringBuilder();
Texture(sb);
Material(sb);
UnityEditor.AssetDatabase.SaveAssets();
UnityEngine.Debug.Log(sb.ToString());
}
public static void Run()
{
var sb = new System.Text.StringBuilder();
Texture(sb);
Material(sb);
Settings(sb);
UnityEditor.AssetDatabase.SaveAssets();
UnityEngine.Debug.Log(sb.ToString());
}
// ── ① 8×8 텍스처 ──────────────────────────────────────────────────
static void Texture(System.Text.StringBuilder sb)
{
System.IO.Directory.CreateDirectory(kDir + "Textures");
var t = new UnityEngine.Texture2D(8, 8, UnityEngine.TextureFormat.RGBA32, false, false);
// 원본 Island01.png 배치: (PNG 행 0 = 위) 위 왼쪽 = 모래 · 위 오른쪽 = 흙/절벽 · 아래 = 초록 2종(체크무늬)
// Unity 의 y 는 아래가 0 → v<0.5(아래 절반) = 초록.
for (int y = 0; y < 8; y++)
for (int x = 0; x < 8; x++)
{
UnityEngine.Color c;
if (y < 4) c = kGrassLin; // 초록 두 칸을 **한 색**으로 = 체크무늬 소멸
else c = (x < 4) ? kSandLin : kDirtLin;
t.SetPixel(x, y, c.gamma);
}
t.Apply();
System.IO.File.WriteAllBytes(kTexPath, UnityEngine.ImageConversion.EncodeToPNG(t));
UnityEngine.Object.DestroyImmediate(t);
UnityEditor.AssetDatabase.ImportAsset(kTexPath, UnityEditor.ImportAssetOptions.ForceUpdate);
var imp = UnityEditor.AssetImporter.GetAtPath(kTexPath) as UnityEditor.TextureImporter;
if (imp != null)
{
imp.textureType = UnityEditor.TextureImporterType.Default;
imp.sRGBTexture = true;
imp.filterMode = UnityEngine.FilterMode.Point;
imp.wrapMode = UnityEngine.TextureWrapMode.Clamp;
imp.mipmapEnabled = false;
imp.npotScale = UnityEditor.TextureImporterNPOTScale.None;
imp.textureCompression = UnityEditor.TextureImporterCompression.Uncompressed;
imp.maxTextureSize = 32;
imp.SaveAndReimport();
}
sb.AppendLine("텍스처 " + kTexPath + " — 초록 " + Hex(kGrassLin) + " · 모래 " + Hex(kSandLin) + " · 흙 " + Hex(kDirtLin));
}
static string Hex(UnityEngine.Color lin)
{
var g = lin.gamma;
return "#" + UnityEngine.ColorUtility.ToHtmlStringRGB(g);
}
// ── ② 머티리얼 ────────────────────────────────────────────────────
static void Material(System.Text.StringBuilder sb)
{
var src = UnityEditor.AssetDatabase.LoadAssetAtPath<UnityEngine.Material>(kDir + "Materials/Farm_Island01_ToonTex.mat");
if (src == null) { sb.AppendLine("🔴 816a 의 Farm_Island01_ToonTex.mat 이 없다"); return; }
var tex = UnityEditor.AssetDatabase.LoadAssetAtPath<UnityEngine.Texture2D>(kTexPath);
var m = UnityEngine.Object.Instantiate(src);
m.name = "Farm_IslandTop_Demo";
if (m.HasProperty("_BaseMap")) m.SetTexture("_BaseMap", tex);
if (m.HasProperty("_ShadowBaseMap")) m.SetTexture("_ShadowBaseMap", tex);
if (m.HasProperty("_DiffuseColor")) m.SetColor("_DiffuseColor", UnityEngine.Color.white);
if (m.HasProperty("_ShadowDiffuseColor")) m.SetColor("_ShadowDiffuseColor", kShadowRatio);
var old = UnityEditor.AssetDatabase.LoadAssetAtPath<UnityEngine.Material>(kMatPath);
if (old != null) { old.CopyPropertiesFromMaterial(m); UnityEngine.Object.DestroyImmediate(m); UnityEditor.EditorUtility.SetDirty(old); }
else UnityEditor.AssetDatabase.CreateAsset(m, kMatPath);
sb.AppendLine("머티리얼 " + kMatPath + " (shader " + src.shader.name + " · _ShadowDiffuseColor " + kShadowRatio.ToString("F4") + ")");
}
// ── ③ 설정 SO + 마스크 ────────────────────────────────────────────
static void Settings(System.Text.StringBuilder sb)
{
System.IO.Directory.CreateDirectory(kDir + "Resources/WL");
UnityEditor.AssetDatabase.Refresh();
// 🔴 마스크를 **먼저** 굽는다 — Bake 가 씬을 열면 AssetDatabase 가 재로드돼
// 앞서 잡아 둔 SO 참조가 끊긴다(MissingReferenceException). 구운 뒤에 SO 를 잡는다.
var bakedTiles = new System.Collections.Generic.List<WL.Look.Farm.WLTileMask>();
var bakedRoads = new System.Collections.Generic.List<WL.Look.Farm.WLTileMask>();
Bake(bakedTiles, bakedRoads, sb);
var so = UnityEditor.AssetDatabase.LoadAssetAtPath<WL.Look.Farm.WLIslandLookSettings>(kSoPath);
if (so == null)
{
so = UnityEngine.ScriptableObject.CreateInstance<WL.Look.Farm.WLIslandLookSettings>();
UnityEditor.AssetDatabase.CreateAsset(so, kSoPath);
UnityEditor.AssetDatabase.SaveAssets();
so = UnityEditor.AssetDatabase.LoadAssetAtPath<WL.Look.Farm.WLIslandLookSettings>(kSoPath);
}
so.tileMasks = bakedTiles.ToArray();
so.roadMasks = bakedRoads.ToArray();
// 머티리얼 교체 표
var remap = new System.Collections.Generic.List<WL.Look.Farm.WLMatRemap>();
string[,] pairs = {
{ "Assets/FarmingIsland/Materials/Palette.mat", kDir + "Materials/Farm_Palette_ToonTex.mat" },
{ "Assets/FarmingIsland/Materials/Islands/Island01.mat", kDir + "Materials/Farm_IslandTop_Demo.mat" },
{ "Assets/FarmingIsland/Materials/Islands/Road01.mat", kDir + "Materials/Farm_Road01_ToonTex.mat" },
{ "Assets/FarmingIsland/Materials/Skins/Skin_Purple.mat", kDir + "Materials/Farm_Skin_Purple_ToonTex.mat" },
{ "Assets/FarmingIsland/Materials/Skins/Skin_Yellow.mat", kDir + "Materials/Farm_Skin_Yellow_ToonTex.mat" },
{ "Assets/FarmingIsland/Materials/Environments/SimpleWater.mat", kDir + "Materials/Farm_SimpleWater_Demo.mat" },
};
for (int i = 0; i < pairs.GetLength(0); i++)
{
var a = UnityEditor.AssetDatabase.LoadAssetAtPath<UnityEngine.Material>(pairs[i, 0]);
var b = UnityEditor.AssetDatabase.LoadAssetAtPath<UnityEngine.Material>(pairs[i, 1]);
if (a == null || b == null) { sb.AppendLine("🔴 remap 실패 " + pairs[i, 0] + " → " + pairs[i, 1]); continue; }
remap.Add(new WL.Look.Farm.WLMatRemap { enabled_ = 1, from = a, to = b });
}
so.materialRemap = remap.ToArray();
so.islandTopMaterial = UnityEditor.AssetDatabase.LoadAssetAtPath<UnityEngine.Material>(kMatPath);
so.skybox = UnityEditor.AssetDatabase.GetBuiltinExtraResource<UnityEngine.Material>("Default-Skybox.mat");
// 흩뿌릴 것 — 데모 실측값 그대로
var grassMesh = FindMesh("Assets/3DPixelArtEnvironment/Meshes/Instanced/Grass_Leaf.fbx", "Grass_Leaf");
var flowerMesh = FindMesh("Assets/3DPixelArtEnvironment/Meshes/Instanced/Flower_Leaf.fbx", "Flower_Leaf");
var stoneMesh = FindMeshByName("Stone_1");
var mGrass = UnityEditor.AssetDatabase.LoadAssetAtPath<UnityEngine.Material>("Assets/3DPixelArtEnvironment/Materials/Instanced_Grass.mat");
var mFlower = UnityEditor.AssetDatabase.LoadAssetAtPath<UnityEngine.Material>("Assets/3DPixelArtEnvironment/Materials/Instanced_Flower.mat");
var mGravel = UnityEditor.AssetDatabase.LoadAssetAtPath<UnityEngine.Material>("Assets/3DPixelArtEnvironment/Materials/Instanced_Gravel.mat");
var sc = new System.Collections.Generic.List<WL.Look.Farm.WLScatterDef>();
sc.Add(new WL.Look.Farm.WLScatterDef { label = "풀", mesh = grassMesh, material = mGrass, probability = 100f, scale = 0.35f, normalOffset = 0.04f, density = 2.5f });
sc.Add(new WL.Look.Farm.WLScatterDef { label = "꽃", mesh = flowerMesh, material = mFlower, probability = 1f, scale = 0.35f, normalOffset = 0.04f, density = 2.5f });
sc.Add(new WL.Look.Farm.WLScatterDef { label = "자갈", mesh = stoneMesh, material = mGravel, probability = 1f, scale = 0.7f, normalOffset = 0.1f, density = 0.05f });
so.scatter = sc.ToArray();
sb.AppendLine("scatter — 풀 " + (grassMesh == null ? "메시없음" : grassMesh.name) +
" · 꽃 " + (flowerMesh == null ? "메시없음" : flowerMesh.name) +
" · 자갈 " + (stoneMesh == null ? "메시없음" : stoneMesh.name));
UnityEditor.EditorUtility.SetDirty(so);
sb.AppendLine("SO " + kSoPath + " — remap " + so.materialRemap.Length +
" · tileMasks " + so.tileMasks.Length + " · roadMasks " + so.roadMasks.Length);
}
static UnityEngine.Mesh FindMesh(string path, string name)
{
var all = UnityEditor.AssetDatabase.LoadAllAssetsAtPath(path);
for (int i = 0; i < all.Length; i++)
{
var m = all[i] as UnityEngine.Mesh;
if (m != null) return m;
}
return null;
}
static UnityEngine.Mesh FindMeshByName(string name)
{
var guids = UnityEditor.AssetDatabase.FindAssets(name + " t:Mesh");
for (int i = 0; i < guids.Length; i++)
{
var p = UnityEditor.AssetDatabase.GUIDToAssetPath(guids[i]);
var all = UnityEditor.AssetDatabase.LoadAllAssetsAtPath(p);
for (int k = 0; k < all.Length; k++)
{
var m = all[k] as UnityEngine.Mesh;
if (m != null && m.name == name) return m;
}
}
// 데모 씬의 인스턴싱 설정에서 직접 집는다
return null;
}
// ── 마스크 굽기 ───────────────────────────────────────────────────
public static void Bake(System.Collections.Generic.List<WL.Look.Farm.WLTileMask> tiles,
System.Collections.Generic.List<WL.Look.Farm.WLTileMask> roads,
System.Text.StringBuilder sb)
{
var prev = UnityEditor.SceneManagement.EditorSceneManager.GetActiveScene().path;
UnityEditor.SceneManagement.EditorSceneManager.OpenScene("Assets/FarmingIsland/Scenes/Level01.unity",
UnityEditor.SceneManagement.OpenSceneMode.Single);
var mgr = UnityEngine.Object.FindFirstObjectByType<CryingSnow.FarmingIsland.IslandManager>(UnityEngine.FindObjectsInactive.Include);
var t = typeof(CryingSnow.FarmingIsland.IslandManager);
var islMeshes = t.GetField("islandMeshes", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance)
.GetValue(mgr) as System.Collections.Generic.List<UnityEngine.Mesh>;
var roadMeshes = t.GetField("roadMeshes", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance)
.GetValue(mgr) as System.Collections.Generic.List<UnityEngine.Mesh>;
int res = 64;
for (int i = 0; islMeshes != null && i < islMeshes.Count; i++)
{
var m = islMeshes[i];
if (m == null) continue;
if (m.name == "Bridge") continue; // 다리에는 안 깐다
var mask = Rasterize(m, res, 4f, true, out int on);
mask.meshName = m.name;
tiles.Add(mask);
if (i <= 1 || i == 46 || i == 47)
sb.AppendLine(" 마스크 isl[" + i + "] " + m.name + " 초록칸 " + on + "/" + (res * res)
+ " (" + (on * 64f / (res * res)).ToString("F1") + "㎡)");
}
for (int i = 0; roadMeshes != null && i < roadMeshes.Count; i++)
{
var m = roadMeshes[i];
if (m == null) continue;
var mask = Rasterize(m, res, 4f, false, out int on);
mask.meshName = m.name;
roads.Add(mask);
}
if (!string.IsNullOrEmpty(prev)) UnityEditor.SceneManagement.EditorSceneManager.OpenScene(prev, UnityEditor.SceneManagement.OpenSceneMode.Single);
}
/// <summary>
/// 메시의 **윗면**(법선 +Y · 최상단)을 XZ 격자에 굽는다.
/// greenOnly = true 면 **UV 가 Island01.png 의 초록 사분면(v &lt; 0.5)** 인 삼각형만 센다
/// = FI 가 「여기는 잔디」라고 표시한 자리. 모래 해변·흙 절벽에는 풀을 안 깐다.
/// </summary>
static WL.Look.Farm.WLTileMask Rasterize(UnityEngine.Mesh m, int res, float half, bool greenOnly, out int onCells)
{
var mask = new WL.Look.Farm.WLTileMask { res = res, half = half, bits = new byte[(res * res + 7) / 8] };
var v = m.vertices; var tri = m.triangles; var uv = m.uv;
float maxY = -99999f;
for (int i = 0; i < v.Length; i++) if (v[i].y > maxY) maxY = v[i].y;
float cell = half * 2f / res;
onCells = 0;
for (int i = 0; i + 2 < tri.Length; i += 3)
{
int i0 = tri[i], i1 = tri[i + 1], i2 = tri[i + 2];
var a = v[i0]; var b = v[i1]; var c = v[i2];
var n = UnityEngine.Vector3.Cross(b - a, c - a);
if (n.y <= 0f) continue;
if ((a.y + b.y + c.y) / 3f < maxY - 0.05f) continue;
if (greenOnly && uv != null && uv.Length > i2)
{
float vv = (uv[i0].y + uv[i1].y + uv[i2].y) / 3f;
if (vv >= 0.5f) continue; // 모래(위 왼쪽)·흙(위 오른쪽) = 잔디 아님
}
float minx = UnityEngine.Mathf.Min(a.x, UnityEngine.Mathf.Min(b.x, c.x));
float maxx = UnityEngine.Mathf.Max(a.x, UnityEngine.Mathf.Max(b.x, c.x));
float minz = UnityEngine.Mathf.Min(a.z, UnityEngine.Mathf.Min(b.z, c.z));
float maxz = UnityEngine.Mathf.Max(a.z, UnityEngine.Mathf.Max(b.z, c.z));
int cx0 = UnityEngine.Mathf.Max(0, UnityEngine.Mathf.FloorToInt((minx + half) / cell));
int cx1 = UnityEngine.Mathf.Min(res - 1, UnityEngine.Mathf.CeilToInt((maxx + half) / cell));
int cz0 = UnityEngine.Mathf.Max(0, UnityEngine.Mathf.FloorToInt((minz + half) / cell));
int cz1 = UnityEngine.Mathf.Min(res - 1, UnityEngine.Mathf.CeilToInt((maxz + half) / cell));
for (int cx = cx0; cx <= cx1; cx++)
for (int cz = cz0; cz <= cz1; cz++)
{
float px = -half + (cx + 0.5f) * cell;
float pz = -half + (cz + 0.5f) * cell;
float d1 = (px - b.x) * (a.z - b.z) - (a.x - b.x) * (pz - b.z);
float d2 = (px - c.x) * (b.z - c.z) - (b.x - c.x) * (pz - c.z);
float d3 = (px - a.x) * (c.z - a.z) - (c.x - a.x) * (pz - a.z);
bool neg = (d1 < 0) || (d2 < 0) || (d3 < 0);
bool pos = (d1 > 0) || (d2 > 0) || (d3 > 0);
if (neg && pos) continue;
if (!mask.At(px, pz)) onCells++;
mask.Set(cx, cz, true);
}
}
return mask;
}
// ── 마스크 눈으로 확인 ────────────────────────────────────────────
public static void Show()
{
var so = UnityEditor.AssetDatabase.LoadAssetAtPath<WL.Look.Farm.WLIslandLookSettings>(kSoPath);
var sb = new System.Text.StringBuilder();
string[] want = { "47", "46", "33", "01" };
for (int w = 0; w < want.Length; w++)
{
var m = so.FindTileMask(want[w]);
if (m == null) { sb.AppendLine("마스크 없음 " + want[w]); continue; }
sb.AppendLine("--- TILE " + want[w] + " ---");
for (int r = 31; r >= 0; r--)
{
var line = new System.Text.StringBuilder(" ");
for (int c = 0; c < 32; c++)
line.Append(m.At(-4f + (c + 0.5f) * 0.25f, -4f + (r + 0.5f) * 0.25f) ? '#' : '.');
sb.AppendLine(line.ToString());
}
}
var rm = so.FindRoadMask("15");
if (rm != null)
{
sb.AppendLine("--- ROAD 15 ---");
for (int r = 31; r >= 0; r--)
{
var line = new System.Text.StringBuilder(" ");
for (int c = 0; c < 32; c++)
line.Append(rm.At(-4f + (c + 0.5f) * 0.25f, -4f + (r + 0.5f) * 0.25f) ? '#' : '.');
sb.AppendLine(line.ToString());
}
}
UnityEngine.Debug.Log(sb.ToString());
}
}