引入 BGM 切换+文档初版草稿

This commit is contained in:
SepComet 2026-04-25 23:49:38 +08:00
parent 5ae7ca8d56
commit 251c257489
97 changed files with 676 additions and 41 deletions

View File

@ -1,7 +1,11 @@
# 音乐配置表 # 音乐配置表 列1 列2
# Id AssetName # Id AssetName
# int string # int string
# 音乐编号 策划备注 资源名称 # 音乐编号 策划备注 资源名称
1 菜单音乐 music_menu 1 菜单音乐 music_menu
2 战斗音乐 music_background 2 战斗音乐 music_background
3 关于音乐 music_about 3 关于音乐 music_about
4 Chapter1 music_chapter1
5 Combine music_combine
6 Chapter3 music_chapter3
7 Chapter4 music_chapter4

View File

@ -1,7 +1,6 @@
# 场景配置表 # 场景配置表 列1 列2
# Id AssetName BackgroundMusicId # Id AssetName
# int string int # int string
# 场景编号 备注 资源名称 背景音乐编号 # 场景编号 备注 资源名称
1 菜单场景 Menu 1 1 菜单场景 Menu
2 战斗场景 Main 2 2 Main
3 GameplayA 0

Binary file not shown.

View File

@ -0,0 +1,23 @@
fileFormatVersion: 2
guid: 9e64c81855f973b4fb936c535cbd79d2
AudioImporter:
externalObjects: {}
serializedVersion: 7
defaultSettings:
serializedVersion: 2
loadType: 0
sampleRateSetting: 0
sampleRateOverride: 44100
compressionFormat: 1
quality: 1
conversionMode: 0
preloadAudioData: 0
platformSettingOverrides: {}
forceToMono: 0
normalize: 1
loadInBackground: 0
ambisonic: 0
3D: 1
userData:
assetBundleName:
assetBundleVariant:

Binary file not shown.

View File

@ -0,0 +1,23 @@
fileFormatVersion: 2
guid: 92f8dd3dabff14341abb15b5048284e8
AudioImporter:
externalObjects: {}
serializedVersion: 7
defaultSettings:
serializedVersion: 2
loadType: 0
sampleRateSetting: 0
sampleRateOverride: 44100
compressionFormat: 1
quality: 1
conversionMode: 0
preloadAudioData: 0
platformSettingOverrides: {}
forceToMono: 0
normalize: 1
loadInBackground: 0
ambisonic: 0
3D: 1
userData:
assetBundleName:
assetBundleVariant:

Binary file not shown.

View File

@ -0,0 +1,23 @@
fileFormatVersion: 2
guid: fd6750467748e384caced25fde2c774a
AudioImporter:
externalObjects: {}
serializedVersion: 7
defaultSettings:
serializedVersion: 2
loadType: 0
sampleRateSetting: 0
sampleRateOverride: 44100
compressionFormat: 1
quality: 1
conversionMode: 0
preloadAudioData: 0
platformSettingOverrides: {}
forceToMono: 0
normalize: 1
loadInBackground: 0
ambisonic: 0
3D: 1
userData:
assetBundleName:
assetBundleVariant:

Binary file not shown.

View File

@ -0,0 +1,23 @@
fileFormatVersion: 2
guid: 913ea5fc4da43ab4d8f125284d4e0852
AudioImporter:
externalObjects: {}
serializedVersion: 7
defaultSettings:
serializedVersion: 2
loadType: 0
sampleRateSetting: 0
sampleRateOverride: 44100
compressionFormat: 1
quality: 1
conversionMode: 0
preloadAudioData: 0
platformSettingOverrides: {}
forceToMono: 0
normalize: 1
loadInBackground: 0
ambisonic: 0
3D: 1
userData:
assetBundleName:
assetBundleVariant:

Binary file not shown.

View File

@ -0,0 +1,23 @@
fileFormatVersion: 2
guid: ada32f4de8605cd4bb04aba4adb943bf
AudioImporter:
externalObjects: {}
serializedVersion: 7
defaultSettings:
serializedVersion: 2
loadType: 0
sampleRateSetting: 0
sampleRateOverride: 44100
compressionFormat: 1
quality: 1
conversionMode: 0
preloadAudioData: 0
platformSettingOverrides: {}
forceToMono: 0
normalize: 1
loadInBackground: 0
ambisonic: 0
3D: 1
userData:
assetBundleName:
assetBundleVariant:

Binary file not shown.

View File

@ -1,19 +1,22 @@
fileFormatVersion: 2 fileFormatVersion: 2
guid: caa829ab2ffc71340a69253afdf58365 guid: ad8872525ae4eb5459dd61903ceff393
AudioImporter: AudioImporter:
serializedVersion: 6 externalObjects: {}
serializedVersion: 7
defaultSettings: defaultSettings:
loadType: 2 serializedVersion: 2
loadType: 0
sampleRateSetting: 0 sampleRateSetting: 0
sampleRateOverride: 44100 sampleRateOverride: 44100
compressionFormat: 1 compressionFormat: 1
quality: 0.01 quality: 1
conversionMode: 0 conversionMode: 0
preloadAudioData: 0
platformSettingOverrides: {} platformSettingOverrides: {}
forceToMono: 0 forceToMono: 0
normalize: 1 normalize: 1
preloadAudioData: 1
loadInBackground: 0 loadInBackground: 0
ambisonic: 0
3D: 1 3D: 1
userData: userData:
assetBundleName: assetBundleName:

View File

