feat(Connect): 完成连线基本玩法,

This commit is contained in:
2025-10-19 19:38:52 +08:00
parent e14e8925de
commit f3d73ab65a
34 changed files with 2528 additions and 988 deletions

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 642ffea658c1425ebfc5a3ef8e35b465
timeCreated: 1760778870

View File

@@ -0,0 +1,66 @@
using System;
using UnityEngine;
namespace Script.Gameplay.Connect
{
// 只负责在场景中绘制连接线,并处理信号传递
public class ConnectionLine : MonoBehaviour
{
[SerializeField] private MonoBehaviour monoSource;
[SerializeField] private MonoBehaviour monoTarget;
private IConnectable _output;
private IConnectable _input;
private LineRenderer line;
private void Awake()
{
_output = monoSource as IConnectable;
_input = monoTarget as IConnectable;
line = GetComponent<LineRenderer>();
if (_output != null && _input != null)
{
SetConnectable(_output, _input);
}
}
public void SetConnectable(IConnectable output, IConnectable input)
{
_output = output;
_input = input;
_output.IsConnectedOutput = true;
_input.IsConnectedInput = true;
line.SetPositions(new Vector3[]
{
_output.GetPosition(),
_input.GetPosition()
});
}
public void ReceiveSignal(bool active)
{
SendSignal(active);
}
private void SendSignal(bool active)
{
_input.ReceiveSignal(active,this.gameObject);
}
private void OnDestroy()
{
if (_output != null)
{
_output.IsConnectedOutput = false;
}
if (_input != null)
{
_input.IsConnectedInput = false;
}
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 73c8f3d3a6144dc780b46b6c363b8ca6
timeCreated: 1760779021

View File

@@ -0,0 +1,25 @@
using UnityEngine;
using System.Collections.Generic;
using Core;
namespace Script.Gameplay.Connect
{
public class ConnectionLineManager : MonoSingleton<ConnectionLineManager>
{
[SerializeField] private GameObject linePrefab;
public List<ConnectionLine> connectionLines = new List<ConnectionLine>();
public ConnectionLine GenerateConnectionLine(IConnectable source, IConnectable target)
{
GameObject lineObject = Instantiate(linePrefab, this.transform);
ConnectionLine connectionLine = lineObject.GetComponent<ConnectionLine>();
if (connectionLine != null)
{
connectionLine.SetConnectable(source, target);
connectionLines.Add(connectionLine);
return connectionLine;
}
return null;
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 943a23c0c32e460bbc9faac4fe2b5a3e
timeCreated: 1760837927

View File

@@ -0,0 +1,19 @@
using UnityEngine;
using System.Collections.Generic;
namespace Script.Gameplay.Connect
{
public interface IConnectable
{
void OnGazeEnter(); // 玩家开始注视时触发
void OnGazeExit(); // 玩家停止注视时触发
Vector3 GetPosition(); // 获取连接点位置
string GetConnectableName(); // 获取连接点名称
public ConnectionLine OutputConnectionLine { get; set; }
public ConnectionLine InputConnectionLine { get; set; }
bool IsConnectedOutput { get; set; } // 是否作为输出端连接
bool IsConnectedInput { get; set; } // 是否作为输入端连接
void ReceiveSignal(bool active, GameObject sender); // 接收信号
void SendSignal(bool active, GameObject sender); // 发出信号
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: e75a5e9f5792435baf0d8e6b9ccc19b1
timeCreated: 1760778892

View File

@@ -1,28 +1,19 @@
using System;
using System.Collections.Generic;
using Gameplay.Player;
using UnityEngine;
using Interface;
using Script.Gameplay.Connect;
namespace Script.Gameplay.Facility
{
public class DoorInteractController : IInteractable, IEditableComponent
public class DoorInteractController : MonoBehaviour, IInteractable, IEditableComponent, IConnectable
{
[SerializeField] private bool isActive = true;
#region Interactable
public bool Interactable = true;
public bool IsActive
{
get => isActive;
set
{
isActive = value;
//具体被编辑的逻辑
Interactable = isActive;
}
}
public string ComponentName { get; set; } = "DoorSwitch";
public LockLevel LockLevel => LockLevel.Red;
private bool isOpened = false;
public string GetInteractPrompt()
{
return "按F进行交互";
@@ -55,12 +46,76 @@ namespace Script.Gameplay.Facility
private void OpenDoor()
{
// 逆时针旋转90度
transform.rotation = Quaternion.Euler(transform.rotation.eulerAngles + new Vector3(0, -90, 0));
Debug.Log("Open door");
}
private void CloseDoor()
{
// 顺时针旋转90度
transform.rotation = Quaternion.Euler(transform.rotation.eulerAngles + new Vector3(0, 90, 0));
Debug.Log("Close door");
}
#endregion
#region EditableComponent
[SerializeField] private bool isActive = true;
public bool IsActive
{
get => isActive;
set
{
isActive = value;
//具体被编辑的逻辑
Interactable = isActive;
}
}
public string ComponentName { get; set; } = "DoorSwitch";
public LockLevel LockLevel => LockLevel.Red;
#endregion
#region Connectable
public void OnGazeEnter()
{
}
public void OnGazeExit()
{
}
public Vector3 GetPosition()
{
return transform.position;
}
public string GetConnectableName()
{
return gameObject.name;
}
public ConnectionLine OutputConnectionLine { get; set; }
public ConnectionLine InputConnectionLine { get; set; }
public bool IsConnectedOutput { get; set; }
public bool IsConnectedInput { get; set; }
public void ReceiveSignal(bool active, GameObject sender)
{
Interact(sender);
}
public void SendSignal(bool active, GameObject sender)
{
OutputConnectionLine.ReceiveSignal(active);
}
#endregion
}
}

View File

@@ -91,6 +91,33 @@ namespace Script.Gameplay.Input
""processors"": """",
""interactions"": """",
""initialStateCheck"": false
},
{
""name"": ""Connect"",
""type"": ""Button"",
""id"": ""8600cc5b-8ea6-421a-a0f8-11237b50721f"",
""expectedControlType"": """",
""processors"": """",
""interactions"": """",
""initialStateCheck"": false
},
{
""name"": ""SetOutput"",
""type"": ""Button"",
""id"": ""c4b0a3bb-49d9-44fe-b302-9ee2b657481a"",
""expectedControlType"": """",
""processors"": """",
""interactions"": """",
""initialStateCheck"": false
},
{
""name"": ""SetInput"",
""type"": ""Button"",
""id"": ""fdd72692-b6cc-48ee-af6f-03ffabf29853"",
""expectedControlType"": """",
""processors"": """",
""interactions"": """",
""initialStateCheck"": false
}
],
""bindings"": [
@@ -207,13 +234,46 @@ namespace Script.Gameplay.Input
{
""name"": """",
""id"": ""9e9bf8ec-8b3e-4230-a10c-ae874395711a"",
""path"": ""<Keyboard>/x"",
""path"": ""<Keyboard>/e"",
""interactions"": """",
""processors"": """",
""groups"": """",
""action"": ""Edit"",
""isComposite"": false,
""isPartOfComposite"": false
},
{
""name"": """",
""id"": ""1c0b4a38-f715-4171-9f41-95afccb50e0f"",
""path"": ""<Keyboard>/c"",
""interactions"": """",
""processors"": """",
""groups"": """",
""action"": ""Connect"",
""isComposite"": false,
""isPartOfComposite"": false
},
{
""name"": """",
""id"": ""86108527-eb72-48d4-99cf-b1135b9b85f1"",
""path"": ""<Mouse>/leftButton"",
""interactions"": """",
""processors"": """",
""groups"": """",
""action"": ""SetOutput"",
""isComposite"": false,
""isPartOfComposite"": false
},
{
""name"": """",
""id"": ""1bb29430-c526-48c4-92c8-ef5e44639a7c"",
""path"": ""<Mouse>/rightButton"",
""interactions"": """",
""processors"": """",
""groups"": """",
""action"": ""SetInput"",
""isComposite"": false,
""isPartOfComposite"": false
}
]
}
@@ -229,6 +289,9 @@ namespace Script.Gameplay.Input
m_Player_Pause = m_Player.FindAction("Pause", throwIfNotFound: true);
m_Player_SwitchWatchMode = m_Player.FindAction("SwitchWatchMode", throwIfNotFound: true);
m_Player_Edit = m_Player.FindAction("Edit", throwIfNotFound: true);
m_Player_Connect = m_Player.FindAction("Connect", throwIfNotFound: true);
m_Player_SetOutput = m_Player.FindAction("SetOutput", throwIfNotFound: true);
m_Player_SetInput = m_Player.FindAction("SetInput", throwIfNotFound: true);
}
~@PlayerInputActions()
@@ -302,6 +365,9 @@ namespace Script.Gameplay.Input
private readonly InputAction m_Player_Pause;
private readonly InputAction m_Player_SwitchWatchMode;
private readonly InputAction m_Player_Edit;
private readonly InputAction m_Player_Connect;
private readonly InputAction m_Player_SetOutput;
private readonly InputAction m_Player_SetInput;
public struct PlayerActions
{
private @PlayerInputActions m_Wrapper;
@@ -313,6 +379,9 @@ namespace Script.Gameplay.Input
public InputAction @Pause => m_Wrapper.m_Player_Pause;
public InputAction @SwitchWatchMode => m_Wrapper.m_Player_SwitchWatchMode;
public InputAction @Edit => m_Wrapper.m_Player_Edit;
public InputAction @Connect => m_Wrapper.m_Player_Connect;
public InputAction @SetOutput => m_Wrapper.m_Player_SetOutput;
public InputAction @SetInput => m_Wrapper.m_Player_SetInput;
public InputActionMap Get() { return m_Wrapper.m_Player; }
public void Enable() { Get().Enable(); }
public void Disable() { Get().Disable(); }
@@ -343,6 +412,15 @@ namespace Script.Gameplay.Input
@Edit.started += instance.OnEdit;
@Edit.performed += instance.OnEdit;
@Edit.canceled += instance.OnEdit;
@Connect.started += instance.OnConnect;
@Connect.performed += instance.OnConnect;
@Connect.canceled += instance.OnConnect;
@SetOutput.started += instance.OnSetOutput;
@SetOutput.performed += instance.OnSetOutput;
@SetOutput.canceled += instance.OnSetOutput;
@SetInput.started += instance.OnSetInput;
@SetInput.performed += instance.OnSetInput;
@SetInput.canceled += instance.OnSetInput;
}
private void UnregisterCallbacks(IPlayerActions instance)
@@ -368,6 +446,15 @@ namespace Script.Gameplay.Input
@Edit.started -= instance.OnEdit;
@Edit.performed -= instance.OnEdit;
@Edit.canceled -= instance.OnEdit;
@Connect.started -= instance.OnConnect;
@Connect.performed -= instance.OnConnect;
@Connect.canceled -= instance.OnConnect;
@SetOutput.started -= instance.OnSetOutput;
@SetOutput.performed -= instance.OnSetOutput;
@SetOutput.canceled -= instance.OnSetOutput;
@SetInput.started -= instance.OnSetInput;
@SetInput.performed -= instance.OnSetInput;
@SetInput.canceled -= instance.OnSetInput;
}
public void RemoveCallbacks(IPlayerActions instance)
@@ -394,6 +481,9 @@ namespace Script.Gameplay.Input
void OnPause(InputAction.CallbackContext context);
void OnSwitchWatchMode(InputAction.CallbackContext context);
void OnEdit(InputAction.CallbackContext context);
void OnConnect(InputAction.CallbackContext context);
void OnSetOutput(InputAction.CallbackContext context);
void OnSetInput(InputAction.CallbackContext context);
}
}
}

View File

@@ -0,0 +1,121 @@
using System.Linq;
using Core;
using UnityEngine;
using Script.Gameplay.Connect;
using Script.Gameplay.Input;
using System;
namespace Gameplay.Player
{
public class PlayerConnectController : MonoBehaviour
{
public bool IsEnableConnecting = true; // 是否启用连接功能
[SerializeField] private FirstPersonRaycaster raycaster; // 第一人称射线检测器
private IConnectable currentTarget; // 当前注视的可连接对象
private IConnectable previousGazedTarget; // 上一次注视的 IConnectable用于触发进入/离开)
private InputManager inputManager;
private IConnectable outTarget;
private IConnectable inputTarget;
public event Action<IConnectable> OnSetOutputTarget;
public event Action<IConnectable> OnSetInputTarget;
void Start()
{
inputManager = InputManager.Instance;
inputManager.Input.Player.SetOutput.performed += ctx => SetOutTarget(currentTarget);
inputManager.Input.Player.SetInput.performed += ctx => SetInputTarget(currentTarget);
inputManager.Input.Player.Connect.performed += ctx => CreateConnection();
if (raycaster == null)
raycaster = GetComponent<FirstPersonRaycaster>() ?? GetComponentInChildren<FirstPersonRaycaster>();
if (raycaster == null)
raycaster = FindObjectOfType<FirstPersonRaycaster>();
if (raycaster == null)
Debug.LogWarning("FirstPersonRaycaster not found! Please assign or add it to the player.");
ControllerLocator.Instance.Register(this);
}
void Update()
{
DetectConnectable();
}
void DetectConnectable()
{
if (raycaster == null) return;
GameObject lookAtObj = raycaster.CurrentLookAtObject;
IConnectable hitConnectable = lookAtObj != null ? lookAtObj.GetComponent<IConnectable>() : null;
// 注视对象变化时触发进入/离开(使用接口方法)
if (hitConnectable != previousGazedTarget)
{
if (previousGazedTarget != null)
{
previousGazedTarget.OnGazeExit();
}
if (hitConnectable != null)
{
hitConnectable.OnGazeEnter();
}
previousGazedTarget = hitConnectable;
}
currentTarget = hitConnectable;
}
public IConnectable GetCurrentTarget()
{
return currentTarget;
}
public void SetOutTarget(IConnectable target)
{
if (target == null) return;
if(!IsEnableConnecting) return;
if (!target.IsConnectedOutput)
{
outTarget = target;
OnSetOutputTarget?.Invoke(outTarget);
}
}
public void SetInputTarget(IConnectable target)
{
if (target == null) return;
if(!IsEnableConnecting) return;
if (!target.IsConnectedInput)
{
inputTarget = currentTarget;
OnSetInputTarget?.Invoke(inputTarget);
}
}
private void CreateConnection()
{
if(!IsEnableConnecting) return;
if (outTarget != null && inputTarget != null && outTarget != inputTarget)
{
ConnectionLine connectionLine = ConnectionLineManager.Instance.GenerateConnectionLine(outTarget, inputTarget);
// 重置信号目标
outTarget = null;
inputTarget = null;
}
else
{
Debug.Log("Cannot create connection: invalid targets.");
}
}
void OnDrawGizmos()
{
// 射线可视化交由 FirstPersonRaycaster 处理
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 5981311fef324f939be05186f7936173
timeCreated: 1760838186

View File

@@ -0,0 +1,9 @@
using UnityEngine;
namespace Gameplay.Player
{
public class PlayerConnectionLineController : MonoBehaviour
{
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: e152f6521db343f3aac46c7c448491bc
timeCreated: 1760841639

View File

@@ -16,29 +16,26 @@ namespace Gameplay.Player
public class PlayerWatchModeController : MonoBehaviour
{
[SerializeField] private Camera cam;
public event Action OnEnterInsideWatchMode;
public event Action OnExitInsideWatchMode;
public event Action<WatchMode> OnEnterWatchMode;
public event Action<WatchMode> OnExitWatchMode;
private InputManager inputManager;
private TimePauseManager timePauseManager;
private WatchMode currentMode = WatchMode.Normal;
private PlayerInteractorController playerInteractorController;
private PlayerConnectController _playerConnectController;
private WatchMode previousMode = WatchMode.Normal;
private WatchMode currentMode = WatchMode.Normal;
public WatchMode CurrentWatchMode
{
get => currentMode;
set
{
if (value != WatchMode.Inside)
{
OnExitInsideWatchMode?.Invoke();
}
if (value == WatchMode.Inside)
{
OnEnterInsideWatchMode?.Invoke();
}
previousMode = currentMode;
currentMode = value;
// 触发退出事件
OnExitWatchMode?.Invoke(previousMode);
OnEnterWatchMode?.Invoke(currentMode);
SwitchWatchMode(currentMode);
}
}
@@ -48,6 +45,7 @@ namespace Gameplay.Player
inputManager = InputManager.Instance;
timePauseManager = TimePauseManager.Instance;
playerInteractorController = GetComponent<PlayerInteractorController>();
_playerConnectController = GetComponent<PlayerConnectController>();
var input = inputManager.Input;
input.Player.SwitchWatchMode.performed += ctx =>
@@ -80,6 +78,11 @@ namespace Gameplay.Player
playerInteractorController.SetPlayerInteractionEnabled(true);
}
if (_playerConnectController != null)
{
_playerConnectController.IsEnableConnecting = false;
}
break;
case WatchMode.Inside:
SetSeeLines(false);
@@ -95,6 +98,11 @@ namespace Gameplay.Player
{
playerInteractorController.SetPlayerInteractionEnabled(false);
}
if (_playerConnectController != null)
{
_playerConnectController.IsEnableConnecting = false;
}
break;
case WatchMode.Outside:
@@ -111,6 +119,11 @@ namespace Gameplay.Player
{
playerInteractorController.SetPlayerInteractionEnabled(true);
}
if (_playerConnectController != null)
{
_playerConnectController.IsEnableConnecting = true;
}
break;
default:

View File

@@ -2,11 +2,13 @@ using System;
using UnityEngine;
using UnityEngine.UI;
using Script.Gameplay.Global;
using TMPro;
namespace UI
{
public class GameCountDownViewer : MonoBehaviour
{
public Text countdownText;
public TMP_Text countdownText;
private GameCountdownManager _countdownManager;
private void Start()

View File

@@ -0,0 +1,63 @@
using System;
using Core;
using Gameplay.Player;
using Script.Gameplay.Connect;
using TMPro;
using UnityEngine;
namespace UI
{
public class PlayerConnectViewer : UIBase
{
public TMP_Text inputText;
public TMP_Text outputText;
private PlayerConnectController _playerConnectController;
private PlayerWatchModeController _playerWatchModeController;
private void Awake()
{
ControllerLocator.Instance.TryGetWait<PlayerConnectController>(OnGetConnectController);
ControllerLocator.Instance.TryGetWait<PlayerWatchModeController>(OnGetWatchModeController);
}
private void OnGetConnectController(PlayerConnectController controller)
{
_playerConnectController = controller;
_playerConnectController.OnSetInputTarget += UpdateInputText;
_playerConnectController.OnSetOutputTarget += UpdateOutputText;
UpdateInputText(null);
UpdateOutputText(null);
}
private void UpdateInputText(IConnectable input)
{
string text = input != null ? input.GetConnectableName() : "None";
if (inputText != null)
{
inputText.text = "Input: " + text;
}
}
private void UpdateOutputText(IConnectable output)
{
string text = output != null ? output.GetConnectableName() : "None";
if (outputText != null)
{
outputText.text = "Output: " + text;
}
}
private void OnGetWatchModeController(PlayerWatchModeController controller)
{
_playerWatchModeController = controller;
_playerWatchModeController.OnEnterWatchMode += (mode) =>
{
if (mode == WatchMode.Outside) Show();
};
_playerWatchModeController.OnExitWatchMode += (mode) =>
{
if (mode == WatchMode.Outside) Hide();
};
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: f22aa900f1d1442e9f5ae9750b6ead86
timeCreated: 1760842150

View File

@@ -56,13 +56,12 @@ namespace UI
public void OnExitInsideWatchMode()
{
this.gameObject.SetActive(false);
ClearComponentUI();
if (noEditableTargetPanel != null)
{
noEditableTargetPanel.SetActive(false);
}
}
// 获取 PlayerEditController 的回调
@@ -76,8 +75,17 @@ namespace UI
private void GetPlayerWatchModeControllerHandle(PlayerWatchModeController watchModeCtrl)
{
watchModeController = watchModeCtrl;
watchModeController.OnEnterInsideWatchMode += OnEnterInsideWatchMode;
watchModeController.OnExitInsideWatchMode += OnExitInsideWatchMode;
watchModeController.OnEnterWatchMode += (WatchMode mode) =>
{
if (mode == WatchMode.Inside)
OnEnterInsideWatchMode();
};
watchModeController.OnExitWatchMode += (WatchMode mode) =>
{
if (mode == WatchMode.Inside)
OnExitInsideWatchMode();
};
//Debug.Log("PlayerEditViewer subscribed to WatchMode events.");
}

View File

@@ -1,6 +1,7 @@
using System;
using Core;
using Gameplay.Player;
using TMPro;
using UnityEngine;
using UnityEngine.UI;
@@ -9,7 +10,7 @@ namespace UI
public class PlayerHpViewer : UIBase
{
private PlayerController _playerController;
public Text hpText;
public TMP_Text hpText;
private void Awake()
{
@@ -20,11 +21,12 @@ namespace UI
{
_playerController = playerController;
_playerController.OnHealthChanged += UpdateHpText;
UpdateHpText(_playerController.CurrentHealth);
}
private void UpdateHpText(int currentHealth)
{
hpText.text = "HP: " + currentHealth;
hpText.text = "HP: " + currentHealth.ToString();
}
private void OnDestroy()

View File

@@ -1,6 +1,7 @@
using System;
using Core;
using Gameplay.Player;
using TMPro;
using UnityEngine;
using UnityEngine.UI;
@@ -9,25 +10,18 @@ namespace UI
public class PlayerWatchModeViewer : UIBase
{
private PlayerWatchModeController watchModeController;
[SerializeField] private Text modeText;
[SerializeField] private TMP_Text modeText;
private void Awake()
{
ControllerLocator.Instance.TryGetWait<PlayerWatchModeController>(OnGet);
}
private void Update()
{
if (watchModeController != null)
{
modeText.text = "Watch Mode: " + watchModeController.CurrentWatchMode.ToString();
}
}
private void OnGet(PlayerWatchModeController watchModeCtrl)
{
watchModeController = watchModeCtrl;
//Debug.Log("PlayerWatchModeViewer obtained PlayerWatchModeController.");
modeText.text = "Watch Mode: " + watchModeController.CurrentWatchMode;
watchModeController.OnEnterWatchMode += mode => modeText.text = "Watch Mode: " + mode;
}
}
}