feat(Dialogue): 实现基本的对话系统

This commit is contained in:
2025-10-23 15:46:17 +08:00
parent 4ef8430c6b
commit f6f523e609
29 changed files with 1398 additions and 86 deletions

View File

@@ -30,20 +30,25 @@ namespace Script.Gameplay.Player
[SerializeField] private FirstPersonRaycaster raycaster; // 第一人称射线检测器
[SerializeField] private float maxConnectDistance = 10f; // 最大连接距离
private IConnectable previousTarget;
private IConnectable currentTarget;
public IConnectable CurrentTarget
{
get => currentTarget;
set
{
if (value != null)
previousTarget = currentTarget;
currentTarget = value;
if (previousTarget != currentTarget)
{
currentTarget = value;
OnGazeEnter?.Invoke(currentTarget.GetGameObject());
}
else
{
OnGazeExit?.Invoke(null);
if (currentTarget != null)
{
OnGazeEnter?.Invoke((currentTarget as MonoBehaviour)?.gameObject);
}
if (previousTarget != null)
{
OnGazeExit?.Invoke((previousTarget as MonoBehaviour)?.gameObject);
}
}
}
} // 当前注视的可连接对象
@@ -89,12 +94,6 @@ namespace Script.Gameplay.Player
GameObject lookAtObj = raycaster.CurrentLookAtObject;
IConnectable hitConnectable = lookAtObj != null ? lookAtObj.GetComponent<IConnectable>() : null;
// 注视对象变化时触发进入/离开(使用接口方法)
// if (hitConnectable != previousGazedTarget)
// {
// previousGazedTarget = hitConnectable;
// }
CurrentTarget = hitConnectable;
}

View File

@@ -0,0 +1,155 @@
using System;
using System.Collections;
using System.Collections.Generic;
using Core;
using UnityEngine;
using UnityEngine.Events;
using Script.Gameplay.Interface;
using Script.Gameplay.Input;
using UI;
namespace Script.Gameplay.Player
{
public class PlayerDialogueController : MonoBehaviour
{
[SerializeField] private bool isEnablePlayerDialogue = true; // 是否启用玩家对话功能
[SerializeField] private FirstPersonRaycaster firstPersonRaycaster; // 第一人称射线检测器
private bool isReadingDialogue = false;
public event Action OnPlayerBeginDialogue;
public event Action OnPlayerEndDialogue;
public event Action<GameObject> OnGazeEnterDialogue;
public event Action<GameObject> OnGazeExitDialogue;
private Queue<string> dialogueQueue = new Queue<string>();
private PlayerDialogueViewer playerDialogueViewer;
private IDialogue previousDialogue;
private IDialogue currentDialogueTarget;
public IDialogue CurrentDialogueTarget
{
get => currentDialogueTarget;
set
{
previousDialogue = currentDialogueTarget;
currentDialogueTarget = value;
if (previousDialogue != currentDialogueTarget)
{
if (currentDialogueTarget != null)
{
OnGazeEnterDialogue?.Invoke((currentDialogueTarget as MonoBehaviour)?.gameObject);
}
if (previousDialogue != null)
{
OnGazeExitDialogue?.Invoke((previousDialogue as MonoBehaviour)?.gameObject);
}
}
}
}
void Start()
{
if (firstPersonRaycaster == null)
firstPersonRaycaster = GetComponent<FirstPersonRaycaster>() ??
GetComponentInChildren<FirstPersonRaycaster>();
if (firstPersonRaycaster == null)
firstPersonRaycaster = FindObjectOfType<FirstPersonRaycaster>();
if (firstPersonRaycaster == null)
Debug.LogWarning("FirstPersonRaycaster not found! Please assign or add it to the player.");
var input = InputManager.Instance.Input;
input.Player.Interact.performed += ctx =>
{
if (!isEnablePlayerDialogue) return;
if (CurrentDialogueTarget == null) return;
if (isReadingDialogue) return;
BeginDialogue();
};
input.Player.ShowNextDialogue.performed += ctx =>
{
if (!isEnablePlayerDialogue) return;
if (CurrentDialogueTarget == null) return;
if (!isReadingDialogue) return;
PassNextDialogue();
};
ControllerLocator.Instance.Register(this);
}
void Update()
{
DetectDialogue();
}
private void DetectDialogue()
{
if (!isEnablePlayerDialogue || firstPersonRaycaster == null) return;
GameObject lookAtObj = firstPersonRaycaster.CurrentLookAtObject;
IDialogue hitDialogue = lookAtObj != null ? lookAtObj.GetComponent<IDialogue>() : null;
CurrentDialogueTarget = hitDialogue;
}
private void BeginDialogue()
{
if (CurrentDialogueTarget != null)
{
isReadingDialogue = true;
OnPlayerBeginDialogue?.Invoke();
CurrentDialogueTarget.OnBeginDialogue?.Invoke(true);
dialogueQueue = SplitDialogue(CurrentDialogueTarget.DialogueContent);
PassNextDialogue();
}
}
private void PassNextDialogue()
{
if (CurrentDialogueTarget != null)
{
if (dialogueQueue.Count == 0)
{
EndDialogue();
return;
}
string nextDialogue = dialogueQueue.Dequeue();
if (playerDialogueViewer != null)
{
playerDialogueViewer.ReceiveDialogue(nextDialogue);
}
}
}
private void EndDialogue()
{
if (CurrentDialogueTarget != null)
{
isReadingDialogue = false;
OnPlayerEndDialogue?.Invoke();
CurrentDialogueTarget.OnEndDialogue?.Invoke(true);
if (playerDialogueViewer != null)
{
playerDialogueViewer.ClosePanel();
}
// 可扩展隐藏UI、恢复输入等
}
}
private Queue<string> SplitDialogue(string content)
{
// 按换行分段
return new(content.Split(new[] { '\n' }, StringSplitOptions.RemoveEmptyEntries));
}
public void RegisterDialogueViewer(PlayerDialogueViewer viewer)
{
playerDialogueViewer = viewer;
}
public void SetPlayerDialogueEnabled(bool isEnabled)
{
isEnablePlayerDialogue = isEnabled;
if (!isEnabled)
{
CurrentDialogueTarget = null;
}
}
}
}