@ -1,5 +1,6 @@
using System.Collections.Generic; using System.Collections.Generic;
using Event; using Event;
using Sound;
using UI; using UI;
using UnityEngine; using UnityEngine;
using UnityEngine.Events; using UnityEngine.Events;
@ -53,6 +54,15 @@ namespace CustomComponent
/// </summary> /// </summary>
[SerializeField] private UnityEvent _onPuzzleCompleted = new UnityEvent(); [SerializeField] private UnityEvent _onPuzzleCompleted = new UnityEvent();
[SerializeField] [Tooltip("开始拼装时是否自动切换 BGM。")]
private bool _autoSwitchBgmOnStart = true;
[SerializeField] [Tooltip("拼装阶段 BGM Id。")]
private int _combineBgmId = 5;
[SerializeField] [Tooltip("拼装结束后是否自动恢复被打断的 BGM带进度。")]
private bool _autoRestoreInterruptedBgm = true;
#endregion #endregion
#region Runtime State #region Runtime State
@ -105,6 +115,10 @@ namespace CustomComponent
/// </summary> /// </summary>
private int _nextLevelStructureIndex = 0; private int _nextLevelStructureIndex = 0;
private bool _hasInterruptedBgm = false;
private int _interruptedBgmId = 0;
private float _interruptedBgmTimeSeconds = 0f;
#endregion #endregion
#region Public Query #region Public Query
@ -139,6 +153,16 @@ namespace CustomComponent
} }
} }
private void Start()
{
GameEntry.Event.Subscribe(CombineCompletedEventArgs.EventId, OnCombineCompleted);
}
private void OnDestroy()
{
GameEntry.Event.Unsubscribe(CombineCompletedEventArgs.EventId, OnCombineCompleted);
}
#endregion #endregion
#region Level Lifecycle #region Level Lifecycle
@ -167,6 +191,7 @@ namespace CustomComponent
return null; return null;
} }
SwitchToCombineBgm();
_nextLevelStructureIndex++; _nextLevelStructureIndex++;
return serialId; return serialId;
} }
@ -178,6 +203,7 @@ namespace CustomComponent
{ {
_formController?.CloseUI(); _formController?.CloseUI();
ClearRuntimeContext(); ClearRuntimeContext();
RestoreInterruptedBgmIfNeeded();
} }
#endregion #endregion
@ -435,6 +461,53 @@ namespace CustomComponent
_isPaused = false; _isPaused = false;
} }
private void OnCombineCompleted(object sender, GameFramework.Event.GameEventArgs e)
{
if (!(e is CombineCompletedEventArgs))
{
return;
}
RestoreInterruptedBgmIfNeeded();
}
private void SwitchToCombineBgm()
{
if (!_autoSwitchBgmOnStart || _combineBgmId <= 0 || GameEntry.Sound == null)
{
return;
}
if (!_hasInterruptedBgm && GameEntry.Sound.TryGetCurrentBgmSnapshot(out int bgmId, out float timeSeconds))
{
if (bgmId != _combineBgmId)
{
_hasInterruptedBgm = true;
_interruptedBgmId = bgmId;
_interruptedBgmTimeSeconds = Mathf.Max(0f, timeSeconds);
}
}
GameEntry.Sound.PlayBGM(_combineBgmId);
}
private void RestoreInterruptedBgmIfNeeded()
{
if (!_autoRestoreInterruptedBgm || !_hasInterruptedBgm || GameEntry.Sound == null)
{
return;
}
if (_interruptedBgmId > 0)
{
GameEntry.Sound.PlayBGM(_interruptedBgmId, _interruptedBgmTimeSeconds);
}
_hasInterruptedBgm = false;
_interruptedBgmId = 0;
_interruptedBgmTimeSeconds = 0f;
}
/// <summary> /// <summary>
/// 获取当前拖拽根节点。 /// 获取当前拖拽根节点。
/// </summary> /// </summary>

View File

@ -0,0 +1,23 @@
using UnityEngine;
namespace CustomComponent
{
[CreateAssetMenu(menuName = "Story/Directive/Change BGM", fileName = "Directive_ChangeBGM")]
public sealed class StoryChangeBgmDirectiveAsset : StoryDirectiveAsset
{
[SerializeField] [Tooltip("BGM 配置表 Id<=0 表示停止当前 BGM。")]
private int _bgmId = 0;
public override string ActionName => "ChangeBGM";
public override void Execute(StoryDirectorComponent director)
{
if (director == null)
{
return;
}
director.ExecuteChangeBgm(_bgmId);
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: b2f756304e6e46b48c7cabf5887971fc
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -1,6 +1,7 @@
using System.Collections; using System.Collections;
using System.Collections.Generic; using System.Collections.Generic;
using Event; using Event;
using Sound;
using UI; using UI;
using UnityEngine; using UnityEngine;
using UnityGameFramework.Runtime; using UnityGameFramework.Runtime;
@ -273,6 +274,27 @@ namespace CustomComponent
} }
} }
public void ExecuteChangeBgm(int bgmId)
{
if (GameEntry.Sound == null)
{
Log.Warning("StoryDirector change BGM failed. Sound component is missing.");
return;
}
if (bgmId <= 0)
{
GameEntry.Sound.StopMusic();
return;
}
int? serialId = GameEntry.Sound.PlayBGM(bgmId);
if (!serialId.HasValue)
{
Log.Warning("StoryDirector change BGM failed. bgmId={0}", bgmId.ToString());
}
}
public void ExecuteEndChapter(int chapterId) public void ExecuteEndChapter(int chapterId)
{ {
int finalChapterId = chapterId > 0 int finalChapterId = chapterId > 0

View File

@ -31,15 +31,6 @@ namespace DataTable
private set; private set;
} }
/// <summary>
/// 获取背景音乐编号。
/// </summary>
public int BackgroundMusicId
{
get;
private set;
}
public override bool ParseDataRow(string dataRowString, object userData) public override bool ParseDataRow(string dataRowString, object userData)
{ {
string[] columnStrings = dataRowString.Split(DataTableExtension.DataSplitSeparators); string[] columnStrings = dataRowString.Split(DataTableExtension.DataSplitSeparators);
@ -53,7 +44,6 @@ namespace DataTable
m_Id = int.Parse(columnStrings[index++]); m_Id = int.Parse(columnStrings[index++]);
index++; index++;
AssetName = columnStrings[index++]; AssetName = columnStrings[index++];
BackgroundMusicId = int.Parse(columnStrings[index++]);
GeneratePropertyArray(); GeneratePropertyArray();
return true; return true;
@ -67,7 +57,6 @@ namespace DataTable
{ {
m_Id = binaryReader.Read7BitEncodedInt32(); m_Id = binaryReader.Read7BitEncodedInt32();
AssetName = binaryReader.ReadString(); AssetName = binaryReader.ReadString();
BackgroundMusicId = binaryReader.Read7BitEncodedInt32();
} }
} }

