Improved detective board system
This commit is contained in:
@@ -0,0 +1,380 @@
|
||||
using UnityEngine;
|
||||
using UnityEngine.InputSystem;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Unheimlich.Data.DetectiveBoard;
|
||||
using Unheimlich.Managers;
|
||||
|
||||
namespace Unheimlich.UI.DetectiveBoard
|
||||
{
|
||||
public class DetectiveBoardCanvas : MonoBehaviour
|
||||
{
|
||||
[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);
|
||||
}
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user