Files

370 lines
12 KiB
C#
Raw Permalink Normal View History

2026-07-08 19:53:15 +08:00
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;
}
}
}