View File

@ -14,7 +14,6 @@ namespace Procedure
{ {
private int _nextSceneId = 0; private int _nextSceneId = 0;
private bool _isChangeSceneComplete = false; private bool _isChangeSceneComplete = false;
private int _backgroundMusicId = 0;
public override bool UseNativeDialog => false; public override bool UseNativeDialog => false;
@ -59,7 +58,6 @@ namespace Procedure
GameEntry.Scene.LoadScene(AssetUtility.GetSceneAsset(drScene.AssetName), Constant.AssetPriority.SceneAsset, GameEntry.Scene.LoadScene(AssetUtility.GetSceneAsset(drScene.AssetName), Constant.AssetPriority.SceneAsset,
this); this);
_backgroundMusicId = drScene.BackgroundMusicId;
} }
protected override void OnLeave(ProcedureOwner procedureOwner, bool isShutdown) protected override void OnLeave(ProcedureOwner procedureOwner, bool isShutdown)
@ -110,11 +108,6 @@ namespace Procedure
Log.Info("Load scene '{0}' OK.", ne.SceneAssetName); Log.Info("Load scene '{0}' OK.", ne.SceneAssetName);
if (_backgroundMusicId > 0)
{
GameEntry.Sound.PlayBGM(_backgroundMusicId);
}
_isChangeSceneComplete = true; _isChangeSceneComplete = true;
} }

View File

@ -73,6 +73,8 @@ namespace Procedure
_menuFormController = new MenuFormController(); _menuFormController = new MenuFormController();
_menuFormController.OpenUI(new MenuFormContext()); _menuFormController.OpenUI(new MenuFormContext());
GameEntry.Sound.PlayBGM(1);
} }
protected override void OnLeave(IFsm<IProcedureManager> procedureOwner, bool isShutdown) protected override void OnLeave(IFsm<IProcedureManager> procedureOwner, bool isShutdown)

View File

