做不出来
This commit is contained in:
@@ -0,0 +1,42 @@
|
||||
%YAML 1.1
|
||||
%TAG !u! tag:unity3d.com,2011:
|
||||
--- !u!114 &11400000
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 0}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: 1bbb15cae81a83543bbc5da32d7a03b1, type: 3}
|
||||
m_Name: CieIFD_Back_00
|
||||
m_EditorClassIdentifier: Assembly-CSharp::Cielonos.MainGame.MusicBeatData
|
||||
serializationData:
|
||||
SerializedFormat: 2
|
||||
SerializedBytes:
|
||||
ReferencedUnityObjects: []
|
||||
SerializedBytesString:
|
||||
Prefab: {fileID: 0}
|
||||
PrefabModificationsReferencedUnityObjects: []
|
||||
PrefabModifications: []
|
||||
SerializationNodes: []
|
||||
musicSwitch:
|
||||
idInternal: 0
|
||||
valueGuidInternal:
|
||||
groupIdInternal: 0
|
||||
groupGuidInternal:
|
||||
WwiseObjectReference: {fileID: 11400000, guid: d65f536d666aec348baa6c8fe6a0844a, type: 2}
|
||||
musicEvent:
|
||||
idInternal: 0
|
||||
valueGuidInternal:
|
||||
WwiseObjectReference: {fileID: 11400000, guid: 490938cc29815d54498a3ff46a7b7078, type: 2}
|
||||
stopMusicEvent:
|
||||
idInternal: 0
|
||||
valueGuidInternal:
|
||||
WwiseObjectReference: {fileID: 11400000, guid: 55dc807cde468a0429fb934d73c29793, type: 2}
|
||||
bpm: 240
|
||||
beatsPerBar: 8
|
||||
audioStartOffset: 0
|
||||
totalDuration: 11.346
|
||||
beatMarkers: []
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d6c3f7ba9c0b0b84e8e7953111e59b0d
|
||||
NativeFormatImporter:
|
||||
externalObjects: {}
|
||||
mainObjectFileID: 11400000
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,42 @@
|
||||
%YAML 1.1
|
||||
%TAG !u! tag:unity3d.com,2011:
|
||||
--- !u!114 &11400000
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 0}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: 1bbb15cae81a83543bbc5da32d7a03b1, type: 3}
|
||||
m_Name: CieIFD_Back_01
|
||||
m_EditorClassIdentifier: Assembly-CSharp::Cielonos.MainGame.MusicBeatData
|
||||
serializationData:
|
||||
SerializedFormat: 2
|
||||
SerializedBytes:
|
||||
ReferencedUnityObjects: []
|
||||
SerializedBytesString:
|
||||
Prefab: {fileID: 0}
|
||||
PrefabModificationsReferencedUnityObjects: []
|
||||
PrefabModifications: []
|
||||
SerializationNodes: []
|
||||
musicSwitch:
|
||||
idInternal: 0
|
||||
valueGuidInternal:
|
||||
groupIdInternal: 0
|
||||
groupGuidInternal:
|
||||
WwiseObjectReference: {fileID: 11400000, guid: 53d01043d863483429cc2d9809876da4, type: 2}
|
||||
musicEvent:
|
||||
idInternal: 0
|
||||
valueGuidInternal:
|
||||
WwiseObjectReference: {fileID: 11400000, guid: 490938cc29815d54498a3ff46a7b7078, type: 2}
|
||||
stopMusicEvent:
|
||||
idInternal: 0
|
||||
valueGuidInternal:
|
||||
WwiseObjectReference: {fileID: 11400000, guid: 55dc807cde468a0429fb934d73c29793, type: 2}
|
||||
bpm: 240
|
||||
beatsPerBar: 8
|
||||
audioStartOffset: 0
|
||||
totalDuration: 11.031
|
||||
beatMarkers: []
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ed1413903caf9d84a986254048ff5f1e
|
||||
NativeFormatImporter:
|
||||
externalObjects: {}
|
||||
mainObjectFileID: 11400000
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,42 @@
|
||||
%YAML 1.1
|
||||
%TAG !u! tag:unity3d.com,2011:
|
||||
--- !u!114 &11400000
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 0}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: 1bbb15cae81a83543bbc5da32d7a03b1, type: 3}
|
||||
m_Name: CieIFD_Back_02
|
||||
m_EditorClassIdentifier: Assembly-CSharp::Cielonos.MainGame.MusicBeatData
|
||||
serializationData:
|
||||
SerializedFormat: 2
|
||||
SerializedBytes:
|
||||
ReferencedUnityObjects: []
|
||||
SerializedBytesString:
|
||||
Prefab: {fileID: 0}
|
||||
PrefabModificationsReferencedUnityObjects: []
|
||||
PrefabModifications: []
|
||||
SerializationNodes: []
|
||||
musicSwitch:
|
||||
idInternal: 0
|
||||
valueGuidInternal:
|
||||
groupIdInternal: 0
|
||||
groupGuidInternal:
|
||||
WwiseObjectReference: {fileID: 11400000, guid: 801b89ad79583d84291be4be2c5e9949, type: 2}
|
||||
musicEvent:
|
||||
idInternal: 0
|
||||
valueGuidInternal:
|
||||
WwiseObjectReference: {fileID: 11400000, guid: 490938cc29815d54498a3ff46a7b7078, type: 2}
|
||||
stopMusicEvent:
|
||||
idInternal: 0
|
||||
valueGuidInternal:
|
||||
WwiseObjectReference: {fileID: 11400000, guid: 55dc807cde468a0429fb934d73c29793, type: 2}
|
||||
bpm: 240
|
||||
beatsPerBar: 8
|
||||
audioStartOffset: 0
|
||||
totalDuration: 16.052
|
||||
beatMarkers: []
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 6b565265535aa2943981ecc325e52b51
|
||||
NativeFormatImporter:
|
||||
externalObjects: {}
|
||||
mainObjectFileID: 11400000
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,52 @@
|
||||
%YAML 1.1
|
||||
%TAG !u! tag:unity3d.com,2011:
|
||||
--- !u!114 &11400000
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 0}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: 1bbb15cae81a83543bbc5da32d7a03b1, type: 3}
|
||||
m_Name: CieIFD_Func_00
|
||||
m_EditorClassIdentifier: Assembly-CSharp::Cielonos.MainGame.MusicBeatData
|
||||
serializationData:
|
||||
SerializedFormat: 2
|
||||
SerializedBytes:
|
||||
ReferencedUnityObjects: []
|
||||
SerializedBytesString:
|
||||
Prefab: {fileID: 0}
|
||||
PrefabModificationsReferencedUnityObjects: []
|
||||
PrefabModifications: []
|
||||
SerializationNodes: []
|
||||
musicSwitch:
|
||||
idInternal: 0
|
||||
valueGuidInternal:
|
||||
groupIdInternal: 0
|
||||
groupGuidInternal:
|
||||
WwiseObjectReference: {fileID: 11400000, guid: a585ced711c602c4297ec117c32e24b9, type: 2}
|
||||
musicEvent:
|
||||
idInternal: 0
|
||||
valueGuidInternal:
|
||||
WwiseObjectReference: {fileID: 11400000, guid: 490938cc29815d54498a3ff46a7b7078, type: 2}
|
||||
stopMusicEvent:
|
||||
idInternal: 0
|
||||
valueGuidInternal:
|
||||
WwiseObjectReference: {fileID: 11400000, guid: 55dc807cde468a0429fb934d73c29793, type: 2}
|
||||
bpm: 240
|
||||
beatsPerBar: 8
|
||||
audioStartOffset: 0
|
||||
totalDuration: 4.016
|
||||
beatMarkers:
|
||||
- time: 2
|
||||
tags:
|
||||
- EnemyAttack0
|
||||
barIndex: 1
|
||||
beatInBar: 0
|
||||
- time: 2
|
||||
tags:
|
||||
- Normal
|
||||
barIndex: 1
|
||||
beatInBar: 3
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 119d0222713540649bc507c5436fc808
|
||||
NativeFormatImporter:
|
||||
externalObjects: {}
|
||||
mainObjectFileID: 11400000
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,52 @@
|
||||
%YAML 1.1
|
||||
%TAG !u! tag:unity3d.com,2011:
|
||||
--- !u!114 &11400000
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 0}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: 1bbb15cae81a83543bbc5da32d7a03b1, type: 3}
|
||||
m_Name: CieIFD_Func_01
|
||||
m_EditorClassIdentifier: Assembly-CSharp::Cielonos.MainGame.MusicBeatData
|
||||
serializationData:
|
||||
SerializedFormat: 2
|
||||
SerializedBytes:
|
||||
ReferencedUnityObjects: []
|
||||
SerializedBytesString:
|
||||
Prefab: {fileID: 0}
|
||||
PrefabModificationsReferencedUnityObjects: []
|
||||
PrefabModifications: []
|
||||
SerializationNodes: []
|
||||
musicSwitch:
|
||||
idInternal: 0
|
||||
valueGuidInternal:
|
||||
groupIdInternal: 0
|
||||
groupGuidInternal:
|
||||
WwiseObjectReference: {fileID: 11400000, guid: a7ff460b65cb2334ab875d06bcc2373d, type: 2}
|
||||
musicEvent:
|
||||
idInternal: 0
|
||||
valueGuidInternal:
|
||||
WwiseObjectReference: {fileID: 11400000, guid: 490938cc29815d54498a3ff46a7b7078, type: 2}
|
||||
stopMusicEvent:
|
||||
idInternal: 0
|
||||
valueGuidInternal:
|
||||
WwiseObjectReference: {fileID: 11400000, guid: 55dc807cde468a0429fb934d73c29793, type: 2}
|
||||
bpm: 240
|
||||
beatsPerBar: 8
|
||||
audioStartOffset: 0
|
||||
totalDuration: 4.013
|
||||
beatMarkers:
|
||||
- time: 2
|
||||
tags:
|
||||
- EnemyAttack0
|
||||
barIndex: 1
|
||||
beatInBar: 0
|
||||
- time: 2
|
||||
tags:
|
||||
- Normal
|
||||
barIndex: 1
|
||||
beatInBar: 3
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 73c0bfd6deca2494d871db60f90c7924
|
||||
NativeFormatImporter:
|
||||
externalObjects: {}
|
||||
mainObjectFileID: 11400000
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,52 @@
|
||||
%YAML 1.1
|
||||
%TAG !u! tag:unity3d.com,2011:
|
||||
--- !u!114 &11400000
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 0}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: 1bbb15cae81a83543bbc5da32d7a03b1, type: 3}
|
||||
m_Name: CieIFD_Func_02
|
||||
m_EditorClassIdentifier: Assembly-CSharp::Cielonos.MainGame.MusicBeatData
|
||||
serializationData:
|
||||
SerializedFormat: 2
|
||||
SerializedBytes:
|
||||
ReferencedUnityObjects: []
|
||||
SerializedBytesString:
|
||||
Prefab: {fileID: 0}
|
||||
PrefabModificationsReferencedUnityObjects: []
|
||||
PrefabModifications: []
|
||||
SerializationNodes: []
|
||||
musicSwitch:
|
||||
idInternal: 0
|
||||
valueGuidInternal:
|
||||
groupIdInternal: 0
|
||||
groupGuidInternal:
|
||||
WwiseObjectReference: {fileID: 11400000, guid: e75aa8581e5c3814ca3874c1ac80d6d1, type: 2}
|
||||
musicEvent:
|
||||
idInternal: 0
|
||||
valueGuidInternal:
|
||||
WwiseObjectReference: {fileID: 11400000, guid: 490938cc29815d54498a3ff46a7b7078, type: 2}
|
||||
stopMusicEvent:
|
||||
idInternal: 0
|
||||
valueGuidInternal:
|
||||
WwiseObjectReference: {fileID: 11400000, guid: 55dc807cde468a0429fb934d73c29793, type: 2}
|
||||
bpm: 240
|
||||
beatsPerBar: 8
|
||||
audioStartOffset: 0
|
||||
totalDuration: 4.013
|
||||
beatMarkers:
|
||||
- time: 2
|
||||
tags:
|
||||
- EnemyAttack0
|
||||
barIndex: 1
|
||||
beatInBar: 0
|
||||
- time: 2
|
||||
tags:
|
||||
- Normal
|
||||
barIndex: 1
|
||||
beatInBar: 3
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 23d5a38a23454ab47afb0c9dcd8089f7
|
||||
NativeFormatImporter:
|
||||
externalObjects: {}
|
||||
mainObjectFileID: 11400000
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,52 @@
|
||||
%YAML 1.1
|
||||
%TAG !u! tag:unity3d.com,2011:
|
||||
--- !u!114 &11400000
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 0}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: 1bbb15cae81a83543bbc5da32d7a03b1, type: 3}
|
||||
m_Name: CieIFD_Func_03
|
||||
m_EditorClassIdentifier: Assembly-CSharp::Cielonos.MainGame.MusicBeatData
|
||||
serializationData:
|
||||
SerializedFormat: 2
|
||||
SerializedBytes:
|
||||
ReferencedUnityObjects: []
|
||||
SerializedBytesString:
|
||||
Prefab: {fileID: 0}
|
||||
PrefabModificationsReferencedUnityObjects: []
|
||||
PrefabModifications: []
|
||||
SerializationNodes: []
|
||||
musicSwitch:
|
||||
idInternal: 0
|
||||
valueGuidInternal:
|
||||
groupIdInternal: 0
|
||||
groupGuidInternal:
|
||||
WwiseObjectReference: {fileID: 11400000, guid: ef14fc900d426df4699b590122abc7bf, type: 2}
|
||||
musicEvent:
|
||||
idInternal: 0
|
||||
valueGuidInternal:
|
||||
WwiseObjectReference: {fileID: 11400000, guid: 490938cc29815d54498a3ff46a7b7078, type: 2}
|
||||
stopMusicEvent:
|
||||
idInternal: 0
|
||||
valueGuidInternal:
|
||||
WwiseObjectReference: {fileID: 11400000, guid: 55dc807cde468a0429fb934d73c29793, type: 2}
|
||||
bpm: 240
|
||||
beatsPerBar: 8
|
||||
audioStartOffset: 0
|
||||
totalDuration: 4.349
|
||||
beatMarkers:
|
||||
- time: 2
|
||||
tags:
|
||||
- EnemyAttack0
|
||||
barIndex: 1
|
||||
beatInBar: 0
|
||||
- time: 2
|
||||
tags:
|
||||
- Normal
|
||||
barIndex: 1
|
||||
beatInBar: 3
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f9d2c5b5062107a4cabd67723cdc58eb
|
||||
NativeFormatImporter:
|
||||
externalObjects: {}
|
||||
mainObjectFileID: 11400000
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,421 @@
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using Sirenix.OdinInspector;
|
||||
|
||||
namespace Cielonos.MainGame
|
||||
{
|
||||
/// <summary>
|
||||
/// 战斗音乐控制器,负责处理功能音乐的无缝随机切换,以及并行的 BGM 音轨按小节边界起播/停止。
|
||||
/// </summary>
|
||||
[AddComponentMenu("Cielonos/Rhythm/CombatMusicController")]
|
||||
public class CombatMusicController : MonoBehaviour
|
||||
{
|
||||
[Header("System References")]
|
||||
[Tooltip("全局节拍战斗系统的引用")]
|
||||
public MusicBeatSystem beatSystem;
|
||||
|
||||
[Header("BGM Settings")]
|
||||
[Tooltip("播放背景音乐 Switch 容器的 Wwise Event")]
|
||||
public AK.Wwise.Event bgmMusicEvent;
|
||||
|
||||
[Tooltip("所有可用的背景音乐 Wwise Segment 名称 (对应 Wwise 中的 Switch 值)")]
|
||||
public List<string> bgmSegments = new List<string>
|
||||
{
|
||||
"Back_00",
|
||||
"Back_01",
|
||||
"Back_02"
|
||||
};
|
||||
|
||||
[Header("Functional Music Settings")]
|
||||
[Tooltip("播放功能音乐 Switch 容器的 Wwise Event")]
|
||||
public AK.Wwise.Event functionalMusicEvent;
|
||||
|
||||
[Tooltip("初始播放的音乐片段 Switch 名称")]
|
||||
public string initialSegment = "Func_00";
|
||||
|
||||
[Tooltip("所有可用的功能音乐 Wwise Segment 名称 (对应 Wwise 中的 Switch 值)")]
|
||||
public List<string> functionalSegments = new List<string>
|
||||
{
|
||||
"Func_00",
|
||||
"Func_01",
|
||||
"Func_02",
|
||||
"Func_03"
|
||||
};
|
||||
|
||||
// 运行时 BGM 状态跟踪字典
|
||||
private readonly Dictionary<string, bool> bgmTargetStates = new Dictionary<string, bool>();
|
||||
private readonly Dictionary<string, bool> bgmIsPlaying = new Dictionary<string, bool>();
|
||||
private readonly Dictionary<string, uint> bgmPlayingIDs = new Dictionary<string, uint>();
|
||||
private readonly Dictionary<string, GameObject> bgmGameObjects = new Dictionary<string, GameObject>();
|
||||
|
||||
// 等待下一个全局 PrepareNext 边界才起播的 BGM 片段队列
|
||||
private readonly HashSet<string> pendingBgmStarts = new HashSet<string>();
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
// 初始化每个 BGM 段落的运行状态与子 GameObject
|
||||
foreach (var seg in bgmSegments)
|
||||
{
|
||||
bgmTargetStates[seg] = false;
|
||||
bgmIsPlaying[seg] = false;
|
||||
bgmPlayingIDs[seg] = 0;
|
||||
|
||||
// 为每个 BGM 片段创建独立的子 GameObject,以便并行播放和接收独立的节拍回调
|
||||
GameObject go = new GameObject($"BGM_Layer_{seg}");
|
||||
go.transform.SetParent(transform);
|
||||
bgmGameObjects[seg] = go;
|
||||
|
||||
// 在节拍注册表中注册此子物体
|
||||
MusicBeatSystem.RegisterRhythmGameObject(go);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnEnable()
|
||||
{
|
||||
if (beatSystem == null)
|
||||
{
|
||||
beatSystem = GetComponent<MusicBeatSystem>();
|
||||
}
|
||||
|
||||
if (beatSystem != null)
|
||||
{
|
||||
beatSystem.OnPrepareNextSegment += DecideNextSegment;
|
||||
beatSystem.OnUserCueReceived += HandleTrackUserCue;
|
||||
beatSystem.OnGlobalPrepareNext += OnGlobalPrepareNextFired;
|
||||
}
|
||||
}
|
||||
|
||||
private void OnDisable()
|
||||
{
|
||||
if (beatSystem != null)
|
||||
{
|
||||
beatSystem.OnPrepareNextSegment -= DecideNextSegment;
|
||||
beatSystem.OnUserCueReceived -= HandleTrackUserCue;
|
||||
beatSystem.OnGlobalPrepareNext -= OnGlobalPrepareNextFired;
|
||||
}
|
||||
}
|
||||
|
||||
private void OnDestroy()
|
||||
{
|
||||
// 释放所有动态创建的子 GameObject
|
||||
foreach (var pair in bgmGameObjects)
|
||||
{
|
||||
if (pair.Value != null)
|
||||
{
|
||||
MusicBeatSystem.UnregisterRhythmGameObject(pair.Value);
|
||||
Destroy(pair.Value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#region BGM Inspector Controls
|
||||
|
||||
[Button("Play/Stop Back_00 (8 Bars)")]
|
||||
public void ToggleBack_00() => ToggleBgmTrack("Back_00");
|
||||
|
||||
[Button("Play/Stop Back_01 (8 Bars)")]
|
||||
public void ToggleBack_01() => ToggleBgmTrack("Back_01");
|
||||
|
||||
[Button("Play/Stop Back_02 (16 Bars)")]
|
||||
public void ToggleBack_02() => ToggleBgmTrack("Back_02");
|
||||
|
||||
#endregion
|
||||
|
||||
#region BGM Implementation
|
||||
|
||||
private void ToggleBgmTrack(string segmentName)
|
||||
{
|
||||
if (!bgmTargetStates.ContainsKey(segmentName)) return;
|
||||
|
||||
bgmTargetStates[segmentName] = !bgmTargetStates[segmentName];
|
||||
Debug.Log($"[CombatMusicController] Toggle BGM Segment '{segmentName}': Target state is now {bgmTargetStates[segmentName]}");
|
||||
|
||||
if (bgmTargetStates[segmentName])
|
||||
{
|
||||
// 确保 beatSystem 已激活(用于接收 PrepareNext 通知)
|
||||
if (beatSystem != null && !beatSystem.IsActive)
|
||||
{
|
||||
beatSystem.Activate(null);
|
||||
}
|
||||
|
||||
if (!bgmIsPlaying[segmentName] && !pendingBgmStarts.Contains(segmentName))
|
||||
{
|
||||
// 如果没有任何音乐在播放或排队,立即起播(第一次)
|
||||
// 否则,排入队列等待下一个全局 PrepareNext 边界对齐
|
||||
if (!AnyBgmPlaying() && !AnyBgmPending())
|
||||
{
|
||||
Debug.Log($"[CombatMusicController] No music playing, starting BGM '{segmentName}' immediately.");
|
||||
StartBgmImmediately(segmentName);
|
||||
}
|
||||
else
|
||||
{
|
||||
pendingBgmStarts.Add(segmentName);
|
||||
Debug.Log($"[CombatMusicController] BGM '{segmentName}' queued, will start at next PrepareNext boundary.");
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// 取消播放:从待播队列中移除(如果还没起播)
|
||||
pendingBgmStarts.Remove(segmentName);
|
||||
}
|
||||
}
|
||||
|
||||
private bool AnyBgmPlaying()
|
||||
{
|
||||
foreach (var pair in bgmIsPlaying)
|
||||
{
|
||||
if (pair.Value) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private bool AnyBgmPending()
|
||||
{
|
||||
return pendingBgmStarts.Count > 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 当全局主音乐(Func 轨)的 PrepareNext 到达时,将所有等待中的 BGM 片段一起起播。
|
||||
/// Wwise 引擎会将它们精确对齐到 Exit Cue 边界!
|
||||
/// </summary>
|
||||
private void OnGlobalPrepareNextFired()
|
||||
{
|
||||
if (pendingBgmStarts.Count == 0) return;
|
||||
|
||||
var toStart = new List<string>(pendingBgmStarts);
|
||||
pendingBgmStarts.Clear();
|
||||
|
||||
uint callbackFlags = (uint)(
|
||||
AkCallbackType.AK_MusicSyncBeat |
|
||||
AkCallbackType.AK_MusicSyncEntry |
|
||||
AkCallbackType.AK_MusicSyncUserCue |
|
||||
AkCallbackType.AK_EndOfEvent
|
||||
);
|
||||
|
||||
foreach (var seg in toStart)
|
||||
{
|
||||
if (!bgmTargetStates.ContainsKey(seg) || !bgmTargetStates[seg]) continue;
|
||||
if (bgmIsPlaying[seg]) continue;
|
||||
|
||||
bgmIsPlaying[seg] = true;
|
||||
AkUnitySoundEngine.SetSwitch(beatSystem.musicSegmentSwitchGroup, seg, bgmGameObjects[seg]);
|
||||
|
||||
uint newID = bgmMusicEvent.Post(
|
||||
bgmGameObjects[seg],
|
||||
callbackFlags,
|
||||
beatSystem.OnWwiseMusicCallback,
|
||||
null
|
||||
);
|
||||
|
||||
if (newID != 0)
|
||||
{
|
||||
bgmPlayingIDs[seg] = newID;
|
||||
Debug.Log($"[CombatMusicController] BGM '{seg}' started at PrepareNext boundary. ID={newID}");
|
||||
}
|
||||
else
|
||||
{
|
||||
bgmIsPlaying[seg] = false;
|
||||
Debug.LogError($"[CombatMusicController] Failed to post BGM event for '{seg}'");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void StartBgmImmediately(string segmentName)
|
||||
{
|
||||
if (bgmMusicEvent == null || !bgmMusicEvent.IsValid())
|
||||
{
|
||||
Debug.LogError($"[CombatMusicController] Cannot play BGM: Event is invalid.");
|
||||
return;
|
||||
}
|
||||
|
||||
GameObject go = bgmGameObjects[segmentName];
|
||||
uint callbackFlags = (uint)(
|
||||
AkCallbackType.AK_MusicSyncBeat |
|
||||
AkCallbackType.AK_MusicSyncEntry |
|
||||
AkCallbackType.AK_MusicSyncUserCue |
|
||||
AkCallbackType.AK_EndOfEvent
|
||||
);
|
||||
|
||||
// 设置 Wwise Switch 以让此子物体播放当前 segment 音频
|
||||
AkUnitySoundEngine.SetSwitch(beatSystem.musicSegmentSwitchGroup, segmentName, go);
|
||||
|
||||
uint playingID = bgmMusicEvent.Post(
|
||||
go,
|
||||
callbackFlags,
|
||||
beatSystem.OnWwiseMusicCallback,
|
||||
null
|
||||
);
|
||||
|
||||
if (playingID != 0)
|
||||
{
|
||||
bgmIsPlaying[segmentName] = true;
|
||||
bgmPlayingIDs[segmentName] = playingID;
|
||||
Debug.Log($"[CombatMusicController] Posted BGM Event for '{segmentName}' immediately on '{go.name}', playingID={playingID}");
|
||||
}
|
||||
}
|
||||
|
||||
private void HandleTrackUserCue(uint playingID, string cueName)
|
||||
{
|
||||
// 查找是哪个 BGM 片段触发的回调
|
||||
string triggeringSegment = null;
|
||||
foreach (var pair in bgmPlayingIDs)
|
||||
{
|
||||
if (pair.Value == playingID)
|
||||
{
|
||||
triggeringSegment = pair.Key;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (triggeringSegment == null) return;
|
||||
|
||||
if (cueName == "PrepareNext")
|
||||
{
|
||||
uint callbackFlags = (uint)(
|
||||
AkCallbackType.AK_MusicSyncBeat |
|
||||
AkCallbackType.AK_MusicSyncEntry |
|
||||
AkCallbackType.AK_MusicSyncUserCue |
|
||||
AkCallbackType.AK_EndOfEvent
|
||||
);
|
||||
|
||||
// 1. 如果该 BGM 被要求继续播放,由于 Wwise 可能没有配置原生循环,
|
||||
// 我们在 PrepareNext 时刻(提前)再次 Post Event,让 Wwise 引擎将其调度到下一个 Exit 点无缝衔接起播!
|
||||
if (bgmTargetStates[triggeringSegment])
|
||||
{
|
||||
uint newID = bgmMusicEvent.Post(
|
||||
bgmGameObjects[triggeringSegment],
|
||||
callbackFlags,
|
||||
beatSystem.OnWwiseMusicCallback,
|
||||
null
|
||||
);
|
||||
|
||||
if (newID != 0)
|
||||
{
|
||||
bgmPlayingIDs[triggeringSegment] = newID;
|
||||
Debug.Log($"[CombatMusicController] Loop transition: Re-posted Event for BGM '{triggeringSegment}'. New ID: {newID}");
|
||||
}
|
||||
}
|
||||
// 如果被要求停止,我们什么都不做,让当前片段自然播放完毕并触发 EndOfEvent 即可。
|
||||
|
||||
// 2. 检查是否有排队等待播放的其他 BGM 片段。
|
||||
// 同样在此时刻 Post 它们,Wwise 引擎会自动将它们与当前音频的小节边界完美对齐!
|
||||
foreach (var seg in bgmSegments)
|
||||
{
|
||||
if (seg != triggeringSegment && bgmTargetStates[seg] && !bgmIsPlaying[seg])
|
||||
{
|
||||
bgmIsPlaying[seg] = true;
|
||||
|
||||
// 确保对应的 GameObject Switch 正确
|
||||
AkUnitySoundEngine.SetSwitch(beatSystem.musicSegmentSwitchGroup, seg, bgmGameObjects[seg]);
|
||||
|
||||
uint newSegID = bgmMusicEvent.Post(
|
||||
bgmGameObjects[seg],
|
||||
callbackFlags,
|
||||
beatSystem.OnWwiseMusicCallback,
|
||||
null
|
||||
);
|
||||
|
||||
if (newSegID != 0)
|
||||
{
|
||||
bgmPlayingIDs[seg] = newSegID;
|
||||
Debug.Log($"[CombatMusicController] Queued start: Posted Event for BGM '{seg}' during PrepareNext. ID: {newSegID}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (cueName == "EndOfEvent")
|
||||
{
|
||||
// 注意:由于我们在 PrepareNext 处更新了 bgmPlayingIDs,
|
||||
// 此时旧的 playingID 已经与字典中的不匹配了,所以旧事件的 EndOfEvent 会在上面的循环中返回 null 并忽略!
|
||||
// 只有真正停止播放(未更新 ID)的 EndOfEvent 才会走到这里,实现完美的状态清理!
|
||||
bgmIsPlaying[triggeringSegment] = false;
|
||||
bgmPlayingIDs[triggeringSegment] = 0;
|
||||
Debug.Log($"[CombatMusicController] BGM Segment '{triggeringSegment}' terminated naturally in Wwise (EndOfEvent).");
|
||||
|
||||
if (!AnyBgmPlaying())
|
||||
{
|
||||
bool queuedBgmStarted = false;
|
||||
foreach (var seg in bgmSegments)
|
||||
{
|
||||
if (bgmTargetStates[seg] && !bgmIsPlaying[seg])
|
||||
{
|
||||
Debug.Log($"[CombatMusicController] Starting queued BGM '{seg}' since all others stopped.");
|
||||
if (beatSystem != null && !beatSystem.IsActive)
|
||||
{
|
||||
beatSystem.Activate(null);
|
||||
}
|
||||
StartBgmImmediately(seg);
|
||||
queuedBgmStarted = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!queuedBgmStarted && beatSystem != null && beatSystem.IsActive)
|
||||
{
|
||||
beatSystem.Deactivate();
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (cueName.StartsWith("Entry_"))
|
||||
{
|
||||
bgmIsPlaying[triggeringSegment] = true;
|
||||
Debug.Log($"[CombatMusicController] BGM Segment '{triggeringSegment}' entered and is now running.");
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Functional Music Implementation
|
||||
|
||||
[Button("Play Functional Music")]
|
||||
public void PlayFunctionalMusic()
|
||||
{
|
||||
if (beatSystem == null)
|
||||
{
|
||||
beatSystem = GetComponent<MusicBeatSystem>();
|
||||
}
|
||||
|
||||
if (beatSystem != null)
|
||||
{
|
||||
beatSystem.Activate(functionalMusicEvent, initialSegment);
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.LogError("[CombatMusicController] Cannot play: MusicBeatSystem reference is missing.");
|
||||
}
|
||||
}
|
||||
|
||||
private string DecideNextSegment(GameObject targetGO, string currentSegmentName)
|
||||
{
|
||||
if (beatSystem == null || targetGO != beatSystem.gameObject)
|
||||
{
|
||||
return "";
|
||||
}
|
||||
|
||||
if (functionalSegments == null || functionalSegments.Count == 0)
|
||||
{
|
||||
return "";
|
||||
}
|
||||
|
||||
List<string> candidates = new List<string>(functionalSegments);
|
||||
|
||||
if (!string.IsNullOrEmpty(currentSegmentName))
|
||||
{
|
||||
candidates.Remove(currentSegmentName);
|
||||
}
|
||||
|
||||
if (candidates.Count > 0)
|
||||
{
|
||||
int randomIndex = UnityEngine.Random.Range(0, candidates.Count);
|
||||
string selectedSegment = candidates[randomIndex];
|
||||
|
||||
Debug.Log($"[CombatMusicController] Transition Decision: GameObject '{targetGO.name}' currently playing '{currentSegmentName}', selected next: '{selectedSegment}'");
|
||||
return selectedSegment;
|
||||
}
|
||||
|
||||
return functionalSegments[0];
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 2d333c7739ebda943914444d847d66bc
|
||||
@@ -0,0 +1,60 @@
|
||||
using UnityEngine;
|
||||
|
||||
namespace Cielonos.MainGame
|
||||
{
|
||||
/// <summary>
|
||||
/// 挂载在敌人等 GameObject 上的局部节奏追踪组件,用来追踪该物体上独立播放的 Wwise Segment。
|
||||
/// </summary>
|
||||
[AddComponentMenu("Cielonos/Rhythm/LocalRhythmTracker")]
|
||||
public class LocalRhythmTracker : MonoBehaviour, ILocalRhythmTracker
|
||||
{
|
||||
public MusicBeatData CurrentBeatData { get; private set; }
|
||||
public float CurrentSongTime { get; private set; }
|
||||
public bool IsPlaying { get; private set; }
|
||||
|
||||
private float pendingSyncTime = -1f;
|
||||
|
||||
private void OnEnable()
|
||||
{
|
||||
// 自动将自身注册到全局物体映射表中,使 Wwise 回调能识别此 GameObject
|
||||
MusicBeatSystem.RegisterRhythmGameObject(gameObject);
|
||||
}
|
||||
|
||||
private void OnDisable()
|
||||
{
|
||||
MusicBeatSystem.UnregisterRhythmGameObject(gameObject);
|
||||
}
|
||||
|
||||
public void OnSegmentTransitioned(MusicBeatData localBeatData)
|
||||
{
|
||||
CurrentBeatData = localBeatData;
|
||||
CurrentSongTime = localBeatData != null ? localBeatData.audioStartOffset : 0f;
|
||||
IsPlaying = localBeatData != null;
|
||||
pendingSyncTime = -1f;
|
||||
Debug.Log($"[LocalRhythmTracker] Segment transitioned on '{gameObject.name}' to '{localBeatData?.name}'");
|
||||
}
|
||||
|
||||
public void ReceiveSyncBeat(float musicPositionSec, float beatDuration)
|
||||
{
|
||||
float offset = CurrentBeatData != null ? CurrentBeatData.audioStartOffset : 0f;
|
||||
pendingSyncTime = musicPositionSec + offset;
|
||||
}
|
||||
|
||||
private void Update()
|
||||
{
|
||||
if (CurrentBeatData == null) return;
|
||||
|
||||
if (pendingSyncTime >= 0f)
|
||||
{
|
||||
CurrentSongTime = pendingSyncTime;
|
||||
pendingSyncTime = -1f;
|
||||
IsPlaying = true;
|
||||
}
|
||||
|
||||
if (IsPlaying)
|
||||
{
|
||||
CurrentSongTime += Time.deltaTime;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 59460bbbea550b04ba6b882bfd02692b
|
||||
@@ -7,6 +7,27 @@ using UnityEngine;
|
||||
|
||||
namespace Cielonos.MainGame
|
||||
{
|
||||
[System.Serializable]
|
||||
public struct SegmentDataMapping
|
||||
{
|
||||
[Tooltip("Wwise 中 Music Segment 的名称 (例如 BGM_Intensity_01)")]
|
||||
public string segmentName;
|
||||
[Tooltip("对应的 Unity MusicBeatData 谱面数据")]
|
||||
public MusicBeatData beatData;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 局部节拍追踪器接口,用于接收物体上局部播放的音乐片段切换事件
|
||||
/// </summary>
|
||||
public interface ILocalRhythmTracker
|
||||
{
|
||||
void OnSegmentTransitioned(MusicBeatData localBeatData);
|
||||
void ReceiveSyncBeat(float musicPositionSec, float beatDuration);
|
||||
bool IsPlaying { get; }
|
||||
float CurrentSongTime { get; }
|
||||
MusicBeatData CurrentBeatData { get; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 音乐节拍战斗系统。激活时覆盖 BackgroundMusicManager 播放对应 BGM,
|
||||
/// 通过 Wwise AK_MusicSyncBeat 回调 + MusicBeatData 谱面进行双轨节拍追踪,
|
||||
@@ -71,6 +92,19 @@ namespace Cielonos.MainGame
|
||||
[ShowInInspector, ReadOnly]
|
||||
public float CurrentBPM { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// 当前播放的 Wwise Segment 名称 (例如 Func_00)
|
||||
/// </summary>
|
||||
[ShowInInspector, ReadOnly]
|
||||
public string CurrentWwiseSegmentName { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// 当前小节内的拍号(1-based)。由 Wwise MusicSyncBeat 回调每拍递增,在 Entry 时归 1。
|
||||
/// 例如 4/4 拍时值为 1~4 循环。
|
||||
/// </summary>
|
||||
[ShowInInspector, ReadOnly]
|
||||
public int CurrentBarBeat { get; private set; } = 1;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Events
|
||||
@@ -95,6 +129,18 @@ namespace Cielonos.MainGame
|
||||
/// </summary>
|
||||
public event Action OnDeactivated;
|
||||
|
||||
/// <summary>
|
||||
/// 当收到任何 Wwise User Cue 时触发。
|
||||
/// 参数:1. playingID, 2. cueName。
|
||||
/// </summary>
|
||||
public event Action<uint, string> OnUserCueReceived;
|
||||
|
||||
/// <summary>
|
||||
/// 当全局主音乐(MusicBeatSystem 自身 GameObject)收到 PrepareNext Cue 时触发。
|
||||
/// BGM 系统可以监听此事件,在音乐边界精确地起播 Back 音轨。
|
||||
/// </summary>
|
||||
public event Action OnGlobalPrepareNext;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Private Fields
|
||||
@@ -104,6 +150,11 @@ namespace Cielonos.MainGame
|
||||
/// </summary>
|
||||
private int nextBeatIndex;
|
||||
|
||||
/// <summary>
|
||||
/// 由 MusicSyncBeat 回调累计的原始拍号计数(每个 Entry 时清零),用于推算 CurrentBarBeat
|
||||
/// </summary>
|
||||
private int rawBeatCount;
|
||||
|
||||
/// <summary>
|
||||
/// Wwise 播放实例 ID
|
||||
/// </summary>
|
||||
@@ -120,21 +171,47 @@ namespace Cielonos.MainGame
|
||||
[ShowInInspector]
|
||||
private volatile float pendingSyncTime = -1f;
|
||||
|
||||
/// <summary>
|
||||
/// Wwise 回调报告的 beatDuration(秒),用于反推实际 BPM
|
||||
/// </summary>
|
||||
[ShowInInspector]
|
||||
private volatile float pendingBeatDuration = -1f;
|
||||
|
||||
[Header("Dynamic Music Configurations")]
|
||||
[Tooltip("Wwise 中控制音乐片段切换的 Switch Group 名称")]
|
||||
public string musicSegmentSwitchGroup = "Music_Segment";
|
||||
|
||||
[Tooltip("Wwise 音乐片段与 Unity 谱面资产的映射表")]
|
||||
public List<SegmentDataMapping> segmentMappings = new List<SegmentDataMapping>();
|
||||
|
||||
/// <summary>
|
||||
/// 当音乐运行到 PrepareNext 标记时触发。
|
||||
/// 参数:1. 触发该事件的 GameObject,2. 当前播放的片段名称。
|
||||
/// 返回值:下一个要播放的片段名(Switch 值)。
|
||||
/// </summary>
|
||||
public event Func<GameObject, string, string> OnPrepareNextSegment;
|
||||
|
||||
private struct PendingCallbackData
|
||||
{
|
||||
public enum Type { Entry, Prepare, BeatSync }
|
||||
public Type callbackType;
|
||||
public GameObject targetGO;
|
||||
public string segmentName;
|
||||
public float musicPositionSec;
|
||||
public float beatDuration;
|
||||
public uint playingID;
|
||||
}
|
||||
|
||||
private readonly List<PendingCallbackData> pendingCallbacks = new List<PendingCallbackData>();
|
||||
private readonly object callbackLock = new object();
|
||||
|
||||
private static readonly List<GameObject> activeRhythmGameObjects = new List<GameObject>();
|
||||
|
||||
#endregion
|
||||
|
||||
#region Lifecycle
|
||||
|
||||
public MusicBeatData testData;
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
bgmManager = AudioManager.Instance.backgroundMusicManager;
|
||||
RegisterRhythmGameObject(gameObject);
|
||||
}
|
||||
|
||||
private void Update()
|
||||
@@ -144,6 +221,9 @@ namespace Cielonos.MainGame
|
||||
// 处理 Wwise 回调带来的时间校准
|
||||
ProcessPendingSync();
|
||||
|
||||
// 处理 Entry/Prepare 等回调队列事件
|
||||
ProcessPendingCallbacks();
|
||||
|
||||
if (!IsPlaying) return;
|
||||
|
||||
// 推进音乐时间
|
||||
@@ -155,6 +235,7 @@ namespace Cielonos.MainGame
|
||||
|
||||
private void OnDestroy()
|
||||
{
|
||||
UnregisterRhythmGameObject(gameObject);
|
||||
if (IsActive)
|
||||
{
|
||||
Deactivate();
|
||||
@@ -165,61 +246,60 @@ namespace Cielonos.MainGame
|
||||
|
||||
#region Activate / Deactivate
|
||||
|
||||
[Button("Activate Test Data")]
|
||||
[Button("Play Music")]
|
||||
public void Activate()
|
||||
{
|
||||
if (testData != null)
|
||||
var controller = GetComponent<CombatMusicController>();
|
||||
if (controller != null)
|
||||
{
|
||||
Activate(testData);
|
||||
controller.PlayFunctionalMusic();
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.LogWarning("[MusicBeatSystem] No test data assigned for activation");
|
||||
Debug.LogWarning("[MusicBeatSystem] No CombatMusicController or testData found to play music.");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 激活节拍系统:加载谱面、覆盖 BGM、注册 Wwise 回调
|
||||
/// </summary>
|
||||
/// <param name="beatData">要加载的谱面数据</param>
|
||||
public void Activate(MusicBeatData beatData)
|
||||
{
|
||||
if (beatData == null)
|
||||
{
|
||||
Debug.LogError("[MusicBeatSystem] Activate failed: beatData is null");
|
||||
return;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 激活节拍系统:使用 Wwise 事件和初始 Switch 直接启动动态音乐流程
|
||||
/// </summary>
|
||||
public void Activate(AK.Wwise.Event wwiseEvent, string initialSwitch = null)
|
||||
{
|
||||
if (IsActive)
|
||||
{
|
||||
Deactivate();
|
||||
}
|
||||
|
||||
// Ensure beat markers are sorted before starting tracking
|
||||
beatData.SortBeats();
|
||||
|
||||
CurrentBeatData = beatData;
|
||||
CurrentBPM = beatData.bpm;
|
||||
CurrentBeatData = null;
|
||||
CurrentBPM = 240f; // 默认临时 BPM,直到 Entry Cue 触发表格映射替换
|
||||
CurrentSongTime = 0f;
|
||||
nextBeatIndex = 0;
|
||||
IsPlaying = false;
|
||||
IsActive = true;
|
||||
|
||||
// 覆盖 BackgroundMusicManager:先停止当前 BGM,再标记覆盖
|
||||
// 覆盖 BackgroundMusicManager
|
||||
if (bgmManager != null)
|
||||
{
|
||||
bgmManager.StopMusic();
|
||||
bgmManager.SetOverride(true);
|
||||
}
|
||||
|
||||
// 在 MusicBeatSystem 自身的 gameObject 上播放节拍音乐
|
||||
// 与 BackgroundMusicManager 的 gameObject 隔离,避免 Stop Event 作用域冲突
|
||||
if (beatData.musicEvent != null && beatData.musicEvent.IsValid())
|
||||
if (wwiseEvent != null && wwiseEvent.IsValid())
|
||||
{
|
||||
uint callbackFlags = (uint)(AkCallbackType.AK_MusicSyncBeat | AkCallbackType.AK_EndOfEvent);
|
||||
beatData.musicSwitch.SetValue(gameObject); // 设置 Switch 以选择正确的音乐变体
|
||||
uint callbackFlags = (uint)(
|
||||
AkCallbackType.AK_MusicSyncBeat |
|
||||
AkCallbackType.AK_MusicSyncEntry |
|
||||
AkCallbackType.AK_MusicSyncUserCue |
|
||||
AkCallbackType.AK_EndOfEvent
|
||||
);
|
||||
|
||||
if (!string.IsNullOrEmpty(initialSwitch))
|
||||
{
|
||||
AkUnitySoundEngine.SetSwitch(musicSegmentSwitchGroup, initialSwitch, gameObject);
|
||||
}
|
||||
|
||||
PlayerCanvas.CombatSystemsUIArea.beatTimelineUI.Initialize(this);
|
||||
wwisePlayingID = beatData.musicEvent.Post(
|
||||
wwisePlayingID = wwiseEvent.Post(
|
||||
gameObject,
|
||||
callbackFlags,
|
||||
OnWwiseMusicCallback,
|
||||
@@ -228,24 +308,19 @@ namespace Cielonos.MainGame
|
||||
|
||||
if (wwisePlayingID == 0)
|
||||
{
|
||||
Debug.LogWarning("[MusicBeatSystem] Wwise Post returned playingID 0, music may not play. " +
|
||||
"Check: 1) musicEvent references a Music type (not Sound SFX) " +
|
||||
"2) SoundBank is loaded 3) gameObject is active");
|
||||
Debug.LogWarning("[MusicBeatSystem] Wwise Post returned playingID 0 for dynamic music event");
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.Log($"[MusicBeatSystem] Activated with '{beatData.name}', playingID={wwisePlayingID}, " +
|
||||
$"posting on GameObject '{gameObject.name}'");
|
||||
Debug.Log($"[MusicBeatSystem] Activated dynamic music flow with Event: '{wwiseEvent.Name}', playingID={wwisePlayingID}");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// 无 Wwise Event 时,使用纯谱面模式(仅基于 deltaTime 和谱面数据)
|
||||
Debug.LogWarning("[MusicBeatSystem] No valid Wwise Event on beatData, running in offline mode");
|
||||
IsPlaying = true;
|
||||
Debug.LogWarning("[MusicBeatSystem] No valid Wwise Event provided for dynamic music activation");
|
||||
}
|
||||
|
||||
OnActivated?.Invoke(beatData);
|
||||
OnActivated?.Invoke(null);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -261,7 +336,8 @@ namespace Cielonos.MainGame
|
||||
nextBeatIndex = 0;
|
||||
pendingSyncTime = -1f;
|
||||
pendingBeatDuration = -1f;
|
||||
|
||||
CurrentWwiseSegmentName = null;
|
||||
|
||||
// 停止 MusicBeatSystem 自己 Post 的节拍音乐
|
||||
if (wwisePlayingID != 0)
|
||||
{
|
||||
@@ -421,8 +497,11 @@ namespace Cielonos.MainGame
|
||||
/// <summary>
|
||||
/// Wwise 音乐回调处理器(可能在非主线程调用)
|
||||
/// </summary>
|
||||
private void OnWwiseMusicCallback(object in_cookie, AkCallbackType in_type, AkCallbackInfo in_info)
|
||||
internal void OnWwiseMusicCallback(object in_cookie, AkCallbackType in_type, AkCallbackInfo in_info)
|
||||
{
|
||||
GameObject go = GetGameObjectFromWwiseId(in_info.gameObjID);
|
||||
if (go == null) go = gameObject;
|
||||
|
||||
if (in_type == AkCallbackType.AK_MusicSyncBeat)
|
||||
{
|
||||
if (in_info is AkMusicSyncCallbackInfo syncInfo)
|
||||
@@ -431,19 +510,73 @@ namespace Cielonos.MainGame
|
||||
// segmentInfo_iCurrentPosition 是当前 segment 内的播放位置(毫秒)
|
||||
float musicPositionSec = syncInfo.segmentInfo_iCurrentPosition / 1000f;
|
||||
float beatDuration = syncInfo.segmentInfo_fBeatDuration;
|
||||
|
||||
// 将校准数据传递到主线程处理
|
||||
pendingSyncTime = musicPositionSec + CurrentBeatData.audioStartOffset;
|
||||
pendingBeatDuration = beatDuration;
|
||||
|
||||
if (go == gameObject)
|
||||
{
|
||||
// 将校准数据传递到主线程处理 (防空保护)
|
||||
float offset = CurrentBeatData != null ? CurrentBeatData.audioStartOffset : 0f;
|
||||
pendingSyncTime = musicPositionSec + offset;
|
||||
pendingBeatDuration = beatDuration;
|
||||
}
|
||||
else
|
||||
{
|
||||
lock (callbackLock)
|
||||
{
|
||||
pendingCallbacks.Add(new PendingCallbackData
|
||||
{
|
||||
callbackType = PendingCallbackData.Type.BeatSync,
|
||||
targetGO = go,
|
||||
segmentName = "",
|
||||
musicPositionSec = musicPositionSec,
|
||||
beatDuration = beatDuration
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.LogWarning($"[MusicBeatSystem] Received MusicSync callback with unexpected info type: {in_info.GetType().Name}");
|
||||
}
|
||||
}
|
||||
else if (in_type == AkCallbackType.AK_EndOfEvent)
|
||||
else if (in_type == AkCallbackType.AK_MusicSyncUserCue && in_info is AkMusicSyncCallbackInfo cueInfo)
|
||||
{
|
||||
// 音乐播放结束
|
||||
string cueName = cueInfo.userCueName;
|
||||
lock (callbackLock)
|
||||
{
|
||||
// 所有的 User Cue 都会触发 OnUserCueReceived
|
||||
pendingCallbacks.Add(new PendingCallbackData
|
||||
{
|
||||
callbackType = PendingCallbackData.Type.Prepare,
|
||||
targetGO = go,
|
||||
segmentName = cueName,
|
||||
playingID = cueInfo.playingID
|
||||
});
|
||||
|
||||
// 如果是 Entry_ 开头,则同时触发 Entry 谱面替换逻辑
|
||||
if (!string.IsNullOrEmpty(cueName) && cueName.StartsWith("Entry_"))
|
||||
{
|
||||
string segmentName = cueName.Substring(6);
|
||||
pendingCallbacks.Add(new PendingCallbackData
|
||||
{
|
||||
callbackType = PendingCallbackData.Type.Entry,
|
||||
targetGO = go,
|
||||
segmentName = segmentName
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (in_type == AkCallbackType.AK_EndOfEvent && in_info is AkEventCallbackInfo eventInfo)
|
||||
{
|
||||
lock (callbackLock)
|
||||
{
|
||||
pendingCallbacks.Add(new PendingCallbackData
|
||||
{
|
||||
callbackType = PendingCallbackData.Type.Prepare,
|
||||
targetGO = go,
|
||||
segmentName = "EndOfEvent",
|
||||
playingID = eventInfo.playingID
|
||||
});
|
||||
}
|
||||
Debug.Log("[MusicBeatSystem] Music playback ended");
|
||||
}
|
||||
}
|
||||
@@ -493,6 +626,163 @@ namespace Cielonos.MainGame
|
||||
}
|
||||
}
|
||||
|
||||
private void ProcessPendingCallbacks()
|
||||
{
|
||||
List<PendingCallbackData> localCallbacks = null;
|
||||
|
||||
lock (callbackLock)
|
||||
{
|
||||
if (pendingCallbacks.Count > 0)
|
||||
{
|
||||
localCallbacks = new List<PendingCallbackData>(pendingCallbacks);
|
||||
pendingCallbacks.Clear();
|
||||
}
|
||||
}
|
||||
|
||||
if (localCallbacks == null) return;
|
||||
|
||||
foreach (var callback in localCallbacks)
|
||||
{
|
||||
if (callback.callbackType == PendingCallbackData.Type.Prepare)
|
||||
{
|
||||
OnUserCueReceived?.Invoke(callback.playingID, callback.segmentName);
|
||||
|
||||
if (callback.segmentName == "PrepareNext" && callback.targetGO == gameObject)
|
||||
{
|
||||
string nextSegment = OnPrepareNextSegment?.Invoke(callback.targetGO, CurrentWwiseSegmentName);
|
||||
if (!string.IsNullOrEmpty(nextSegment))
|
||||
{
|
||||
AkUnitySoundEngine.SetSwitch(musicSegmentSwitchGroup, nextSegment, callback.targetGO);
|
||||
Debug.Log($"[MusicBeatSystem] PrepareNext: transition scheduled on '{callback.targetGO.name}' from '{CurrentWwiseSegmentName}' to '{nextSegment}'");
|
||||
}
|
||||
|
||||
// 通知所有订阅者:全局主音乐到达了 PrepareNext 边界
|
||||
OnGlobalPrepareNext?.Invoke();
|
||||
}
|
||||
}
|
||||
else if (callback.callbackType == PendingCallbackData.Type.BeatSync)
|
||||
{
|
||||
var localTracker = callback.targetGO.GetComponent<ILocalRhythmTracker>();
|
||||
if (localTracker != null)
|
||||
{
|
||||
localTracker.ReceiveSyncBeat(callback.musicPositionSec, callback.beatDuration);
|
||||
}
|
||||
}
|
||||
else if (callback.callbackType == PendingCallbackData.Type.Entry)
|
||||
{
|
||||
// Find mapped MusicBeatData
|
||||
MusicBeatData mappedData = null;
|
||||
if (segmentMappings != null)
|
||||
{
|
||||
foreach (var mapping in segmentMappings)
|
||||
{
|
||||
if (mapping.segmentName == callback.segmentName)
|
||||
{
|
||||
mappedData = mapping.beatData;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (mappedData != null)
|
||||
{
|
||||
if (callback.targetGO == gameObject)
|
||||
{
|
||||
// Global Master BGM Transition
|
||||
CurrentWwiseSegmentName = callback.segmentName;
|
||||
CurrentBeatData = mappedData;
|
||||
CurrentBPM = mappedData.bpm;
|
||||
CurrentSongTime = mappedData.audioStartOffset;
|
||||
nextBeatIndex = 0;
|
||||
rawBeatCount = 0;
|
||||
CurrentBarBeat = 1;
|
||||
IsPlaying = true;
|
||||
|
||||
// Re-initialize UI
|
||||
PlayerCanvas.CombatSystemsUIArea.beatTimelineUI.Initialize(this);
|
||||
|
||||
Debug.Log($"[MusicBeatSystem] Global Transitioned to Segment: '{callback.segmentName}', BPM={mappedData.bpm}");
|
||||
OnActivated?.Invoke(mappedData);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Local GameObject Segment Transition (e.g. Enemy)
|
||||
var localTracker = callback.targetGO.GetComponent<ILocalRhythmTracker>();
|
||||
if (localTracker != null)
|
||||
{
|
||||
localTracker.OnSegmentTransitioned(mappedData);
|
||||
}
|
||||
Debug.Log($"[MusicBeatSystem] Local Segment Entry: '{callback.segmentName}' on GameObject '{callback.targetGO.name}'");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.LogWarning($"[MusicBeatSystem] Unmapped segment entry callback: '{callback.segmentName}' on '{callback.targetGO.name}'");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void RegisterRhythmGameObject(GameObject go)
|
||||
{
|
||||
if (go == null) return;
|
||||
lock (activeRhythmGameObjects)
|
||||
{
|
||||
if (!activeRhythmGameObjects.Contains(go))
|
||||
{
|
||||
activeRhythmGameObjects.Add(go);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void UnregisterRhythmGameObject(GameObject go)
|
||||
{
|
||||
if (go == null) return;
|
||||
lock (activeRhythmGameObjects)
|
||||
{
|
||||
activeRhythmGameObjects.Remove(go);
|
||||
}
|
||||
}
|
||||
|
||||
private GameObject GetGameObjectFromWwiseId(ulong gameObjID)
|
||||
{
|
||||
if (gameObjID == (ulong)gameObject.GetInstanceID()) return gameObject;
|
||||
|
||||
lock (activeRhythmGameObjects)
|
||||
{
|
||||
for (int i = activeRhythmGameObjects.Count - 1; i >= 0; i--)
|
||||
{
|
||||
var go = activeRhythmGameObjects[i];
|
||||
if (go == null)
|
||||
{
|
||||
activeRhythmGameObjects.RemoveAt(i);
|
||||
continue;
|
||||
}
|
||||
if ((ulong)go.GetInstanceID() == gameObjID)
|
||||
{
|
||||
return go;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback lookup
|
||||
#if UNITY_6000_0_OR_NEWER
|
||||
var objs = FindObjectsByType<AkGameObj>(FindObjectsSortMode.None);
|
||||
#else
|
||||
var objs = FindObjectsOfType<AkGameObj>();
|
||||
#endif
|
||||
foreach (var obj in objs)
|
||||
{
|
||||
if (obj != null && (ulong)obj.gameObject.GetInstanceID() == gameObjID)
|
||||
{
|
||||
RegisterRhythmGameObject(obj.gameObject);
|
||||
return obj.gameObject;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 检查是否到达下一个节拍,触发 onBeat 事件
|
||||
/// </summary>
|
||||
@@ -508,6 +798,11 @@ namespace Cielonos.MainGame
|
||||
BeatMarker beat = markers[nextBeatIndex];
|
||||
nextBeatIndex++;
|
||||
|
||||
// 更新 CurrentBarBeat(根据 BPM 和小节拍数推算)
|
||||
rawBeatCount++;
|
||||
int beatsPerBar = CurrentBeatData.beatsPerBar > 0 ? CurrentBeatData.beatsPerBar : 4;
|
||||
CurrentBarBeat = ((rawBeatCount - 1) % beatsPerBar) + 1;
|
||||
|
||||
OnBeat?.Invoke(beat);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -43,40 +43,40 @@ MonoBehaviour:
|
||||
- time: 2.5945945
|
||||
tags:
|
||||
- EnemyAttack0
|
||||
barIndex: 4
|
||||
barIndex: 1
|
||||
beatInBar: 0
|
||||
- time: 2.5945945
|
||||
tags:
|
||||
- Normal
|
||||
barIndex: 1
|
||||
beatInBar: 0
|
||||
- time: 5.189189
|
||||
tags:
|
||||
- EnemyAttack0
|
||||
barIndex: 2
|
||||
beatInBar: 0
|
||||
- time: 5.189189
|
||||
tags:
|
||||
- Normal
|
||||
barIndex: 2
|
||||
beatInBar: 0
|
||||
- time: 7.7837834
|
||||
tags:
|
||||
- EnemyAttack0
|
||||
barIndex: 3
|
||||
beatInBar: 0
|
||||
- time: 7.7837834
|
||||
tags:
|
||||
- Normal
|
||||
barIndex: 3
|
||||
beatInBar: 0
|
||||
- time: 10.378378
|
||||
tags:
|
||||
- EnemyAttack0
|
||||
barIndex: 4
|
||||
beatInBar: 0
|
||||
- time: 5.189189
|
||||
tags:
|
||||
- EnemyAttack0
|
||||
barIndex: 8
|
||||
beatInBar: 0
|
||||
- time: 5.189189
|
||||
tags:
|
||||
- Normal
|
||||
barIndex: 8
|
||||
beatInBar: 0
|
||||
- time: 7.7837834
|
||||
tags:
|
||||
- EnemyAttack0
|
||||
barIndex: 12
|
||||
beatInBar: 0
|
||||
- time: 7.7837834
|
||||
tags:
|
||||
- Normal
|
||||
barIndex: 12
|
||||
beatInBar: 0
|
||||
- time: 10.378378
|
||||
tags:
|
||||
- EnemyAttack0
|
||||
barIndex: 16
|
||||
beatInBar: 0
|
||||
- time: 10.378378
|
||||
tags:
|
||||
- Normal
|
||||
barIndex: 16
|
||||
barIndex: 4
|
||||
beatInBar: 0
|
||||
|
||||
Reference in New Issue
Block a user