557 lines
27 KiB
C#
557 lines
27 KiB
C#
using System.Collections.Generic;
|
|
using System.IO;
|
|
using System.Linq;
|
|
using ColbyO.VNTG.CRT;
|
|
using ColbyO.VNTG.PSX;
|
|
using UnityEditor;
|
|
using UnityEditor.Animations;
|
|
using UnityEditor.SceneManagement;
|
|
using UnityEngine;
|
|
using UnityEngine.Rendering;
|
|
using UnityEngine.Rendering.Universal;
|
|
using UnityEngine.SceneManagement;
|
|
|
|
namespace IllOmen.EditorTools
|
|
{
|
|
/// <summary>Builds the visual test scene: the mill counter, a candle and one villager.</summary>
|
|
public static class MillTestSceneBuilder
|
|
{
|
|
private const string KitPath = "Assets/ThirdParty/Models/RetroMedievalKit/3D Retro Medieval Fantasy Kit/3D Retro Medieval Fantasy Kit.blend";
|
|
private const string VillagerPath = "Assets/ThirdParty/Models/VillagerWoman/PSX Villager Woman/PSX_Villager_Woman_Model_And_Animations.fbx";
|
|
private const string VillagerTexturePath = "Assets/ThirdParty/Models/VillagerWoman/PSX Villager Woman/VillageWomanTexture.png";
|
|
private const string BaseMaterialPath = "Assets/ColbyO/VNTG/Example/Material/PSX/WoodMat.mat";
|
|
private const string GeneratedFolder = "Assets/IllOmen/Generated";
|
|
private const string ScenePath = "Assets/IllOmen/Scenes/MillTest.unity";
|
|
private const string LineupScenePath = "Assets/IllOmen/Scenes/CharacterLineup.unity";
|
|
|
|
private static readonly Dictionary<string, Material> MaterialCache = new Dictionary<string, Material>();
|
|
private static GameObject kit;
|
|
private static Material baseMaterial;
|
|
|
|
[MenuItem("Ill Omen/Build Mill Test Scene")]
|
|
public static void Build()
|
|
{
|
|
if (!EditorSceneManager.SaveCurrentModifiedScenesIfUserWantsTo()) return;
|
|
|
|
MaterialCache.Clear();
|
|
kit = AssetDatabase.LoadAssetAtPath<GameObject>(KitPath);
|
|
baseMaterial = AssetDatabase.LoadAssetAtPath<Material>(BaseMaterialPath);
|
|
if (kit == null || baseMaterial == null)
|
|
{
|
|
Debug.LogError($"[IllOmen] Missing asset. Kit found: {kit != null}, base material found: {baseMaterial != null}");
|
|
return;
|
|
}
|
|
|
|
Directory.CreateDirectory(GeneratedFolder + "/Materials");
|
|
Directory.CreateDirectory(Path.GetDirectoryName(ScenePath));
|
|
AssetDatabase.Refresh();
|
|
|
|
Scene scene = EditorSceneManager.NewScene(NewSceneSetup.EmptyScene, NewSceneMode.Single);
|
|
RenderSettings.skybox = null;
|
|
RenderSettings.ambientMode = AmbientMode.Flat;
|
|
RenderSettings.ambientLight = new Color(0.02f, 0.02f, 0.03f);
|
|
|
|
BuildRoom();
|
|
BuildCounter();
|
|
BuildProps();
|
|
BuildVillager();
|
|
BuildCandleLight();
|
|
BuildCamera();
|
|
BuildVolume();
|
|
BuildAmbience();
|
|
|
|
EditorSceneManager.SaveScene(scene, ScenePath);
|
|
AssetDatabase.SaveAssets();
|
|
Debug.Log($"[IllOmen] Built {ScenePath}");
|
|
}
|
|
|
|
[MenuItem("Ill Omen/Build Character Lineup")]
|
|
public static void BuildLineup()
|
|
{
|
|
if (!EditorSceneManager.SaveCurrentModifiedScenesIfUserWantsTo()) return;
|
|
|
|
MaterialCache.Clear();
|
|
kit = AssetDatabase.LoadAssetAtPath<GameObject>(KitPath);
|
|
baseMaterial = AssetDatabase.LoadAssetAtPath<Material>(BaseMaterialPath);
|
|
Directory.CreateDirectory(GeneratedFolder + "/Materials");
|
|
Directory.CreateDirectory(Path.GetDirectoryName(LineupScenePath));
|
|
AssetDatabase.Refresh();
|
|
|
|
Scene scene = EditorSceneManager.NewScene(NewSceneSetup.EmptyScene, NewSceneMode.Single);
|
|
RenderSettings.skybox = null;
|
|
RenderSettings.ambientMode = AmbientMode.Flat;
|
|
RenderSettings.ambientLight = new Color(0.25f, 0.25f, 0.28f);
|
|
|
|
Transform stage = new GameObject("Stage").transform;
|
|
for (int x = -6; x < 6; x++)
|
|
{
|
|
for (int z = -3; z < 3; z++)
|
|
{
|
|
Place("Floor Thin", new Vector3(x + 0.5f, 0f, z + 0.5f), 0f, stage, "Wood-Dark", "Wood-Dark", "Wood-Dark");
|
|
}
|
|
}
|
|
for (int i = 0; i < 6; i++)
|
|
{
|
|
Place("Wall A Plain", new Vector3(-5f + i * 2f, 0f, 3f), 0f, stage, "Wood-Dark", "Wood-Mid", "Wood-Pale");
|
|
}
|
|
|
|
string men = "Assets/ThirdParty/Models/VillagerMen/";
|
|
string adult = men + "adultman__/FBX_mixamo/AdultMan___.fbx";
|
|
string ionDirty = "Assets/IllOmen/Textures/Characters/Ion_Dirty.png";
|
|
string ionClean = "Assets/IllOmen/Textures/Characters/Ion_Clean.png";
|
|
string young = men + "youngman__/FBX_Mixamo/YoungMan___.fbx";
|
|
string youngTexture = men + "youngman__/textures/Texture_LOWRES.jpg";
|
|
Color pale = new Color(0.62f, 0.62f, 0.66f);
|
|
|
|
PlaceCharacter(adult, ionDirty, "Ion Night 1", -3.75f, 1.76f, Color.white, "Ion, night 1\n(dirty)");
|
|
PlaceCharacter(adult, ionClean, "Ion Night 2", -2.25f, 1.76f, Color.white, "Ion, night 2\n(clean)");
|
|
PlaceCharacter(young, youngTexture, "Young Man", -5.25f, 1.75f, Color.white, "Young man\n(Toma)");
|
|
PlaceCharacter("Assets/IllOmen/Models/Characters/Andrei.fbx", "Assets/IllOmen/Textures/Characters/Andrei.png", "Andrei", -0.75f, 1.75f, Color.white, "Andrei");
|
|
PlaceCharacter("Assets/IllOmen/Models/Characters/FatherIlie.fbx", "Assets/IllOmen/Textures/Characters/FatherIlie.png", "Father Ilie", 0.75f, 1.82f, Color.white, "Father Ilie");
|
|
PlaceCharacter(VillagerPath, "Assets/IllOmen/Textures/Characters/Ana_TestOnly.png", "Ana", 2.25f, 1.70f, Color.white, "Ana, test only");
|
|
PlaceCharacter("Assets/IllOmen/Models/Characters/Safta.fbx", "Assets/IllOmen/Textures/Characters/Safta.png", "Safta", 3.75f, 1.58f, Color.white, "Safta");
|
|
|
|
// Bright, even light: this scene exists to judge clothing, not mood.
|
|
RenderSettings.ambientLight = new Color(0.45f, 0.45f, 0.47f);
|
|
foreach (Vector3 position in new[]
|
|
{
|
|
new Vector3(-3f, 2.6f, -1.2f), new Vector3(3f, 2.6f, -1.2f),
|
|
new Vector3(-3f, 0.8f, -1.2f), new Vector3(3f, 0.8f, -1.2f)
|
|
})
|
|
{
|
|
GameObject lamp = new GameObject("Stage Light");
|
|
lamp.transform.position = position;
|
|
Light light = lamp.AddComponent<Light>();
|
|
light.type = LightType.Point;
|
|
light.color = new Color(1f, 0.9f, 0.78f);
|
|
light.range = 10f;
|
|
light.intensity = 8f;
|
|
light.shadows = LightShadows.None;
|
|
}
|
|
|
|
BuildCamera();
|
|
// Starts right in front of Safta, with Ana beside her.
|
|
Camera.main.transform.SetPositionAndRotation(new Vector3(3.75f, 1.5f, -0.9f), Quaternion.identity);
|
|
Camera.main.fieldOfView = 70f;
|
|
Object.DestroyImmediate(Camera.main.GetComponent<LookAround>());
|
|
Camera.main.gameObject.AddComponent<WalkAround>();
|
|
BuildVolume();
|
|
|
|
EditorSceneManager.SaveScene(scene, LineupScenePath);
|
|
AssetDatabase.SaveAssets();
|
|
Debug.Log($"[IllOmen] Built {LineupScenePath}");
|
|
}
|
|
|
|
private static void PlaceCharacter(string modelPath, string texturePath, string name, float x, float height, Color tint, string label)
|
|
{
|
|
GameObject model = AssetDatabase.LoadAssetAtPath<GameObject>(modelPath);
|
|
Texture2D texture = AssetDatabase.LoadAssetAtPath<Texture2D>(texturePath);
|
|
if (model == null || texture == null)
|
|
{
|
|
Debug.LogError($"[IllOmen] Missing character asset for {name}. Model: {model != null}, texture: {texture != null}");
|
|
return;
|
|
}
|
|
|
|
GameObject character = (GameObject)PrefabUtility.InstantiatePrefab(model);
|
|
character.name = name;
|
|
|
|
Material material = GetMaterial(texture, name);
|
|
foreach (string colorProperty in new[] { "_BaseColor", "_Color", "_TintColor" })
|
|
{
|
|
if (material.HasProperty(colorProperty)) material.SetColor(colorProperty, tint);
|
|
}
|
|
EditorUtility.SetDirty(material);
|
|
foreach (Renderer renderer in character.GetComponentsInChildren<Renderer>())
|
|
{
|
|
renderer.sharedMaterials = Enumerable.Repeat(material, renderer.sharedMaterials.Length).ToArray();
|
|
}
|
|
|
|
Bounds bounds = GetBounds(character);
|
|
if (bounds.size.y > 0.01f) character.transform.localScale *= height / bounds.size.y;
|
|
bounds = GetBounds(character);
|
|
character.transform.position += new Vector3(x - bounds.center.x, -bounds.min.y, 0.8f - bounds.center.z);
|
|
character.transform.Rotate(0f, 180f, 0f, Space.World);
|
|
|
|
bool lowered = LowerArm(character, "UpperArm.L", "LowerArm.L") & LowerArm(character, "UpperArm.R", "LowerArm.R");
|
|
if (modelPath == VillagerPath) AttachIdle(character);
|
|
Debug.Log($"[IllOmen] Character {name}: size {bounds.size}, arms lowered: {lowered}");
|
|
|
|
GameObject text = new GameObject(name + " Label");
|
|
text.transform.position = new Vector3(x, height + 0.32f, 0.8f);
|
|
TextMesh mesh = text.AddComponent<TextMesh>();
|
|
mesh.text = label;
|
|
mesh.font = Resources.GetBuiltinResource<Font>("LegacyRuntime.ttf");
|
|
mesh.fontSize = 48;
|
|
mesh.characterSize = 0.022f;
|
|
mesh.anchor = TextAnchor.LowerCenter;
|
|
mesh.alignment = TextAlignment.Center;
|
|
text.GetComponent<MeshRenderer>().sharedMaterial = mesh.font.material;
|
|
}
|
|
|
|
/// <summary>The men have no animations, so we pose them out of the T-pose by pointing each arm down.</summary>
|
|
private static bool LowerArm(GameObject character, string upperName, string lowerName)
|
|
{
|
|
Transform[] bones = character.GetComponentsInChildren<Transform>(true);
|
|
Transform upper = bones.FirstOrDefault(bone => bone.name == upperName);
|
|
Transform lower = bones.FirstOrDefault(bone => bone.name == lowerName);
|
|
if (upper == null || lower == null) return false;
|
|
|
|
Vector3 current = (lower.position - upper.position).normalized;
|
|
float side = Mathf.Sign(Vector3.Dot(current, character.transform.right));
|
|
Vector3 target = (Vector3.down + character.transform.right * side * 0.12f).normalized;
|
|
upper.rotation = Quaternion.FromToRotation(current, target) * upper.rotation;
|
|
return true;
|
|
}
|
|
|
|
private static void BuildRoom()
|
|
{
|
|
Transform room = new GameObject("Room").transform;
|
|
|
|
// The room is 6 x 6 metres: each wall piece is 2 metres wide.
|
|
string[] back = { "Wall A Window Small", "Wall A Door", "Wall A Plain" };
|
|
for (int i = 0; i < 3; i++)
|
|
{
|
|
float offset = -2f + i * 2f;
|
|
PlaceWall(back[i], new Vector3(offset, 0f, 3f), 0f, room);
|
|
PlaceWall("Wall A Plain", new Vector3(offset, 0f, -3f), 180f, room);
|
|
PlaceWall("Wall A Plain", new Vector3(-3f, 0f, offset), 90f, room);
|
|
PlaceWall("Wall A Plain", new Vector3(3f, 0f, offset), 90f, room);
|
|
}
|
|
|
|
Place("Door", new Vector3(0f, 0f, 3f), 0f, room, "Wood-Dark", "Wood-Dark", "Wood-Dark");
|
|
|
|
for (int x = 0; x < 6; x++)
|
|
{
|
|
for (int z = 0; z < 6; z++)
|
|
{
|
|
Vector3 tile = new Vector3(x - 2.5f, 0f, z - 2.5f);
|
|
Place("Floor Thin", tile, 0f, room, "Wood-Dark", "Wood-Dark", "Wood-Dark");
|
|
Place("Floor Thin", tile + Vector3.up * 3.45f, 0f, room, "Wood-Mid", "Wood-Mid", "Wood-Mid");
|
|
}
|
|
}
|
|
|
|
for (int i = -1; i <= 1; i++)
|
|
{
|
|
for (int j = 0; j < 3; j++)
|
|
{
|
|
Place("Beam Long", new Vector3(-2f + j * 2f, 3.25f, i * 2f), 0f, room, "Wood-Dark", "Wood-Dark", "Wood-Dark");
|
|
}
|
|
}
|
|
}
|
|
|
|
private static void PlaceWall(string piece, Vector3 position, float yaw, Transform parent)
|
|
{
|
|
Place(piece, position, yaw, parent, "Wood-Dark", "Wood-Mid", "Wood-Pale");
|
|
}
|
|
|
|
private static void BuildCounter()
|
|
{
|
|
Transform counter = new GameObject("Counter").transform;
|
|
Place("Table Large", new Vector3(-0.75f, 0f, -0.5f), 90f, counter, "Wood-Pale", "Wood-Mid", "Wood-Mid");
|
|
Place("Table Large", new Vector3(0.75f, 0f, -0.5f), 90f, counter, "Wood-Pale", "Wood-Mid", "Wood-Mid");
|
|
Place("Candle", new Vector3(0.5f, 0.68f, -0.5f), 0f, counter, "Wood-Mid", "Fabric-Cotton", "Wood-Pale");
|
|
Place("Cooking Bowl", new Vector3(-0.5f, 0.68f, -0.4f), 0f, counter, "Wood-Mid", "Wood-Mid", "Wood-Mid");
|
|
}
|
|
|
|
private static void BuildProps()
|
|
{
|
|
Transform props = new GameObject("Props").transform;
|
|
Place("Crate", new Vector3(2.3f, 0f, 2.2f), 15f, props, "Wood-Mid", "Wood-Dark", "Wood-Dark");
|
|
Place("Crate", new Vector3(2.3f, 0.5f, 2.2f), -10f, props, "Wood-Pale", "Wood-Dark", "Wood-Dark");
|
|
Place("Trunk", new Vector3(-2.4f, 0f, 1.8f), 90f, props, "Wood-Dark", "Fabric-Cord", "Wood-Mid");
|
|
Place("Logs", new Vector3(2.4f, 0f, -1.2f), 30f, props, "Wood-Dark", "Wood-Dark", "Wood-Dark");
|
|
|
|
// Placeholders for the millstones until we model real ones.
|
|
Material stone = GetMaterial("Brick-Stone");
|
|
CreateMillstone("Millstone Lower", new Vector3(-2f, 0.13f, -1.8f), stone, props);
|
|
CreateMillstone("Millstone Upper", new Vector3(-2f, 0.39f, -1.8f), stone, props);
|
|
}
|
|
|
|
private static void CreateMillstone(string name, Vector3 position, Material material, Transform parent)
|
|
{
|
|
GameObject stone = GameObject.CreatePrimitive(PrimitiveType.Cylinder);
|
|
stone.name = name;
|
|
Object.DestroyImmediate(stone.GetComponent<Collider>());
|
|
stone.transform.SetParent(parent);
|
|
stone.transform.position = position;
|
|
stone.transform.localScale = new Vector3(1.2f, 0.13f, 1.2f);
|
|
stone.GetComponent<Renderer>().sharedMaterial = material;
|
|
}
|
|
|
|
private static void BuildVillager()
|
|
{
|
|
GameObject model = AssetDatabase.LoadAssetAtPath<GameObject>(VillagerPath);
|
|
if (model == null)
|
|
{
|
|
Debug.LogError("[IllOmen] Villager model not found");
|
|
return;
|
|
}
|
|
|
|
MakeClipsLoop();
|
|
|
|
GameObject villager = (GameObject)PrefabUtility.InstantiatePrefab(model);
|
|
villager.name = "Villager (private test only)";
|
|
|
|
Material material = GetMaterial(AssetDatabase.LoadAssetAtPath<Texture2D>(VillagerTexturePath), "Villager");
|
|
foreach (Renderer renderer in villager.GetComponentsInChildren<Renderer>())
|
|
{
|
|
renderer.sharedMaterials = Enumerable.Repeat(material, renderer.sharedMaterials.Length).ToArray();
|
|
}
|
|
|
|
Bounds bounds = GetBounds(villager);
|
|
Debug.Log($"[IllOmen] Villager size: {bounds.size}");
|
|
if (bounds.size.y > 0.01f)
|
|
{
|
|
villager.transform.localScale *= 1.65f / bounds.size.y;
|
|
}
|
|
|
|
bounds = GetBounds(villager);
|
|
villager.transform.position += new Vector3(-bounds.center.x, -bounds.min.y, 0.6f - bounds.center.z);
|
|
villager.transform.Rotate(0f, 180f, 0f);
|
|
|
|
AttachIdle(villager);
|
|
}
|
|
|
|
private static void AttachIdle(GameObject villager)
|
|
{
|
|
MakeClipsLoop();
|
|
|
|
AnimationClip idle = FindIdleClip();
|
|
if (idle == null) return;
|
|
|
|
Animator animator = villager.GetComponent<Animator>();
|
|
if (animator == null) animator = villager.AddComponent<Animator>();
|
|
string controllerPath = GeneratedFolder + "/VillagerTest.controller";
|
|
AnimatorController controller = AssetDatabase.LoadAssetAtPath<AnimatorController>(controllerPath);
|
|
if (controller == null) controller = AnimatorController.CreateAnimatorControllerAtPathWithClip(controllerPath, idle);
|
|
animator.runtimeAnimatorController = controller;
|
|
Debug.Log($"[IllOmen] Villager idle clip: {idle.name}");
|
|
}
|
|
|
|
private static void MakeClipsLoop()
|
|
{
|
|
ModelImporter importer = (ModelImporter)AssetImporter.GetAtPath(VillagerPath);
|
|
ModelImporterClipAnimation[] clips = importer.defaultClipAnimations;
|
|
if (clips == null || clips.Length == 0 || importer.clipAnimations.Length > 0) return;
|
|
foreach (ModelImporterClipAnimation clip in clips) clip.loopTime = true;
|
|
importer.clipAnimations = clips;
|
|
importer.SaveAndReimport();
|
|
}
|
|
|
|
private static AnimationClip FindIdleClip()
|
|
{
|
|
AnimationClip[] clips = AssetDatabase.LoadAllAssetsAtPath(VillagerPath)
|
|
.OfType<AnimationClip>()
|
|
.Where(clip => !clip.name.StartsWith("__preview__"))
|
|
.ToArray();
|
|
Debug.Log("[IllOmen] Villager clips: " + string.Join(", ", clips.Select(clip => clip.name)));
|
|
return clips.FirstOrDefault(clip => clip.name.ToLowerInvariant().Contains("idle")) ?? clips.FirstOrDefault();
|
|
}
|
|
|
|
private static void BuildCandleLight()
|
|
{
|
|
GameObject flame = new GameObject("Candle Light");
|
|
flame.transform.position = new Vector3(0.5f, 1.05f, -0.5f);
|
|
Light light = flame.AddComponent<Light>();
|
|
light.type = LightType.Point;
|
|
light.color = new Color(1f, 0.78f, 0.55f);
|
|
light.range = 9f;
|
|
light.intensity = 6f;
|
|
light.shadows = LightShadows.None;
|
|
flame.AddComponent<CandleFlicker>();
|
|
|
|
// A dim, cold light so the far walls and the door stay readable.
|
|
GameObject fill = new GameObject("Fill Light");
|
|
fill.transform.position = new Vector3(0f, 2.6f, 1.6f);
|
|
Light fillLight = fill.AddComponent<Light>();
|
|
fillLight.type = LightType.Point;
|
|
fillLight.color = new Color(0.55f, 0.65f, 0.9f);
|
|
fillLight.range = 8f;
|
|
fillLight.intensity = 1.5f;
|
|
fillLight.shadows = LightShadows.None;
|
|
}
|
|
|
|
private static void BuildCamera()
|
|
{
|
|
GameObject cameraObject = new GameObject("Main Camera") { tag = "MainCamera" };
|
|
cameraObject.transform.position = new Vector3(0f, 1.5f, -1.6f);
|
|
cameraObject.transform.rotation = Quaternion.Euler(4f, 0f, 0f);
|
|
|
|
Camera camera = cameraObject.AddComponent<Camera>();
|
|
camera.clearFlags = CameraClearFlags.SolidColor;
|
|
camera.backgroundColor = Color.black;
|
|
camera.fieldOfView = 60f;
|
|
camera.nearClipPlane = 0.05f;
|
|
camera.GetUniversalAdditionalCameraData().renderPostProcessing = true;
|
|
|
|
cameraObject.AddComponent<AudioListener>();
|
|
cameraObject.AddComponent<LookAround>();
|
|
}
|
|
|
|
private static void BuildVolume()
|
|
{
|
|
// Reuse the same asset so other scenes keep pointing at it after a rebuild.
|
|
string profilePath = GeneratedFolder + "/MillVolumeProfile.asset";
|
|
VolumeProfile profile = AssetDatabase.LoadAssetAtPath<VolumeProfile>(profilePath);
|
|
if (profile == null)
|
|
{
|
|
profile = ScriptableObject.CreateInstance<VolumeProfile>();
|
|
AssetDatabase.CreateAsset(profile, profilePath);
|
|
}
|
|
foreach (VolumeComponent component in profile.components.ToArray())
|
|
{
|
|
AssetDatabase.RemoveObjectFromAsset(component);
|
|
Object.DestroyImmediate(component, true);
|
|
}
|
|
profile.components.Clear();
|
|
|
|
PSXEffectSettings psx = profile.Add<PSXEffectSettings>(true);
|
|
psx.name = "PSX";
|
|
psx.AmbientColor.value = new Color(0.1f, 0.1f, 0.13f);
|
|
psx.PixelResolution.value = new Vector2(640f, 360f);
|
|
// The filter rounds each colour channel in linear space, which turns dark greys into purple blotches.
|
|
psx.EnableColorPrecision.value = false;
|
|
psx.ColorPrecision.value = 256f;
|
|
psx.EnableFog.value = false;
|
|
|
|
// Readable baseline: only soft scanlines and a vignette. VHS effects return one at a time.
|
|
CRTSettings crt = profile.Add<CRTSettings>(true);
|
|
crt.name = "CRT";
|
|
crt.EnableScreenBend.value = false;
|
|
crt.EnableInterlacedRendering.value = false;
|
|
crt.VignetteOpacity.value = 0.3f;
|
|
crt.ScanLineStrength.value = 0.05f;
|
|
crt.EnableNoise.value = false;
|
|
crt.EnableVhs.value = false;
|
|
crt.EnableTrackerLine.value = false;
|
|
crt.EnableGlitch.value = false;
|
|
crt.EnableSignalInterference.value = false;
|
|
crt.EnableChromaticAberration.value = false;
|
|
crt.EnableSubPixels.value = false;
|
|
crt.EnableUnsharp.value = false;
|
|
crt.ClampBlack.value = 0f;
|
|
crt.ClampWhite.value = 1f;
|
|
crt.Gamma.value = 0.5f;
|
|
|
|
AssetDatabase.AddObjectToAsset(psx, profile);
|
|
AssetDatabase.AddObjectToAsset(crt, profile);
|
|
EditorUtility.SetDirty(profile);
|
|
AssetDatabase.SaveAssets();
|
|
|
|
GameObject volumeObject = new GameObject("Global Volume");
|
|
Volume volume = volumeObject.AddComponent<Volume>();
|
|
volume.isGlobal = true;
|
|
volume.sharedProfile = profile;
|
|
}
|
|
|
|
private static void BuildAmbience()
|
|
{
|
|
string guid = AssetDatabase.FindAssets("Those_Who_Wait t:AudioClip").FirstOrDefault();
|
|
if (guid == null)
|
|
{
|
|
Debug.LogWarning("[IllOmen] Ambience clip not found");
|
|
return;
|
|
}
|
|
|
|
GameObject ambience = new GameObject("Ambience");
|
|
AudioSource source = ambience.AddComponent<AudioSource>();
|
|
source.clip = AssetDatabase.LoadAssetAtPath<AudioClip>(AssetDatabase.GUIDToAssetPath(guid));
|
|
source.loop = true;
|
|
source.volume = 0.35f;
|
|
source.playOnAwake = true;
|
|
}
|
|
|
|
/// <summary>Places a kit piece by its base centre, whatever pivot the model has.</summary>
|
|
private static GameObject Place(string piece, Vector3 position, float yaw, Transform parent, string primary, string secondary, string tertiary)
|
|
{
|
|
Transform source = kit.GetComponentsInChildren<Transform>(true).FirstOrDefault(t => t.name == piece);
|
|
if (source == null)
|
|
{
|
|
Debug.LogError($"[IllOmen] Kit piece not found: {piece}");
|
|
return null;
|
|
}
|
|
|
|
GameObject model = Object.Instantiate(source.gameObject);
|
|
model.name = "Model";
|
|
model.transform.SetPositionAndRotation(Vector3.zero, source.rotation);
|
|
model.transform.localScale = source.lossyScale;
|
|
|
|
foreach (Renderer renderer in model.GetComponentsInChildren<Renderer>())
|
|
{
|
|
Material[] materials = renderer.sharedMaterials;
|
|
for (int i = 0; i < materials.Length; i++)
|
|
{
|
|
string slot = materials[i] != null ? materials[i].name : string.Empty;
|
|
string texture = slot.Contains("Plum") ? secondary : slot.Contains("LightGreen") ? tertiary : primary;
|
|
materials[i] = GetMaterial(texture);
|
|
}
|
|
renderer.sharedMaterials = materials;
|
|
}
|
|
|
|
Bounds bounds = GetBounds(model);
|
|
model.transform.position = new Vector3(-bounds.center.x, -bounds.min.y, -bounds.center.z);
|
|
|
|
GameObject holder = new GameObject(piece);
|
|
model.transform.SetParent(holder.transform, true);
|
|
holder.transform.SetPositionAndRotation(position, Quaternion.Euler(0f, yaw, 0f));
|
|
holder.transform.SetParent(parent, true);
|
|
return holder;
|
|
}
|
|
|
|
/// <summary>The example material cuts holes by texture alpha, which our solid surfaces must not do.</summary>
|
|
private static void MakeOpaque(Material material)
|
|
{
|
|
material.DisableKeyword("_ALPHATEST_ON");
|
|
material.SetFloat("_AlphaClip", 0f);
|
|
material.SetFloat("_AlphaToMask", 0f);
|
|
material.SetOverrideTag("RenderType", "Opaque");
|
|
material.renderQueue = (int)RenderQueue.Geometry;
|
|
}
|
|
|
|
private static Bounds GetBounds(GameObject target)
|
|
{
|
|
Renderer[] renderers = target.GetComponentsInChildren<Renderer>();
|
|
Bounds bounds = renderers[0].bounds;
|
|
foreach (Renderer renderer in renderers) bounds.Encapsulate(renderer.bounds);
|
|
return bounds;
|
|
}
|
|
|
|
private static Material GetMaterial(string kitTexture)
|
|
{
|
|
if (MaterialCache.TryGetValue(kitTexture, out Material cached)) return cached;
|
|
|
|
string guid = AssetDatabase.FindAssets($"{kitTexture} t:Texture2D", new[] { "Assets/ThirdParty/Models/RetroMedievalKit" }).FirstOrDefault();
|
|
if (guid == null) Debug.LogError($"[IllOmen] Kit texture not found: {kitTexture}");
|
|
Texture2D texture = guid == null ? null : AssetDatabase.LoadAssetAtPath<Texture2D>(AssetDatabase.GUIDToAssetPath(guid));
|
|
return GetMaterial(texture, kitTexture);
|
|
}
|
|
|
|
private static Material GetMaterial(Texture2D texture, string name)
|
|
{
|
|
if (MaterialCache.TryGetValue(name, out Material cached)) return cached;
|
|
|
|
// Update existing materials in place so other scenes keep their references.
|
|
string path = $"{GeneratedFolder}/Materials/{name}.mat";
|
|
Material existing = AssetDatabase.LoadAssetAtPath<Material>(path);
|
|
Material material = existing != null ? existing : new Material(baseMaterial) { name = name };
|
|
if (existing != null) material.CopyPropertiesFromMaterial(baseMaterial);
|
|
material.SetTexture("_MainTex", texture);
|
|
if (material.HasProperty("_BaseMap")) material.SetTexture("_BaseMap", texture);
|
|
MakeOpaque(material);
|
|
|
|
// Finer vertices and milder texture warping keep large, close surfaces readable.
|
|
material.SetFloat("_VertexPrecision", 64f);
|
|
material.SetFloat("_AffineWrapStrength", 0.25f);
|
|
|
|
// The example material rounds colours to 16 levels in linear space: dark surfaces turn
|
|
// pure black, with noisy purple and green stripes wherever they cross a level.
|
|
material.SetFloat("_UseColorQuantization", 0f);
|
|
|
|
if (existing == null) AssetDatabase.CreateAsset(material, path);
|
|
EditorUtility.SetDirty(material);
|
|
MaterialCache[name] = material;
|
|
return material;
|
|
}
|
|
}
|
|
}
|