@ -3,6 +3,7 @@ using DataTable;
using Definition; using Definition;
using GameFramework.DataTable; using GameFramework.DataTable;
using GameFramework.Sound; using GameFramework.Sound;
using UnityEngine;
using UnityGameFramework.Runtime; using UnityGameFramework.Runtime;
namespace Sound namespace Sound
@ -11,8 +12,17 @@ namespace Sound
{ {
private const float FadeVolumeDuration = 1f; private const float FadeVolumeDuration = 1f;
private static int? s_MusicSerialId = null; private static int? s_MusicSerialId = null;
private static int? s_CurrentBgmId = null;
private static float s_CurrentBgmStartOffsetSeconds = 0f;
private static float s_CurrentBgmStartRealtime = 0f;
public static int? PlayBGM(this SoundComponent soundComponent, int musicId, object userData = null) public static int? PlayBGM(this SoundComponent soundComponent, int musicId, object userData = null)
{
return PlayBGM(soundComponent, musicId, 0f, userData);
}
public static int? PlayBGM(this SoundComponent soundComponent, int musicId, float startTimeSeconds,
object userData = null)
{ {
soundComponent.StopMusic(); soundComponent.StopMusic();
@ -30,8 +40,12 @@ namespace Sound
playSoundParams.VolumeInSoundGroup = 1f; playSoundParams.VolumeInSoundGroup = 1f;
playSoundParams.FadeInSeconds = FadeVolumeDuration; playSoundParams.FadeInSeconds = FadeVolumeDuration;
playSoundParams.SpatialBlend = 0f; playSoundParams.SpatialBlend = 0f;
playSoundParams.Time = Mathf.Max(0f, startTimeSeconds);
s_MusicSerialId = soundComponent.PlaySound(AssetUtility.GetMusicAsset(drBgm.AssetName), "BGM", s_MusicSerialId = soundComponent.PlaySound(AssetUtility.GetMusicAsset(drBgm.AssetName), "BGM",
Constant.AssetPriority.MusicAsset, playSoundParams, null, userData); Constant.AssetPriority.MusicAsset, playSoundParams, null, userData);
s_CurrentBgmId = musicId;
s_CurrentBgmStartOffsetSeconds = playSoundParams.Time;
s_CurrentBgmStartRealtime = Time.realtimeSinceStartup;
return s_MusicSerialId; return s_MusicSerialId;
} }
@ -44,6 +58,26 @@ namespace Sound
soundComponent.StopSound(s_MusicSerialId.Value, FadeVolumeDuration); soundComponent.StopSound(s_MusicSerialId.Value, FadeVolumeDuration);
s_MusicSerialId = null; s_MusicSerialId = null;
s_CurrentBgmId = null;
s_CurrentBgmStartOffsetSeconds = 0f;
s_CurrentBgmStartRealtime = 0f;
}
public static bool TryGetCurrentBgmSnapshot(this SoundComponent soundComponent, out int bgmId,
out float timeSeconds)
{
bgmId = 0;
timeSeconds = 0f;
if (!s_MusicSerialId.HasValue || !s_CurrentBgmId.HasValue)
{
return false;
}
bgmId = s_CurrentBgmId.Value;
timeSeconds = s_CurrentBgmStartOffsetSeconds +
Mathf.Max(0f, Time.realtimeSinceStartup - s_CurrentBgmStartRealtime);
return true;
} }
public static int? PlaySE(this SoundComponent soundComponent, int uiSoundId, object userData = null) public static int? PlaySE(this SoundComponent soundComponent, int uiSoundId, object userData = null)

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 0e39aea8e25b93e43b730fa652754ced
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,18 @@
%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: b2f756304e6e46b48c7cabf5887971fc, type: 3}
m_Name: ChangeBGM1001
m_EditorClassIdentifier:
_enabled: 1
_triggerType: 0
_triggerId: 1001
_bgmId: 4

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 68e4ca396e0f3904d9c130d93d0913e3
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 11400000
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,18 @@
%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: b2f756304e6e46b48c7cabf5887971fc, type: 3}
m_Name: ChangeBGM3001
m_EditorClassIdentifier:
_enabled: 1
_triggerType: 0
_triggerId: 3001
_bgmId: 6

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 09c4f714e027812448469d44cc86d2d2
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 11400000
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,18 @@
%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: b2f756304e6e46b48c7cabf5887971fc, type: 3}
m_Name: ChangeBGM4001
m_EditorClassIdentifier:
_enabled: 1
_triggerType: 0
_triggerId: 4001
_bgmId: 7

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 157a6b4146ed51d4fb33729f7bf4b94f
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 11400000
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 85d19bdf6032a2f439ca1316169f03f1
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 90f2d30d7767cf346800345bf35d7837
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 6b1b56786ef9b8044bc50680f986568b
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 7c15202953ab22f4a9ad24f9f333a791
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -1382,6 +1382,9 @@ MonoBehaviour:
_onPuzzleCompleted: _onPuzzleCompleted:
m_PersistentCalls: m_PersistentCalls:
m_Calls: [] m_Calls: []
_autoSwitchBgmOnStart: 1
_combineBgmId: 5
_autoRestoreInterruptedBgm: 1
--- !u!1 &2000155821 --- !u!1 &2000155821
GameObject: GameObject:
m_ObjectHideFlags: 0 m_ObjectHideFlags: 0
@ -1443,7 +1446,6 @@ MonoBehaviour:
- {fileID: 11400000, guid: 752da669fb380ba48a43fa85c5928d30, type: 2} - {fileID: 11400000, guid: 752da669fb380ba48a43fa85c5928d30, type: 2}
- {fileID: 11400000, guid: dceb29efc859912488c6a669e02a3119, type: 2} - {fileID: 11400000, guid: dceb29efc859912488c6a669e02a3119, type: 2}
- {fileID: 11400000, guid: a1399a928de585b4db36dd6971370db1, type: 2} - {fileID: 11400000, guid: a1399a928de585b4db36dd6971370db1, type: 2}
- {fileID: 11400000, guid: 7ebcab6e00f2ca9409a747113df30146, type: 2}
- {fileID: 11400000, guid: b212fd0c183de84469231d272cb97369, type: 2} - {fileID: 11400000, guid: b212fd0c183de84469231d272cb97369, type: 2}
- {fileID: 11400000, guid: a57f0cef82d7b0c4891685fa9bf91caa, type: 2} - {fileID: 11400000, guid: a57f0cef82d7b0c4891685fa9bf91caa, type: 2}
- {fileID: 11400000, guid: ae1fa1f6bf239ae4797a2817dcddfee5, type: 2} - {fileID: 11400000, guid: ae1fa1f6bf239ae4797a2817dcddfee5, type: 2}
@ -1454,6 +1456,9 @@ MonoBehaviour:
- {fileID: 11400000, guid: 864c5f9e92613564c8dbd1864266461a, type: 2} - {fileID: 11400000, guid: 864c5f9e92613564c8dbd1864266461a, type: 2}
- {fileID: 11400000, guid: 6fb570be0c658844681401443b633d1f, type: 2} - {fileID: 11400000, guid: 6fb570be0c658844681401443b633d1f, type: 2}
- {fileID: 11400000, guid: 778a252e3434f3b4e868a99cd8313ac5, type: 2} - {fileID: 11400000, guid: 778a252e3434f3b4e868a99cd8313ac5, type: 2}
- {fileID: 11400000, guid: 68e4ca396e0f3904d9c130d93d0913e3, type: 2}
- {fileID: 11400000, guid: 09c4f714e027812448469d44cc86d2d2, type: 2}
- {fileID: 11400000, guid: 157a6b4146ed51d4fb33729f7bf4b94f, type: 2}
_allowRepeatTrigger: 1 _allowRepeatTrigger: 1
_verboseLog: 1 _verboseLog: 1
_backgroundAssetNamePrefix: UISprites/Background/ _backgroundAssetNamePrefix: UISprites/Background/

View File

