Files
Echo/Assets/_Project/Scripts/UI/EndingUIScreenController.cs
T
2026-07-08 20:42:51 +08:00

370 lines
12 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using System;
using System.Collections;
using System.Collections.Generic;
using LLM;
using Michsky.UI.Reach;
using TMPro;
using UnityEngine;
using UnityEngine.Serialization;
using UnityEngine.SceneManagement;
using UnityEngine.UI;
namespace UI.Ending
{
public class EndingUIScreenController : MonoBehaviour
{
[SerializeField] private bool debugLogs = true;
public enum EndingOutcome
{
Escaped,
Fainted
}
[Header("Scene")]
[SerializeField] private int mainMenuSceneBuildIndex = 1;
[SerializeField] private bool beginPreloadOnEnable = true;
[Header("UI")]
[FormerlySerializedAs("summaryText")]
[SerializeField] private TMP_Text summarayText;
[SerializeField] private TMP_Text informationText;
[SerializeField] private string requestingText = "正在生成总结...";
[Header("Information Text")]
[SerializeField] private EndingOutcome outcome = EndingOutcome.Escaped;
[SerializeField] private bool applyOutcomeTextOnEnable = true;
[TextArea]
[SerializeField] private string escapedInformationString = "(成功逃离时的提示词,自己填写)";
[TextArea]
[SerializeField] private string faintedInformationString = "(倒下/昏倒时的提示词,自己填写)";
[Header("Buttons")]
[SerializeField] private ButtonManager returnToMenuButton;
[SerializeField] private Button returnToMenuButtonUGUI;
[Header("AI Summary")]
[SerializeField] private bool requestAISummaryOnEnable = true;
[SerializeField] private LLMChatManager chatManager;
[SerializeField] private int maxHistoryMessages = 30;
[SerializeField] private bool includeKnownFactsFromContextMemory = true;
[TextArea]
[SerializeField] private string summarySystemPrompt = "你是一个游戏结局总结助手。你需要根据对话历史与已知事实,生成一段简短的结局总结,面向玩家。";
[TextArea]
[SerializeField] private string summaryUserPromptTemplate =
"请为玩家生成结局总结。\n" +
"要求:\n" +
"- 80~160 字\n" +
"- 只输出严格 JSON{\"summary\":\"...\"}\n" +
"- 不要输出代码块,不要多余字段\n\n" +
"已知事实:\n{KNOWN_FACTS}\n\n" +
"对话历史:\n{CHAT_HISTORY}\n";
[Header("AI Summary Debug")]
[SerializeField] private ButtonManager debugSummaryButton;
[SerializeField] private Button debugSummaryButtonUGUI;
[TextArea]
[SerializeField] private string debugSummaryString = "(这里是预设的 Debug 总结字符串)";
private AsyncOperation preloadOperation;
private bool isWired;
private Coroutine summaryCoroutine;
[Serializable]
private class SummaryResponse
{
public string summary;
}
private void Awake()
{
if (summarayText == null) { summarayText = FindTMPByNameHint("Summaray", "Summary"); }
if (informationText == null) { informationText = FindTMPByNameHint("Information", "Info"); }
if (chatManager == null)
{
chatManager = FindObjectOfType<LLMChatManager>();
}
WireOnce();
}
private void OnEnable()
{
WireOnce();
if (beginPreloadOnEnable)
{
BeginPreloadMainMenu();
}
if (applyOutcomeTextOnEnable)
{
ApplyOutcomeInformationText();
}
if (requestAISummaryOnEnable)
{
GenerateSummaryWithAI();
}
}
public void BeginPreloadMainMenu()
{
if (preloadOperation != null) { return; }
if (mainMenuSceneBuildIndex < 0 || mainMenuSceneBuildIndex >= SceneManager.sceneCountInBuildSettings)
{
if (debugLogs) Debug.Log($"[EndingUI] Invalid mainMenuSceneBuildIndex={mainMenuSceneBuildIndex}");
return;
}
StartCoroutine(PreloadRoutine());
}
private IEnumerator PreloadRoutine()
{
preloadOperation = SceneManager.LoadSceneAsync(mainMenuSceneBuildIndex);
if (preloadOperation == null) { yield break; }
preloadOperation.allowSceneActivation = false;
while (preloadOperation.progress < 0.9f)
{
yield return null;
}
if (debugLogs) Debug.Log("[EndingUI] Main menu preload ready (progress>=0.9).");
}
private bool IsPreloadReady()
{
return preloadOperation != null && preloadOperation.progress >= 0.9f;
}
public void ReturnToMainMenu()
{
if (mainMenuSceneBuildIndex < 0 || mainMenuSceneBuildIndex >= SceneManager.sceneCountInBuildSettings)
{
if (debugLogs) Debug.Log($"[EndingUI] Invalid mainMenuSceneBuildIndex={mainMenuSceneBuildIndex}");
return;
}
if (preloadOperation != null)
{
preloadOperation.allowSceneActivation = true;
return;
}
SceneManager.LoadSceneAsync(mainMenuSceneBuildIndex);
}
public void GenerateSummaryWithAI()
{
if (summarayText != null && !string.IsNullOrWhiteSpace(requestingText))
{
summarayText.text = requestingText;
}
if (summaryCoroutine != null)
{
StopCoroutine(summaryCoroutine);
summaryCoroutine = null;
}
summaryCoroutine = StartCoroutine(GenerateSummaryRoutine());
}
private IEnumerator GenerateSummaryRoutine()
{
if (chatManager == null)
{
chatManager = FindObjectOfType<LLMChatManager>();
}
ILLMService service = GetActiveService();
if (service == null)
{
ApplySummaryText("AI 服务未连接。");
yield break;
}
string knownFacts = includeKnownFactsFromContextMemory ? BuildKnownFactsText(chatManager.contextMemory) : "无";
string historyText = BuildHistoryText(chatManager.fullChatLog, maxHistoryMessages);
string userPrompt = summaryUserPromptTemplate.Replace("{KNOWN_FACTS}", knownFacts).Replace("{CHAT_HISTORY}", historyText);
List<Message> messages = new List<Message>
{
new Message { role = "system", content = summarySystemPrompt },
new Message { role = "user", content = userPrompt }
};
string result = null;
bool ok = false;
yield return service.SendStatelessMessage(messages, (reply, success) =>
{
result = reply;
ok = success;
});
if (!ok)
{
ApplySummaryText(string.IsNullOrWhiteSpace(result) ? "AI 总结失败。" : result);
yield break;
}
string summary = TryParseSummaryJson(result);
ApplySummaryText(string.IsNullOrWhiteSpace(summary) ? result : summary);
}
public void ApplyDebugSummary()
{
ApplySummaryText(debugSummaryString);
}
public void SetOutcome(EndingOutcome newOutcome)
{
outcome = newOutcome;
ApplyOutcomeInformationText();
}
public void SetOutcomeEscaped()
{
SetOutcome(EndingOutcome.Escaped);
}
public void SetOutcomeFainted()
{
SetOutcome(EndingOutcome.Fainted);
}
public void ApplyOutcomeInformationText()
{
switch (outcome)
{
case EndingOutcome.Escaped:
ApplyInformationText(escapedInformationString);
break;
case EndingOutcome.Fainted:
ApplyInformationText(faintedInformationString);
break;
}
}
private void ApplySummaryText(string value)
{
if (summarayText == null) { return; }
summarayText.text = value ?? string.Empty;
}
private void ApplyInformationText(string value)
{
if (informationText == null) { return; }
informationText.text = value ?? string.Empty;
}
private ILLMService GetActiveService()
{
if (chatManager == null) { return null; }
switch (chatManager.currentProvider)
{
case LLMChatManager.LLMProvider.DeepSeek:
return chatManager.deepSeekService;
case LLMChatManager.LLMProvider.Doubao:
return chatManager.doubaoService;
default:
return null;
}
}
private string BuildKnownFactsText(ContextMemoryManager memory)
{
if (memory == null || memory.facts == null || memory.facts.Count == 0) { return "无"; }
System.Text.StringBuilder sb = new System.Text.StringBuilder();
int count = Mathf.Min(30, memory.facts.Count);
for (int i = 0; i < count; i++)
{
var fact = memory.facts[i];
sb.Append(i + 1).Append(". [").Append(fact.key).Append("]: ").Append(fact.value).Append('\n');
}
return sb.ToString();
}
private string BuildHistoryText(List<Message> fullLog, int maxCount)
{
if (fullLog == null || fullLog.Count == 0) { return "无"; }
int take = Mathf.Max(1, maxCount);
int start = Mathf.Max(0, fullLog.Count - take);
System.Text.StringBuilder sb = new System.Text.StringBuilder();
for (int i = start; i < fullLog.Count; i++)
{
var msg = fullLog[i];
if (msg == null) { continue; }
if (msg.role != "user" && msg.role != "assistant") { continue; }
sb.Append(msg.role).Append(": ").Append(msg.content).Append('\n');
}
return sb.ToString();
}
private string TryParseSummaryJson(string raw)
{
if (string.IsNullOrWhiteSpace(raw)) { return null; }
string json = raw.Trim();
json = json.Replace("```json", "").Replace("```", "").Trim();
SummaryResponse parsed = null;
try
{
parsed = JsonUtility.FromJson<SummaryResponse>(json);
}
catch
{
parsed = null;
}
if (parsed == null || string.IsNullOrWhiteSpace(parsed.summary)) { return null; }
return parsed.summary.Trim();
}
private TMP_Text FindTMPByNameHint(string hint1, string hint2)
{
TMP_Text[] all = GetComponentsInChildren<TMP_Text>(true);
if (all == null || all.Length == 0) { return null; }
for (int i = 0; i < all.Length; i++)
{
var t = all[i];
if (t == null) { continue; }
string n = t.gameObject.name;
if (!string.IsNullOrEmpty(n) && (n.IndexOf(hint1, StringComparison.OrdinalIgnoreCase) >= 0 || n.IndexOf(hint2, StringComparison.OrdinalIgnoreCase) >= 0))
{
return t;
}
}
return all[0];
}
private void WireOnce()
{
if (isWired) { return; }
if (returnToMenuButton != null) { returnToMenuButton.onClick.AddListener(ReturnToMainMenu); }
if (returnToMenuButtonUGUI != null) { returnToMenuButtonUGUI.onClick.AddListener(ReturnToMainMenu); }
if (debugSummaryButton != null) { debugSummaryButton.onClick.AddListener(ApplyDebugSummary); }
if (debugSummaryButtonUGUI != null) { debugSummaryButtonUGUI.onClick.AddListener(ApplyDebugSummary); }
isWired = true;
}
}
}