Files
Ipseity-Demo/Assets/Unheimlich_POC/Scripts/UI/DetectiveBoard/DetectiveBoardCanvas.cs
T

382 lines
13 KiB
C#

using UnityEngine;
using UnityEngine.InputSystem;
using System.Collections.Generic;
using System.Linq;
using Unheimlich.Data.DetectiveBoard;
using Unheimlich.Managers;
using Unheimlich.Core;
namespace Unheimlich.UI.DetectiveBoard
{
public class DetectiveBoardCanvas : Singleton<DetectiveBoardCanvas>
{
[Header("References")]
[SerializeField] private Canvas _canvas;
[SerializeField] private RectTransform _boardArea;
[SerializeField] private GameObject _postItNodePrefab;
[SerializeField] private GameObject _connectionLinePrefab;
[Header("World Space Setup")]
[SerializeField] private Camera _targetCamera;
[Header("Connection Settings")]
[SerializeField] private Color _connectionColor = Color.white;
[SerializeField] private bool _allowFreeConnections = false;
[Header("Input")]
[SerializeField] private Key _connectModeKey = Key.LeftCtrl;
[SerializeField] private Key _eliminateKey = Key.X;
private Dictionary<string, PostItNode> _activeNodes = new Dictionary<string, PostItNode>();
private List<ConnectionLine> _activeConnections = new List<ConnectionLine>();
private PostItNode _selectedNode;
private bool _isConnectMode;
private DetectiveBoardSaveData _saveData;
public bool IsConnectMode => _isConnectMode;
/// <summary>
/// Called by DetectiveBoardInteraction when a node is left-clicked.
/// Handles connection creation in connect mode, or selection in normal mode.
/// </summary>
public void OnNodeClicked(PostItNode node)
{
if (_isConnectMode)
{
HandleConnectionModeClick(node);
}
else
{
_selectedNode = node;
UpdateAllNodeSelections();
}
}
/// <summary>
/// Called by DetectiveBoardInteraction when a node is right-clicked.
/// Toggles the eliminated state of the node.
/// </summary>
public void OnNodeRightClicked(PostItNode node)
{
ToggleNodeEliminated(node);
}
protected override void OnAwake()
{
if (_canvas == null)
_canvas = GetComponent<Canvas>();
if (_canvas != null && _canvas.renderMode == RenderMode.WorldSpace)
{
if (_targetCamera == null)
_targetCamera = Camera.main;
_canvas.worldCamera = _targetCamera;
}
_saveData = new DetectiveBoardSaveData();
}
private void OnEnable()
{
TrySubscribeToManager();
}
private void Start()
{
TrySubscribeToManager();
}
private void TrySubscribeToManager()
{
if (DetectiveBoardManager.Instance != null)
{
DetectiveBoardManager.Instance.OnClueUnlocked -= OnClueUnlocked;
DetectiveBoardManager.Instance.OnClueUnlocked += OnClueUnlocked;
foreach (var clue in DetectiveBoardManager.Instance.GetUnlockedClues())
{
if (!_activeNodes.ContainsKey(clue.ClueID))
{
AddClueNode(clue);
}
}
}
}
private void OnDisable()
{
if (DetectiveBoardManager.Instance != null)
{
DetectiveBoardManager.Instance.OnClueUnlocked -= OnClueUnlocked;
}
}
private void OnClueUnlocked(ClueData clueData)
{
Debug.Log($"[DetectiveBoardCanvas] OnClueUnlocked received for: {clueData.ClueID}");
AddClueNode(clueData);
}
private void Update()
{
HandleConnectModeInput();
HandleEliminateInput();
}
private void HandleConnectModeInput()
{
if (Keyboard.current == null)
return;
bool wasConnectMode = _isConnectMode;
_isConnectMode = Keyboard.current[_connectModeKey].isPressed;
if (wasConnectMode && !_isConnectMode)
{
_selectedNode = null;
UpdateAllNodeSelections();
}
}
private void HandleEliminateInput()
{
if (Keyboard.current == null)
return;
if (Keyboard.current[_eliminateKey].wasPressedThisFrame && _selectedNode != null)
{
ToggleNodeEliminated(_selectedNode);
}
}
public void AddClueNode(ClueData clueData)
{
if (_activeNodes.ContainsKey(clueData.ClueID))
return;
if (_postItNodePrefab == null)
{
Debug.LogError("[DetectiveBoardCanvas] _postItNodePrefab is null! Assign the prefab in the inspector.");
return;
}
if (_boardArea == null)
{
Debug.LogError("[DetectiveBoardCanvas] _boardArea is null! Assign NodesContainer in the inspector.");
return;
}
PostItNodeData nodeData = _saveData.GetNodeData(clueData.ClueID);
if (nodeData == null)
{
Vector2 canvasSize = _boardArea.rect.size;
Vector2 randomPos = new Vector2(
Random.Range(-canvasSize.x * 0.4f, canvasSize.x * 0.4f),
Random.Range(-canvasSize.y * 0.4f, canvasSize.y * 0.4f)
);
nodeData = new PostItNodeData(clueData.ClueID, randomPos);
_saveData.AddOrUpdateNode(nodeData);
}
GameObject nodeObj = Instantiate(_postItNodePrefab, _boardArea);
PostItNode node = nodeObj.GetComponent<PostItNode>();
if (node == null)
{
Debug.LogError("[DetectiveBoardCanvas] PostItNode component not found on prefab");
Destroy(nodeObj);
return;
}
node.Initialize(clueData, nodeData);
_activeNodes.Add(clueData.ClueID, node);
RestoreConnectionsForNode(node);
}
public void RemoveClueNode(string clueID)
{
if (!_activeNodes.TryGetValue(clueID, out PostItNode node))
return;
RemoveConnectionsForNode(node);
_activeNodes.Remove(clueID);
Destroy(node.gameObject);
}
private void HandleConnectionModeClick(PostItNode clickedNode)
{
if (_selectedNode == null)
{
_selectedNode = clickedNode;
UpdateAllNodeSelections();
}
else if (_selectedNode == clickedNode)
{
_selectedNode = null;
UpdateAllNodeSelections();
}
else
{
ToggleConnection(_selectedNode, clickedNode);
_selectedNode = null;
UpdateAllNodeSelections();
}
}
private void ToggleConnection(PostItNode nodeA, PostItNode nodeB)
{
if (!CanConnect(nodeA, nodeB))
return;
ConnectionLine existingConnection = _activeConnections.Find(c => c.ConnectsNodes(nodeA, nodeB));
if (existingConnection != null)
{
RemoveConnection(existingConnection);
}
else
{
CreateConnection(nodeA, nodeB);
}
}
private bool CanConnect(PostItNode nodeA, PostItNode nodeB)
{
if (nodeA == null || nodeB == null)
return false;
if (_allowFreeConnections)
return true;
if (nodeA.ClueData.PossibleConnections == null || nodeA.ClueData.PossibleConnections.Length == 0)
return true;
return nodeA.ClueData.PossibleConnections.Contains(nodeB.NodeID);
}
private void CreateConnection(PostItNode startNode, PostItNode endNode)
{
GameObject lineObj = Instantiate(_connectionLinePrefab, _boardArea);
lineObj.transform.SetAsFirstSibling();
ConnectionLine line = lineObj.GetComponent<ConnectionLine>();
if (line == null)
{
Debug.LogError("[DetectiveBoardCanvas] ConnectionLine component not found on prefab");
Destroy(lineObj);
return;
}
line.Initialize(startNode, endNode, _connectionColor);
_activeConnections.Add(line);
if (!startNode.NodeData.ConnectedNodeIDs.Contains(endNode.NodeID))
startNode.NodeData.ConnectedNodeIDs.Add(endNode.NodeID);
if (!endNode.NodeData.ConnectedNodeIDs.Contains(startNode.NodeID))
endNode.NodeData.ConnectedNodeIDs.Add(startNode.NodeID);
}
private void RemoveConnection(ConnectionLine connection)
{
if (connection.StartNode != null && connection.EndNode != null)
{
connection.StartNode.NodeData.ConnectedNodeIDs.Remove(connection.EndNode.NodeID);
connection.EndNode.NodeData.ConnectedNodeIDs.Remove(connection.StartNode.NodeID);
}
_activeConnections.Remove(connection);
Destroy(connection.gameObject);
}
private void RemoveConnectionsForNode(PostItNode node)
{
var connectionsToRemove = _activeConnections
.Where(c => c.StartNode == node || c.EndNode == node)
.ToList();
foreach (var connection in connectionsToRemove)
{
RemoveConnection(connection);
}
}
/// <summary>
/// Updates all connection lines attached to the specified node.
/// Called during node dragging to keep connections visually updated.
/// </summary>
public void UpdateConnectionsForNode(PostItNode node)
{
foreach (var connection in _activeConnections)
{
if (connection.StartNode == node || connection.EndNode == node)
{
connection.UpdateLine();
}
}
}
private void RestoreConnectionsForNode(PostItNode node)
{
if (node.NodeData.ConnectedNodeIDs == null || node.NodeData.ConnectedNodeIDs.Count == 0)
return;
foreach (string connectedID in node.NodeData.ConnectedNodeIDs)
{
if (_activeNodes.TryGetValue(connectedID, out PostItNode otherNode))
{
bool connectionExists = _activeConnections.Any(c => c.ConnectsNodes(node, otherNode));
if (!connectionExists)
{
CreateConnection(node, otherNode);
}
}
}
}
private void ToggleNodeEliminated(PostItNode node)
{
node.SetEliminated(!node.IsEliminated);
}
private void UpdateAllNodeSelections()
{
foreach (var node in _activeNodes.Values)
{
node.SetSelected(node == _selectedNode);
}
}
public DetectiveBoardSaveData GetSaveData()
{
return _saveData;
}
public void LoadSaveData(DetectiveBoardSaveData saveData)
{
_saveData = saveData;
foreach (var node in _activeNodes.Values)
{
PostItNodeData nodeData = _saveData.GetNodeData(node.NodeID);
if (nodeData != null)
{
node.SetPosition(nodeData.Position);
node.SetEliminated(nodeData.IsEliminated);
}
}
_activeConnections.Clear();
foreach (var node in _activeNodes.Values)
{
RestoreConnectionsForNode(node);
}
}
}
}