@ -0,0 +1,107 @@
# 设计与开发文档填写草稿03设计与开发文档
作品名称:李诫传——重修《营造法式》
作品编号:【待补充】
作者信息:【待补充】
版本号V1.0.0
日期2026年4月【待补充】日
## 绪论
“李诫传——重修《营造法式》”是一款以中国古代建筑制度史为主题的剧情交互游戏。作品以北宋将作监的历史语境为背景,以“画图令史”的成长视角串联叙事,通过章节化剧情推进、场景演出、对话交互和榫卯拼装玩法,将“材份制”“图样标准化”“制度化营造”等核心历史概念转化为可参与、可感知、可理解的互动体验。本文档围绕需求分析、概要设计、详细设计、测试与交付说明,对项目当前版本的设计思路与实现结果进行概述,重点体现作品的主要完成内容与可评审要点。
## 第一章 需求分析
### 1.1 开发背景与问题提出
在传统历史类数字内容中,用户往往只能被动接收背景知识,难以通过操作行为理解制度如何产生、如何落地以及如何影响社会实践。围绕《营造法式》这一主题,团队在前期调研中明确了一个核心问题:如果只做史料陈述,用户很难建立对“标准化营造”价值的直观认知;如果只做玩法而脱离历史语境,又会削弱作品的文化表达深度。因此,项目将“叙事表达”与“机制表达”并行设计,让玩家在剧情矛盾中理解问题,在交互任务中验证解决路径,从而把历史知识从“读到”转化为“做到”和“感到”。
### 1.2 目标用户与使用场景
本作品面向对历史文化、古代建筑、剧情互动游戏感兴趣的高校学生与普通玩家,也可用于课程展示、比赛答辩和文化传播场景。典型使用情境包括课堂演示中的短时体验、评审过程中的完整流程展示,以及玩家在课后进行章节化体验和 AI 问答延展学习。作品在设计上兼顾了首次体验的可理解性与完整通关后的主题留存,尽量让用户在有限时长内抓住主线思想,在重复体验中进一步理解细节。
### 1.3 核心需求与功能定位
项目的核心需求不是单纯完成“可运行的剧情游戏”,而是构建一个以历史问题驱动的交互闭环。该闭环由“历史冲突引入—制度认知建立—交互验证—章节推进—主题回收”组成,要求剧情、演出和玩法在逻辑上相互支撑。基于这一目标,作品在功能定位上形成了四条主线:一是以章节对话为主的叙事推进系统;二是以规则校验为核心的榫卯拼装交互系统;三是以背景与音乐联动为表达载体的演出系统;四是以 AIChat 为补充的问答式延展学习系统。四条主线共同服务于“重修《营造法式》”这一主题表达,而不是彼此割裂的功能堆叠。
## 第二章 概要设计
### 2.1 系统总体架构设计
作品基于 Unity 2022.3.62f3c1 开发,整体采用“流程驱动 + 组件协作 + 数据表配置”的架构思路。运行时由 Procedure 流程控制主状态切换,核心逻辑由 GameEntry 下的自定义组件承载包括对话、拼装、故事导演、AI 对话与资源缓存等模块。剧情内容与界面配置主要通过数据表驱动,触发关系通过 StoryDirector 指令资产组织,使叙事推进、玩法切换、背景音乐和章节结束能够形成稳定的事件链路。该架构兼顾了开发阶段的可迭代性和提交阶段的稳定性,能够在不改动大量底层代码的前提下对剧情内容进行持续调整。
### 2.2 功能模块划分
从玩家视角看系统由主菜单与设置、章节对话、拼装玩法、场景演出、AI 对话和音频系统构成完整体验。从实现视角看项目将功能划分为流程控制层、剧情数据层、UI 展示层与交互执行层。流程控制层负责菜单进入与主线启动;剧情数据层通过 Dialog 与 DialogLine 等表驱动内容UI 展示层负责底部对话、遮罩对话、章节标题、拼装界面与 AI 对话界面的呈现;交互执行层负责拖拽放置规则判定、事件反馈与章节推进。这样的划分使得每一层边界明确,便于在比赛前进行针对性修订和问题定位。
### 2.3 核心业务流程设计
玩家从主菜单进入主线后,系统先以章节标题和对话引导建立历史情境,再通过 StoryDirector 在关键节点触发背景切换、音乐切换、拼装任务或章节收束事件。拼装关卡完成后系统将结果回写到故事流程并进入后续叙事。当前版本按照四章主线组织内容章节主题依次围绕“无规矩则屋不立”“凡构屋之制皆以材为祖”“图文并重”“建筑是凝固的历史”展开形成由问题提出、制度建立、图样落地到历史回响的叙事闭环。AIChat 入口在流程中常驻,用户可在不打断主线的前提下进行补充问答。
## 第三章 详细设计
### 3.1 剧情与交互设计
剧情设计采用“冲突驱动学习”的结构:先通过偷工减料、尺寸失准、工序失序等事件制造问题,再通过李诫与工匠群体的对话推动制度解释,最后以玩家可操作的拼装任务完成认知验证。拼装玩法并非独立小游戏,而是剧情论证的一部分,其规则重点在于类型匹配、顺序约束和完成反馈。玩家在交互中感受到“按法度拼装”与“凭经验凑合”的差异,进而将抽象制度理解为可执行的技术标准。这种设计避免了剧情和玩法两张皮的问题,也提升了主题表达的说服力。
### 3.2 数据与内容组织设计
项目的剧情文本主要通过 Dialog 与 DialogLine 数据表组织,章节、对话和行内容之间保持可追踪映射关系。场景与背景音乐分别通过 Scene 与 BGM 配置表管理,界面资源由 UIForm 配置表统一索引。故事触发逻辑通过 StoryDirector 指令资产落地,指令类型覆盖开始对话、启动拼装、切换背景、切换 BGM 和章节结束等关键节点。该组织方式使内容策划与程序逻辑可以相对解耦,既降低了后期调整成本,也提升了流程联调效率。
### 3.3 AIChat 模块设计
AIChat 模块采用可配置接入方式,运行时从 `StreamingAssets/openai_config.json` 读取 `apiBaseUrl`、`apiKey` 与 `model` 参数,并通过心跳请求检测接口可用性。正式对话请求采用流式返回,界面端按增量文本逐步渲染,从而获得更自然的响应体验。模块同时提供错误兜底机制,在密钥失效、网络异常或请求超时时显示可识别错误信息,避免主流程被阻断。为增强历史语境适配,当前界面支持现代白话与文言风格指令切换,使 AI 回答语体能够与作品氛围保持一致。
### 3.4 界面与体验设计
界面设计遵循“信息清晰、语境统一、交互直达”的原则。主菜单承担入口分流设置页提供音量与画面参数控制主线中根据叙事状态切换不同对话形态并通过章节标题强化阶段感。拼装界面在视觉上突出部件与槽位关系在反馈上区分成功、顺序错误和位置错误帮助玩家快速建立规则理解。AI 对话界面采用历史记录滚动与输入区分离布局,确保连续问答时的可读性。整体体验上,系统优先保证流程流畅与信息可理解,再在此基础上补充沉浸表达。
## 第四章 测试报告
### 4.1 测试目标与测试环境
本阶段测试目标是验证作品在比赛交付场景下的可运行性、流程完整性与关键功能稳定性。测试环境以 Windows 桌面端为主,开发与联调环境使用 Unity 2022.3.62f3c1。测试重点覆盖主菜单进入、章节推进、对话切换、拼装交互、故事触发、背景音乐切换、设置项保存以及 AIChat 连通性等核心路径,优先确保评审演示链路不出现阻断性问题。
### 4.2 主要测试结论
当前版本已完成主线全流程联调四章剧情可顺序推进关键触发节点能够按预期执行。拼装玩法在主要场景下可完成拖拽放置、规则校验与完成反馈章节结束后可正确进入下一阶段。设置模块可保存并应用主要参数音频切换与界面表现基本稳定。AIChat 在配置可用且网络正常时能够完成流式问答,在异常条件下可给出错误提示并保持主流程可继续。综合测试结果表明,作品已达到“可提交、可演示、可评审”的交付标准。
### 4.3 已知问题与处理策略
目前剩余风险主要集中在外部网络依赖与个别表现层细节。AIChat 的可用性受接口连通和额度状态影响,因此提交版本将采用可用测试密钥并准备异常情况下的说明口径,以避免评审误判。表现层方面,部分文案节奏、镜头衔接和细节反馈仍有优化空间,但不影响主线逻辑与核心体验。整体策略是保持功能冻结,优先保障稳定性与可复现性,在剩余时间内进行有限度的打磨而不引入高风险改动。
## 第五章 安装及使用
### 5.1 运行与安装环境
本作品最终提交形态为 Windows 平台打包压缩包,评审端无需安装 Unity 或额外开发环境。运行所需条件为常规桌面系统环境与可执行程序支持AIChat 体验阶段需要网络可访问对应接口。除 AI 功能外,主线剧情与拼装玩法不依赖外部服务,可在本地独立运行。
### 5.2 安装与启动方式
评审使用时将压缩包解压到本地任意目录,进入解压后的主文件夹并双击 `exe` 文件即可启动作品。启动后进入主菜单,选择“开始”即可按章节体验完整主线。若需要体验 AI 对话,可在运行过程中通过 AIChat 入口进入问答界面。提交包内将包含可用测试配置,供评审现场体验该功能。
### 5.3 典型使用流程
典型体验路径为“进入主菜单—开始主线—阅读并推进章节对话—在指定节点完成拼装交互—继续章节演出—在任意阶段可选进入 AI 问答—完成整条叙事收束”。如果评审时间有限,可优先体验第一章与第二章关键节点,以快速理解“制度问题提出—材份制验证”的核心表达;若时间允许,完整体验四章可更清晰地感受到从制度建立到历史回响的完整主题闭环。
## 第六章 项目总结
### 6.1 项目实施过程总结
本项目在实施过程中坚持“先打通主流程,再提升表达质量”的开发策略。前期以流程可跑通为目标,完成章节骨架、事件触发与玩法闭环;中期集中处理剧情文本、交互反馈和界面联动;后期围绕评审场景进行稳定性加固与交付材料整理。实践证明,以流程稳定性作为阶段验收标准,有助于在时间受限的比赛开发中持续保持进度并降低返工成本。
### 6.2 关键问题与解决思路
项目最核心的挑战是如何把历史制度表达转化为可玩机制,而不让作品落入“重讲述、轻交互”或“重玩法、轻主题”的两难。团队的解决思路是让每一次交互都服务于前置剧情提出的问题,让每一次剧情推进都对应一个可验证的行为结果,从而实现叙事与机制的相互印证。另一个挑战在于 AI 模块的工程稳定性,项目通过配置化接入、流式渲染和异常兜底,确保 AI 功能可加分但不成为主流程风险点。
### 6.3 后续优化方向
后续版本将在不改变主线结构的前提下继续完善表现层品质重点包括局部演出节奏、交互反馈细节和文案润色一致性。同时AIChat 可进一步扩展为与章节进度联动的上下文问答模式,使角色化回应与历史知识解释更加贴合当前剧情节点。在传播层面,项目也可考虑增加面向课堂与展陈场景的短流程演示模式,以提升作品在教学与科普场景中的复用价值。
## 参考说明
本文档为“03设计与开发文档”填写草稿重点提供可直接转写到正式 Word 版的段落文本。最终提交前,请按实际情况补齐作品编号、作者名单、截图编号、测试日期与参考文献信息,并确保与官网信息概要表、答辩材料和演示视频中的表述保持一致。