View File

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

View File

@@ -11,27 +11,28 @@ namespace Script.Gameplay.Player
[SerializeField] private FirstPersonRaycaster raycaster; // 新增:第一人称射线检测器
[SerializeField] private bool isEnablePlayerInteraction = true; // 是否启用玩家交互功能
private IInteractable previousTarget;
private IInteractable currentTarget;
private IInteractable CurrentTarget
{
get => currentTarget;
set
{
previousTarget = currentTarget;
currentTarget = value;
if (currentTarget != null)
if (previousTarget != currentTarget)
{
// 可以在这里添加一些逻辑比如显示交互提示UI
OnGazeEnter?.Invoke(this.gameObject);
}
else
{
// 可以在这里隐藏交互提示UI
OnGazeExit?.Invoke(this.gameObject);
if (currentTarget != null)
{
OnGazeEnter?.Invoke((currentTarget as MonoBehaviour)?.gameObject);
}
if (previousTarget != null)
{
OnGazeExit?.Invoke((previousTarget as MonoBehaviour)?.gameObject);
}
}
}
} // 被射线命中的当前可交互对象(用于按键交互)
private IInteractable previousGazedTarget; // 上一次注视的对象(用于注视进入/离开事件)
public event Action<GameObject> OnGazeEnter;
public event Action<GameObject> OnGazeExit;
@@ -68,34 +69,14 @@ namespace Script.Gameplay.Player
GameObject lookAtObj = raycaster.CurrentLookAtObject;
IInteractable hitInteractable = lookAtObj != null ? lookAtObj.GetComponent<IInteractable>() : null;
// 如果命中对象与之前注视的不一样,触发进入/离开事件
if (hitInteractable != previousGazedTarget)
{
// if (previousGazedTarget != null)
// {
// previousGazedTarget.OnGazeExit(this.gameObject);
// }
//
// if (hitInteractable != null)
// {
// hitInteractable.OnGazeEnter(this.gameObject);
// // 这里可以显示交互提示UI例如 “E - 开门”
// //Debug.Log(hitInteractable.GetInteractPrompt());
// }
previousGazedTarget = hitInteractable;
}
CurrentTarget = hitInteractable;
}
public void SetPlayerInteractionEnabled(bool isEnabled)
{
isEnablePlayerInteraction = isEnabled;
if (!isEnabled && previousGazedTarget != null)
if (!isEnabled)
{
// previousGazedTarget.OnGazeExit(this.gameObject);
previousGazedTarget = null;
CurrentTarget = null;
}
}