diff --git a/Assets/Scripts/umamusume/Gallop/Live/Cutt/LiveTimelineControl.cs b/Assets/Scripts/umamusume/Gallop/Live/Cutt/LiveTimelineControl.cs index 34d6691..1bbbd70 100644 --- a/Assets/Scripts/umamusume/Gallop/Live/Cutt/LiveTimelineControl.cs +++ b/Assets/Scripts/umamusume/Gallop/Live/Cutt/LiveTimelineControl.cs @@ -1877,6 +1877,8 @@ namespace Gallop.Live.Cutt LiveTimelineKeyBgColor2Data d0 = curKey as LiveTimelineKeyBgColor2Data; LiveTimelineKeyBgColor2Data d1 = nextKey as LiveTimelineKeyBgColor2Data; BgColor2UpdateInfo updateInfo = default; + // 组名从 ILiveTimelineGroupDataWithName 基类继承而来,一直可读,只是从没被传下去。 + updateInfo.TimelineName = sheet.bgColor2List[i].name; if (d1 != null && d1.interpolateType != 0) { float t = CalculateInterpolationValue(d0, d1, currentFrame); diff --git a/Assets/Scripts/umamusume/Gallop/Live/Cutt/UpdateInfo/BgColorUpdateInfo.cs b/Assets/Scripts/umamusume/Gallop/Live/Cutt/UpdateInfo/BgColorUpdateInfo.cs index 61cbe76..b54245f 100644 --- a/Assets/Scripts/umamusume/Gallop/Live/Cutt/UpdateInfo/BgColorUpdateInfo.cs +++ b/Assets/Scripts/umamusume/Gallop/Live/Cutt/UpdateInfo/BgColorUpdateInfo.cs @@ -53,6 +53,14 @@ namespace Gallop.Live.Cutt public Color color2; // 0x24 public float value; // 0x34 public int rndValueIdx; // 0x38 + + /// + /// 轨道组名(BgColor1UpdateInfo 同名字段的对应物)。 + /// 原先 dispatcher 从不填这个信息,handler 也不读,于是一首歌里最多 15 个组 + /// (son1010 实测 BgWashA..BgWashO)每帧各把整个舞台刷一遍,只有列表里最后一个 + /// 留得下来 —— 前面所有组的关键帧等于不存在。 + /// + public string TimelineName; } public delegate void BgColor2UpdateInfoDelegate(ref BgColor2UpdateInfo updateInfo); diff --git a/Assets/Scripts/umamusume/Gallop/Live/Director.cs b/Assets/Scripts/umamusume/Gallop/Live/Director.cs index 470a91a..f67be85 100644 --- a/Assets/Scripts/umamusume/Gallop/Live/Director.cs +++ b/Assets/Scripts/umamusume/Gallop/Live/Director.cs @@ -336,37 +336,57 @@ namespace Gallop.Live CharaContainerScript[position].FaceDrivenKeyTarget.AlterUpdateFacialNew(ref updateInfo_, liveTime_); } + /// + /// 角色渲染器共用的 MaterialPropertyBlock。 + /// + /// GlobalLight 和 BgColor1 的角色分支写的是同一批 container.Renderers,而 + /// Renderer.SetPropertyBlock() 是「整块替换」而不是合并。两者在同一帧里按 + /// AlterLateUpdate 的顺序先后跑(GlobalLight → BgColor1),所以各自 new 一个空 block + /// 再 Set 的话,后跑的 BgColor1 每帧都会把 GlobalLight 刚写进去的 13 个 rim 属性 + /// 抹回材质默认值 —— 全语料 59/59 首都同时带这两条轨道的数据(歌曲 1177 是 + /// GlobalLight×2 组 + CharaColor×2 组),于是 GlobalLight 实际上从来没生效过。 + /// + /// 正确做法是先 GetPropertyBlock 把渲染器上已有的块取回来,改完再写回去 —— + /// 舞台侧的 和 + /// 本来就是这么写的,这两个角色侧的 handler 是仅有的两处例外。 + /// 顺带干掉每个 locator 每帧一次的 new。 + /// + private MaterialPropertyBlock _charaBlock; + private void OnGlobalLightUpdate(ref GlobalLightUpdateInfo updateInfo) { var tmpPos = -(updateInfo.lightRotation * Vector3.forward).normalized; + _charaBlock ??= new MaterialPropertyBlock(); foreach (var locator in _liveTimelineControl.liveCharactorLocators) { if (locator == null || !updateInfo.flags.hasFlag(locator.liveCharaStandingPosition) || locator is not LiveTimelineCharaLocator charaLocator) continue; var container = charaLocator.UmaContainer; if (!container) continue; - MaterialPropertyBlock propertyBlock = new MaterialPropertyBlock(); - propertyBlock.SetFloat("_RimShadowRate", updateInfo.globalRimShadowRate); - propertyBlock.SetColor("_RimColor", updateInfo.rimColor); - propertyBlock.SetFloat("_RimStep", updateInfo.rimStep); - propertyBlock.SetFloat("_RimFeather", updateInfo.rimFeather); - propertyBlock.SetFloat("_RimSpecRate", updateInfo.rimSpecRate); - propertyBlock.SetFloat("_RimHorizonOffset", updateInfo.RimHorizonOffset); - propertyBlock.SetFloat("_RimVerticalOffset", updateInfo.RimVerticalOffset); - propertyBlock.SetFloat("_RimHorizonOffset2", updateInfo.RimHorizonOffset2); - propertyBlock.SetFloat("_RimVerticalOffset2", updateInfo.RimVerticalOffset2); - propertyBlock.SetColor("_RimColor2", updateInfo.rimColor2); - propertyBlock.SetFloat("_RimStep2", updateInfo.rimStep2); - propertyBlock.SetFloat("_RimFeather2", updateInfo.rimFeather2); - propertyBlock.SetFloat("_RimSpecRate2", updateInfo.rimSpecRate2); - propertyBlock.SetFloat("_RimShadowRate2", updateInfo.globalRimShadowRate2); foreach (var renderer in container.Renderers) { - renderer.SetPropertyBlock(propertyBlock); - foreach (var mat in renderer.materials) - { - mat.SetFloat("_UseOriginalDirectionalLight", 1); - mat.SetVector("_OriginalDirectionalLightDir", tmpPos); - } + if (renderer == null) continue; + renderer.GetPropertyBlock(_charaBlock); + _charaBlock.SetFloat("_RimShadowRate", updateInfo.globalRimShadowRate); + _charaBlock.SetColor("_RimColor", updateInfo.rimColor); + _charaBlock.SetFloat("_RimStep", updateInfo.rimStep); + _charaBlock.SetFloat("_RimFeather", updateInfo.rimFeather); + _charaBlock.SetFloat("_RimSpecRate", updateInfo.rimSpecRate); + _charaBlock.SetFloat("_RimHorizonOffset", updateInfo.RimHorizonOffset); + _charaBlock.SetFloat("_RimVerticalOffset", updateInfo.RimVerticalOffset); + _charaBlock.SetFloat("_RimHorizonOffset2", updateInfo.RimHorizonOffset2); + _charaBlock.SetFloat("_RimVerticalOffset2", updateInfo.RimVerticalOffset2); + _charaBlock.SetColor("_RimColor2", updateInfo.rimColor2); + _charaBlock.SetFloat("_RimStep2", updateInfo.rimStep2); + _charaBlock.SetFloat("_RimFeather2", updateInfo.rimFeather2); + _charaBlock.SetFloat("_RimSpecRate2", updateInfo.rimSpecRate2); + _charaBlock.SetFloat("_RimShadowRate2", updateInfo.globalRimShadowRate2); + // 这两个原先走 renderer.materials,那个 getter 每次调用都会实例化材质副本 + // 并新分配数组(每帧、每渲染器)。两者在 bundle 的 Gallop/3D/Chara/* 上 + // 分别是 Float 和 Vector,MPB 能直接写;ToonEye/T 上的 [MaterialToggle] + // 只是编辑器 drawer,运行时 SetFloat 一样不会开关键字,所以行为不变。 + _charaBlock.SetFloat("_UseOriginalDirectionalLight", 1); + _charaBlock.SetVector("_OriginalDirectionalLightDir", tmpPos); + renderer.SetPropertyBlock(_charaBlock); } } } @@ -410,14 +430,20 @@ namespace Gallop.Live if (locator == null || (updateInfo.flags != 0 && !EFlags.hasFlag(locator.liveCharaStandingPosition)) || locator is not LiveTimelineCharaLocator charaLocator) continue; var container = charaLocator.UmaContainer; if (!container) continue; - MaterialPropertyBlock propertyBlock = new MaterialPropertyBlock(); - propertyBlock.SetColor("_CharaColor", updateInfo.color); - propertyBlock.SetColor("_ToonDarkColor", updateInfo.toonDarkColor); - propertyBlock.SetColor("_ToonBrightColor", updateInfo.toonBrightColor); - propertyBlock.SetColor("_OutlineColor", updateInfo.outlineColor); - propertyBlock.SetFloat("_Saturation", updateInfo.Saturation); + // 必须 Get→改→Set,否则会把 GlobalLight 同一帧写进去的 rim 属性整块抹掉。 + // 详见 _charaBlock 的注释。 + _charaBlock ??= new MaterialPropertyBlock(); foreach (var renderer in container.Renderers) - renderer.SetPropertyBlock(propertyBlock); + { + if (renderer == null) continue; + renderer.GetPropertyBlock(_charaBlock); + _charaBlock.SetColor("_CharaColor", updateInfo.color); + _charaBlock.SetColor("_ToonDarkColor", updateInfo.toonDarkColor); + _charaBlock.SetColor("_ToonBrightColor", updateInfo.toonBrightColor); + _charaBlock.SetColor("_OutlineColor", updateInfo.outlineColor); + _charaBlock.SetFloat("_Saturation", updateInfo.Saturation); + renderer.SetPropertyBlock(_charaBlock); + } } } @@ -479,7 +505,7 @@ namespace Gallop.Live } /// 用 sharedMaterials 探测(不会实例化材质),只保留确实有候选属性的 Renderer。 - private List ResolveStageTargets(string timelineName) + private List ResolveStageTargets(string timelineName, string[] props = null) { var result = new List(); @@ -525,7 +551,7 @@ namespace Gallop.Live } if (byName.Count > 0) { - CollectTargets(byName, result); + CollectTargets(byName, result, props); if (result.Count > 0) return result; } @@ -544,7 +570,7 @@ namespace Gallop.Live } } } - CollectTargets(matched, result); + CollectTargets(matched, result, props); return result; } @@ -555,8 +581,10 @@ namespace Gallop.Live private int _bgColor1RendererCount; private readonly List _bgColor1MatInfo = new List(); - private static void CollectTargets(IEnumerable renderers, List result) + private static void CollectTargets(IEnumerable renderers, List result, + string[] props = null) { + props ??= kStageBgColor1Props; foreach (var r in renderers) { if (r == null) continue; @@ -564,7 +592,7 @@ namespace Gallop.Live { if (mat == null) continue; string hit = null; - foreach (string p in kStageBgColor1Props) + foreach (string p in props) { if (mat.HasProperty(p)) { hit = p; break; } } @@ -661,14 +689,81 @@ namespace Gallop.Live _activeCameraIndex = kTimelineCameraIndices[cameraIndex_]; } + // BgColor2 的通道。_MulColor0 归 BgColor1,两条轨道不能抢同一个属性。 + private static readonly string[] kStageBgColor2Props = { "_AmbientColor" }; + + private readonly Dictionary> _bgColor2StageCache = + new Dictionary>(); + private List _bgColor2AllTargets; + private MaterialPropertyBlock _bgColor2Block; + + /// + /// BgColor2。原实现有三个问题,这里修掉两个半: + /// + /// 1. **组名被完全丢弃**(已修)。dispatcher 现在把 TimelineName 传下来了。 + /// 2. **每帧 r.materials**(已修)。那个 getter 每次调用都会实例化材质副本并新分配数组, + /// 这里是「整个舞台的渲染器 × 每帧 × 最多 15 个组」,是全项目最重的一处。 + /// 改用 sharedMaterials 解析一次 + MaterialPropertyBlock 写入,和 BgColor1 + /// 舞台分支、BlinkLight 的做法统一。 + /// 3. **组名到底指向什么,仍然未知**(未修,缺 ground truth)。 + /// + /// 关于 3:全语料 15 首里出现过的组名只有 BgWashA..BgWashO、LaserA..LaserC、BgColor2。 + /// 已核实这些**既不是 GameObject 名,也不是 _stageObjectUnits 的 unit 名** + /// (已下载的 live10132/10149/10151 三个舞台的 unit 名分别是 + /// blinklight_washlight_wall_a_vertical_000_set / neonsign / stage000_shop 这类具体名字), + /// BlinkLight 的键里也没有回指 BgColor2 的索引字段。它和 BgColor1 的 + /// BgBL / FollowSpotColor / Shadow 属于同一类「非物件名的组」,映射关系尚未找到。 + /// + /// 所以这里**不猜**:名字能解析到渲染器就只写那些,解析不到就退回原来的全舞台写入 + /// 并打一次警告。行为上不会比改动前更差,映射一旦查明,只需删掉 fallback 分支。 + /// private void OnBgColor2Update(ref BgColor2UpdateInfo updateInfo) { if (_stageController == null) return; Color c = Color.Lerp(updateInfo.color1, updateInfo.color2, updateInfo.value); - foreach (var r in _stageController.GetComponentsInChildren()) - foreach (var mat in r.materials) - if (mat.HasProperty("_AmbientColor")) - mat.SetColor("_AmbientColor", c); + + string key = updateInfo.TimelineName ?? string.Empty; + if (!_bgColor2StageCache.TryGetValue(key, out var targets)) + { + targets = string.IsNullOrEmpty(key) + ? new List() + : ResolveStageTargets(key, kStageBgColor2Props); + _bgColor2StageCache[key] = targets; + if (targets.Count == 0) + Debug.LogWarning($"[BgColor2] 组名 '{key}' 解析不到任何渲染器,退回全舞台写入。" + + $"该组与其它未解析的组会互相覆盖 —— 映射关系待查,见 LIVE_TRACKS.md 「已知未解问题」"); + else + Debug.Log($"[BgColor2] 组名 '{key}' → {targets.Count} 个渲染器"); + } + + _bgColor2Block ??= new MaterialPropertyBlock(); + + // 名字解析成功:只写这一组。 + if (targets.Count > 0) + { + WriteAmbient(targets, c); + return; + } + + // Fallback:维持改动前的全舞台语义(缺映射依据,不改画面行为),但只解析一次。 + if (_bgColor2AllTargets == null) + { + _bgColor2AllTargets = new List(); + CollectTargets(_stageController.GetComponentsInChildren(true), + _bgColor2AllTargets, kStageBgColor2Props); + } + WriteAmbient(_bgColor2AllTargets, c); + } + + private void WriteAmbient(List targets, Color c) + { + foreach (var t in targets) + { + if (t.renderer == null) continue; + t.renderer.GetPropertyBlock(_bgColor2Block); + _bgColor2Block.SetColor(t.prop, c); + t.renderer.SetPropertyBlock(_bgColor2Block); + } } private void OnGlobalFogUpdate(LiveTimelineGlobalFogData fogData, LiveTimelineKeyGlobalFogData keyData) @@ -699,31 +794,132 @@ namespace Gallop.Live go.transform.eulerAngles = keyData.rotation; go.transform.localScale = keyData.scale; - foreach (var r in go.GetComponentsInChildren(true)) - foreach (var mat in r.materials) + // 之前写的是 _Color —— 枚举 shader bundle 里全部 133 个 Gallop/3D/{Live,Bg,Stage} + // shader 后确认,Gallop/3D/Live/Stage/* 下**没有任何一个** shader 声明 _Color + // (整个 bundle 里只有 Bg/BgShadowOnly 和 Bg/RedAlphaGreenColorShadowFogUVScroll 有, + // 都和灯柱无关)。灯柱用的是 StageBeamLight 系列,通道是 _MulColor0/_MulColor1 + + // _ColorPower。所以颜色写入一直是空操作,而 _ColorPower 是有效的 —— + // 表现为「材质原色 × 关键帧亮度」:亮度跟着音乐动,颜色永远不对。 + // 这和 BlinkLight 之前那个 _Color bug 是同一份,复用它的 shader 探测逻辑。 + if (!_spotlightRenderers.TryGetValue(go, out var renderers)) + { + renderers = go.GetComponentsInChildren(true); + _spotlightRenderers[go] = renderers; + } + + _spotlightBlock ??= new MaterialPropertyBlock(); + foreach (var r in renderers) + { + if (r == null) continue; + BlinkTarget t = ResolveBlinkTarget(r); + if (t.colorProp == null) continue; + + r.GetPropertyBlock(_spotlightBlock); + if (t.hasColorPower) { - mat.SetColor("_Color", keyData.color); - mat.SetFloat("_ColorPower", keyData.colorPower); + // 亮度是独立通道,不要折进颜色里,合成交给 shader。 + _spotlightBlock.SetColor(t.colorProp, keyData.color); + _spotlightBlock.SetFloat(kColorPowerProp, keyData.colorPower); } + else + { + _spotlightBlock.SetColor(t.colorProp, keyData.color * keyData.colorPower); + } + r.SetPropertyBlock(_spotlightBlock); + } + // TODO: localHeight / targetCameraType / targetCameraIndex 未实现。 } + private MaterialPropertyBlock _spotlightBlock; + private readonly Dictionary _spotlightRenderers = + new Dictionary(); + + /// 按材质名解析出来的 UVScroll 目标:渲染器 + 该材质原始的 _MainTex 缩放。 + private struct UVScrollTarget + { + public Renderer renderer; + public Vector2 baseScale; + } + + private readonly Dictionary> _uvScrollTargets = + new Dictionary>(); + private readonly HashSet _uvScrollLoggedShaders = new HashSet(); + private MaterialPropertyBlock _uvScrollBlock; + private void OnUVScrollLightUpdate(LiveTimelineUVScrollLightData data, LiveTimelineKeyUVScrollLightData keyData) { if (keyData == null || _stageController == null) return; + if (!_uvScrollAccum.ContainsKey(data.name)) _uvScrollAccum[data.name] = Vector2.zero; + // TODO: 这里用 Time.deltaTime 累积,拖动进度条/暂停后滚动相位会和时间轴对不上。 + // 改成按 currentLiveTime 积分才是确定性的,但「相位从歌曲起点算还是从关键帧起点算」 + // 没有依据,先不动 —— 换算错了比现在更难发现。 _uvScrollAccum[data.name] += new Vector2(keyData.scrollSpeedX, keyData.scrollSpeedY) * Time.deltaTime; Vector2 totalOffset = new Vector2(keyData.scrollOffsetX, keyData.scrollOffsetY) + _uvScrollAccum[data.name]; - foreach (var r in _stageController.GetComponentsInChildren(true)) + + // 首次按材质名解析并缓存。原实现每帧遍历整个舞台的 Renderer 并访问 r.materials, + // 那个 getter 每次都会实例化材质副本 + 新分配数组。 + if (!_uvScrollTargets.TryGetValue(data.name, out var targets)) { - foreach (var mat in r.materials) + targets = new List(); + foreach (var r in _stageController.GetComponentsInChildren(true)) { - if (mat.name.Replace(" (Instance)", "") != data.name) continue; - mat.SetTextureOffset("_MainTex", totalOffset); - mat.SetColor("_Color", keyData.mulColor0 * keyData.colorPower); - // TODO: mulColor1, ColorType0/1, CharacterIndex0/1, IsColorBlend0/1, loopType/loopCount + foreach (var mat in r.sharedMaterials) + { + if (mat == null) continue; + if (mat.name.Replace(" (Instance)", "") != data.name) continue; + targets.Add(new UVScrollTarget + { + renderer = r, + baseScale = mat.HasProperty("_MainTex") ? mat.GetTextureScale("_MainTex") : Vector2.one, + }); + if (!_uvScrollLoggedShaders.Add(data.name)) + break; + // 把实际命中的 shader 记一次,取代那个猜测性的兜底分支: + // 万一将来碰上没有 _ColorPower 的材质,这条日志能直接看出来。 + Debug.Log($"[UVScrollLight] '{data.name}' shader={mat.shader?.name} " + + $"_ColorPower={mat.HasProperty(kColorPowerProp)} _MulColor1={mat.HasProperty("_MulColor1")}"); + break; + } } + _uvScrollTargets[data.name] = targets; + Debug.Log($"[UVScrollLight] 材质 '{data.name}' → {targets.Count} 个渲染器"); } + if (targets.Count == 0) return; + + _uvScrollBlock ??= new MaterialPropertyBlock(); + + foreach (var t in targets) + { + if (t.renderer == null) continue; + t.renderer.GetPropertyBlock(_uvScrollBlock); + + // MPB 没有 SetTextureOffset,等价写法是 _MainTex_ST = (scaleX, scaleY, offsetX, offsetY)。 + _uvScrollBlock.SetVector("_MainTex_ST", + new Vector4(t.baseScale.x, t.baseScale.y, totalOffset.x, totalOffset.y)); + + // 之前写的是 _Color。实测这些材质用的是 + // Gallop/3D/Live/Stage/LightAdd1_UV + // Gallop/3D/Live/Stage/StageLightAdd1_UVAlphaMask_TransmittedLightMask + // 两者的属性都是 _MulColor0/_MulColor1/_ColorPower/_ColorPowerMultiply, + // **没有 _Color** —— 全部 499 个 shader 里带 _Color 的舞台 shader 只有 + // BgShadowOnly 和 RedAlphaGreenColorShadowFogUVScroll,都跟这里无关。 + // 所以颜色写入一直是空操作。这是 BlinkLight、Spotlight3d 之后的同一个 bug 第三例。 + // 字段名 mulColor0/mulColor1/colorPower 与属性名逐字对应,映射关系是硬的。 + // 亮度是独立通道,不折进颜色里,合成交给 shader。 + // 不做「没有 _ColorPower 就乘进颜色」的兜底:实测这条轨道的目标材质 + // (LightAdd1_UV / StageLightAdd1_UVAlphaMask_TransmittedLightMask)都有该属性, + // 兜底分支永远跑不到,却会在真跑到时悄悄产生另一种画面。 + // MPB 往 shader 没有的属性写入本身是无害空操作,不需要守卫。 + _uvScrollBlock.SetColor("_MulColor0", keyData.mulColor0); + _uvScrollBlock.SetColor("_MulColor1", keyData.mulColor1); + _uvScrollBlock.SetFloat(kColorPowerProp, keyData.colorPower); + + t.renderer.SetPropertyBlock(_uvScrollBlock); + } + // TODO: ColorType0/1、CharacterIndex0/1、IsColorBlend0/1、ColorBlendRate0/1、 + // AltCharaColor0/1、loopType/loopCount 未实现。 } private void OnChromaticAberrationUpdate(LiveTimelineChromaticAberrationData data, LiveTimelineKeyChromaticAberrationData keyData) diff --git a/Assets/Scripts/umamusume/Gallop/Live/StageController.cs b/Assets/Scripts/umamusume/Gallop/Live/StageController.cs index 4de650b..9a1fe60 100644 --- a/Assets/Scripts/umamusume/Gallop/Live/StageController.cs +++ b/Assets/Scripts/umamusume/Gallop/Live/StageController.cs @@ -50,6 +50,20 @@ namespace Gallop.Live name.Contains("_wash_") || name.Contains("laser"); + /// + /// 祖先里是否已经有一盏受时间轴控制的灯。有的话本对象跟着祖先一起显隐, + /// 不该在初始化时被单独关掉(关了就再也没人打开)。 + /// + private bool HasTimelineControlledAncestor(Transform t) + { + for (Transform p = t.parent; p != null && p != transform; p = p.parent) + { + if (IsTimelineControlledLight(p.name.Replace("(Clone)", ""))) + return true; + } + return false; + } + public void InitializeStage() { foreach (GameObject stage_part in _stageObjects) @@ -57,11 +71,27 @@ namespace Gallop.Live var instance = Instantiate(stage_part, transform); foreach (var child in instance.GetComponentsInChildren(true)) { - if (!StageObjectMap.ContainsKey(child.name)) + // 判重和插入统一用剥掉 "(Clone)" 的名字。 + // 原先判重用 child.name(未剥)、插入用剥掉的,两个键不一致。 + var tmp_name = child.name.Replace("(Clone)", ""); + + // 只关「本分支里最上层」的那盏受控灯。 + // + // 轨道寻址的是**容器**:live10149 实测 28 个 BlinkLight 组名全部一一对应 + // 一个 GameObject(27 个命中、1 个 wash_discoball 没有对应物件),真正的灯是 + // 它的子物件,由 handler 的 GetComponentsInChildren 覆盖。 + // + // 原来 SetActive(false) 写在判重 if 里面,于是**同名子物件只有第一个被关**, + // 而 handler 只会 SetActive(true) 容器 —— 那第一个子物件再没有人打开,全程是黑的。 + // live10149 上的受害者:light001_wash_ground_a(×4)、swing_wash_ground_a(×2)、 + // base_rotate_wash_ground_a(×2)、light000_wash_ground_a(×2),各有 1 盏常黑。 + // + // 祖先已经是受控灯的,跟着祖先显隐即可,不该再单独关。 + if (IsTimelineControlledLight(tmp_name) && !HasTimelineControlledAncestor(child)) + child.gameObject.SetActive(false); + + if (!StageObjectMap.ContainsKey(tmp_name)) { - if (IsTimelineControlledLight(child.name)) - child.gameObject.SetActive(false); - var tmp_name = child.name.Replace("(Clone)", ""); StageObjectMap.Add(tmp_name, child.gameObject); StageParentMap.TryAdd(tmp_name, child.gameObject.transform.parent); } @@ -75,6 +105,9 @@ namespace Gallop.Live StageObjectUnitMap.Add(unit.UnitName, unit); } } + + // 地板发白的诊断,只打日志不改任何东西。见 StageEnvMapDiag 的说明。 + StageEnvMapDiag.Dump(this); } public void UpdateObject(ref ObjectUpdateInfo updateInfo) { @@ -177,32 +210,58 @@ namespace Gallop.Live : updateInfo.updateData.scale; } + /// + /// Transform 轨道。 + /// + /// 原实现只查 StageObjectUnitMap,组名不是 unit 名就直接什么都不做、也不报错 —— + /// 这是 UpdateObject 那个 bug 的**镜像**(那次是只查 StageObjectMap、漏了 unit 名)。 + /// 实测组名两种都有:son1028 的第一个组叫 light001_glow_001,是 GameObject 名, + /// 于是这 12 首歌 / 681 个关键帧整条轨道从来没生效过。两种都查。 + /// + /// 注:TypeTree 实测 LiveTimelineKeyTransformData 只有 position/rotate/scale, + /// **没有 OffsetType 字段**(和 Object 轨道不同),所以这里一律绝对写入是对的, + /// 不需要 ApplyObjectTrs 那套 Add 相对语义。 + /// public void UpdateTransform(ref TransformUpdateInfo updateInfo) { if (updateInfo.data == null) { return; } + if (StageObjectUnitMap.TryGetValue(updateInfo.data.name, out StageObjectUnit objectUnit)) { - foreach(var child in objectUnit.ChildObjects) + foreach (var child in objectUnit.ChildObjects) { - if (StageObjectMap.TryGetValue(child.name, out GameObject gameObject)) - { - if (updateInfo.data.enablePosition) - { - gameObject.transform.localPosition = updateInfo.updateData.position; - } - if (updateInfo.data.enableRotate) - { - gameObject.transform.localRotation = updateInfo.updateData.rotation; - } - if (updateInfo.data.enableScale) - { - gameObject.transform.localScale = updateInfo.updateData.scale; - } - } + if (child == null) continue; + // StageObjectMap 存的是剥掉 "(Clone)" 的名字,查的时候也得剥 + // —— UpdateObject 有这一步,这里原先漏了。 + if (!StageObjectMap.TryGetValue(child.name.Replace("(Clone)", ""), out var childGo)) + childGo = child; + ApplyTransformTrs(childGo.transform, ref updateInfo); } + return; + } + + if (StageObjectMap.TryGetValue(updateInfo.data.name, out GameObject gameObject)) + { + ApplyTransformTrs(gameObject.transform, ref updateInfo); + } + } + + private static void ApplyTransformTrs(Transform tr, ref TransformUpdateInfo updateInfo) + { + if (updateInfo.data.enablePosition) + { + tr.localPosition = updateInfo.updateData.position; + } + if (updateInfo.data.enableRotate) + { + tr.localRotation = updateInfo.updateData.rotation; + } + if (updateInfo.data.enableScale) + { + tr.localScale = updateInfo.updateData.scale; } } }