53
docs/TODO.md Normal file
View File

@ -0,0 +1,53 @@
# TODO
## 官网信息
### 作品信息
#### 作品简介
#### 开源代码与组件使用情况说明
#### 作品安装说明
#### 作品效果图
#### 设计思路
#### 设计重点难点
#### 指导老师自评(不需要)
#### 学校推荐意见(不需要)
#### 其他说明
### 链接信息(可选)
#### 作品主文件夹链接、密码
#### 作品文件夹链接、密码
#### 作品代码文件夹链接、密码
#### 作品文档文件夹链接、密码
#### 作品演示文件夹链接、密码
## 提交材料(详见 ./docs 下文件)
### 作品与答辩材料
游戏可执行文件 / 安装包、运行说明、依赖库说明;推广演示网址或二维码;答辩 PPT 及 PDF 版本;答辩视频等。
### 素材与源码
游戏工程源代码、脚本、配置文件;代表性图片、模型、音效、音乐、动画等素材。要求:若超过 10 个文件,则必须压缩后上传
### 设计与开发文档
“中国大学生计算机设计大赛作品信息摘要表”、“中国大学生计算机设计大赛 AI 工具使用说明”若有、AI 工具使用佐证材料(若有)、游戏设计文档、创作说明、操作手册等相关文档的 PDF 版本。
### 作品演示视频
游戏实际运行演示视频时长≤5 分钟分辨率≥1280P格式为 MP4/MOV/AVI大小≤300MB同时提供 35 幅 JPEG 成片原尺寸截图。

