Files
UmaViewer/Assets/Scripts/UmaViewerBuilder.cs

1466 lines
58 KiB
C#

using CriWareFormats;
using Gallop;
using Gallop.Live;
using Gallop.Live.Cutt;
using NAudio.Wave;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Data;
using System.IO;
using System.Linq;
using System.Text;
using UmaMusumeAudio;
using UnityEngine;
using UnityEngine.SceneManagement;
using Debug = UnityEngine.Debug;
using Random = UnityEngine.Random;
public class UmaViewerBuilder : MonoBehaviour
{
public static UmaViewerBuilder Instance;
static UmaViewerMain Main => UmaViewerMain.Instance;
static UmaViewerUI UI => UmaViewerUI.Instance;
static UISettingsModel ModelSettings => UmaViewerUI.Instance.ModelSettings;
public List<Shader> ShaderList = new List<Shader>();
public Material TransMaterialCharas;
public UmaContainerCharacter CurrentUMAContainer;
public UmaContainer CurrentOtherContainer;
public UmaHeadData CurrentHead;
public List<AudioSource> CurrentAudioSources = new List<AudioSource>();
public List<UmaLyricsData> CurrentLyrics = new List<UmaLyricsData>();
// Used for keeping track for exports
public List<UmaDatabaseEntry> CurrentLiveSoundAWB = new List<UmaDatabaseEntry>();
public int CurrentLiveSoundAWBIndex = -1;
public AnimatorOverrideController OverrideController;
public AnimatorOverrideController FaceOverrideController;
public AnimatorOverrideController CameraOverrideController;
public Animator AnimationCameraAnimator;
public Camera AnimationCamera;
public GameObject LiveControllerPrefab;
// ---- Normal (non-mob, non-mini) costume visibility options ----
public enum NormalCostumeHideMode
{
None = 0,
// Hides the entire body renderer hierarchy (clothes are usually baked into the body prefab).
HideAllBodyRenderers = 1,
// Tries to hide only "clothes" renderers by matching renderer/material names (adjust keywords after checking logs).
HideByNameOrMaterial = 2
}
[Header("Normal Costume Visibility")]
public NormalCostumeHideMode NormalHideMode = NormalCostumeHideMode.None;
[Tooltip("Prints body renderers/materials once after loading a normal model (useful to tune keywords).")]
public bool DumpNormalRenderersOnce = false;
// Keywords used by HideByNameOrMaterial (edit these based on Dump logs).
public string[] NormalClothKeywords = new[] { "cloth", "clothes", "dress", "wear", "costume", "skirt", "uniform", "outer" };
public string[] NormalBodyKeywords = new[] { "skin", "body", "bdy", "face", "head", "eye", "hair" };
private void Awake()
{
Instance = this;
}
public IEnumerator LoadUma(CharaEntry chara, string costumeId, bool mini, string haedCostumeId = "")
{
int id = chara.Id;
var umaContainer = new GameObject($"Chara_{id}_{costumeId}").AddComponent<UmaContainerCharacter>();
CurrentUMAContainer = umaContainer;
if (mini)
{
umaContainer.CharaData = UmaDatabaseController.ReadCharaData(chara);
LoadMiniUma(umaContainer, chara, costumeId);
}
else if (chara.IsMob)
{
umaContainer.CharaData = UmaDatabaseController.ReadCharaData(chara);
LoadMobUma(umaContainer, chara, costumeId, loadMotion: true);
}
else if (ModelSettings.IsHeadFix && CurrentHead != null && CurrentHead.chara.IsMob)
{
umaContainer.CharaData = UmaDatabaseController.ReadCharaData(CurrentHead.chara);
LoadMobUma(umaContainer, CurrentHead.chara, costumeId, chara.Id, true);
}
else
{
umaContainer.CharaData = UmaDatabaseController.ReadCharaData(chara);
LoadNormalUma(umaContainer, chara, costumeId, true, haedCostumeId);
}
yield break;
}
public void LoadLiveUma(List<LiveCharacterLoadData> characters)
{
for (int i = 0; i < characters.Count; i++)
{
if (characters[i].CharaEntry.Name != "")
{
var umaContainer = new GameObject($"Chara_{characters[i].CharaEntry.Id}_{characters[i].CostumeId}").AddComponent<UmaContainerCharacter>();
umaContainer.IsLive = true;
umaContainer.CharaData = UmaDatabaseController.ReadCharaData(characters[i].CharaEntry);
var charObjs = Gallop.Live.Director.instance.charaObjs;
umaContainer.transform.parent = charObjs[i];
umaContainer.transform.localPosition = new Vector3();
if (characters[i].CharaEntry.IsMob)
{
LoadMobUma(umaContainer, characters[i].CharaEntry, characters[i].CostumeId);
}
else
{
LoadNormalUma(umaContainer, characters[i].CharaEntry, characters[i].CostumeId, false, characters[i].HeadCostumeId);
}
if (umaContainer.UmaAnimator != null)
{
umaContainer.UmaAnimator.enabled = false;
umaContainer.isAnimatorControl = false;
}
umaContainer.ConfigureLivePhysics();
Gallop.Live.LiveRuntimeDiagnostics.RecordInstantiation($"character_slot_{i}", umaContainer.gameObject);
Gallop.Live.Director.instance.CharaContainerScript.Add(umaContainer);
}
}
}
private void LoadNormalUma(UmaContainerCharacter umaContainer, CharaEntry chara, string costumeId, bool loadMotion = false, string haedCostumeId = "")
{
int id = chara.Id;
umaContainer.CharaEntry = chara;
DataRow charaData = umaContainer.CharaData;
bool genericCostume = umaContainer.IsGeneric = costumeId.Length >= 4;
string skin, height, socks, bust, sex, shape, costumeIdShort = "";
skin = charaData["skin"].ToString();
height = charaData["height"].ToString();
socks = charaData["socks"].ToString();
bust = charaData["bust"].ToString();
sex = charaData["sex"].ToString();
shape = charaData["shape"].ToString();
UmaDatabaseEntry asset = null;
umaContainer.VarCostumeIdLong = costumeId;
if (genericCostume)
{
costumeIdShort = costumeId.Remove(costumeId.LastIndexOf('_'));
umaContainer.VarCostumeIdShort = costumeIdShort;
umaContainer.VarBust = bust;
umaContainer.VarSkin = skin;
umaContainer.VarSocks = socks;
umaContainer.VarHeight = height;
// Pattern for generic body type is as follows:
//
// (costume id)_(body_type_sub)_(body_setting)_(height)_(shape)_(bust)
//
// body_type_sub is used for variants like the summer/winter uniform or the swimsuit/towel
// body_setting is used for subvariants of each variant like the big belly version of the uniform, and the genders for the tracksuits
//
// Some models will naturally be missing due to how this system is designed.
string body = UmaDatabaseController.BodyPath + $"bdy{costumeIdShort}/pfb_bdy{costumeId}_{height}_{shape}_{bust}";
UmaViewerMain.Instance.AbList.TryGetValue(body, out asset);
}
else UmaViewerMain.Instance.AbList.TryGetValue(UmaDatabaseController.BodyPath + $"bdy{id}_{costumeId}/pfb_bdy{id}_{costumeId}", out asset);
if (asset == null)
{
Debug.LogError("No body, can't load!");
UmaViewerUI.Instance?.ShowMessage("No body, can't load!", UIMessageType.Error);
return;
}
else if (genericCostume)
{
string texPattern1 = "", texPattern2 = "", texPattern3 = "", texPattern4 = "", texPattern5 = "";
switch (costumeId.Split('_')[0])
{
case "0001":
texPattern1 = $"tex_bdy{costumeIdShort}_00_{skin}_{bust}_0{socks}";
texPattern2 = $"tex_bdy{costumeIdShort}_00_0_{bust}";
texPattern3 = $"tex_bdy{costumeIdShort}_zekken";
texPattern4 = $"tex_bdy{costumeIdShort}_00_waku";
texPattern5 = $"tex_bdy{costumeIdShort}_num";
break;
case "0003":
texPattern1 = $"tex_bdy{costumeIdShort}_00_{skin}_{bust}";
texPattern2 = $"tex_bdy{costumeIdShort}_00_0_{bust}";
break;
case "0006": //last var is color?
texPattern1 = $"tex_bdy{costumeId}_{skin}_{bust}_0{0}";
texPattern2 = $"tex_bdy{costumeId}_0_{bust}_00_";
break;
default:
texPattern1 = $"tex_bdy{costumeId}_{skin}_{bust}";
texPattern2 = $"tex_bdy{costumeId}_0_{bust}";
break;
}
Debug.Log(texPattern1 + " " + texPattern2);
//Load Body Textures
foreach (var asset1 in UmaViewerMain.Instance.AbChara.Where(a => a.Name.StartsWith(UmaDatabaseController.BodyPath)
&& (a.Name.Contains(texPattern1)
|| a.Name.Contains(texPattern2)
|| (string.IsNullOrEmpty(texPattern3) ? false : a.Name.Contains(texPattern3))
|| (string.IsNullOrEmpty(texPattern4) ? false : a.Name.Contains(texPattern4))
|| (string.IsNullOrEmpty(texPattern5) ? false : a.Name.Contains(texPattern5)))))
{
umaContainer.LoadTextures(asset1);
}
//Load Body
umaContainer.LoadBody(asset);
//Load Physics
if (Main.AbList.TryGetValue(UmaDatabaseController.BodyPath + $"bdy{costumeIdShort}/clothes/pfb_bdy{costumeIdShort}_cloth00", out _))
{
var asset1 = Main.AbList[UmaDatabaseController.BodyPath + $"bdy{costumeIdShort}/clothes/pfb_bdy{costumeIdShort}_cloth00"];
umaContainer.LoadPhysics(asset1);
asset1 = Main.AbList[UmaDatabaseController.BodyPath + $"bdy{costumeIdShort}/clothes/pfb_bdy{costumeIdShort}_bust{bust}_cloth00"];
umaContainer.LoadPhysics(asset1);
}
}
else
{
umaContainer.LoadBody(asset);
// Preserve master5's verified thigh/hip skirt collision fallback for winner outfits.
const string schoolUniformCollision = "3d/chara/body/bdy0002_00/clothes/pfb_bdy0002_00_cloth00";
if (Main.AbList.TryGetValue(schoolUniformCollision, out UmaDatabaseEntry schoolCollisionAsset))
umaContainer.LoadSchoolUniformSkirtCollision(schoolCollisionAsset);
//Load Physics
var asset1 = Main.AbList[UmaDatabaseController.BodyPath + $"bdy{id}_{costumeId}/clothes/pfb_bdy{id}_{costumeId}_cloth00"];
umaContainer.LoadPhysics(asset1);
asset1 = Main.AbList[UmaDatabaseController.BodyPath + $"bdy{id}_{costumeId}/clothes/pfb_bdy{id}_{costumeId}_bust_cloth00"];
umaContainer.LoadPhysics(asset1);
}
// Record Head Data
int head_id;
string head_costumeId;
int tailId = Convert.ToInt32(charaData["tail_model_id"]);
if (ModelSettings.IsHeadFix && CurrentHead != null)
{
head_id = CurrentHead.id;
head_costumeId = CurrentHead.costumeId;
tailId = CurrentHead.tailId;
}
else
{
head_id = id;
head_costumeId = string.IsNullOrEmpty(haedCostumeId) ? costumeId : haedCostumeId;
CurrentHead = new UmaHeadData
{
id = id,
costumeId = head_costumeId,
tailId = tailId,
chara = chara
};
}
string head = UmaDatabaseController.HeadPath + $"chr{head_id}_{head_costumeId}/pfb_chr{head_id}_{head_costumeId}";
asset = null;
Main.AbList.TryGetValue(head, out asset);
bool isDefaultHead = false;
//Some costumes don't have custom heads
if (head_costumeId != "00" && asset == null)
{
asset = Main.AbList[UmaDatabaseController.HeadPath + $"chr{head_id}_00/pfb_chr{head_id}_00"];
isDefaultHead = true;
}
if (asset != null)
{
//Load Hair Textures
foreach (var asset1 in UmaViewerMain.Instance.AbChara.Where(a => a.Name.StartsWith($"{UmaDatabaseController.HeadPath}chr{head_id}_{head_costumeId}/textures")))
{
umaContainer.LoadTextures(asset1);
}
//Load Head
umaContainer.LoadHead(asset);
//Load Physics
// Load head CySpring physics.
//角色实际加载了哪个头,就必须加载同一个头目录下的 cloth。
string actualHeadCostumeId = isDefaultHead ? "00" : head_costumeId;
string headClothPath =
UmaDatabaseController.HeadPath +
$"chr{head_id}_{actualHeadCostumeId}/clothes/" +
$"pfb_chr{head_id}_{actualHeadCostumeId}_cloth00";
if (Main.AbList.TryGetValue(headClothPath, out UmaDatabaseEntry headClothAsset))
{
Debug.Log($"[UmaViewerBuilder] Load head physics: {headClothPath}");
umaContainer.LoadPhysics(headClothAsset);
}
else
{
string message =
$"[UmaViewerBuilder] Head CySpring physics not found: {headClothPath}";
Debug.LogError(message);
// CySpring 缺失会继续加载角色,但必须通过用户界面的错误级别提示用户。
UmaViewerUI.Instance?.ShowMessage(
$"Head CySpring physics not found:\n{headClothPath}",
UIMessageType.Error
);
}
}
//修改(载入专用尾巴相关)
if (tailId > 0)
{
string costumePrefixForTail = $"{id}_{costumeId}";
string exclusiveTailName = $"tail{costumePrefixForTail}";
string exclusiveTailPath = $"3d/chara/tail/{exclusiveTailName}/";
string exclusiveTailPfb = $"{exclusiveTailPath}pfb_{exclusiveTailName}";
if (Main.AbList.TryGetValue(exclusiveTailPfb, out asset))
{
umaContainer.LoadExclusiveTail(asset);
string clothPath = $"{exclusiveTailPath}clothes/pfb_{exclusiveTailName}_cloth00";
if (Main.AbList.TryGetValue(clothPath, out var clothAsset))
{
umaContainer.LoadPhysics(clothAsset);
}
}
else
{
string tailName = $"tail{tailId.ToString().PadLeft(4, '0')}_00";
string tailPath = $"3d/chara/tail/{tailName}/";
string tailPfb = $"{tailPath}pfb_{tailName}";
asset = null;
if (Main.AbList.TryGetValue(tailPfb, out asset) && asset != null)
{
foreach (var asset2 in UmaViewerMain.Instance.AbChara.Where(a => a.Name.StartsWith($"{tailPath}textures/tex_{tailName}_{head_id}") || a.Name.StartsWith($"{tailPath}textures/tex_{tailName}_0000")))
{
umaContainer.LoadTextures(asset2);
}
umaContainer.LoadTail(asset);
string clothPath = $"{tailPath}clothes/pfb_{tailName}_cloth00";
if (Main.AbList.TryGetValue(clothPath, out var clothAsset))
{
umaContainer.LoadPhysics(clothAsset);
}
}
else
{
Debug.Log("no tail");
}
}
}
umaContainer.LoadPhysics();
umaContainer.SetDynamicBoneEnable(ModelSettings.DynamicBoneEnable);
umaContainer.LoadFaceMorph(id, costumeId);
umaContainer.TearControllers.ForEach(a => a.SetDir(a.CurrentDir));
umaContainer.HeadBone = (GameObject)umaContainer.Body.GetComponent<AssetHolder>()._assetTable["head"];
umaContainer.EyeHeight = umaContainer.Head.GetComponent<AssetHolder>()._assetTableValue["head_center_offset_y"];
umaContainer.MergeModel();
ApplyNormalCostumeVisibilityOptions(umaContainer);
umaContainer.SetHeight(-1);
umaContainer.Initialize(!ModelSettings.IsTPose);
umaContainer.Position = umaContainer.transform.Find("Position");
umaContainer.SetupBoneHandles();
if (!ModelSettings.IsTPose && loadMotion)
{
if (Main.AbList.TryGetValue($"3d/motion/event/body/chara/chr{id}_00/anm_eve_chr{id}_00_idle01_loop", out UmaDatabaseEntry entry))
{
umaContainer.LoadAnimation(entry);
}
}
//修改(载入通用服装ColorSet相关)
UI.ClearColorSetButtons();
UI.LoadColorSetButtons();
}
private void LoadMobUma(UmaContainerCharacter umaContainer, CharaEntry chara, string costumeId, int bodyid = -1, bool loadMotion = false)
{
int id = chara.Id;
umaContainer.CharaEntry = chara;
umaContainer.IsMob = chara.IsMob;
umaContainer.MobDressColor = UmaDatabaseController.ReadMobDressColor(umaContainer.CharaData["dress_color_id"].ToString());
umaContainer.MobHeadColor = UmaDatabaseController.ReadMobHairColor(umaContainer.CharaData["chara_hair_color"].ToString());
DataRow charaData = umaContainer.CharaData;
bool genericCostume = umaContainer.IsGeneric = costumeId.Length >= 4;
string skin, height, socks, bust, sex, shape, costumeIdShort = "";
string faceid, hairid, personality;
faceid = charaData["chara_face_model"].ToString();
hairid = charaData["chara_hair_model"].ToString();
personality = charaData["default_personality"].ToString();
skin = charaData["chara_skin_color"].ToString();
height = "1";
socks = charaData["socks"].ToString();
bust = charaData["chara_bust_size"].ToString();
sex = charaData["sex"].ToString();
shape = "0";
UmaDatabaseEntry asset = null;
if (genericCostume)
{
costumeIdShort = costumeId.Remove(costumeId.LastIndexOf('_'));
umaContainer.VarCostumeIdShort = costumeIdShort;
umaContainer.VarCostumeIdLong = costumeId;
umaContainer.VarBust = bust;
umaContainer.VarSkin = skin;
umaContainer.VarSocks = socks;
umaContainer.VarHeight = height;
// Pattern for generic body type is as follows:
//
// (costume id)_(body_type_sub)_(body_setting)_(height)_(shape)_(bust)
//
// body_type_sub is used for variants like the summer/winter uniform or the swimsuit/towel
// body_setting is used for subvariants of each variant like the big belly version of the uniform, and the genders for the tracksuits
//
// Some models will naturally be missing due to how this system is designed.
string body = "";
body = UmaDatabaseController.BodyPath + $"bdy{costumeIdShort}/pfb_bdy{costumeId}_{height}_{shape}_{bust}";
Debug.Log("Looking for " + body);
Main.AbList.TryGetValue(body, out asset);
}
else Main.AbList.TryGetValue($"{UmaDatabaseController.BodyPath}bdy{bodyid}_{costumeId}/pfb_bdy{bodyid}_{costumeId}", out asset);
if (asset == null)
{
Debug.LogError("No body, can't load!");
UmaViewerUI.Instance?.ShowMessage("No body, can't load!", UIMessageType.Error);
return;
}
else if (genericCostume)
{
string texPattern1 = "", texPattern2 = "", texPattern3 = "", texPattern4 = "", texPattern5 = "";
switch (costumeId.Split('_')[0])
{
case "0001":
texPattern1 = $"tex_bdy{costumeIdShort}_00_{skin}_{bust}_0{socks}";
texPattern2 = $"tex_bdy{costumeIdShort}_00_0_{bust}";
texPattern3 = $"tex_bdy{costumeIdShort}_zekken";
texPattern4 = $"tex_bdy{costumeIdShort}_00_waku";
texPattern5 = $"tex_bdy{costumeIdShort}_num";
break;
case "0003":
texPattern1 = $"tex_bdy{costumeIdShort}_00_{skin}_{bust}";
texPattern2 = $"tex_bdy{costumeIdShort}_00_0_{bust}";
break;
case "0006": //last var is color?
texPattern1 = $"tex_bdy{costumeId}_{skin}_{bust}_0{0}";
texPattern2 = $"tex_bdy{costumeId}_0_{bust}_00_";
break;
default:
texPattern1 = $"tex_bdy{costumeId}_{skin}_{bust}";
texPattern2 = $"tex_bdy{costumeId}_0_{bust}";
break;
}
Debug.Log(texPattern1 + " " + texPattern2);
//Load Body Textures
foreach (var asset1 in UmaViewerMain.Instance.AbChara.Where(a => a.Name.StartsWith(UmaDatabaseController.BodyPath)
&& (a.Name.Contains(texPattern1)
|| a.Name.Contains(texPattern2)
|| (string.IsNullOrEmpty(texPattern3) ? false : a.Name.Contains(texPattern3))
|| (string.IsNullOrEmpty(texPattern4) ? false : a.Name.Contains(texPattern4))
|| (string.IsNullOrEmpty(texPattern5) ? false : a.Name.Contains(texPattern5)))))
{
umaContainer.LoadTextures(asset1);
}
//Load Body
umaContainer.LoadBody(asset);
//Load Physics
if (Main.AbList.TryGetValue(UmaDatabaseController.BodyPath + $"bdy{costumeIdShort}/clothes/pfb_bdy{costumeIdShort}_cloth00", out _))
{
var asset1 = Main.AbList[UmaDatabaseController.BodyPath + $"bdy{costumeIdShort}/clothes/pfb_bdy{costumeIdShort}_cloth00"];
umaContainer.LoadPhysics(asset1);
asset1 = Main.AbList[UmaDatabaseController.BodyPath + $"bdy{costumeIdShort}/clothes/pfb_bdy{costumeIdShort}_bust{bust}_cloth00"];
umaContainer.LoadPhysics(asset1);
}
}
else
{
umaContainer.LoadBody(asset);
//Load Physics
var asset1 = Main.AbList[UmaDatabaseController.BodyPath + $"bdy{bodyid}_{costumeId}/clothes/pfb_bdy{bodyid}_{costumeId}_cloth00"];
umaContainer.LoadPhysics(asset1);
asset1 = Main.AbList[UmaDatabaseController.BodyPath + $"bdy{bodyid}_{costumeId}/clothes/pfb_bdy{bodyid}_{costumeId}_bust_cloth00"];
umaContainer.LoadPhysics(asset1);
}
// Record Head Data
int head_id = 1;
string head_costumeId = "00";
int tailId = 1;
CurrentHead = new UmaHeadData
{
id = id,
costumeId = costumeId,
tailId = tailId,
chara = chara
};
var head_s = head_id.ToString().PadLeft(4, '0');
string head = UmaDatabaseController.HeadPath + $"chr{head_s}_{head_costumeId}/pfb_chr{head_s}_{head_costumeId}_face{faceid.PadLeft(3, '0')}";
string hair = UmaDatabaseController.HeadPath + $"chr{head_s}_{head_costumeId}/pfb_chr{head_s}_{head_costumeId}_hair{hairid.PadLeft(3, '0')}";
Main.AbList.TryGetValue(head, out asset);
Main.AbList.TryGetValue(hair, out UmaDatabaseEntry hairasset);
bool isDefaultHead = true;
if (asset != null && hairasset != null)
{
//Load Face And Hair Textures
foreach (var asset1 in UmaViewerMain.Instance.AbChara.Where(a => a.Name.StartsWith($"{UmaDatabaseController.HeadPath}chr{head_s}_{head_costumeId}/textures")))
{
if (asset1.Name.Contains($"tex_chr{head_s}_{head_costumeId}_face{faceid.PadLeft(3, '0')}_0")
|| asset1.Name.Contains($"tex_chr{head_s}_{head_costumeId}_face{faceid.PadLeft(3, '0')}_{skin}"))
{
umaContainer.LoadTextures(asset1);
}
if (asset1.Name.Contains($"tex_chr{head_s}_{head_costumeId}_hair{hairid.PadLeft(3, '0')}"))
{
umaContainer.LoadTextures(asset1);
}
}
//Load face
umaContainer.LoadHead(asset);
//Load hair
umaContainer.LoadHair(hairasset);
umaContainer.MergeHairModel();
//Load Physics
// Load mob hair CySpring physics.
string paddedHairId = hairid.PadLeft(3, '0');
string hairClothPath =
$"{UmaDatabaseController.HeadPath}" +
$"chr{head_s}_{head_costumeId}/clothes/" +
$"pfb_chr{head_s}_{head_costumeId}_hair{paddedHairId}_cloth00";
if (Main.AbList.TryGetValue(
hairClothPath,
out UmaDatabaseEntry hairClothAsset))
{
Debug.Log($"[UmaViewerBuilder] Load mob hair physics: {hairClothPath}");
umaContainer.LoadPhysics(hairClothAsset);
}
else
{
// 部分资源可能使用普通 cloth00,而不是 hairXXX_cloth00。
string fallbackHeadClothPath =
$"{UmaDatabaseController.HeadPath}" +
$"chr{head_s}_{head_costumeId}/clothes/" +
$"pfb_chr{head_s}_{head_costumeId}_cloth00";
if (Main.AbList.TryGetValue(
fallbackHeadClothPath,
out UmaDatabaseEntry fallbackHeadClothAsset))
{
// 缺失专用 CySpring 时先报错,仍使用普通头部物理维持可预览状态。
string message =
$"[UmaViewerBuilder] Mob hair CySpring is missing. " +
$"Fallback to head physics: {fallbackHeadClothPath}\n" +
$"missing={hairClothPath}";
Debug.LogError(message);
UmaViewerUI.Instance?.ShowMessage(
$"Mob hair CySpring physics not found. Using fallback physics.\n" +
$"missing={hairClothPath}\nfallback={fallbackHeadClothPath}",
UIMessageType.Error
);
umaContainer.LoadPhysics(fallbackHeadClothAsset);
}
else
{
string message =
"[UmaViewerBuilder] Mob head CySpring physics not found.\n" +
$"hair={hairClothPath}\n" +
$"fallback={fallbackHeadClothPath}";
Debug.LogError(message);
UmaViewerUI.Instance?.ShowMessage(
"Mob head CySpring physics not found.\n" +
$"hair={hairClothPath}\nfallback={fallbackHeadClothPath}",
UIMessageType.Error
);
}
}
}
if (tailId > 0)
{
string tailName = $"tail{tailId.ToString().PadLeft(4, '0')}_00";
string tailPath = $"3d/chara/tail/{tailName}/";
string tailPfb = tailPath + $"pfb_{tailName}";
asset = UmaViewerMain.Instance.AbChara.FirstOrDefault(a => a.Name == tailPfb);
if (asset != null)
{
foreach (var asset1 in UmaViewerMain.Instance.AbChara.Where(a => a.Name.StartsWith($"{tailPath}textures/tex_{tailName}_{head_s}") || a.Name.StartsWith($"{tailPath}textures/tex_{tailName}_0000")))
{
umaContainer.LoadTextures(asset1);
}
umaContainer.LoadTail(asset);
//Load Physics
if (Main.AbList.TryGetValue($"{tailPath}clothes/pfb_{tailName}_cloth00", out UmaDatabaseEntry asset2))
{
umaContainer.LoadPhysics(asset2);
}
}
else
{
Debug.Log("no tail");
}
}
umaContainer.LoadPhysics(); //Need to load physics before loading FacialMorph
umaContainer.SetDynamicBoneEnable(ModelSettings.DynamicBoneEnable);
//Load FacialMorph
umaContainer.LoadFaceMorph(id, costumeId);
umaContainer.TearControllers.ForEach(a => a.SetDir(a.CurrentDir));
umaContainer.HeadBone = (GameObject)umaContainer.Body.GetComponent<AssetHolder>()._assetTable["head"];
umaContainer.EyeHeight = umaContainer.Head.GetComponent<AssetHolder>()._assetTableValue["head_center_offset_y"];
umaContainer.MergeModel();
umaContainer.SetHeight(-1);
umaContainer.Initialize(!ModelSettings.IsTPose);
umaContainer.Position = umaContainer.transform.Find("Position");
umaContainer.SetupBoneHandles();
if (!ModelSettings.IsTPose && loadMotion)
{
if (Main.AbList.TryGetValue($"3d/motion/event/body/type00/anm_eve_type00_homestand{personality.PadLeft(2, '0')}_loop", out UmaDatabaseEntry entry))
{
umaContainer.LoadAnimation(entry);
}
}
//修改(载入通用服装ColorSet相关)
UI.ClearColorSetButtons();
}
private void LoadMiniUma(UmaContainerCharacter umaContainer, CharaEntry chara, string costumeId)
{
int id = chara.Id;
umaContainer.CharaEntry = chara;
DataRow charaData = umaContainer.CharaData;
umaContainer.IsMini = true;
bool isGeneric = umaContainer.IsGeneric = costumeId.Length >= 4;
int tailId = Convert.ToInt32(charaData["tail_model_id"]);
string skin = charaData["skin"].ToString(),
height = charaData["height"].ToString(),
socks = charaData["socks"].ToString(),
bust = charaData["bust"].ToString(),
sex = charaData["sex"].ToString(),
costumeIdShort = "";
bool customHead = true;
UmaDatabaseEntry asset = null;
if (isGeneric)
{
costumeIdShort = costumeId.Remove(costumeId.LastIndexOf('_'));
string body = $"3d/chara/mini/body/mbdy{costumeIdShort}/pfb_mbdy{costumeId}_0";
Main.AbList.TryGetValue(body,out asset);
}
else Main.AbList.TryGetValue($"3d/chara/mini/body/mbdy{id}_{costumeId}/pfb_mbdy{id}_{costumeId}",out asset);
if (asset == null)
{
Debug.LogError("No body, can't load!");
UmaViewerUI.Instance?.ShowMessage("No body, can't load!", UIMessageType.Error);
return;
}
else if (isGeneric)
{
string texPattern1 = "";
switch (costumeId.Split('_')[0])
{
case "0003":
texPattern1 = $"tex_mbdy{costumeIdShort}_00_{skin}_{0}";
break;
default:
texPattern1 = $"tex_mbdy{costumeId}_{skin}_{0}";
break;
}
//Load Body Textures
foreach (var asset1 in UmaViewerMain.Instance.AbChara.Where(a => a.Name.StartsWith("3d/chara/mini/body/") && a.Name.Contains(texPattern1)))
{
umaContainer.LoadTextures(asset1);
}
//Load Body
umaContainer.LoadBody(asset);
}
else
umaContainer.LoadBody(asset);
string hair = $"3d/chara/mini/head/mchr{id}_{costumeId}/pfb_mchr{id}_{costumeId}_hair";
Main.AbList.TryGetValue(hair, out asset);
if (costumeId != "00" && asset == null)
{
customHead = false;
Main.AbList.TryGetValue($"3d/chara/mini/head/mchr{id}_00/pfb_mchr{id}_00_hair", out asset);
}
if (asset != null)
{
//Load Hair Textures
if (customHead)
{
foreach (var asset1 in UmaViewerMain.Instance.AbChara.Where(a => a.Name.StartsWith($"3d/chara/mini/head/mchr{id}_{costumeId}/textures")))
{
umaContainer.LoadTextures(asset1);
}
}
else
{
foreach (var asset1 in UmaViewerMain.Instance.AbChara.Where(a => a.Name.StartsWith($"3d/chara/mini/head/mchr{id}_00/textures")))
{
umaContainer.LoadTextures(asset1);
}
}
//Load Hair (don't know why loadhead is used)
umaContainer.LoadHead(asset);
}
string head = $"3d/chara/mini/head/mchr0001_00/pfb_mchr0001_00_face0";
Main.AbList.TryGetValue(head,out asset);
if (asset != null)
{
//Load Head Textures
foreach (var asset1 in UmaViewerMain.Instance.AbChara.Where(a => a.Name.StartsWith($"3d/chara/mini/head/mchr0001_00/textures/tex_mchr0001_00_face0_{skin}")))
{
umaContainer.LoadTextures(asset1);
}
//Load Head
umaContainer.LoadHead(asset);
}
if (tailId > 0)
{
string tailName = $"mtail{tailId.ToString().PadLeft(4, '0')}_00";
string tailPath = $"3d/chara/mini/tail/{tailName}/";
string tailPfb = tailPath + $"pfb_{tailName}";
asset = null;
Main.AbList.TryGetValue(tailPfb, out asset);
if (asset != null)
{
foreach (var asset2 in UmaViewerMain.Instance.AbChara.Where(a => a.Name.StartsWith($"{tailPath}textures/tex_{tailName}_{id.ToString().PadLeft(4,'0')}")))
{
umaContainer.LoadTextures(asset2);
}
umaContainer.LoadTail(asset);
//Load Physics
var asset1 = Main.AbList[$"{tailPath}clothes/pfb_{tailName}_cloth00"];
umaContainer.LoadPhysics(asset1);
}
else
{
Debug.Log("no tail");
}
}
umaContainer.MergeModel();
var matList = new List<MeshRenderer>(umaContainer.GetComponentsInChildren<MeshRenderer>());
var eyemat = matList.FirstOrDefault(a => a.gameObject.name.Equals("M_Eye"));
var mouthmat = matList.FirstOrDefault(a => a.gameObject.name.Equals("M_Mouth"));
var eyebrowLmat = matList.FirstOrDefault(a => a.gameObject.name.Equals("M_Mayu_L"));
var eyebrowRmat = matList.FirstOrDefault(a => a.gameObject.name.Equals("M_Mayu_R"));
UI.LoadFacialPanelsMini(eyemat.material, eyebrowLmat.material, eyebrowRmat.material, mouthmat.material);
umaContainer.Position = umaContainer.transform.Find("Position");
umaContainer.SetupBoneHandles();
if (Main.AbList.TryGetValue($"3d/motion/mini/event/body/chara/chr{id}_00/anm_min_eve_chr{id}_00_idle01_loop",out asset))
{
umaContainer.LoadAnimation(asset);
}
//修改(载入通用服装ColorSet相关)
UI.ClearColorSetButtons();
}
public void LoadProp(UmaDatabaseEntry entry)
{
UnloadProp();
UmaAssetManager.UnloadAllBundle();
var prop = new GameObject(Path.GetFileName(entry.Name)).AddComponent<UmaContainerProp>();
if (!prop.LoadProp(entry))
{
Destroy(prop.gameObject);
return;
}
CurrentOtherContainer = prop;
}
public void LoadLive(LiveEntry live, List<LiveCharacterSelect> characters)
{
if (!LiveAutoSelection.FillMissing(Main, live, characters, UI.CostumeIconDefault))
{
UI.ShowMessage("Cannot fill the live selection: no available character-owned winning costume matches an empty slot.", UIMessageType.Error);
return;
}
// 在旧 UI 场景卸载前复制成普通 C# 数据。后续异步加载不会丢角色选择。
List<LiveCharacterLoadData> liveCharacters =
LiveCharacterLoadData.CaptureAll(characters);
bool requireStage = UI.isRequireStage;
int liveMode = UI.LiveMode;
// Opt-in diagnostics begin before root registration, then follow the
// exact dependency-expanded request count shown by the progress UI.
LiveRuntimeDiagnostics.BeginPreload(live, liveCharacters, requireStage);
List<UmaDatabaseEntry> preloadEntries =
LiveResourceRegister.RegisterDownload(live, liveCharacters, requireStage);
UmaAssetManager.PreLoadAndRun(preloadEntries, delegate
{
LiveRuntimeDiagnostics.RecordPhase("preload_callback");
UmaSceneController.LoadScene(
"LiveScene",
delegate
{
GameObject mainLive = Instantiate(LiveControllerPrefab);
Director controller = mainLive.GetComponentInChildren<Director>();
controller.live = live;
controller.IsRecordVMD = UI.isRecordVMD;
controller.RequireStage = requireStage;
var binder = mainLive.GetComponent<CyalumeAutoBinder>() ??
mainLive.AddComponent<CyalumeAutoBinder>();
if (mainLive.GetComponent<Gallop.Live.StageBlinkLightDriver>() == null)
mainLive.AddComponent<Gallop.Live.StageBlinkLightDriver>();
if (mainLive.GetComponent<Gallop.Live.StageUVScrollLightDriver>() == null)
mainLive.AddComponent<Gallop.Live.StageUVScrollLightDriver>();
if (mainLive.GetComponent<Gallop.Live.MonitorUvMovieProvider>() == null)
mainLive.AddComponent<Gallop.Live.MonitorUvMovieProvider>();
if (mainLive.GetComponent<Gallop.Live.StageMonitorDriver>() == null)
mainLive.AddComponent<Gallop.Live.StageMonitorDriver>();
binder.musicId = live.MusicId;
var transferObjs = new List<GameObject>
{
mainLive,
GameObject.Find("ViewerMain"),
GameObject.Find("Directional Light"),
GameObject.Find("GlobalShaderController"),
GameObject.Find("AudioManager")
};
foreach (GameObject obj in transferObjs)
{
if (obj != null)
{
SceneManager.MoveGameObjectToScene(
obj,
SceneManager.GetSceneByName("LiveScene"));
}
}
controller.Initialize();
LiveRuntimeDiagnostics.RecordPhase("director_initialized", controller);
// Native full-mode callback (RVA 0x1ba8981..0x1ba8b76) cycles
// selected identities through the raw authored channel count.
// Simple mode uses only the UI's basic-motion selection capacity.
int motionChannelCount = controller._liveTimelineControl.data.worksheetList[0].charaMotSeqList.Count;
if (liveMode == 1 && liveCharacters.Count > 0 && motionChannelCount > liveCharacters.Count)
{
var channelCharacters = new List<LiveCharacterLoadData>(motionChannelCount);
for (int i = 0; i < motionChannelCount; i++)
channelCharacters.Add(liveCharacters[i % liveCharacters.Count]);
liveCharacters = channelCharacters;
}
LoadLiveUma(liveCharacters);
LiveRuntimeDiagnostics.RecordPhase("characters_loaded", controller);
var lyrics = LoadLiveLyrics(live.MusicId);
if (lyrics != null)
LiveViewerUI.Instance.CurrentLyrics = lyrics;
},
delegate
{
var director = Director.instance;
var liveAssetPaths = new HashSet<string>(StringComparer.Ordinal);
LivePropsController.CollectResourcePaths(
director._liveTimelineControl.data.propsSettings,
director.CharaContainerScript, liveAssetPaths);
LiveEffectController.CollectResourcePaths(
director._liveTimelineControl.GetWorkSheetBySheetIndex(LiveTimelineDefine.SheetIndex.MainLive),
TimelinePlayerMode.Default, variation => variation == 0, liveAssetPaths);
foreach (var path in LiveLightShaftsResources.GetTexturePaths(
director._liveTimelineControl.data.indirectLightShaftsSettings))
if (!string.IsNullOrEmpty(path)) liveAssetPaths.Add(path);
if (director.RequireStage)
{
liveAssetPaths.Add(FootLightRuntime.DefaultSpotlightPath);
liveAssetPaths.Add(FootLightRuntime.DefaultShadowPath);
}
if (director.RequireStage && director._liveTimelineControl.data.worksheetList.Any(
sheet => sheet?.spotlight3dList != null && sheet.spotlight3dList.Count != 0))
liveAssetPaths.Add(Director.Spotlight3dControllerPath);
foreach (var sheet in director._liveTimelineControl.data.worksheetList)
{
if (sheet?.lightProjectionList == null) continue;
foreach (var group in sheet.lightProjectionList)
{
if (group?.keys?.thisList == null) continue;
foreach (var key in group.keys.thisList)
{
if (key.TextureId >= 0 && !key.UseMonitorMovie())
liveAssetPaths.Add(group.ContentType == 1
? Director.GetMirrorBallProjectionTexturePath(key.TextureId)
: Director.GetLightProjectionTexturePath(key.TextureId));
if (key.AnimationParam != null && key.AnimationParam.TextureId >= 0)
liveAssetPaths.Add(Director.GetLightProjectionTexturePath(key.AnimationParam.TextureId));
}
}
}
var effectEntries = new List<UmaDatabaseEntry>();
foreach (var path in liveAssetPaths)
{
var entry = LiveEffectController.FindResourceEntry(path);
if (entry != null && !effectEntries.Contains(entry)) effectEntries.Add(entry);
}
UmaAssetManager.PreLoadAndRun(effectEntries, delegate
{
try
{
if (director == null) return;
director.InitializeUI();
director.InitializeTimeline(liveCharacters, liveMode);
director.InitializeMusic(live.MusicId, liveCharacters);
LiveRuntimeDiagnostics.RecordPhase("timeline_music_initialized", director);
director.Play();
}
finally
{
// Effect.Load holds its own recursive references; release this preload's roots.
foreach (var entry in effectEntries) UmaAssetManager.UnloadAssetBundle(entry, false);
}
});
});
});
}
//Use decrypt function
public void LoadLiveSound(int songid, UmaDatabaseEntry SongAwb, bool needLyrics = true)
{
CurrentLiveSoundAWBIndex = -1; // mix awb together
ClearLiveSounds();
//load character voice
if (SongAwb != null)
{
PlaySound(SongAwb);
AddLiveSound(SongAwb);
}
//load BG
string nameVar = $"snd_bgm_live_{songid}_oke";
UmaDatabaseEntry BGawb = Main.AbSounds.FirstOrDefault(a => a.Name.Contains(nameVar) && a.Name.EndsWith("awb"));
if (BGawb != null)
{
var BGclip = LoadAudio(BGawb);
if (BGclip.Count > 0)
{
AddAudioSource(BGclip[0]);
AddLiveSound(BGawb);
}
}
if (needLyrics)
{
LoadLiveLyrics(songid);
}
}
public void loadLivePreviewSound(int songid)
{
string nameVar = $"snd_bgm_live_{songid}_preview_02";
UmaDatabaseEntry previewAwb = Main.AbSounds.FirstOrDefault(a => a.Name.Contains(nameVar) && a.Name.EndsWith("awb"));
if (previewAwb != null)
{
PlaySound(previewAwb, volume : 0.4f, loop : true);
}
}
public void PlaySound(UmaDatabaseEntry SongAwb, int subindex = -1, float volume = 1, bool loop = false)
{
CurrentLyrics.Clear();
if (CurrentAudioSources.Count > 0)
{
var tmp = CurrentAudioSources[0];
CurrentAudioSources.Clear();
Destroy(tmp.gameObject);
UI.AudioSettings.ResetPlayer();
}
if (subindex == -1)
{
foreach (AudioClip clip in LoadAudio(SongAwb))
{
AddAudioSource(clip, volume, loop);
}
}
else
{
AddAudioSource(LoadAudio(SongAwb)[subindex], volume, loop);
}
}
public void SetLastAudio(UmaDatabaseEntry AudioAwb, int index)
{
ClearLiveSounds();
AddLiveSound(AudioAwb);
CurrentLiveSoundAWBIndex = index;
}
private void AddAudioSource(AudioClip clip, float volume = 1, bool loop = false)
{
AudioSource source;
if (CurrentAudioSources.Count > 0)
{
source = CurrentAudioSources[0].gameObject.AddComponent<AudioSource>();
}
else
{
source = new GameObject("SoundController").AddComponent<AudioSource>();
}
CurrentAudioSources.Add(source);
source.clip = clip;
source.volume = volume;
source.loop = loop;
source.Play();
}
public List<UmaWaveStream> LoadAudioStreams(UmaDatabaseEntry awb)
{
var streams = new List<UmaWaveStream>();
string awbPath = awb.FilePath;
if (!File.Exists(awbPath)) return streams;
FileStream awbFile = File.OpenRead(awbPath);
AwbReader awbReader = new AwbReader(awbFile);
foreach (Wave wave in awbReader.Waves)
{
var stream = new UmaWaveStream(awbReader, wave.WaveId);
streams.Add(stream);
}
return streams;
}
public static List<AudioClip> LoadAudio(UmaDatabaseEntry awb)
{
List<AudioClip> clips = new List<AudioClip>();
string awbPath = awb.FilePath;
if (!File.Exists(awbPath)) return clips;
FileStream awbFile = File.OpenRead(awbPath);
AwbReader awbReader = new AwbReader(awbFile);
foreach (Wave wave in awbReader.Waves)
{
var stream = new UmaWaveStream(awbReader, wave.WaveId);
var sampleProvider = stream.ToSampleProvider();
int channels = stream.WaveFormat.Channels;
int bytesPerSample = stream.WaveFormat.BitsPerSample / 8;
int sampleRate = stream.WaveFormat.SampleRate;
AudioClip clip = AudioClip.Create(
Path.GetFileNameWithoutExtension(awb.Name) + "_" + wave.WaveId.ToString(),
(int)(stream.Length / channels / bytesPerSample),
channels,
sampleRate,
true,
data => sampleProvider.Read(data, 0, data.Length),
position => stream.Position = position * channels * bytesPerSample);
clips.Add(clip);
}
return clips;
}
public List<UmaLyricsData> LoadLiveLyrics(int songid)
{
if (CurrentLyrics.Count > 0) CurrentLyrics.Clear();
string lyricsVar = $"live/musicscores/m{songid}/m{songid}_lyrics";
UmaDatabaseEntry lyricsAsset = Main.AbList[lyricsVar];
AssetBundle bundle = UmaAssetManager.LoadAssetBundle(lyricsAsset);
TextAsset asset = bundle.LoadAsset<TextAsset>(Path.GetFileNameWithoutExtension(lyricsVar));
string[] lines = asset.text.Split("\n"[0]);
for (int i = 1; i < lines.Length; i++)
{
string[] words = lines[i].Split(',');
if (words.Length > 0)
{
try
{
UmaLyricsData lyricsData = new UmaLyricsData()
{
time = float.Parse(words[0]) / 1000,
text = (words.Length > 1) ? words[1] : ""
};
CurrentLyrics.Add(lyricsData);
}
catch { }
}
}
return CurrentLyrics;
}
public void LoadAssetPath(string path, Transform SetParent)
{
var go = Instantiate(UmaViewerMain.Instance.AbList[path].Get<GameObject>(), SetParent);
Gallop.Live.LiveRuntimeDiagnostics.RecordInstantiation(path, go);
// 统计 missing script 数量并输出一次性汇总警告(避免刷屏)
int missingCount = Gallop.Live.StageController.CountMissingScripts(go);
if (missingCount > 0)
{
Debug.LogWarning($"[LoadAssetPath] '{path}' 实例化后有 {missingCount} 个 missing script 组件(来自原始游戏的不可用脚本)");
}
}
public void SetPreviewCamera(AnimationClip clip)
{
if (clip)
{
if (!AnimationCameraAnimator.runtimeAnimatorController)
{
AnimationCameraAnimator.runtimeAnimatorController = Instantiate(CameraOverrideController);
}
(AnimationCameraAnimator.runtimeAnimatorController as AnimatorOverrideController)["clip_1"] = clip;
AnimationCamera.enabled = true;
AnimationCameraAnimator.Play("motion_1", 0, 0);
CurrentUMAContainer?.SetHeight(0);
}
else
{
AnimationCamera.enabled = false;
CurrentUMAContainer?.SetHeight(-1);
}
}
public Sprite LoadCharaIcon(string id)
{
string value = $"chara/chr{id}/chr_icon_{id}";
if (UmaViewerMain.Instance.AbList.TryGetValue(value, out UmaDatabaseEntry entry))
{
AssetBundle assetBundle = UmaAssetManager.LoadAssetBundle(entry, true);
if (assetBundle.Contains($"chr_icon_{id}"))
{
Texture2D texture = (Texture2D)assetBundle.LoadAsset($"chr_icon_{id}");
Sprite sprite = Sprite.Create(texture, new Rect(0, 0, texture.width, texture.height), Vector2.zero);
UmaAssetManager.UnloadAssetBundle(entry, false);
return sprite;
}
}
return null;
}
public Sprite LoadMobCharaIcon(string id)
{
string value = $"mob/mob_chr_icon_{id}_000001_01";
if (UmaViewerMain.Instance.AbList.TryGetValue(value, out UmaDatabaseEntry entry))
{
string path = entry.FilePath;
AssetBundle assetBundle = UmaAssetManager.LoadAssetBundle(entry, true);
if (assetBundle.Contains($"mob_chr_icon_{id}_000001_01"))
{
Texture2D texture = (Texture2D)assetBundle.LoadAsset($"mob_chr_icon_{id}_000001_01");
Sprite sprite = Sprite.Create(texture, new Rect(0, 0, texture.width, texture.height), Vector2.zero);
UmaAssetManager.UnloadAssetBundle(entry, false);
return sprite;
}
}
return null;
}
public Sprite LoadSprite(UmaDatabaseEntry item)
{
AssetBundle assetBundle = UmaAssetManager.LoadAssetBundle(item, true);
Texture2D texture = (Texture2D)assetBundle.LoadAsset(assetBundle.GetAllAssetNames()[0]);
Sprite sprite = Sprite.Create(texture, new Rect(0, 0, texture.width, texture.height), Vector2.zero);
UmaAssetManager.UnloadAssetBundle(item, false);
return sprite;
}
public Sprite LoadLiveIcon(int musicid)
{
string value = $"live/jacket/jacket_icon_l_{musicid}";
if (UmaViewerMain.Instance.AbList.TryGetValue(value, out UmaDatabaseEntry entry))
{
AssetBundle assetBundle = UmaAssetManager.LoadAssetBundle(entry, true);
if (assetBundle.Contains($"jacket_icon_l_{musicid}"))
{
Texture2D texture = (Texture2D)assetBundle.LoadAsset($"jacket_icon_l_{musicid}");
Sprite sprite = Sprite.Create(texture, new Rect(0, 0, texture.width, texture.height), Vector2.zero);
UmaAssetManager.UnloadAssetBundle(entry, false);
return sprite;
}
}
return null;
}
public void UnloadProp()
{
if (CurrentOtherContainer != null)
{
Destroy(CurrentOtherContainer.gameObject);
}
}
public void UnloadUma()
{
PoseManager.SetPoseModeStatic(false);
if (CurrentUMAContainer != null)
{
//It seems that OnDestroy will executed after new model loaded, which cause new FacialPanels empty...
UI.currentFaceDrivenKeyTarget = null;
UI.LoadEmotionPanels(null);
UI.LoadFacialPanels(null);
if (UI.ModelSettings.MaterialsList)
foreach (Transform t in UI.ModelSettings.MaterialsList.content)
{
Destroy(t.gameObject);
}
if (CurrentUMAContainer.transform.parent && CurrentUMAContainer.transform.parent.name.Contains("Root"))
{
Destroy(CurrentUMAContainer.transform.parent.gameObject);
}
else
{
Destroy(CurrentUMAContainer.gameObject);
}
}
}
public void ClearMorphs()
{
var umaContainer = CurrentUMAContainer;
if (umaContainer != null && umaContainer.FaceDrivenKeyTarget != null)
{
foreach (var container in UI.EmotionList.GetComponentsInChildren<UmaUIContainer>())
{
if (container.Slider != null)
container.Slider.value = 0;
}
foreach (var container in UI.FacialList.GetComponentsInChildren<UmaUIContainer>())
{
if (container.Slider != null)
container.Slider.SetValueWithoutNotify(0);
}
if (umaContainer.FaceDrivenKeyTarget)
{
umaContainer.FaceDrivenKeyTarget.ClearAllWeights();
umaContainer.FaceDrivenKeyTarget.ChangeMorph();
}
}
}
public void UnloadAllBundle() => UmaAssetManager.UnloadAllBundle(true);
private void FixedUpdate()
{
if (AnimationCamera && AnimationCamera.enabled == true)
{
var fov = AnimationCamera.gameObject.transform.parent.transform.localScale.x;
AnimationCamera.fieldOfView = fov;
}
}
private void AddLiveSound(UmaDatabaseEntry entry)
{
UmaAssetManager.LoadAssetBundle(entry, false, false);
CurrentLiveSoundAWB.Add(entry);
}
private void ClearLiveSounds()
{
foreach (var liveSound in CurrentLiveSoundAWB)
{
UmaAssetManager.UnloadAssetBundle(liveSound, true);
}
CurrentLiveSoundAWB.Clear();
}
private bool _dumpedNormalRenderers = false;
private void ApplyNormalCostumeVisibilityOptions(UmaContainerCharacter uma)
{
if (uma == null) return;
if (DumpNormalRenderersOnce && !_dumpedNormalRenderers)
{
DumpBodyRenderersAndMaterials(uma);
_dumpedNormalRenderers = true;
}
switch (NormalHideMode)
{
case NormalCostumeHideMode.HideAllBodyRenderers:
HideAllBodyRenderers(uma);
break;
case NormalCostumeHideMode.HideByNameOrMaterial:
HideCostumeRenderersByNameOrMaterial(uma);
break;
default:
break;
}
}
private void HideAllBodyRenderers(UmaContainerCharacter uma)
{
// Clothes are typically baked into the body prefab, so hiding Body renderers hides "costume" too.
var body = uma.Body;
if (!body) return;
foreach (var r in body.GetComponentsInChildren<Renderer>(true))
r.enabled = false;
}
private void HideCostumeRenderersByNameOrMaterial(UmaContainerCharacter uma)
{
// Conservative: hide only renderers that look like clothes, and avoid anything that looks like skin/body/face/hair.
// Tune NormalClothKeywords / NormalBodyKeywords after checking Dump logs.
var root = uma.gameObject;
if (!root) return;
foreach (var r in root.GetComponentsInChildren<SkinnedMeshRenderer>(true))
{
if (r == null) continue;
bool looksCloth = MatchesAny(r.name, NormalClothKeywords) || MaterialsMatchAny(r.sharedMaterials, NormalClothKeywords);
if (!looksCloth) continue;
bool looksBody = MatchesAny(r.name, NormalBodyKeywords) || MaterialsMatchAny(r.sharedMaterials, NormalBodyKeywords);
if (looksBody) continue;
r.enabled = false;
}
}
private void DumpBodyRenderersAndMaterials(UmaContainerCharacter uma)
{
if (uma == null) return;
var body = uma.Body;
if (!body)
{
Debug.LogWarning("[UmaViewerBuilder] DumpNormalRenderersOnce: Body is null.");
return;
}
var sb = new StringBuilder();
var renderers = body.GetComponentsInChildren<Renderer>(true);
sb.AppendLine($"[UmaViewerBuilder] Body renderer dump: {renderers.Length} renderers under {body.name}");
foreach (var r in renderers)
{
sb.Append($"- {r.GetType().Name}: {r.name} mats=[");
var mats = r.sharedMaterials;
if (mats != null)
{
for (int i = 0; i < mats.Length; i++)
{
var m = mats[i];
sb.Append(m ? m.name : "null");
if (i < mats.Length - 1) sb.Append(", ");
}
}
sb.AppendLine("]");
}
Debug.Log(sb.ToString());
}
private static bool MatchesAny(string value, string[] keywords)
{
if (string.IsNullOrEmpty(value) || keywords == null) return false;
var v = value.ToLowerInvariant();
for (int i = 0; i < keywords.Length; i++)
{
var k = keywords[i];
if (string.IsNullOrEmpty(k)) continue;
if (v.Contains(k.ToLowerInvariant())) return true;
}
return false;
}
private static bool MaterialsMatchAny(Material[] materials, string[] keywords)
{
if (materials == null || keywords == null) return false;
for (int i = 0; i < materials.Length; i++)
{
var m = materials[i];
if (!m) continue;
if (MatchesAny(m.name, keywords)) return true;
}
return false;
}
}