Project_WL/AgentScripts/WL816e_Build.cs

324 lines
18 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// 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());
}
}