534 lines
14 KiB
C#
534 lines
14 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using UnityEngine;
|
|
|
|
public class gameplayGalgame : MonoBehaviour
|
|
{
|
|
private ingame_galgame_so.JsonDefinition resolvedDefinition;
|
|
|
|
private sealed class RuntimeDialogueEntry
|
|
{
|
|
public int sequence;
|
|
public ingame_galgame_so.DialogueElement.PositionTag positionTag;
|
|
public ingame_galgame_so.DialogueText dialogue;
|
|
public Speaker speaker;
|
|
}
|
|
|
|
[Header("启用开关")]
|
|
public bool enableGalgame = false;
|
|
public bool enableLyricsSO = true;
|
|
|
|
[Header("对话数据")]
|
|
public ingame_galgame_so golSO;
|
|
|
|
[Header("预制体")]
|
|
public GameObject galgamePrefab;
|
|
public GameObject where_to_put;
|
|
|
|
[Header("时间轴")]
|
|
public AudioSource musicTimelineSource;
|
|
|
|
[Header("输入")]
|
|
public bool allowGlobalClickToAdvance = true;
|
|
public KeyCode keyboardAdvanceKey = KeyCode.Space;
|
|
public KeyCode keyboardAdvanceKeyAlt = KeyCode.Return;
|
|
|
|
private readonly List<RuntimeDialogueEntry> runtimeEntries = new List<RuntimeDialogueEntry>();
|
|
|
|
private galPrefab galController;
|
|
private GameObject galInstance;
|
|
private int currentEntryIndex = -1;
|
|
private bool isRunning;
|
|
private bool waitingForClick;
|
|
private bool pauseQueueRequested;
|
|
private bool entryVisible;
|
|
private bool advanceRequested;
|
|
private float sceneStartRealtime;
|
|
private float entryShownRealtime;
|
|
private float customTimelineTime;
|
|
private float cachedTimeScale = 1f;
|
|
private bool timeActionApplied;
|
|
|
|
public bool IsRunning => isRunning;
|
|
public bool IsPausedQueue => pauseQueueRequested;
|
|
public bool HasActiveEntry => isRunning && currentEntryIndex >= 0 && currentEntryIndex < runtimeEntries.Count;
|
|
|
|
private void Awake()
|
|
{
|
|
sceneStartRealtime = Time.unscaledTime;
|
|
}
|
|
|
|
private void Start()
|
|
{
|
|
TryPrepareRuntime();
|
|
|
|
if (ShouldAutoStart())
|
|
{
|
|
StartSequence();
|
|
}
|
|
}
|
|
|
|
private void Update()
|
|
{
|
|
if (!isRunning || golSO == null || runtimeEntries.Count == 0)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (golSO.timelineStandard == ingame_galgame_so.TimelineBase.CustomTimer)
|
|
{
|
|
customTimelineTime += Time.unscaledDeltaTime;
|
|
}
|
|
|
|
if (pauseQueueRequested)
|
|
{
|
|
if (advanceRequested || DetectAdvanceInput())
|
|
{
|
|
advanceRequested = false;
|
|
ResumeSequence();
|
|
}
|
|
return;
|
|
}
|
|
|
|
RuntimeDialogueEntry entry = GetCurrentEntry();
|
|
if (entry == null)
|
|
{
|
|
FinishSequence();
|
|
return;
|
|
}
|
|
|
|
if (!entryVisible)
|
|
{
|
|
if (GetTimelineTime() >= Mathf.Max(0f, entry.dialogue.appearTime))
|
|
{
|
|
ShowCurrentEntry(entry);
|
|
}
|
|
return;
|
|
}
|
|
|
|
if (waitingForClick)
|
|
{
|
|
if (advanceRequested || DetectAdvanceInput())
|
|
{
|
|
advanceRequested = false;
|
|
ResolveCurrentEntry(entry);
|
|
}
|
|
return;
|
|
}
|
|
|
|
float requiredDuration = Mathf.Max(0f, entry.dialogue.duration);
|
|
if (Time.unscaledTime - entryShownRealtime >= requiredDuration)
|
|
{
|
|
ResolveCurrentEntry(entry);
|
|
}
|
|
}
|
|
|
|
public void read_gol_so()
|
|
{
|
|
if (golSO == null)
|
|
{
|
|
Debug.LogWarning("[gameplayGalgame] 没有配置 galgame SO。");
|
|
return;
|
|
}
|
|
|
|
ResolveDefinition();
|
|
if (resolvedDefinition == null)
|
|
{
|
|
Debug.LogWarning("[gameplayGalgame] 无法解析 galgame 配置。");
|
|
return;
|
|
}
|
|
|
|
Debug.Log($"[gameplayGalgame] dialogueType={resolvedDefinition.dialogueType}, triggerMethod={resolvedDefinition.triggerMethod}, timeline={resolvedDefinition.timelineStandard}");
|
|
}
|
|
|
|
public void StartSequence()
|
|
{
|
|
if (!ShouldPlayConfiguredSo())
|
|
{
|
|
return;
|
|
}
|
|
|
|
TryPrepareRuntime();
|
|
if (golSO == null || runtimeEntries.Count == 0)
|
|
{
|
|
return;
|
|
}
|
|
|
|
StopSequenceInternal(false);
|
|
|
|
sceneStartRealtime = Time.unscaledTime;
|
|
customTimelineTime = 0f;
|
|
currentEntryIndex = 0;
|
|
isRunning = true;
|
|
waitingForClick = false;
|
|
pauseQueueRequested = false;
|
|
entryVisible = false;
|
|
advanceRequested = false;
|
|
|
|
EnsureGalInstance();
|
|
if (galController != null)
|
|
{
|
|
galController.HideAllModes();
|
|
}
|
|
}
|
|
|
|
public void RestartSequence()
|
|
{
|
|
StartSequence();
|
|
}
|
|
|
|
public void ResumeSequence()
|
|
{
|
|
if (!pauseQueueRequested)
|
|
{
|
|
return;
|
|
}
|
|
|
|
pauseQueueRequested = false;
|
|
AdvanceToNextEntry();
|
|
}
|
|
|
|
public void StopSequence()
|
|
{
|
|
StopSequenceInternal(true);
|
|
}
|
|
|
|
public void RequestAdvance()
|
|
{
|
|
advanceRequested = true;
|
|
}
|
|
|
|
public void SetCustomTimelineTime(float value)
|
|
{
|
|
customTimelineTime = Mathf.Max(0f, value);
|
|
}
|
|
|
|
public void AdvanceCustomTimeline(float delta)
|
|
{
|
|
customTimelineTime = Mathf.Max(0f, customTimelineTime + delta);
|
|
}
|
|
|
|
private bool ShouldAutoStart()
|
|
{
|
|
if (!ShouldPlayConfiguredSo() || golSO == null)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
ResolveDefinition();
|
|
if (resolvedDefinition == null)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
return resolvedDefinition.triggerMethod == ingame_galgame_so.TriggerMethod.TimerTrigger
|
|
|| resolvedDefinition.triggerMethod == ingame_galgame_so.TriggerMethod.MixedTrigger;
|
|
}
|
|
|
|
private bool ShouldPlayConfiguredSo()
|
|
{
|
|
if (golSO == null)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
ResolveDefinition();
|
|
if (resolvedDefinition == null)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (resolvedDefinition.dialogueType == ingame_galgame_so.DialogueType.Galgame)
|
|
{
|
|
return enableGalgame;
|
|
}
|
|
|
|
if (resolvedDefinition.dialogueType == ingame_galgame_so.DialogueType.Lyrics)
|
|
{
|
|
return enableLyricsSO;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
private void TryPrepareRuntime()
|
|
{
|
|
ResolveDefinition();
|
|
BuildRuntimeEntries();
|
|
EnsureGalInstance();
|
|
}
|
|
|
|
private void BuildRuntimeEntries()
|
|
{
|
|
runtimeEntries.Clear();
|
|
|
|
if (golSO == null || resolvedDefinition == null || resolvedDefinition.dialogueList == null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
int sequence = 0;
|
|
for (int i = 0; i < resolvedDefinition.dialogueList.Count; i++)
|
|
{
|
|
ingame_galgame_so.DialogueElement element = resolvedDefinition.dialogueList[i];
|
|
if (element == null || element.textList == null)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
for (int j = 0; j < element.textList.Count; j++)
|
|
{
|
|
ingame_galgame_so.DialogueText text = element.textList[j];
|
|
if (text == null)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
runtimeEntries.Add(new RuntimeDialogueEntry
|
|
{
|
|
sequence = sequence++,
|
|
positionTag = element.positionTag,
|
|
dialogue = text,
|
|
speaker = ResolveSpeaker(text.senderIndex)
|
|
});
|
|
}
|
|
}
|
|
}
|
|
|
|
private Speaker ResolveSpeaker(int senderIndex)
|
|
{
|
|
if (golSO == null || golSO.speakersDataSO == null || golSO.speakersDataSO.speakers == null)
|
|
{
|
|
return null;
|
|
}
|
|
|
|
if (senderIndex < 0 || senderIndex >= golSO.speakersDataSO.speakers.Count)
|
|
{
|
|
return null;
|
|
}
|
|
|
|
return golSO.speakersDataSO.speakers[senderIndex];
|
|
}
|
|
|
|
private void EnsureGalInstance()
|
|
{
|
|
if (galController != null)
|
|
{
|
|
galController.Initialize(RequestAdvance);
|
|
return;
|
|
}
|
|
|
|
if (galgamePrefab == null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
Transform parent = where_to_put != null ? where_to_put.transform : transform;
|
|
|
|
if (galInstance == null)
|
|
{
|
|
galInstance = Instantiate(galgamePrefab, parent, false);
|
|
}
|
|
|
|
galController = galInstance != null ? galInstance.GetComponent<galPrefab>() : null;
|
|
if (galController != null)
|
|
{
|
|
galController.Initialize(RequestAdvance);
|
|
}
|
|
}
|
|
|
|
private RuntimeDialogueEntry GetCurrentEntry()
|
|
{
|
|
if (currentEntryIndex < 0 || currentEntryIndex >= runtimeEntries.Count)
|
|
{
|
|
return null;
|
|
}
|
|
|
|
return runtimeEntries[currentEntryIndex];
|
|
}
|
|
|
|
private void ShowCurrentEntry(RuntimeDialogueEntry entry)
|
|
{
|
|
if (entry == null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
EnsureGalInstance();
|
|
RestoreTimeAction();
|
|
|
|
if (galController != null)
|
|
{
|
|
galController.Present(entry.positionTag, entry.dialogue, entry.speaker);
|
|
bool clickToNext = entry.dialogue.stepMethod == ingame_galgame_so.DialogueText.StepMethod.ClickToNext;
|
|
galController.SetAdvanceEnabled(clickToNext);
|
|
}
|
|
|
|
ApplyTimeAction(entry.dialogue);
|
|
|
|
entryVisible = true;
|
|
entryShownRealtime = Time.unscaledTime;
|
|
waitingForClick = entry.dialogue.stepMethod == ingame_galgame_so.DialogueText.StepMethod.ClickToNext;
|
|
}
|
|
|
|
private void ResolveCurrentEntry(RuntimeDialogueEntry entry)
|
|
{
|
|
if (entry == null)
|
|
{
|
|
FinishSequence();
|
|
return;
|
|
}
|
|
|
|
RestoreTimeAction();
|
|
waitingForClick = false;
|
|
entryVisible = false;
|
|
|
|
switch (entry.dialogue.endAction)
|
|
{
|
|
case ingame_galgame_so.DialogueText.EndAction.NextText:
|
|
AdvanceToNextEntry();
|
|
break;
|
|
case ingame_galgame_so.DialogueText.EndAction.PauseQueue:
|
|
pauseQueueRequested = true;
|
|
if (galController != null)
|
|
{
|
|
galController.SetAdvanceEnabled(true);
|
|
}
|
|
break;
|
|
case ingame_galgame_so.DialogueText.EndAction.EndPerformance:
|
|
FinishSequence();
|
|
break;
|
|
default:
|
|
AdvanceToNextEntry();
|
|
break;
|
|
}
|
|
}
|
|
|
|
private void AdvanceToNextEntry()
|
|
{
|
|
currentEntryIndex++;
|
|
if (currentEntryIndex >= runtimeEntries.Count)
|
|
{
|
|
FinishSequence();
|
|
return;
|
|
}
|
|
|
|
advanceRequested = false;
|
|
waitingForClick = false;
|
|
entryVisible = false;
|
|
}
|
|
|
|
private void FinishSequence()
|
|
{
|
|
StopSequenceInternal(true);
|
|
}
|
|
|
|
private void StopSequenceInternal(bool hideUi)
|
|
{
|
|
RestoreTimeAction();
|
|
isRunning = false;
|
|
waitingForClick = false;
|
|
pauseQueueRequested = false;
|
|
entryVisible = false;
|
|
advanceRequested = false;
|
|
currentEntryIndex = -1;
|
|
|
|
if (hideUi && galController != null)
|
|
{
|
|
galController.HideAllModes();
|
|
galController.SetAdvanceEnabled(false);
|
|
}
|
|
}
|
|
|
|
private void ApplyTimeAction(ingame_galgame_so.DialogueText dialogue)
|
|
{
|
|
if (dialogue == null || dialogue.timeAction == ingame_galgame_so.DialogueText.TimeAction.None)
|
|
{
|
|
return;
|
|
}
|
|
|
|
cachedTimeScale = Time.timeScale;
|
|
timeActionApplied = true;
|
|
|
|
switch (dialogue.timeAction)
|
|
{
|
|
case ingame_galgame_so.DialogueText.TimeAction.SlowMotion:
|
|
Time.timeScale = Mathf.Clamp(dialogue.slowMotionScale, 0f, 1f);
|
|
break;
|
|
case ingame_galgame_so.DialogueText.TimeAction.Pause:
|
|
Time.timeScale = 0f;
|
|
break;
|
|
}
|
|
}
|
|
|
|
private void RestoreTimeAction()
|
|
{
|
|
if (!timeActionApplied)
|
|
{
|
|
return;
|
|
}
|
|
|
|
Time.timeScale = cachedTimeScale;
|
|
timeActionApplied = false;
|
|
}
|
|
|
|
private float GetTimelineTime()
|
|
{
|
|
if (golSO == null)
|
|
{
|
|
return 0f;
|
|
}
|
|
|
|
ResolveDefinition();
|
|
if (resolvedDefinition == null)
|
|
{
|
|
return 0f;
|
|
}
|
|
|
|
switch (resolvedDefinition.timelineStandard)
|
|
{
|
|
case ingame_galgame_so.TimelineBase.SceneStartTime:
|
|
return Mathf.Max(0f, Time.unscaledTime - sceneStartRealtime);
|
|
case ingame_galgame_so.TimelineBase.CustomTimer:
|
|
return customTimelineTime;
|
|
case ingame_galgame_so.TimelineBase.MusicTime:
|
|
if (musicTimelineSource != null)
|
|
{
|
|
return Mathf.Max(0f, musicTimelineSource.time);
|
|
}
|
|
return Mathf.Max(0f, Time.unscaledTime - sceneStartRealtime);
|
|
default:
|
|
return Mathf.Max(0f, Time.unscaledTime - sceneStartRealtime);
|
|
}
|
|
}
|
|
|
|
private void ResolveDefinition()
|
|
{
|
|
resolvedDefinition = golSO != null ? golSO.GetResolvedDefinition() : null;
|
|
}
|
|
|
|
private bool DetectAdvanceInput()
|
|
{
|
|
if (!allowGlobalClickToAdvance)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (Input.GetMouseButtonDown(0))
|
|
{
|
|
return true;
|
|
}
|
|
|
|
if (Input.touchCount > 0)
|
|
{
|
|
Touch touch = Input.GetTouch(0);
|
|
if (touch.phase == TouchPhase.Began)
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return Input.GetKeyDown(keyboardAdvanceKey) || Input.GetKeyDown(keyboardAdvanceKeyAlt);
|
|
}
|
|
}
|