Add Live Effect track (track ID 60) implementation
- LiveTimelineEffectData.cs: new data class with full field set
(color, offset, owner, occurrenceSpot, IsLinkOwner*, etc.)
derived from actual cutt asset bundle TypeTree dump
- LiveTimelineWorkSheet: add effectList field
- LiveTimelineControl: add OnUpdateEffect event and AlterUpdate_EffectControl
- Director: add OnEffectUpdate handler and ApplyEffectTransform helper
loads prefab from 3d/effect/live/pfb_{name}, instantiates per key frame
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -62,6 +62,8 @@ namespace Gallop.Live.Cutt
|
||||
|
||||
public event ObjectUpdateInfoDelegate OnUpdateObject;
|
||||
|
||||
public event Action<LiveTimelineEffectData, LiveTimelineKeyEffectData> OnUpdateEffect;
|
||||
|
||||
private static Func<LiveTimelineKeyCameraPositionData, LiveTimelineControl, FindTimelineConfig, Vector3> fnGetCameraPosValue = GetCameraPosValue;
|
||||
|
||||
private static Func<LiveTimelineKeyCameraLookAtData, LiveTimelineControl, Vector3, FindTimelineConfig, Vector3> fnGetCameraLookAtValue = GetCameraLookAtValue;
|
||||
@@ -341,6 +343,7 @@ namespace Gallop.Live.Cutt
|
||||
AlterUpdate_BgColor1(workSheet, _currentFrame);
|
||||
AlterUpdate_TransformControl(workSheet, _currentFrame);
|
||||
AlterUpdate_ObjectControl(workSheet, _currentFrame);
|
||||
AlterUpdate_EffectControl(workSheet, _currentFrame);
|
||||
|
||||
_isNowAlterUpdate = false;
|
||||
|
||||
@@ -618,7 +621,7 @@ namespace Gallop.Live.Cutt
|
||||
|
||||
|
||||
|
||||
//修改(Live演出的显隐控制和位置变换相关)
|
||||
//��(Live�ݳ����������ƺ�λ�ñ任���)
|
||||
public void LateUpdateFormationOffset_Transform(int targetIndex, LiveTimelineKeyIndex curKeyIndex, float time)
|
||||
{
|
||||
bool ControlMode = UmaViewerUI.Instance != null && UmaViewerUI.Instance.isControlMode;
|
||||
@@ -1158,7 +1161,7 @@ namespace Gallop.Live.Cutt
|
||||
{
|
||||
camera.cacheTransform.position = pos;
|
||||
|
||||
// TODO 这里用的还是CGSS的layer枚举,要进行替换
|
||||
// TODO �����õĻ���CGSS��layerö�٣�Ҫ�����滻
|
||||
/*
|
||||
int num = liveTimelineKeyCameraPositionData.GetCullingMask();
|
||||
if (num == 0)
|
||||
@@ -1823,6 +1826,22 @@ namespace Gallop.Live.Cutt
|
||||
}
|
||||
}
|
||||
|
||||
private void AlterUpdate_EffectControl(LiveTimelineWorkSheet sheet, float currentFrame)
|
||||
{
|
||||
if (sheet.effectList == null || OnUpdateEffect == null) return;
|
||||
int count = sheet.effectList.Count;
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
var effectData = sheet.effectList[i];
|
||||
var keys = effectData.keys;
|
||||
if (keys == null || keys.HasAttribute(LiveTimelineKeyDataListAttr.Disable) || !keys.EnablePlayModeTimeline(_playMode))
|
||||
continue;
|
||||
FindTimelineKey(out var curKey, out var _, keys, currentFrame);
|
||||
if (curKey == null) continue;
|
||||
OnUpdateEffect.Invoke(effectData, curKey as LiveTimelineKeyEffectData);
|
||||
}
|
||||
}
|
||||
|
||||
private static float LerpWithoutClamp(float a, float b, float t)
|
||||
{
|
||||
return a + (b - a) * t;
|
||||
|
||||
+43
@@ -0,0 +1,43 @@
|
||||
using System;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Gallop.Live.Cutt
|
||||
{
|
||||
[Serializable]
|
||||
public class LiveTimelineKeyEffectData : LiveTimelineKeyWithInterpolate
|
||||
{
|
||||
public Color color;
|
||||
public float colorPower;
|
||||
public int ColorProperty;
|
||||
public int owner;
|
||||
public int occurrenceSpot;
|
||||
public string ParentStageObjectName;
|
||||
public bool IsAttachProps;
|
||||
public int PropsIndex;
|
||||
public Vector3 offset;
|
||||
public Vector3 offsetAngle;
|
||||
public Vector3 offsetScale;
|
||||
public string BlinkLightName;
|
||||
public int BlinkLightNameHash;
|
||||
public int BlinkLightContainerIndex;
|
||||
public float BlinkLightBrightnessPower;
|
||||
public bool IsAdjustedBlinkLightColor;
|
||||
public bool IsSyncBlinkLight;
|
||||
public bool IsLinkOwnerPositionX;
|
||||
public bool IsLinkOwnerPositionY;
|
||||
public bool IsLinkOwnerPositionZ;
|
||||
public bool IsLinkOwnerRotate;
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
public class LiveTimelineKeyEffectDataList : LiveTimelineKeyDataListTemplate<LiveTimelineKeyEffectData>
|
||||
{
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
public class LiveTimelineEffectData : ILiveTimelineGroupDataWithName
|
||||
{
|
||||
private const string default_name = "Effect";
|
||||
public LiveTimelineKeyEffectDataList keys;
|
||||
}
|
||||
}
|
||||
@@ -634,7 +634,7 @@ namespace Gallop.Live.Cutt
|
||||
public float Timescale;
|
||||
}
|
||||
|
||||
//正在处理这里
|
||||
//���ڴ�������
|
||||
[System.Serializable]
|
||||
public class LiveTimelineKeyCameraPositionData : LiveTimelineKeyWithInterpolate
|
||||
{
|
||||
@@ -915,7 +915,7 @@ namespace Gallop.Live.Cutt
|
||||
[SerializeField] public List<LiveTimelineMultiCameraPositionData> multiCameraPosKeys;
|
||||
[SerializeField] public List<LiveTimelineMultiCameraLookAtData> multiCameraLookAtKeys;
|
||||
|
||||
//[SerializeField]用于该类在别的脚本里定义的时候
|
||||
//[SerializeField]���ڸ����ڱ�Ľű��ﶨ���ʱ��
|
||||
[SerializeField] public LiveTimelineKeyCameraLookAtDataList cameraLookAtKeys;
|
||||
[SerializeField] public LiveTimelineKeyCameraFovDataList cameraFovKeys;
|
||||
[SerializeField] public LiveTimelineKeyCameraRollDataList cameraRollKeys;
|
||||
@@ -936,9 +936,10 @@ namespace Gallop.Live.Cutt
|
||||
|
||||
[SerializeField] public List<LiveTimelineTransformData> transformList;
|
||||
[SerializeField] public List<LiveTimelineObjectData> objectList;
|
||||
[SerializeField] public List<LiveTimelineEffectData> effectList;
|
||||
|
||||
/*
|
||||
//终于可以调用AB包了,虽然后面发现没什么用...说不定什么时候能用到
|
||||
//���ڿ��Ե���AB���ˣ���Ȼ���淢��ûʲô��...˵����ʲôʱ�����õ�
|
||||
private void Start()
|
||||
{
|
||||
LoadCharaMotion();
|
||||
|
||||
@@ -34,6 +34,7 @@ namespace Gallop.Live
|
||||
private const string VOCAL_PATH = "sound/l/{0}/snd_bgm_live_{0}_chara_{1}_01";
|
||||
private const string RANDOM_VOCAL_PATH = "sound/l/{0}/snd_bgm_live_{0}_chara";
|
||||
private const string LIVE_PART_PATH = "live/musicscores/m{0}/m{0}_part";
|
||||
private const string EFFECT_PATH = "3d/effect/live/pfb_{0}";
|
||||
|
||||
private UmaViewerBuilder Builder => UmaViewerBuilder.Instance;
|
||||
|
||||
@@ -43,6 +44,10 @@ namespace Gallop.Live
|
||||
|
||||
public List<Animation> charaAnims;
|
||||
public List<UmaViewerAudio.CuteAudioSource> liveVocal = new List<UmaViewerAudio.CuteAudioSource>();
|
||||
|
||||
// effect track: maps effectList entry -> (last key frame, active instance)
|
||||
private Dictionary<LiveTimelineEffectData, (int frame, GameObject instance)> _activeEffects
|
||||
= new Dictionary<LiveTimelineEffectData, (int, GameObject)>();
|
||||
public UmaViewerAudio.CuteAudioSource liveMusic = new UmaViewerAudio.CuteAudioSource();
|
||||
|
||||
public PartEntry partInfo;
|
||||
@@ -152,7 +157,7 @@ namespace Gallop.Live
|
||||
|
||||
sliderControl = UI.ProgressBar.GetComponent<SliderControl>();
|
||||
LiveViewerUI.Instance.RecordingUI.SetActive(IsRecordVMD);
|
||||
LiveViewerUI.Instance.RecordingText.text = $"¡ñ Recording...\r\n VMD will be saved in {Path.GetFullPath(Application.dataPath + UnityHumanoidVMDRecorder.FileSavePath)}";
|
||||
LiveViewerUI.Instance.RecordingText.text = $"�� Recording...\r\n VMD will be saved in {Path.GetFullPath(Application.dataPath + UnityHumanoidVMDRecorder.FileSavePath)}";
|
||||
}
|
||||
|
||||
public void InitializeTimeline(List<LiveCharacterSelect> characters, int mode)
|
||||
@@ -289,6 +294,76 @@ namespace Gallop.Live
|
||||
_activeCameraIndex = kTimelineCameraIndices[cameraIndex_];
|
||||
}
|
||||
};
|
||||
|
||||
_liveTimelineControl.OnUpdateEffect += OnEffectUpdate;
|
||||
}
|
||||
|
||||
private void OnEffectUpdate(LiveTimelineEffectData effectData, LiveTimelineKeyEffectData keyData)
|
||||
{
|
||||
if (keyData == null) return;
|
||||
|
||||
// Only (re)instantiate when the key frame changes
|
||||
if (_activeEffects.TryGetValue(effectData, out var current) && current.frame == keyData.frame)
|
||||
{
|
||||
// Same key — update position if following owner
|
||||
if (current.instance != null)
|
||||
ApplyEffectTransform(current.instance.transform, keyData);
|
||||
return;
|
||||
}
|
||||
|
||||
// Destroy previous instance
|
||||
if (_activeEffects.TryGetValue(effectData, out var old) && old.instance != null)
|
||||
Destroy(old.instance);
|
||||
|
||||
// Load prefab
|
||||
string path = string.Format(EFFECT_PATH, effectData.name);
|
||||
if (!UmaViewerMain.Instance.AbList.ContainsKey(path))
|
||||
{
|
||||
_activeEffects[effectData] = (keyData.frame, null);
|
||||
return;
|
||||
}
|
||||
|
||||
AssetBundle bundle = UmaAssetManager.LoadAssetBundle(UmaViewerMain.Instance.AbList[path]);
|
||||
if (bundle == null)
|
||||
{
|
||||
_activeEffects[effectData] = (keyData.frame, null);
|
||||
return;
|
||||
}
|
||||
|
||||
GameObject prefab = bundle.LoadAsset<GameObject>(System.IO.Path.GetFileName(path));
|
||||
if (prefab == null)
|
||||
{
|
||||
_activeEffects[effectData] = (keyData.frame, null);
|
||||
return;
|
||||
}
|
||||
|
||||
GameObject instance = Instantiate(prefab, transform);
|
||||
ApplyEffectTransform(instance.transform, keyData);
|
||||
_activeEffects[effectData] = (keyData.frame, instance);
|
||||
}
|
||||
|
||||
private void ApplyEffectTransform(Transform t, LiveTimelineKeyEffectData keyData)
|
||||
{
|
||||
Vector3 basePos = Vector3.zero;
|
||||
|
||||
// owner == World (18) or out of range: world origin
|
||||
int ownerIndex = keyData.owner;
|
||||
if (ownerIndex >= 0 && ownerIndex < CharaContainerScript.Count)
|
||||
{
|
||||
var container = CharaContainerScript[ownerIndex];
|
||||
if (container != null)
|
||||
{
|
||||
basePos = new Vector3(
|
||||
keyData.IsLinkOwnerPositionX ? container.transform.position.x : 0f,
|
||||
keyData.IsLinkOwnerPositionY ? container.transform.position.y : 0f,
|
||||
keyData.IsLinkOwnerPositionZ ? container.transform.position.z : 0f
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
t.position = basePos + keyData.offset;
|
||||
t.eulerAngles = keyData.offsetAngle;
|
||||
t.localScale = keyData.offsetScale;
|
||||
}
|
||||
|
||||
public void InitializeCamera()
|
||||
|
||||
Reference in New Issue
Block a user