Binary file not shown.

View File

@ -0,0 +1,81 @@
# 参赛文档与文本描述草稿(用于官网与 Word 填报)
本文档用于快速整理当前版本“李诫传——重修《营造法式》”项目的提交文字内容。正文按照常规参赛文档写作习惯,尽量采用成段叙述,便于直接复制到官网表单或 Word 模板中。文中出现“【待补充】”的位置,表示需要你在最终提交前按真实情况补齐。
## 一、官网信息填写草稿
### 作品简介
本作品是一款以北宋营造制度发展为主题的剧情交互游戏。玩家以将作监“画图令史”的身份进入历史现场,在李诫的带领下,从识木辨材、验工纠弊到制图立样,逐步理解“凡构屋之制,皆以材为祖”的核心思想。项目通过章节化叙事、对话推进与榫卯拼装交互,将《营造法式》中的材份制、构件标准化和制度化治理逻辑转化为可体验、可操作、可理解的游戏流程,使玩家在互动过程中感受中国古代建筑技术与制度文明的演进脉络。
### 作品简介100字以内版本可用于概要表
本作以李诫重修《营造法式》为主线,融合剧情对话与榫卯拼装交互。玩家从将作监新吏成长为制图与验工参与者,在“可玩、可感、可理解”的流程中认识材份制与古代营造法度价值。
### 设计思路
本作品的设计思路是以“历史问题驱动学习体验”为核心,将“工地失范—制度重建—标准落地—影响传承”的历史过程拆解为可交互的游戏阶段。叙事上采用章节推进结构,以人物冲突和制度争论形成持续动机;机制上以对话、场景切换、阶段任务和拼装验证相结合,使玩家既能通过文本理解历史语境,也能通过操作验证“标准化优于经验口传”的实践结果。整体上并未将历史知识作为独立说明附加在玩法之外,而是把知识点嵌入玩家每一步行动的前后因果中,确保“剧情推进即学习推进,玩法完成即认知完成”。
### 设计重点与难点
本项目的设计重点在于历史叙事与交互机制的一体化表达。为了避免“知识讲解化”和“玩法空转化”两种常见问题,项目在对话文本、任务目标和反馈文案之间建立了统一语义:玩家在每次验工、拼装或制图任务中,都需要回应前文提出的具体历史矛盾,例如偷工减料、尺寸失准和工序无序等。开发难点主要体现在三方面:其一是多章节对话数据的组织与流程衔接,需要保证章节切换、对话起止和事件触发稳定一致;其二是拼装玩法的规则校验,需要在拖拽交互中同时处理类型匹配、顺序校验与进度反馈;其三是 AI 对话功能的工程化接入,需要兼顾流式返回、异常提示与可降级运行,确保在配置缺失时不影响主线流程可玩性。
### 开源代码与组件使用情况说明
本项目基于 Unity 2022.3.62f3c1 开发,核心运行依赖 Unity 官方包(如 URP、Input System、TextMeshPro、UGUI 等)以及项目内集成的第三方组件。框架层使用了 Game Framework项目内 `Assets/GameFramework`,附带 MIT License 文件用于流程管理、数据表、UI、事件等通用能力封装动画与界面过渡使用 DOTween项目内 `Assets/Plugins/Demigiant/DOTween`);同时集成了 LitJson 与 protobuf-net 动态库用于数据处理与协议扩展。以上组件均用于通用工程能力支撑,不改变作品原创剧情、美术组织与玩法设计的主导性。最终提交时将按组委会要求在“相关文件”与“版权状态”栏逐项标注来源、授权方式与链接信息。
### 作品安装说明
本作品最终提交形态为 Windows 平台打包后的压缩包。评审使用时无需额外安装环境,只需将压缩包解压到本地目录,进入解压后的主文件夹并双击 `exe` 主程序即可启动。游戏启动后进入主菜单,点击“开始”即可按章节体验完整主线流程,包含剧情对话推进、场景过渡与拼装交互等核心内容。除常规系统运行组件外,本版本不要求评审手动安装额外依赖。
AIChat 功能随提交包一并提供可用配置,评审可在运行过程中直接进入 AI 对话界面体验问答能力。为保证评审期间可用性配置中使用的是本作品准备的测试密钥与测试额度若评审时网络环境异常或额度临时耗尽AIChat 可能出现请求失败提示,但不影响主线流程体验与其他功能运行。
### 作品效果图(配图文字说明草稿)
建议效果图按“叙事建立—机制演示—成果呈现”顺序组织。第一张可展示章节标题或主界面,体现历史题材与整体风格;第二张与第三张可展示对话界面与人物互动,体现剧情推进方式;第四张可展示榫卯拼装交互过程,突出拖拽、顺序校验与即时反馈;第五张可展示章节阶段性成果或结局场景,体现“法度落地”的主题收束。每张图建议配 40 至 80 字说明,强调该画面在完整学习链路中的作用。
### 其他说明
本作品当前版本已完成主线流程联调与稳定运行剧情、交互与章节衔接均可在提交包中直接复现。AIChat 功能在最终提交版本中将提供可用 `apikey` 供评委现场试用,密钥仅用于本次作品评审与展示场景,并采用测试额度与可轮换策略进行管理。即使 AI 接口在特定时段出现波动,作品的主线叙事与拼装玩法仍可完整运行,不影响主体评审内容。
## 二、《作品信息概要表》可直接填写文本草稿
### 创新描述100字以内版本
本作将《营造法式》“材份制”由文本知识转化为可交互规则,采用“剧情冲突+流程任务+拼装验证”的一体化设计,让玩家在操作中理解古代建筑标准化与制度治理逻辑,实现历史知识的可感知传播。
### 特别说明第2条如需填写“前期基础”
本作品在课程与项目实践积累的 Unity 基础工程能力之上围绕参赛主题完成了新一轮系统化创作。本次参赛新增并重点完善了历史主线剧本结构、多章节对话数据组织、榫卯拼装玩法规则、章节触发与过场衔接、AI 对话界面与流式通信能力,并对 UI 表达、音频反馈和运行稳定性进行了集中优化。若需进一步细化,可在最终版中按“新增脚本数量、重构模块范围、素材增量”补充量化说明。
### 开发制作平台与运行展示平台(文字说明版)
开发制作平台为 Windows + Unity 2022.3.62f3c1。运行展示平台为 Windows 桌面端。项目采用 Unity URP 渲染管线与 UGUI 界面体系,适配常见 16:9 分辨率场景展示需求。
### 开发制作工具(文字说明版)
开发过程中主要使用 Unity Editor、JetBrains Rider / Visual Studio Code脚本编写与调试、Excel数据表维护与导出、图像与音频处理工具素材整理与压缩以及 Git版本管理。具体工具名称与版本号可按你实际使用记录在最终表单中补齐。
### 参考文献、项目或作品(示例草稿)
参考资料以《营造法式》相关史料、宋代建筑研究文献及中国古代建筑史研究成果为主,用于剧情考据与机制设定。建议最终提交时列出 3 条可检索文献,保持“作者、题名、出版信息/来源链接”完整格式,避免仅写书名。
## 三、《AI工具使用说明》文字草稿若提交
本项目在策划与开发过程中使用了 AI 工具辅助部分工作,主要集中于资料梳理、文案润色、技术问题排查与代码实现讨论,不将 AI 输出直接视为最终成果而是由开发者逐条校核后再进行人工改写与工程落地。就使用原则而言团队始终坚持“AI 仅作辅助,创作责任归属团队”的规范,涉及历史叙事、核心机制与最终表达的关键内容均由人工定稿。
在技术实现层面AI 相关能力一方面体现在开发过程中的辅助性工具使用,另一方面体现在作品内置的 AIChat 互动模块。该模块通过外部配置文件读取接口地址、密钥与模型参数采用流式返回方式提升交互连贯性并在网络异常、鉴权失败或配置缺失时给出可识别错误提示以保证主线玩法不受阻断。提交《AI工具使用说明》时建议按模板逐行列出使用时间、使用环节、关键提示词、人工修改情况与采纳比例并在附录中提供截图或录屏作为佐证。
## 四、操作手册(可放“设计与开发文档”中的运行说明)
本作品启动后先进入主菜单界面,玩家可选择开始游戏、设置或退出。开始游戏后,流程按照章节推进,玩家通过对话阅读、事件触发和交互任务逐步完成主线内容。在交互环节中,玩家需根据界面提示完成榫卯拼装,系统会根据部件类型、放置顺序与槽位规则给出即时反馈,完成后自动推进后续剧情。
设置界面提供音量、画面与对话相关参数玩家可根据设备情况调整分辨率、窗口模式、垂直同步与抗锯齿等选项。AIChat 入口在运行流程中常驻显示,若配置文件完整且网络可用,可进行实时问答;若未配置密钥,系统会提示不可用状态。评审使用时建议优先体验主线流程与拼装玩法,再根据现场网络条件选择是否演示 AIChat 模块。
## 五、明日2026-04-26补齐 AIChat 后建议补写的一段说明
在完成密钥配置并验证 AIChat 后,建议在官网“其他说明”与答辩材料中追加一段简短说明:本作品 AIChat 功能已在独立测试环境完成连通性与流式回复验证,支持以现代白话或文言风格回答与剧情相关问题。该功能定位为学习型延展交互,不影响主线流程评分项,主要用于增强历史知识问答体验与角色沉浸感。
## 六、提交前替换检查
请在转写到 Word 与官网前统一检查并替换以下占位信息作品名称、作品编号、团队成员与分工比例、指导教师信息、网盘链接与提取码、实际参考文献条目、第三方素材授权证明链接、AI 工具使用记录时间戳与佐证文件名。

View File

@ -1,7 +1,11 @@
# 音乐配置表 # 音乐配置表 列1 列2
# Id AssetName # Id AssetName
# int string # int string
# 音乐编号 策划备注 资源名称 # 音乐编号 策划备注 资源名称
1 菜单音乐 music_menu 1 菜单音乐 music_menu
2 战斗音乐 music_background 2 战斗音乐 music_background
3 关于音乐 music_about 3 关于音乐 music_about
4 Chapter1 music_chapter1
5 Combine music_combine
6 Chapter3 music_chapter3
7 Chapter4 music_chapter4

Binary file not shown.

View File

@ -1,7 +1,6 @@
# 场景配置表 # 场景配置表 列1 列2
# Id AssetName BackgroundMusicId # Id AssetName
# int string int # int string
# 场景编号 备注 资源名称 背景音乐编号 # 场景编号 备注 资源名称
1 菜单场景 Menu 1 1 菜单场景 Menu
2 战斗场景 Main 2 2 Main
3 GameplayA 0

Binary file not shown.