- UI SFX v3: procedurally synthesized one-shots (Tools/synth_ui_sfx.py) wired to buttons, rewards, errors and red dots via UiSfx, gated by the SFX setting. - Five formal enemies (Hornlet, Dashlet, Shellhorn, HornCaptain, GreatHorn): Hunyuan 50k -> 2000-tri XPbr, DGXA TokenRig, Idle/Walk/Attack, nested per type in PrototypeArena; PrototypeBattle.Enemy gains presentation-only Moving/Strikes. - Formal partners for all ten pets in the arena and portraits, including new VineSprite, ThunderLeopard and TideWhale. - Pipeline fixes: reject local TokenRig fallback, drop loose mesh islands, configurable Unity run_script timeout, companion static stage. - Also includes concurrent work from another tool in this working tree (FlameLion, enemy combat FX, challenge mode, home explorer, server updates). - Resources registered in Doc/ResDesc_*.md; verified on isolated :18081. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
407 lines
28 KiB
C#
407 lines
28 KiB
C#
using System;
|
|
using System.IO;
|
|
using System.Linq;
|
|
using AIGame.EditorTools;
|
|
using UnityEditor;
|
|
using UnityEditor.SceneManagement;
|
|
using UnityEngine;
|
|
using UnityEngine.SceneManagement;
|
|
using YooAsset.Editor;
|
|
|
|
/// <summary>Pipeline authoring for G0 assets. No scene or prefab YAML is written by external tools.</summary>
|
|
public static class BuildPrototype
|
|
{
|
|
const string World = "Assets/Bundles/Scenes/Prototype";
|
|
const string Pictures = "Assets/Bundles/UI/Texture/Prototype";
|
|
static string Repo => Directory.GetParent(Application.dataPath).Parent.FullName;
|
|
public static readonly string[] Screens = { "Stages", "Pinball", "Capture", "Pets", "Formation", "Battle" };
|
|
const int PetCount = 10;
|
|
const string Animal = "Assets/Bundles/Characters/Animal/";
|
|
/// <summary>Validated formal partner art by pet id; other ids keep primitive stand-ins until their art passes review.</summary>
|
|
static readonly System.Collections.Generic.Dictionary<int, string> FormalPets = new System.Collections.Generic.Dictionary<int, string>
|
|
{
|
|
[0] = Animal + "RockBall/RockBall_Rigged.prefab", [1] = Animal + "FireCat/FireCat_Companion.prefab", [2] = Animal + "BudBud/BudBud_Companion.prefab",
|
|
[3] = Animal + "SparkBird/SparkBird_Companion.prefab", [4] = Animal + "BubbleTurtle/BubbleTurtle_Companion.prefab",
|
|
[5] = Animal + "RockRhino/RockRhino_Companion.prefab", [6] = Animal + "FlameLion/FlameLion_Companion.prefab",
|
|
[7] = Animal + "VineSprite/VineSprite_Companion.prefab", [8] = Animal + "ThunderLeopard/ThunderLeopard_Companion.prefab", [9] = Animal + "TideWhale/TideWhale_Companion.prefab",
|
|
};
|
|
/// <summary>Battle height in arena units; the authored models are ~0.55m and the lanes are 3 units apart.</summary>
|
|
public const float FormalHeight = 1.5f;
|
|
const string Enemy = "Assets/Bundles/Characters/Enemy/";
|
|
/// <summary>Formal enemy art by enemy type id (greenfield.json enemies 0..4), nested under every Enemies/EnemyN slot as Type{t}.</summary>
|
|
static readonly string[] FormalEnemies = { Enemy + "Hornlet/Hornlet_Enemy.prefab", Enemy + "Dashlet/Dashlet_Enemy.prefab", Enemy + "Shellhorn/Shellhorn_Enemy.prefab", Enemy + "HornCaptain/HornCaptain_Enemy.prefab", Enemy + "GreatHorn/GreatHorn_Enemy.prefab" };
|
|
/// <summary>Base enemy height in arena units; the runtime still scales captains x1.2 and bosses x1.55.</summary>
|
|
public const float EnemyHeight = 1.2f;
|
|
static Material _cream, _rock, _orange, _leaf, _dark, _white, _cyan, _grass, _path, _monster, _yellow, _water, _ember, _violet;
|
|
|
|
static Material Material(string name, string color)
|
|
{
|
|
string path = World + "/Materials/" + name + ".mat";
|
|
var material = AssetDatabase.LoadAssetAtPath<Material>(path);
|
|
if (material == null)
|
|
{
|
|
material = new Material(Shader.Find("Universal Render Pipeline/Lit")); AssetDatabase.CreateAsset(material, path);
|
|
}
|
|
ColorUtility.TryParseHtmlString(color, out var c); material.SetColor("_BaseColor", c); material.SetFloat("_Smoothness", .22f);
|
|
EditorUtility.SetDirty(material); AssetDatabase.SaveAssetIfDirty(material); return material;
|
|
}
|
|
static Transform Group(string name, Transform parent)
|
|
{
|
|
var go = new GameObject(name); go.layer = 30; go.transform.SetParent(parent, false); return go.transform;
|
|
}
|
|
static Transform Shape(string name, Transform parent, PrimitiveType type, Vector3 position, Vector3 scale, Material material)
|
|
{
|
|
var go = GameObject.CreatePrimitive(type); go.name = name; go.layer = 30; go.transform.SetParent(parent, false);
|
|
go.transform.localPosition = position; go.transform.localScale = scale; go.GetComponent<Renderer>().sharedMaterial = material;
|
|
UnityEngine.Object.DestroyImmediate(go.GetComponent<Collider>()); return go.transform;
|
|
}
|
|
static void Face(Transform root, float y, float z, float spacing)
|
|
{
|
|
foreach (float x in new[] { -spacing, spacing })
|
|
{
|
|
Shape("EyeWhite", root, PrimitiveType.Sphere, new Vector3(x, y, z), Vector3.one * .21f, _white);
|
|
Shape("Pupil", root, PrimitiveType.Sphere, new Vector3(x, y, z - .1f), Vector3.one * .1f, _dark);
|
|
}
|
|
}
|
|
static void SetLayer(Transform t)
|
|
{
|
|
t.gameObject.layer = 30; foreach (Transform child in t) SetLayer(child);
|
|
}
|
|
/// <summary>Top of the bind-pose vertices in <paramref name="space"/>; skinned Renderer.bounds is padded and unreliable in Edit Mode.</summary>
|
|
public static float MeshTop(Transform visual, Transform space)
|
|
{
|
|
float top = 0; var mesh = new Mesh();
|
|
try
|
|
{
|
|
foreach (var skin in visual.GetComponentsInChildren<SkinnedMeshRenderer>(true))
|
|
{
|
|
skin.BakeMesh(mesh, true);
|
|
foreach (var v in mesh.vertices) top = Mathf.Max(top, space.InverseTransformPoint(skin.transform.TransformPoint(v)).y);
|
|
}
|
|
}
|
|
finally { UnityEngine.Object.DestroyImmediate(mesh); }
|
|
return top;
|
|
}
|
|
/// <summary>Nests the formal partner prefab under a "Visual" node scaled to the battle height; returns false when none exists.</summary>
|
|
static bool FormalVisual(int id, Transform root)
|
|
{
|
|
if (!FormalPets.TryGetValue(id, out var path)) return false;
|
|
var asset = AssetDatabase.LoadAssetAtPath<GameObject>(path);
|
|
if (asset == null) throw new Exception("Missing formal partner " + path);
|
|
var go = (GameObject)PrefabUtility.InstantiatePrefab(asset, root.gameObject.scene);
|
|
go.name = "Visual"; go.transform.SetParent(root, false);
|
|
go.transform.localPosition = Vector3.zero; go.transform.localRotation = Quaternion.identity; go.transform.localScale = Vector3.one;
|
|
go.transform.localScale = Vector3.one * (FormalHeight / Mathf.Max(.1f, MeshTop(go.transform, root)));
|
|
SetLayer(go.transform); return true;
|
|
}
|
|
/// <summary>Nests all formal enemy types under one enemy slot, inactive; the runtime shows the one matching the spawned type. False until all five exist.</summary>
|
|
static bool FormalEnemyVisuals(Transform slot)
|
|
{
|
|
var assets = FormalEnemies.Select(AssetDatabase.LoadAssetAtPath<GameObject>).ToArray();
|
|
if (assets.Any(a => a == null)) return false;
|
|
for (int t = 0; t < assets.Length; t++)
|
|
{
|
|
var go = (GameObject)PrefabUtility.InstantiatePrefab(assets[t], slot.gameObject.scene);
|
|
go.name = "Type" + t; go.transform.SetParent(slot, false);
|
|
go.transform.localPosition = Vector3.zero; go.transform.localRotation = Quaternion.identity; go.transform.localScale = Vector3.one;
|
|
go.transform.localScale = Vector3.one * (EnemyHeight / Mathf.Max(.1f, MeshTop(go.transform, slot)));
|
|
SetLayer(go.transform); go.SetActive(false);
|
|
}
|
|
return true;
|
|
}
|
|
static Transform Pet(int id, Transform parent, string name)
|
|
{
|
|
var root = Group(name, parent);
|
|
if (FormalVisual(id, root)) return root;
|
|
if (id == 0)
|
|
{
|
|
Shape("Body", root, PrimitiveType.Sphere, new Vector3(0, .7f, 0), new Vector3(1.3f, 1.3f, 1), _rock);
|
|
foreach (int side in new[] { -1, 1 })
|
|
{
|
|
Shape("Arm", root, PrimitiveType.Cube, new Vector3(side * .77f, .65f, 0), new Vector3(.4f, .65f, .55f), _rock);
|
|
Shape("Foot", root, PrimitiveType.Sphere, new Vector3(side * .34f, .15f, -.1f), new Vector3(.5f, .3f, .6f), _cream);
|
|
}
|
|
Shape("Crystal", root, PrimitiveType.Cube, new Vector3(0, 1.4f, 0), new Vector3(.32f, .4f, .32f), _cyan).localRotation = Quaternion.Euler(0, 45, 12);
|
|
Face(root, .9f, -.48f, .24f);
|
|
}
|
|
else if (id == 1)
|
|
{
|
|
Shape("Body", root, PrimitiveType.Sphere, new Vector3(0, .47f, 0), new Vector3(.8f, .9f, .7f), _orange);
|
|
Shape("Head", root, PrimitiveType.Sphere, new Vector3(0, 1.08f, -.06f), new Vector3(1.04f, .86f, .82f), _orange);
|
|
Shape("Muzzle", root, PrimitiveType.Sphere, new Vector3(0, .92f, -.39f), new Vector3(.6f, .34f, .24f), _cream);
|
|
foreach (int side in new[] { -1, 1 })
|
|
{
|
|
Shape("Ear", root, PrimitiveType.Cube, new Vector3(side * .32f, 1.52f, -.03f), new Vector3(.28f, .48f, .26f), _orange).localRotation = Quaternion.Euler(0, 0, side * -18);
|
|
Shape("Foot", root, PrimitiveType.Sphere, new Vector3(side * .24f, .12f, -.14f), new Vector3(.32f, .24f, .45f), _cream);
|
|
}
|
|
Shape("Tail", root, PrimitiveType.Capsule, new Vector3(.5f, .5f, .25f), new Vector3(.2f, .5f, .2f), _orange).localRotation = Quaternion.Euler(15, 0, -45);
|
|
Shape("Scarf", root, PrimitiveType.Cube, new Vector3(0, .73f, -.32f), new Vector3(.65f, .14f, .2f), _cyan);
|
|
Face(root, 1.15f, -.43f, .23f);
|
|
}
|
|
else if (id == 2)
|
|
{
|
|
Shape("Pot", root, PrimitiveType.Cylinder, new Vector3(0, .25f, 0), new Vector3(.85f, .25f, .85f), _cream);
|
|
Shape("Stem", root, PrimitiveType.Cylinder, new Vector3(0, .66f, 0), new Vector3(.16f, .4f, .16f), _leaf);
|
|
for (int i = 0; i < 5; i++)
|
|
{
|
|
float a = i * Mathf.PI * 2 / 5;
|
|
Shape("Petal", root, PrimitiveType.Sphere, new Vector3(Mathf.Cos(a) * .43f, 1.14f + Mathf.Sin(a) * .43f, 0), new Vector3(.58f, .58f, .25f), _leaf);
|
|
}
|
|
Shape("Face", root, PrimitiveType.Sphere, new Vector3(0, 1.14f, -.12f), new Vector3(.7f, .7f, .35f), _cream);
|
|
Face(root, 1.22f, -.29f, .16f);
|
|
}
|
|
else if (id == 3)
|
|
{
|
|
Shape("Body", root, PrimitiveType.Sphere, new Vector3(0, .65f, 0), new Vector3(.9f, 1.05f, .8f), _yellow);
|
|
Shape("Head", root, PrimitiveType.Sphere, new Vector3(0, 1.23f, -.06f), new Vector3(1, .88f, .85f), _yellow);
|
|
Shape("Beak", root, PrimitiveType.Cube, new Vector3(0, 1.1f, -.54f), new Vector3(.23f, .16f, .3f), _orange);
|
|
foreach (int side in new[] { -1, 1 })
|
|
{
|
|
Shape("Wing", root, PrimitiveType.Sphere, new Vector3(side * .53f, .75f, .03f), new Vector3(.24f, .72f, .5f), _cyan).localRotation = Quaternion.Euler(0, 0, side * -25);
|
|
Shape("Foot", root, PrimitiveType.Sphere, new Vector3(side * .22f, .1f, -.12f), new Vector3(.28f, .2f, .46f), _orange);
|
|
}
|
|
Shape("LightningCrest", root, PrimitiveType.Cube, new Vector3(0, 1.77f, -.02f), new Vector3(.16f, .4f, .18f), _cyan).localRotation = Quaternion.Euler(0, 0, 22);
|
|
Face(root, 1.3f, -.45f, .23f);
|
|
}
|
|
else if (id == 4)
|
|
{
|
|
Shape("Shell", root, PrimitiveType.Sphere, new Vector3(0, .52f, .12f), new Vector3(1.35f, .92f, 1.5f), _water);
|
|
Shape("ShellTop", root, PrimitiveType.Sphere, new Vector3(0, .72f, .16f), new Vector3(1.12f, .64f, 1.15f), _cyan);
|
|
Shape("Head", root, PrimitiveType.Sphere, new Vector3(0, .58f, -.75f), new Vector3(.74f, .72f, .68f), _water);
|
|
foreach (int side in new[] { -1, 1 }) foreach (int end in new[] { -1, 1 })
|
|
Shape("Foot", root, PrimitiveType.Sphere, new Vector3(side * .53f, .14f, end * .5f), new Vector3(.4f, .28f, .5f), _cream);
|
|
Shape("Tail", root, PrimitiveType.Capsule, new Vector3(0, .35f, .96f), new Vector3(.18f, .28f, .18f), _water).localRotation = Quaternion.Euler(90, 0, 0);
|
|
Face(root, .68f, -1.04f, .18f);
|
|
}
|
|
else if (id == 5)
|
|
{
|
|
Shape("Body", root, PrimitiveType.Sphere, new Vector3(0, .78f, 0), new Vector3(1.5f, 1.1f, 1.1f), _rock);
|
|
Shape("Horn", root, PrimitiveType.Cube, new Vector3(0, 1.42f, -.42f), new Vector3(.3f, .5f, .26f), _cream).localRotation = Quaternion.Euler(18, 0, 0);
|
|
foreach (int side in new[] { -1, 1 })
|
|
{
|
|
Shape("Leg", root, PrimitiveType.Cube, new Vector3(side * .52f, .22f, .1f), new Vector3(.38f, .48f, .44f), _rock);
|
|
Shape("Plate", root, PrimitiveType.Cube, new Vector3(side * .38f, 1.16f, .1f), new Vector3(.44f, .2f, .62f), _cyan);
|
|
}
|
|
Shape("Tail", root, PrimitiveType.Capsule, new Vector3(0, .62f, .82f), new Vector3(.2f, .34f, .2f), _rock).localRotation = Quaternion.Euler(80, 0, 0);
|
|
Face(root, .92f, -.58f, .26f);
|
|
}
|
|
else if (id == 6)
|
|
{
|
|
Shape("Body", root, PrimitiveType.Sphere, new Vector3(0, .58f, .05f), new Vector3(.95f, .95f, .85f), _ember);
|
|
Shape("Head", root, PrimitiveType.Sphere, new Vector3(0, 1.26f, -.12f), new Vector3(1.05f, .92f, .9f), _orange);
|
|
for (int i = 0; i < 7; i++)
|
|
{
|
|
float a = -1.05f + i * .35f;
|
|
Shape("Mane", root, PrimitiveType.Sphere, new Vector3(Mathf.Sin(a) * .72f, 1.26f + Mathf.Cos(a) * .72f, .12f), new Vector3(.34f, .34f, .3f), _ember);
|
|
}
|
|
foreach (int side in new[] { -1, 1 })
|
|
{
|
|
Shape("Ear", root, PrimitiveType.Cube, new Vector3(side * .4f, 1.78f, -.1f), new Vector3(.22f, .3f, .2f), _orange).localRotation = Quaternion.Euler(0, 0, side * -14);
|
|
Shape("Paw", root, PrimitiveType.Sphere, new Vector3(side * .3f, .12f, -.2f), new Vector3(.34f, .24f, .44f), _cream);
|
|
}
|
|
Shape("Tail", root, PrimitiveType.Capsule, new Vector3(0, .52f, .78f), new Vector3(.2f, .5f, .2f), _ember).localRotation = Quaternion.Euler(60, 0, 0);
|
|
Face(root, 1.32f, -.52f, .24f);
|
|
}
|
|
else if (id == 7)
|
|
{
|
|
Shape("Stem", root, PrimitiveType.Cylinder, new Vector3(0, .72f, 0), new Vector3(.22f, .62f, .22f), _leaf);
|
|
Shape("Bud", root, PrimitiveType.Sphere, new Vector3(0, 1.5f, 0), new Vector3(.86f, .8f, .78f), _leaf);
|
|
for (int i = 0; i < 4; i++)
|
|
{
|
|
float a = i * Mathf.PI / 2 + .4f;
|
|
Shape("Vine", root, PrimitiveType.Capsule, new Vector3(Mathf.Cos(a) * .72f, 1.1f, Mathf.Sin(a) * .72f), new Vector3(.12f, .5f, .12f), _leaf).localRotation = Quaternion.Euler(28, -a * 57.3f, 0);
|
|
Shape("Leaf", root, PrimitiveType.Sphere, new Vector3(Mathf.Cos(a) * .95f, .78f, Mathf.Sin(a) * .95f), new Vector3(.5f, .16f, .36f), _grass);
|
|
}
|
|
Shape("Face", root, PrimitiveType.Sphere, new Vector3(0, 1.52f, -.32f), new Vector3(.56f, .56f, .24f), _cream);
|
|
Face(root, 1.58f, -.48f, .14f);
|
|
}
|
|
else if (id == 8)
|
|
{
|
|
Shape("Body", root, PrimitiveType.Sphere, new Vector3(0, .6f, .05f), new Vector3(1.35f, .86f, .72f), _violet);
|
|
Shape("Head", root, PrimitiveType.Sphere, new Vector3(0, 1.06f, -.68f), new Vector3(.82f, .74f, .78f), _violet);
|
|
foreach (int side in new[] { -1, 1 })
|
|
{
|
|
Shape("Ear", root, PrimitiveType.Cube, new Vector3(side * .3f, 1.5f, -.66f), new Vector3(.2f, .34f, .18f), _violet).localRotation = Quaternion.Euler(0, 0, side * -10);
|
|
foreach (int end in new[] { -1, 1 })
|
|
Shape("Leg", root, PrimitiveType.Capsule, new Vector3(side * .46f, .24f, end * .42f), new Vector3(.16f, .3f, .16f), _violet);
|
|
Shape("Bolt", root, PrimitiveType.Cube, new Vector3(side * .62f, 1.02f, -.1f), new Vector3(.16f, .36f, .14f), _cyan).localRotation = Quaternion.Euler(0, 0, side * 26);
|
|
}
|
|
Shape("Tail", root, PrimitiveType.Capsule, new Vector3(0, .82f, .78f), new Vector3(.14f, .56f, .14f), _violet).localRotation = Quaternion.Euler(58, 0, 0);
|
|
Face(root, 1.14f, -1.02f, .2f);
|
|
}
|
|
else if (id == 9)
|
|
{
|
|
Shape("Body", root, PrimitiveType.Sphere, new Vector3(0, .7f, .1f), new Vector3(1.15f, 1.1f, 1.85f), _water);
|
|
Shape("Belly", root, PrimitiveType.Sphere, new Vector3(0, .48f, -.05f), new Vector3(.86f, .62f, 1.4f), _cream);
|
|
Shape("Fin", root, PrimitiveType.Cube, new Vector3(0, 1.42f, .2f), new Vector3(.16f, .5f, .62f), _cyan).localRotation = Quaternion.Euler(-18, 0, 0);
|
|
foreach (int side in new[] { -1, 1 })
|
|
{
|
|
Shape("Flipper", root, PrimitiveType.Cube, new Vector3(side * .82f, .5f, -.1f), new Vector3(.44f, .14f, .5f), _cyan).localRotation = Quaternion.Euler(0, 0, side * -22);
|
|
Shape("Spout", root, PrimitiveType.Cylinder, new Vector3(side * .2f, 1.28f, -.7f), new Vector3(.1f, .24f, .1f), _cyan);
|
|
}
|
|
Face(root, .9f, -1.68f, .22f);
|
|
}
|
|
return root;
|
|
}
|
|
static void SetupMaterials()
|
|
{
|
|
Directory.CreateDirectory(World + "/Materials"); Directory.CreateDirectory(Pictures); AssetDatabase.Refresh();
|
|
_cream = Material("ProtoCream", "#F6EBCF"); _rock = Material("ProtoRock", "#879BC0");
|
|
_orange = Material("ProtoOrange", "#F5A144"); _leaf = Material("ProtoLeaf", "#7BC95A");
|
|
_dark = Material("ProtoDark", "#283A44"); _white = Material("ProtoWhite", "#FFFFFF");
|
|
_cyan = Material("ProtoCyan", "#42BED7"); _grass = Material("ProtoGrass", "#92C989");
|
|
_path = Material("ProtoPath", "#DCCDA9"); _monster = Material("ProtoMonster", "#BA77CB");
|
|
_yellow = Material("ProtoYellow", "#F6D553"); _water = Material("ProtoWater", "#69C8DF");
|
|
_ember = Material("ProtoEmber", "#E86A3C"); _violet = Material("ProtoViolet", "#9B7BD4");
|
|
}
|
|
static void MakeArena()
|
|
{
|
|
var scene = EditorSceneManager.NewPreviewScene(); var root = new GameObject("PrototypeArena");
|
|
SceneManager.MoveGameObjectToScene(root, scene); root.layer = 30;
|
|
try
|
|
{
|
|
Shape("Ground", root.transform, PrimitiveType.Cube, new Vector3(0, -.3f, 4), new Vector3(10, .4f, 20), _grass);
|
|
foreach (float x in new[] { -3f, 0f, 3f })
|
|
Shape("Lane", root.transform, PrimitiveType.Cube, new Vector3(x, -.065f, 3.5f), new Vector3(2.3f, .06f, 17), x == 0 ? _path : _rock);
|
|
Shape("DefenceLine", root.transform, PrimitiveType.Cube, new Vector3(0, .05f, -4), new Vector3(9.6f, .15f, .2f), _cyan);
|
|
for (int i = 0; i < 7; i++)
|
|
{
|
|
Shape("LeftBorder", root.transform, PrimitiveType.Sphere, new Vector3(-4.65f, .1f, -3 + i * 2.7f), new Vector3(.7f, .6f, .85f), _leaf);
|
|
Shape("RightBorder", root.transform, PrimitiveType.Sphere, new Vector3(4.65f, .1f, -3 + i * 2.7f), new Vector3(.7f, .6f, .85f), _leaf);
|
|
}
|
|
var heroes = Group("Heroes", root.transform);
|
|
for (int i = 0; i < PetCount; i++)
|
|
{
|
|
var pet = Pet(i, heroes, "Hero" + i); pet.localRotation = Quaternion.Euler(0, 180, 0); pet.gameObject.SetActive(false);
|
|
}
|
|
var enemies = Group("Enemies", root.transform);
|
|
for (int i = 0; i < 9; i++)
|
|
{
|
|
var enemy = Group("Enemy" + i, enemies);
|
|
if (!FormalEnemyVisuals(enemy))
|
|
{
|
|
Shape("Body", enemy, PrimitiveType.Sphere, new Vector3(0, .48f, 0), new Vector3(.85f, .95f, .85f), _monster);
|
|
Shape("Horn", enemy, PrimitiveType.Cube, new Vector3(0, 1.02f, 0), new Vector3(.2f, .35f, .2f), _cream).localRotation = Quaternion.Euler(0, 0, 20);
|
|
Face(enemy, .65f, -.38f, .17f);
|
|
}
|
|
enemy.gameObject.SetActive(false);
|
|
}
|
|
var projectiles = Group("Projectiles", root.transform);
|
|
for (int i = 0; i < PetCount; i++)
|
|
Shape("Shot" + i, projectiles, PrimitiveType.Sphere, Vector3.zero, Vector3.one * .26f, i == 2 ? _leaf : _orange).gameObject.SetActive(false);
|
|
var camera = Group("BattleCamera", root.transform).gameObject.AddComponent<Camera>();
|
|
camera.depth = 20; camera.clearFlags = CameraClearFlags.SolidColor; camera.backgroundColor = new Color(.85f, .94f, .81f);
|
|
camera.orthographic = true; camera.orthographicSize = 9.5f; camera.nearClipPlane = .1f; camera.farClipPlane = 100;
|
|
camera.cullingMask = 1 << 30; camera.transform.position = new Vector3(0, 16, -11); camera.transform.LookAt(new Vector3(0, 0, 4));
|
|
var light = Group("BattleLight", root.transform).gameObject.AddComponent<Light>();
|
|
light.type = LightType.Directional; light.intensity = 1; light.cullingMask = 1 << 30; light.transform.rotation = Quaternion.Euler(45, -30, 0);
|
|
// A full rebuild must keep the authored battlefield (same result as AttachGreenfield.Build); BuildLanes runs after this.
|
|
var battlefield = AssetDatabase.LoadAssetAtPath<GameObject>(World + "/Battlefield/Battlefield_Greenfield.prefab");
|
|
if (battlefield != null)
|
|
{
|
|
var greenfield = (GameObject)PrefabUtility.InstantiatePrefab(battlefield, scene); greenfield.name = "Greenfield";
|
|
greenfield.transform.SetParent(root.transform, false); greenfield.transform.SetSiblingIndex(0); SetLayer(greenfield.transform);
|
|
foreach (Transform child in root.transform)
|
|
if (child.name == "Ground" || child.name == "Lane" || child.name == "LeftBorder" || child.name == "RightBorder" || child.name == "BattleLight") child.gameObject.SetActive(false);
|
|
}
|
|
var enemyFeedback = AssetDatabase.LoadAssetAtPath<GameObject>("Assets/Bundles/Effects/EnemyCombat/EnemyCombatPresentation.prefab");
|
|
if (enemyFeedback != null) PrefabUtility.InstantiatePrefab(enemyFeedback, root.transform);
|
|
PrefabUtility.SaveAsPrefabAsset(root, World + "/PrototypeArena.prefab");
|
|
}
|
|
finally { UnityEngine.Object.DestroyImmediate(root); EditorSceneManager.ClosePreviewScene(scene); }
|
|
}
|
|
static void Portrait(int id)
|
|
{
|
|
var scene = EditorSceneManager.NewPreviewScene(); var previous = RenderTexture.active;
|
|
var rt = new RenderTexture(256, 256, 24, RenderTextureFormat.ARGB32);
|
|
try
|
|
{
|
|
var root = new GameObject("PortraitRoot"); SceneManager.MoveGameObjectToScene(root, scene);
|
|
Pet(id, root.transform, "PortraitPet");
|
|
var camera = Group("PortraitCamera", root.transform).gameObject.AddComponent<Camera>(); camera.scene = scene;
|
|
camera.clearFlags = CameraClearFlags.SolidColor; camera.backgroundColor = Color.clear; camera.cullingMask = 1 << 30;
|
|
bool primitiveBird = id == 3 && !FormalPets.ContainsKey(id);
|
|
camera.orthographic = true; camera.orthographicSize = primitiveBird ? 1.22f : id >= 9 ? 1.34f : 1.08f; camera.allowHDR = false;
|
|
camera.transform.position = new Vector3(1.6f, 1.55f, -4); camera.transform.LookAt(new Vector3(0, primitiveBird ? .94f : id >= 5 ? .86f : .82f, 0)); camera.targetTexture = rt;
|
|
var light = Group("PortraitLight", root.transform).gameObject.AddComponent<Light>(); light.type = LightType.Directional;
|
|
light.intensity = 1.15f; light.cullingMask = 1 << 30; light.transform.rotation = Quaternion.Euler(35, -35, 0);
|
|
camera.Render(); RenderTexture.active = rt;
|
|
var texture = new Texture2D(256, 256, TextureFormat.RGBA32, false);
|
|
try { texture.ReadPixels(new Rect(0, 0, 256, 256), 0, 0); texture.Apply(); File.WriteAllBytes(Pictures + "/proto_pet_" + id + ".png", texture.EncodeToPNG()); }
|
|
finally { UnityEngine.Object.DestroyImmediate(texture); }
|
|
}
|
|
finally { RenderTexture.active = previous; EditorSceneManager.ClosePreviewScene(scene); rt.Release(); UnityEngine.Object.DestroyImmediate(rt); }
|
|
string path = Pictures + "/proto_pet_" + id + ".png";
|
|
AssetDatabase.ImportAsset(path);
|
|
var importer = (TextureImporter)AssetImporter.GetAtPath(path); importer.textureType = TextureImporterType.Sprite;
|
|
importer.spriteImportMode = SpriteImportMode.Single; importer.mipmapEnabled = false; importer.alphaIsTransparency = true;
|
|
importer.textureCompression = TextureImporterCompression.Uncompressed; importer.filterMode = FilterMode.Bilinear;
|
|
var settings = new TextureImporterSettings(); importer.ReadTextureSettings(settings); settings.spriteMeshType = SpriteMeshType.FullRect; importer.SetTextureSettings(settings); importer.SaveAndReimport();
|
|
}
|
|
public static string Build()
|
|
{
|
|
if (EditorApplication.isPlaying) throw new Exception("Build G0 assets in Edit Mode.");
|
|
SetupMaterials(); MakeArena(); for (int i = 0; i < PetCount; i++) Portrait(i);
|
|
UiSpriteNineSliceRules.ApplyExisting();
|
|
foreach (var screen in Screens)
|
|
{
|
|
var output = JsonUIPrefabBuilder.BuildFromJsonFile(Path.Combine(Repo, "Doc/UIPrefabCreater/UI_Prototype" + screen + ".json"));
|
|
if (string.IsNullOrEmpty(output)) throw new Exception("Failed to build " + screen);
|
|
}
|
|
var home = JsonUIPrefabBuilder.BuildFromJsonFile(Path.Combine(Repo, "Doc/UIPrefabCreater/UI_MainHUD.json"));
|
|
if (string.IsNullOrEmpty(home)) throw new Exception("Failed to build home.");
|
|
SetupUiBundleCollector.Setup();
|
|
var package = BundleCollectorSettingData.Setting.GetPackage("Art");
|
|
var group = package.Groups.FirstOrDefault(g => g.GroupName == "GameplayPrototype") ?? BundleCollectorSettingData.CreateGroup(package, "GameplayPrototype");
|
|
group.GroupDesc = "G0 teaching arena"; group.ActiveRuleName = nameof(EnableGroup); BundleCollectorSettingData.ModifyGroup(package, group);
|
|
if (!group.Collectors.Any(c => c.CollectPath == World))
|
|
BundleCollectorSettingData.CreateCollector(group, new BundleCollector { CollectPath = World, CollectorGUID = AssetDatabase.AssetPathToGUID(World), CollectorType = ECollectorType.MainAssetCollector, AddressRuleName = nameof(AddressByFileName), PackRuleName = nameof(PackSeparately), FilterRuleName = nameof(CollectPrefab) });
|
|
package.CheckConfigError(); BundleCollectorSettingData.SaveFile();
|
|
return "Built authored arena with ten pets, ten portraits and six JSON UI prefabs; updated Art collectors.";
|
|
}
|
|
/// <summary>Swaps formal partners into the existing arena in place (keeps Lanes/Greenfield edits) and re-renders their portraits.</summary>
|
|
public static string ApplyFormalHeroes()
|
|
{
|
|
if (EditorApplication.isPlaying) throw new Exception("Apply formal heroes in Edit Mode");
|
|
SetupMaterials();
|
|
var root = PrefabUtility.LoadPrefabContents(World + "/PrototypeArena.prefab");
|
|
try
|
|
{
|
|
foreach (int id in FormalPets.Keys)
|
|
{
|
|
var hero = root.transform.Find("Heroes/Hero" + id) ?? throw new Exception("Arena missing Hero" + id);
|
|
foreach (var child in hero.Cast<Transform>().Where(c => c.name != "T2Crest").ToArray()) UnityEngine.Object.DestroyImmediate(child.gameObject);
|
|
FormalVisual(id, hero);
|
|
var crest = hero.Find("T2Crest"); if (crest != null) crest.localPosition = new Vector3(0, FormalHeight + .3f, 0);
|
|
}
|
|
PrefabUtility.SaveAsPrefabAsset(root, World + "/PrototypeArena.prefab");
|
|
}
|
|
finally { PrefabUtility.UnloadPrefabContents(root); }
|
|
foreach (int id in FormalPets.Keys) Portrait(id);
|
|
return "Arena Hero" + string.Join("/", FormalPets.Keys) + " now nest formal partner prefabs; portraits re-rendered.";
|
|
}
|
|
/// <summary>Swaps the enemy stand-ins in the existing arena for the five formal enemy types, in place (keeps SlowMark and other edits).</summary>
|
|
public static string ApplyFormalEnemies()
|
|
{
|
|
if (EditorApplication.isPlaying) throw new Exception("Apply formal enemies in Edit Mode");
|
|
var root = PrefabUtility.LoadPrefabContents(World + "/PrototypeArena.prefab");
|
|
try
|
|
{
|
|
var enemies = root.transform.Find("Enemies") ?? throw new Exception("Arena missing Enemies");
|
|
foreach (Transform slot in enemies)
|
|
{
|
|
foreach (var child in slot.Cast<Transform>().Where(c => c.name != "SlowMark").ToArray()) UnityEngine.Object.DestroyImmediate(child.gameObject);
|
|
if (!FormalEnemyVisuals(slot)) throw new Exception("Formal enemy prefabs incomplete: " + string.Join(", ", FormalEnemies.Where(p => AssetDatabase.LoadAssetAtPath<GameObject>(p) == null)));
|
|
}
|
|
PrefabUtility.SaveAsPrefabAsset(root, World + "/PrototypeArena.prefab");
|
|
return "Arena " + enemies.childCount + " enemy slots now nest formal Type0-" + (FormalEnemies.Length - 1) + " visuals.";
|
|
}
|
|
finally { PrefabUtility.UnloadPrefabContents(root); }
|
|
}
|
|
public static string RefreshNewPortraits()
|
|
{
|
|
if (EditorApplication.isPlaying) throw new Exception("Render portraits in Edit Mode");
|
|
SetupMaterials(); for (int i = 3; i < PetCount; i++) Portrait(i); return "Rendered portraits for pets 3-9.";
|
|
}
|
|
}
|