存档重构,游戏内容解锁机制;教程完善(未完成)
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Ichni.Menu
|
||||
@@ -9,28 +9,123 @@ namespace Ichni.Menu
|
||||
public List<DifficultySelectionButton> buttons;
|
||||
public DifficultySelectionButton selectedButton;
|
||||
|
||||
/// <summary>
|
||||
/// 在当前歌曲中解析一个可实际游玩的难度。
|
||||
/// <para>preferredListIndex 是 <see cref="SongSelectionRecord.difficultyListIndex"/>,即难度在当前歌曲
|
||||
/// List 中的位置;它不能替代 <see cref="DifficultyData.saveDifficultyId"/>,后者是游戏记录的稳定 ID。</para>
|
||||
/// <para>规则:只考虑已配置 Button 且 <c>isAvailable</c> 为 true 的难度,优先选择与目标位置距离最小者;
|
||||
/// 距离相同则选择位置较低者。因此目标难度不存在时,玩家会落到最接近、且偏低的可用难度。</para>
|
||||
/// </summary>
|
||||
private bool TryResolveAvailableDifficultyIndex(
|
||||
IReadOnlyList<DifficultyData> difficulties,
|
||||
int preferredListIndex,
|
||||
out int resolvedListIndex)
|
||||
{
|
||||
resolvedListIndex = -1;
|
||||
if (difficulties == null || buttons == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
int candidateCount = Mathf.Min(difficulties.Count, buttons.Count);
|
||||
long bestDistance = long.MaxValue;
|
||||
|
||||
for (int index = 0; index < candidateCount; index++)
|
||||
{
|
||||
DifficultyData difficulty = difficulties[index];
|
||||
if (difficulty == null || !difficulty.isAvailable || buttons[index] == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
long distance = Math.Abs((long)index - preferredListIndex);
|
||||
if (distance < bestDistance || (distance == bestDistance && index < resolvedListIndex))
|
||||
{
|
||||
bestDistance = distance;
|
||||
resolvedListIndex = index;
|
||||
}
|
||||
}
|
||||
|
||||
return resolvedListIndex >= 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 清除当前的难度选择,并同时关闭标准的进入游戏按钮。
|
||||
/// SongSelectionTab 的快速进入入口也会单独检查 <c>selectedDifficulty</c>,避免绕过此 UI 状态。
|
||||
/// </summary>
|
||||
private void ClearDifficultySelection()
|
||||
{
|
||||
selectedButton?.Deselect();
|
||||
selectedButton = null;
|
||||
|
||||
SongSelectionUIPage songSelectionUIPage = MenuManager.instance.songSelectionUIPage;
|
||||
songSelectionUIPage.selectedDifficulty = null;
|
||||
songSelectionUIPage.playSongUI.SetEnterGameAvailable(false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 根据当前歌曲刷新难度按钮,并将章节内存缓存解析为一个实际可玩的难度。
|
||||
/// 该方法用于首次进入、从游戏返回以及拖动切歌,所以所有自动难度回退都必须经过此处。
|
||||
/// </summary>
|
||||
public void SetUp(List<DifficultyData> difficulties)
|
||||
{
|
||||
int difficultyCount = difficulties.Count;
|
||||
int difficultyCount = difficulties?.Count ?? 0;
|
||||
int buttonCount = buttons?.Count ?? 0;
|
||||
|
||||
for (var i = 0; i < buttons.Count; i++)
|
||||
ClearDifficultySelection();
|
||||
|
||||
if (difficultyCount > buttonCount)
|
||||
{
|
||||
buttons[i].gameObject.SetActive(i < difficultyCount);
|
||||
Debug.LogError($"Song '{MenuManager.instance.songSelectionUIPage.selectedSong?.songName}' has {difficultyCount} difficulties but only {buttonCount} difficulty buttons. " +
|
||||
"Only difficulties with configured buttons can be selected.");
|
||||
}
|
||||
|
||||
for (int i = 0; i < difficultyCount; i++)
|
||||
for (int i = 0; i < buttonCount; i++)
|
||||
{
|
||||
buttons[i].SetUp(difficulties[i]);
|
||||
DifficultySelectionButton button = buttons[i];
|
||||
if (button == null)
|
||||
{
|
||||
Debug.LogError($"Difficulty button at index {i} is missing.");
|
||||
continue;
|
||||
}
|
||||
|
||||
bool hasDifficulty = i < difficultyCount && difficulties[i] != null;
|
||||
button.gameObject.SetActive(hasDifficulty);
|
||||
if (hasDifficulty)
|
||||
{
|
||||
// SetUp 会进一步按 isAvailable 隐藏不可用难度。
|
||||
button.SetUp(difficulties[i]);
|
||||
}
|
||||
}
|
||||
|
||||
SongSelectionRecord songSelectionRecord = MenuInformationRecorder.instance.GetRecordOfThisChapter();
|
||||
|
||||
if (songSelectionRecord.difficultyIndex >= difficultyCount)
|
||||
if (difficultyCount == 0)
|
||||
{
|
||||
songSelectionRecord.difficultyIndex = difficultyCount - 1;
|
||||
Debug.LogWarning($"Song '{MenuManager.instance.songSelectionUIPage.selectedSong?.songName}' has no configured difficulties.");
|
||||
return;
|
||||
}
|
||||
|
||||
buttons[songSelectionRecord.difficultyIndex % buttons.Count].Select();
|
||||
if (!MenuInformationRecorder.instance.TryGetRecordOfThisChapter(out SongSelectionRecord songSelectionRecord))
|
||||
{
|
||||
Debug.LogWarning("Cannot select a difficulty because the current chapter has no valid song selection record.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!TryResolveAvailableDifficultyIndex(difficulties, songSelectionRecord.difficultyListIndex, out int resolvedListIndex))
|
||||
{
|
||||
Debug.LogWarning($"Song '{MenuManager.instance.songSelectionUIPage.selectedSong?.songName}' has no available difficulty to select.");
|
||||
return;
|
||||
}
|
||||
|
||||
// Select 会把实际选中的难度写回章节缓存;之后继续切歌时,以这次真实选择作为新的偏好。
|
||||
buttons[resolvedListIndex].Select();
|
||||
|
||||
// 难度可用不等于内容已解锁。保持按钮状态与统一授权服务一致,
|
||||
// 而 PlaySongUI / 快速点击仍会在真正进入前再次检查,形成双层防御。
|
||||
UnlockSaveModule unlockSaveModule = GameSaveManager.instance?.UnlockSaveModule;
|
||||
bool canEnterSong = unlockSaveModule != null && unlockSaveModule.CanEnterSong(
|
||||
ChapterSelectionManager.instance?.currentChapter,
|
||||
MenuManager.instance.songSelectionUIPage.selectedSong);
|
||||
MenuManager.instance.songSelectionUIPage.playSongUI.SetEnterGameAvailable(canEnterSong);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user