Mini demo with all features
This commit is contained in:
@@ -411,6 +411,14 @@ namespace Unheimlich.Audio
|
||||
StopListening();
|
||||
}
|
||||
|
||||
private void OnEnable()
|
||||
{
|
||||
if (_continuousAudioSource != null && !_continuousAudioSource.isPlaying && _emitContinuously)
|
||||
{
|
||||
StartContinuousEmission();
|
||||
}
|
||||
}
|
||||
|
||||
private void OnDrawGizmosSelected()
|
||||
{
|
||||
Gizmos.color = Color.cyan;
|
||||
|
||||
@@ -2,10 +2,6 @@ using UnityEngine;
|
||||
|
||||
namespace Unheimlich.Data.DetectiveBoard
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a single clue/evidence node on the detective board.
|
||||
/// Unlocked through various triggers (audio, objects, dialogues, hallucinations, etc.).
|
||||
/// </summary>
|
||||
[CreateAssetMenu(fileName = "ClueData", menuName = "Unheimlich/Detective Board/Clue Data")]
|
||||
public class ClueData : ScriptableObject
|
||||
{
|
||||
@@ -27,15 +23,8 @@ namespace Unheimlich.Data.DetectiveBoard
|
||||
[Tooltip("Color tint for the node")]
|
||||
public Color NodeColor = Color.white;
|
||||
|
||||
[Header("Connection Rules")]
|
||||
[Tooltip("IDs of clues this can connect to")]
|
||||
public string[] PossibleConnections;
|
||||
|
||||
[Header("Meta")]
|
||||
[Tooltip("Category for organization (e.g., 'Evidence', 'Witness', 'Location')")]
|
||||
public string Category;
|
||||
|
||||
[Tooltip("Initial position on the board (normalized 0-1)")]
|
||||
public Vector2 InitialPosition = new Vector2(0.5f, 0.5f);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,42 +0,0 @@
|
||||
using UnityEngine;
|
||||
|
||||
namespace Unheimlich.Data.DetectiveBoard
|
||||
{
|
||||
/// <summary>
|
||||
/// Configuration for detective board UI behavior and appearance.
|
||||
/// Defines node visuals, connection rules, and interaction parameters.
|
||||
/// </summary>
|
||||
[CreateAssetMenu(fileName = "DetectiveBoardSettings", menuName = "Unheimlich/Detective Board/Board Settings")]
|
||||
public class DetectiveBoardSettings : ScriptableObject
|
||||
{
|
||||
[Header("Node Visuals")]
|
||||
[Tooltip("Default sprite for nodes (post-it/card)")]
|
||||
public Sprite DefaultNodeSprite;
|
||||
|
||||
[Tooltip("Scale of node objects on the board")]
|
||||
public float NodeScale = 1f;
|
||||
|
||||
[Tooltip("Spacing between nodes when auto-arranged")]
|
||||
public float NodeSpacing = 0.5f;
|
||||
|
||||
[Header("Connection Visuals")]
|
||||
[Tooltip("Line width for connections")]
|
||||
public float ConnectionLineWidth = 0.05f;
|
||||
|
||||
[Tooltip("Color for confirmed connections")]
|
||||
public Color ConfirmedConnectionColor = Color.green;
|
||||
|
||||
[Tooltip("Color for uncertain connections")]
|
||||
public Color UncertainConnectionColor = Color.yellow;
|
||||
|
||||
[Tooltip("Color for eliminated connections")]
|
||||
public Color EliminatedConnectionColor = Color.red;
|
||||
|
||||
[Header("Interaction")]
|
||||
[Tooltip("Distance at which nodes can be interacted with")]
|
||||
public float InteractionRange = 2f;
|
||||
|
||||
[Tooltip("Highlight color when hovering over a node")]
|
||||
public Color HoverHighlightColor = new Color(1f, 1f, 0.5f, 1f);
|
||||
}
|
||||
}
|
||||
@@ -1,2 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f6b20768fe570fb429fcf6a348431232
|
||||
@@ -1,6 +1,7 @@
|
||||
using UnityEngine;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Unheimlich.UI.DetectiveBoard;
|
||||
|
||||
namespace Unheimlich.Data.DetectiveBoard
|
||||
{
|
||||
@@ -9,15 +10,43 @@ namespace Unheimlich.Data.DetectiveBoard
|
||||
{
|
||||
public string NodeID;
|
||||
public Vector2 Position;
|
||||
public float Rotation;
|
||||
public bool IsEliminated;
|
||||
public List<string> ConnectedNodeIDs = new List<string>();
|
||||
public List<ConnectionData> Connections = new List<ConnectionData>();
|
||||
|
||||
public PostItNodeData(string id, Vector2 position)
|
||||
{
|
||||
NodeID = id;
|
||||
Position = position;
|
||||
Rotation = 0f;
|
||||
IsEliminated = false;
|
||||
}
|
||||
|
||||
public List<string> ConnectedNodeIDs
|
||||
{
|
||||
get
|
||||
{
|
||||
List<string> ids = new List<string>();
|
||||
foreach (var conn in Connections)
|
||||
{
|
||||
ids.Add(conn.TargetNodeID);
|
||||
}
|
||||
return ids;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
public class ConnectionData
|
||||
{
|
||||
public string TargetNodeID;
|
||||
public LineColorState ColorState;
|
||||
|
||||
public ConnectionData(string targetId, LineColorState colorState = LineColorState.Green)
|
||||
{
|
||||
TargetNodeID = targetId;
|
||||
ColorState = colorState;
|
||||
}
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
|
||||
@@ -16,6 +16,17 @@ namespace Unheimlich.Data.Dialogues
|
||||
[Tooltip("Default scale multiplier for spawned choices (1.0 = normal size)")]
|
||||
public float DefaultScaleMultiplier = 1.0f;
|
||||
|
||||
[Tooltip("Minimum scale variation multiplier (e.g., 0.5 = 50% of base scale)")]
|
||||
[Range(0.1f, 2f)]
|
||||
public float MinScaleVariation = 0.5f;
|
||||
|
||||
[Tooltip("Maximum scale variation multiplier (e.g., 1.5 = 150% of base scale)")]
|
||||
[Range(0.1f, 2f)]
|
||||
public float MaxScaleVariation = 1.5f;
|
||||
|
||||
[Tooltip("Enable random scale variation for each word")]
|
||||
public bool EnableScaleVariation = false;
|
||||
|
||||
[Header("Spawn Settings")]
|
||||
[Tooltip("Radius around anchor where choices spawn")]
|
||||
public float SpawnRadius = 2f;
|
||||
|
||||
@@ -9,6 +9,9 @@ namespace Unheimlich.Data.Dialogues
|
||||
[Header("Choice Content")]
|
||||
[Tooltip("Localized text displayed on the choice button")]
|
||||
public OctoLocalizedTextData ChoiceText;
|
||||
|
||||
[HideInInspector]
|
||||
public string ChoiceTextOverride;
|
||||
|
||||
[Header("Flow")]
|
||||
[Tooltip("Direct reference to the next DialogueNode")]
|
||||
@@ -20,6 +23,9 @@ namespace Unheimlich.Data.Dialogues
|
||||
|
||||
public string GetLocalizedChoiceText()
|
||||
{
|
||||
if (!string.IsNullOrEmpty(ChoiceTextOverride))
|
||||
return ChoiceTextOverride;
|
||||
|
||||
if (ChoiceText != null)
|
||||
return OctoLocalization.GetLocalizedText(ChoiceText);
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using OctoCore.Localization;
|
||||
using Unheimlich.Data.DetectiveBoard;
|
||||
|
||||
namespace Unheimlich.Data.Dialogues
|
||||
{
|
||||
@@ -19,6 +20,10 @@ namespace Unheimlich.Data.Dialogues
|
||||
[Tooltip("For end nodes (no choices), delay in seconds before automatic close. 0 = requires player interaction to close")]
|
||||
public float AutoCloseDelay = 3f;
|
||||
|
||||
[Header("Detective Board Integration")]
|
||||
[Tooltip("Clue to unlock when this node is reached. Useful for end nodes to unlock evidence after a conversation")]
|
||||
public ClueData ClueToUnlock;
|
||||
|
||||
public string GetLocalizedDialogueText()
|
||||
{
|
||||
if (DialogueText != null)
|
||||
|
||||
@@ -8,12 +8,14 @@ public class DialogueNodeEditor : Editor
|
||||
private SerializedProperty _dialogueTextProp;
|
||||
private SerializedProperty _choicesProp;
|
||||
private SerializedProperty _autoCloseDelayProp;
|
||||
private SerializedProperty _clueToUnlockProp;
|
||||
|
||||
private void OnEnable()
|
||||
{
|
||||
_dialogueTextProp = serializedObject.FindProperty("DialogueText");
|
||||
_choicesProp = serializedObject.FindProperty("Choices");
|
||||
_autoCloseDelayProp = serializedObject.FindProperty("AutoCloseDelay");
|
||||
_clueToUnlockProp = serializedObject.FindProperty("ClueToUnlock");
|
||||
}
|
||||
|
||||
public override void OnInspectorGUI()
|
||||
@@ -52,6 +54,20 @@ public class DialogueNodeEditor : Editor
|
||||
|
||||
EditorGUILayout.PropertyField(_choicesProp, true);
|
||||
|
||||
EditorGUILayout.Space(10);
|
||||
|
||||
EditorGUILayout.LabelField("Detective Board Integration", EditorStyles.boldLabel);
|
||||
EditorGUILayout.PropertyField(_clueToUnlockProp, new GUIContent("Clue To Unlock"));
|
||||
|
||||
if (_clueToUnlockProp.objectReferenceValue != null)
|
||||
{
|
||||
EditorGUILayout.HelpBox("This clue will be unlocked on the Detective Board when this dialogue node is reached.", MessageType.Info);
|
||||
}
|
||||
else
|
||||
{
|
||||
EditorGUILayout.HelpBox("Optional: Assign a ClueData to unlock an evidence on the Detective Board when reaching this node.", MessageType.None);
|
||||
}
|
||||
|
||||
EditorGUILayout.Space(10);
|
||||
|
||||
EditorGUILayout.BeginHorizontal();
|
||||
|
||||
+23
-39
@@ -12,17 +12,13 @@ namespace Unheimlich.Gameplay.Player
|
||||
{
|
||||
public class DiapasonListeningHandlerV2 : Singleton<DiapasonListeningHandlerV2>
|
||||
{
|
||||
[Header("Systems")]
|
||||
[SerializeField] private DiapasonEquipmentManager _equipmentManager;
|
||||
[SerializeField] private CameraPanController _cameraPanController;
|
||||
|
||||
[Header("Settings")]
|
||||
[SerializeField] private bool _disablePlayerMovementDuringListen = true;
|
||||
[SerializeField] private bool _enablePanMode = true;
|
||||
|
||||
[Header("Visual Feedback")]
|
||||
[SerializeField] private float _vignetteIntensity = 0.5f;
|
||||
[SerializeField] private ListeningVignetteEffect _vignetteEffect;
|
||||
[Tooltip("Vignette intensity during listening (0-1)")]
|
||||
[SerializeField] [Range(0f, 1f)] private float _vignetteIntensity = 0.3f;
|
||||
|
||||
[Header("Audio")]
|
||||
[SerializeField] private AudioClip _breathInClip;
|
||||
@@ -46,23 +42,9 @@ namespace Unheimlich.Gameplay.Player
|
||||
if (_playerAudioSource == null)
|
||||
_playerAudioSource = gameObject.AddComponent<AudioSource>();
|
||||
|
||||
if (_cameraPanController == null)
|
||||
{
|
||||
_cameraPanController = GetComponent<CameraPanController>();
|
||||
if (_cameraPanController == null)
|
||||
{
|
||||
Debug.LogWarning("[DiapasonV2] CameraPanController not found!");
|
||||
}
|
||||
}
|
||||
|
||||
if (_vignetteEffect == null)
|
||||
{
|
||||
_vignetteEffect = GetComponentInChildren<ListeningVignetteEffect>();
|
||||
}
|
||||
|
||||
_cameraHeadBob = GetComponentInChildren<CameraHeadBob>();
|
||||
|
||||
if (_equipmentManager != null)
|
||||
if (PlayerController.Instance != null)
|
||||
{
|
||||
SubscribeToEquipmentEvents();
|
||||
}
|
||||
@@ -70,7 +52,7 @@ namespace Unheimlich.Gameplay.Player
|
||||
|
||||
void OnDestroy()
|
||||
{
|
||||
if (_equipmentManager != null)
|
||||
if (PlayerController.Instance != null && PlayerController.Instance.DiapasonEquipment != null)
|
||||
{
|
||||
UnsubscribeFromEquipmentEvents();
|
||||
}
|
||||
@@ -99,15 +81,15 @@ namespace Unheimlich.Gameplay.Player
|
||||
}
|
||||
}
|
||||
|
||||
if (IsListening && _cameraPanController != null)
|
||||
if (IsListening && PlayerController.Instance?.CameraPanController != null)
|
||||
{
|
||||
_cameraPanController.UpdatePan();
|
||||
PlayerController.Instance.CameraPanController.UpdatePan();
|
||||
}
|
||||
}
|
||||
|
||||
private void StartListeningMode()
|
||||
{
|
||||
if (_isFadingOut || _equipmentManager == null)
|
||||
if (_isFadingOut || PlayerController.Instance?.DiapasonEquipment == null)
|
||||
return;
|
||||
|
||||
if (_listeningCoroutine != null)
|
||||
@@ -126,7 +108,7 @@ namespace Unheimlich.Gameplay.Player
|
||||
PlayerController.Instance.Movement.enabled = false;
|
||||
}
|
||||
|
||||
_equipmentManager.EquipDiapason();
|
||||
PlayerController.Instance.DiapasonEquipment.EquipDiapason();
|
||||
|
||||
PlayBreathIn();
|
||||
yield return new WaitForSeconds(_breathInDuration);
|
||||
@@ -144,10 +126,10 @@ namespace Unheimlich.Gameplay.Player
|
||||
|
||||
EnableVignette(true);
|
||||
|
||||
if (_enablePanMode && _cameraPanController != null)
|
||||
if (_enablePanMode && PlayerController.Instance?.CameraPanController != null)
|
||||
{
|
||||
_cameraPanController.SaveOriginalState();
|
||||
_cameraPanController.StartPan();
|
||||
PlayerController.Instance.CameraPanController.SaveOriginalState();
|
||||
PlayerController.Instance.CameraPanController.StartPan();
|
||||
}
|
||||
|
||||
Cursor.lockState = CursorLockMode.Confined;
|
||||
@@ -166,18 +148,18 @@ namespace Unheimlich.Gameplay.Player
|
||||
IsConcentrating = false;
|
||||
_isFadingOut = true;
|
||||
|
||||
_equipmentManager.UnequipDiapason();
|
||||
PlayerController.Instance.DiapasonEquipment.UnequipDiapason();
|
||||
|
||||
EnableVignette(false);
|
||||
|
||||
if (_enablePanMode && _cameraPanController != null)
|
||||
if (_enablePanMode && PlayerController.Instance?.CameraPanController != null)
|
||||
{
|
||||
_cameraPanController.StopPan();
|
||||
_cameraPanController.ReturnToOriginalPosition(() =>
|
||||
PlayerController.Instance.CameraPanController.StopPan();
|
||||
PlayerController.Instance.CameraPanController.ReturnToOriginalPosition(() =>
|
||||
{
|
||||
if (_originalCameraSettings != null)
|
||||
if (_originalCameraSettings != null && PlayerController.Instance?.CameraPanController != null)
|
||||
{
|
||||
_cameraPanController.Settings = _originalCameraSettings;
|
||||
PlayerController.Instance.CameraPanController.Settings = _originalCameraSettings;
|
||||
_originalCameraSettings = null;
|
||||
}
|
||||
|
||||
@@ -215,15 +197,17 @@ namespace Unheimlich.Gameplay.Player
|
||||
|
||||
private void EnableVignette(bool enable)
|
||||
{
|
||||
if (_vignetteEffect == null)
|
||||
if (ListeningVignetteEffect.Instance == null)
|
||||
return;
|
||||
|
||||
if (enable)
|
||||
{
|
||||
_vignetteEffect.SetIntensity(_vignetteIntensity);
|
||||
ListeningVignetteEffect.Instance.EnableVignetteWithIntensity(_vignetteIntensity);
|
||||
}
|
||||
else
|
||||
{
|
||||
ListeningVignetteEffect.Instance.EnableVignette(false);
|
||||
}
|
||||
|
||||
_vignetteEffect.EnableVignette(enable);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+13
-3
@@ -1,24 +1,28 @@
|
||||
using UnityEngine;
|
||||
using UnityEngine.Rendering;
|
||||
using UnityEngine.Rendering.Universal;
|
||||
using Unheimlich.Core;
|
||||
|
||||
namespace Unheimlich.Gameplay.Player
|
||||
{
|
||||
public class ListeningVignetteEffect : MonoBehaviour
|
||||
public class ListeningVignetteEffect : Singleton<ListeningVignetteEffect>
|
||||
{
|
||||
[Header("References")]
|
||||
[Tooltip("Dedicated Volume for listening mode (not shared with dialogue system)")]
|
||||
[SerializeField] private Volume _listeningVolume;
|
||||
|
||||
[Header("Settings")]
|
||||
[SerializeField] private float _targetIntensity = 0.5f;
|
||||
[Tooltip("Target intensity when listening (0-1)")]
|
||||
[SerializeField] [Range(0f, 1f)] private float _targetIntensity = 0.3f;
|
||||
|
||||
[Tooltip("Speed of vignette fade in/out")]
|
||||
[SerializeField] private float _transitionSpeed = 2f;
|
||||
|
||||
private Vignette _vignette;
|
||||
private bool _isEnabled;
|
||||
private VolumeProfile _volumeProfile;
|
||||
|
||||
private void Awake()
|
||||
protected override void OnAwake()
|
||||
{
|
||||
if (_listeningVolume == null)
|
||||
{
|
||||
@@ -79,6 +83,12 @@ namespace Unheimlich.Gameplay.Player
|
||||
if (_vignette != null)
|
||||
_vignette.intensity.value = _targetIntensity;
|
||||
}
|
||||
|
||||
public void EnableVignetteWithIntensity(float intensity)
|
||||
{
|
||||
SetIntensity(intensity);
|
||||
EnableVignette(true);
|
||||
}
|
||||
|
||||
private void OnDestroy()
|
||||
{
|
||||
|
||||
@@ -4,6 +4,8 @@ using Unheimlich.Core;
|
||||
using Unheimlich.Data;
|
||||
using Unheimlich.Managers;
|
||||
using Unheimlich.Gameplay.CameraControl;
|
||||
using Unheimlich.Interactions.Core;
|
||||
using Unheimlich.Gameplay.DetectiveBoard;
|
||||
|
||||
namespace Unheimlich.Gameplay.Player
|
||||
{
|
||||
@@ -23,6 +25,12 @@ namespace Unheimlich.Gameplay.Player
|
||||
public Transform CameraPivot { get; private set; }
|
||||
public CameraEffects CameraEffects { get; private set; }
|
||||
public CameraPanController CameraPanController { get; private set; }
|
||||
public DiapasonListeningHandlerV2 DiapasonHandler { get; private set; }
|
||||
public DiapasonEquipmentManager DiapasonEquipment { get; private set; }
|
||||
public PlayerInspectionHandler InspectionHandler { get; private set; }
|
||||
public DetectiveBoardController DetectiveBoardController { get; private set; }
|
||||
public OctoIkArmsImproved ArmsSystem { get; private set; }
|
||||
|
||||
public InputManager Inputs => InputManager.Instance;
|
||||
|
||||
public bool IsMoving
|
||||
@@ -61,7 +69,13 @@ namespace Unheimlich.Gameplay.Player
|
||||
CharController = GetComponent<CharacterController>();
|
||||
CameraEffects = GetComponent<CameraEffects>();
|
||||
CameraPanController = GetComponent<CameraPanController>();
|
||||
DiapasonHandler = GetComponent<DiapasonListeningHandlerV2>();
|
||||
DiapasonEquipment = GetComponent<DiapasonEquipmentManager>();
|
||||
InspectionHandler = GetComponent<PlayerInspectionHandler>();
|
||||
DetectiveBoardController = GetComponent<DetectiveBoardController>();
|
||||
|
||||
PlayerCamera = GetComponentInChildren<Camera>();
|
||||
ArmsSystem = GetComponentInChildren<OctoIkArmsImproved>();
|
||||
|
||||
if (PlayerCamera != null)
|
||||
{
|
||||
|
||||
@@ -56,9 +56,9 @@ namespace Unheimlich.Interactions.Dialogues
|
||||
|
||||
private void Update()
|
||||
{
|
||||
if (_isDialogueActive && Player.Instance != null)
|
||||
if (_isDialogueActive && PlayerController.Instance != null)
|
||||
{
|
||||
float distance = Vector3.Distance(transform.position, Player.Instance.transform.position);
|
||||
float distance = Vector3.Distance(transform.position, PlayerController.Instance.transform.position);
|
||||
|
||||
if (distance > _maxDialogueDistance && DialogueManager.Instance != null && DialogueManager.Instance.IsDialogueActive)
|
||||
{
|
||||
@@ -131,9 +131,8 @@ namespace Unheimlich.Interactions.Dialogues
|
||||
|
||||
public void LookAtPlayer()
|
||||
{
|
||||
if (Player.Instance == null) return;
|
||||
|
||||
Vector3 lookAtPosition = Player.Instance.transform.position;
|
||||
if (PlayerController.Instance == null) return;
|
||||
Vector3 lookAtPosition = PlayerController.Instance.transform.position;
|
||||
lookAtPosition.y = transform.position.y;
|
||||
|
||||
transform.LookAt(lookAtPosition);
|
||||
|
||||
+6
-10
@@ -42,7 +42,7 @@ namespace Unheimlich.Interactions.FloatingWords
|
||||
[Tooltip("Maximum width for short texts (1-3 words)")]
|
||||
[SerializeField] private float _shortTextMaxWidth = 80f;
|
||||
[Tooltip("Maximum width for medium texts (4-5 words)")]
|
||||
[SerializeField] private float _mediumTextMaxWidth = 150f;
|
||||
[SerializeField] private float _mediumTextMaxWidth = 120f;
|
||||
[Tooltip("Maximum width for long texts (6-10 words)")]
|
||||
[SerializeField] private float _longTextMaxWidth = 200f;
|
||||
[Tooltip("Maximum width for very long texts (11+ words)")]
|
||||
@@ -123,10 +123,6 @@ namespace Unheimlich.Interactions.FloatingWords
|
||||
ghostColor.a = GetGhostTrailAlpha();
|
||||
_ghostTextComponent.color = ghostColor;
|
||||
|
||||
Canvas ghostCanvas = ghostObj.AddComponent<Canvas>();
|
||||
ghostCanvas.overrideSorting = true;
|
||||
ghostCanvas.sortingOrder = -1;
|
||||
|
||||
_positionHistory.Clear();
|
||||
}
|
||||
}
|
||||
@@ -266,18 +262,18 @@ namespace Unheimlich.Interactions.FloatingWords
|
||||
DialogueAnimationSettings settings = GetAnimationSettings();
|
||||
float textScaleMultiplier = settings != null ? settings.FocusedTextScaleMultiplier : 0.9f;
|
||||
float textScaleDuration = settings != null ? settings.FocusedTextScaleDuration : 0.3f;
|
||||
SetTextScale(textScaleMultiplier, textScaleDuration);
|
||||
SetTextScale(_originalTextScale * textScaleMultiplier, textScaleDuration);
|
||||
}
|
||||
|
||||
transform.localScale = Vector3.one * Mathf.Lerp(_originalScale, MAX_FOCUS_SCALE, progress);
|
||||
// transform.localScale = Vector3.one * Mathf.Lerp(_originalScale, MAX_FOCUS_SCALE, progress);
|
||||
|
||||
bool shouldMoveToPlayer = GetMoveTowardPlayerOnFocus();
|
||||
|
||||
if (shouldMoveToPlayer && Player.Instance != null)
|
||||
if (shouldMoveToPlayer && PlayerController.Instance != null)
|
||||
{
|
||||
float distance = GetFocusDistanceFromPlayer();
|
||||
Vector3 targetPos = Player.Instance.transform.position
|
||||
+ Player.Instance.transform.forward * distance;
|
||||
Vector3 targetPos = PlayerController.Instance.transform.position
|
||||
+ PlayerController.Instance.transform.forward * distance;
|
||||
transform.position = Vector3.Lerp(transform.position, targetPos, Time.deltaTime * _focusLerpSpeed);
|
||||
}
|
||||
|
||||
|
||||
+43
-12
@@ -38,6 +38,9 @@ namespace Unheimlich.Interactions.FloatingWords
|
||||
public float FocusDistanceFromPlayer;
|
||||
public bool DisableFloatingEffects;
|
||||
public float DefaultScaleMultiplier;
|
||||
public bool EnableScaleVariation;
|
||||
public float MinScaleVariation;
|
||||
public float MaxScaleVariation;
|
||||
}
|
||||
|
||||
private static SpawnConfig GetConfig(ObjectDescriptionSettings settings)
|
||||
@@ -96,7 +99,10 @@ namespace Unheimlich.Interactions.FloatingWords
|
||||
MoveTowardPlayerOnFocus = settings.MoveTowardPlayerOnFocus,
|
||||
FocusDistanceFromPlayer = settings.FocusDistanceFromPlayer,
|
||||
DisableFloatingEffects = false,
|
||||
DefaultScaleMultiplier = settings.DefaultScaleMultiplier
|
||||
DefaultScaleMultiplier = settings.DefaultScaleMultiplier,
|
||||
EnableScaleVariation = settings.EnableScaleVariation,
|
||||
MinScaleVariation = settings.MinScaleVariation,
|
||||
MaxScaleVariation = settings.MaxScaleVariation
|
||||
};
|
||||
}
|
||||
|
||||
@@ -144,7 +150,13 @@ namespace Unheimlich.Interactions.FloatingWords
|
||||
ChoiceFocusHandler word = Object.Instantiate(wordPrefab, parent);
|
||||
word.transform.localPosition = localOffset;
|
||||
word.transform.localRotation = Quaternion.identity;
|
||||
word.transform.localScale = Vector3.one * config.DefaultScaleMultiplier;
|
||||
|
||||
float scaleMultiplier = config.DefaultScaleMultiplier;
|
||||
if (config.EnableScaleVariation)
|
||||
{
|
||||
scaleMultiplier *= Random.Range(config.MinScaleVariation, config.MaxScaleVariation);
|
||||
}
|
||||
word.transform.localScale = Vector3.one * scaleMultiplier;
|
||||
|
||||
CanvasGroup canvasGroup = word.GetComponent<CanvasGroup>();
|
||||
if (canvasGroup == null)
|
||||
@@ -170,7 +182,13 @@ namespace Unheimlich.Interactions.FloatingWords
|
||||
ChoiceFocusHandler word = Object.Instantiate(prefab, parent);
|
||||
word.transform.localPosition = Vector3.up * i * 50f;
|
||||
word.transform.localRotation = Quaternion.identity;
|
||||
word.transform.localScale = Vector3.one * config.DefaultScaleMultiplier;
|
||||
|
||||
float scaleMultiplier = config.DefaultScaleMultiplier;
|
||||
if (config.EnableScaleVariation)
|
||||
{
|
||||
scaleMultiplier *= Random.Range(config.MinScaleVariation, config.MaxScaleVariation);
|
||||
}
|
||||
word.transform.localScale = Vector3.one * scaleMultiplier;
|
||||
word.Setup(choices[i], config);
|
||||
words.Add(word);
|
||||
}
|
||||
@@ -180,16 +198,22 @@ namespace Unheimlich.Interactions.FloatingWords
|
||||
private static List<ChoiceFocusHandler> SpawnInFrontOfPlayer(List<DialogueChoiceData> choices, ChoiceFocusHandler prefab, Transform parent, SpawnConfig config)
|
||||
{
|
||||
List<ChoiceFocusHandler> words = new List<ChoiceFocusHandler>();
|
||||
if (Player.Instance == null) return words;
|
||||
if (PlayerController.Instance == null) return words;
|
||||
|
||||
Vector3 playerForward = Player.Instance.transform.forward;
|
||||
Vector3 playerForward = PlayerController.Instance.transform.forward;
|
||||
for (int i = 0; i < choices.Count; i++)
|
||||
{
|
||||
ChoiceFocusHandler word = Object.Instantiate(prefab, parent);
|
||||
Vector3 offset = playerForward * 2f + Vector3.up * (i - choices.Count / 2f) * (config.SpawnRadius / choices.Count / 100f);
|
||||
word.transform.position = Player.Instance.transform.position + offset;
|
||||
word.transform.LookAt(Player.Instance.transform);
|
||||
word.transform.localScale = Vector3.one * config.DefaultScaleMultiplier;
|
||||
word.transform.position = PlayerController.Instance.transform.position + offset;
|
||||
word.transform.LookAt(PlayerController.Instance.transform);
|
||||
|
||||
float scaleMultiplier = config.DefaultScaleMultiplier;
|
||||
if (config.EnableScaleVariation)
|
||||
{
|
||||
scaleMultiplier *= Random.Range(config.MinScaleVariation, config.MaxScaleVariation);
|
||||
}
|
||||
word.transform.localScale = Vector3.one * scaleMultiplier;
|
||||
word.Setup(choices[i], config);
|
||||
words.Add(word);
|
||||
}
|
||||
@@ -200,9 +224,9 @@ namespace Unheimlich.Interactions.FloatingWords
|
||||
{
|
||||
List<ChoiceFocusHandler> words = new List<ChoiceFocusHandler>();
|
||||
originalPositions = new Vector3[choices.Count];
|
||||
if (Player.Instance == null) return words;
|
||||
if (PlayerController.Instance == null) return words;
|
||||
|
||||
parent.position = Player.Instance.transform.position;
|
||||
parent.position = PlayerController.Instance.transform.position;
|
||||
parent.rotation = Quaternion.identity;
|
||||
|
||||
float angleStep = 360f / choices.Count;
|
||||
@@ -214,8 +238,15 @@ namespace Unheimlich.Interactions.FloatingWords
|
||||
|
||||
ChoiceFocusHandler word = Object.Instantiate(prefab, parent);
|
||||
word.transform.localPosition = localOffset;
|
||||
word.transform.LookAt(Player.Instance.transform);
|
||||
word.transform.localScale = Vector3.one * config.DefaultScaleMultiplier;
|
||||
word.transform.LookAt(PlayerController.Instance.transform);
|
||||
|
||||
float scaleMultiplier = config.DefaultScaleMultiplier;
|
||||
if (config.EnableScaleVariation)
|
||||
{
|
||||
scaleMultiplier *= Random.Range(config.MinScaleVariation, config.MaxScaleVariation);
|
||||
}
|
||||
word.transform.localScale = Vector3.one * scaleMultiplier;
|
||||
|
||||
word.Setup(choices[i], config);
|
||||
words.Add(word);
|
||||
}
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
using UnityEngine;
|
||||
using System.Collections.Generic;
|
||||
using Unheimlich.Data.Dialogues;
|
||||
using Unheimlich.Data.DetectiveBoard;
|
||||
using Unheimlich.Gameplay.Player;
|
||||
using Unheimlich.Managers;
|
||||
using OctoCore.Localization;
|
||||
|
||||
namespace Unheimlich.Interactions.FloatingWords
|
||||
@@ -17,6 +19,7 @@ namespace Unheimlich.Interactions.FloatingWords
|
||||
|
||||
[Header("Settings")]
|
||||
[SerializeField] private ObjectDescriptionSettings _settings;
|
||||
[SerializeField] private float _raycastDistance = 100f;
|
||||
|
||||
[Header("Anchor")]
|
||||
[SerializeField] private Transform _descriptionAnchor;
|
||||
@@ -24,6 +27,10 @@ namespace Unheimlich.Interactions.FloatingWords
|
||||
[Header("Prefab")]
|
||||
[SerializeField] private ChoiceFocusHandler _wordPrefab;
|
||||
|
||||
[Header("Detective Board Integration")]
|
||||
[Tooltip("Clue to unlock when words are fully merged (optional)")]
|
||||
[SerializeField] private ClueData _clueToUnlock;
|
||||
|
||||
private Transform _playerTransform;
|
||||
private Camera _mainCamera;
|
||||
private List<ChoiceFocusHandler> _spawnedWords = new List<ChoiceFocusHandler>();
|
||||
@@ -42,7 +49,7 @@ namespace Unheimlich.Interactions.FloatingWords
|
||||
|
||||
void Start()
|
||||
{
|
||||
_playerTransform = Player.Instance.transform;
|
||||
_playerTransform = PlayerController.Instance.transform;
|
||||
_mainCamera = Camera.main;
|
||||
|
||||
if (_playerTransform == null)
|
||||
@@ -113,7 +120,8 @@ namespace Unheimlich.Interactions.FloatingWords
|
||||
{
|
||||
dummyChoices.Add(new DialogueChoiceData
|
||||
{
|
||||
ChoiceText = _descriptionTextLocalized,
|
||||
ChoiceText = null,
|
||||
ChoiceTextOverride = descText,
|
||||
NextNode = null
|
||||
});
|
||||
}
|
||||
@@ -162,7 +170,7 @@ namespace Unheimlich.Interactions.FloatingWords
|
||||
|
||||
bool isHoveringObject = false;
|
||||
|
||||
if (Physics.Raycast(ray, out hit, 100f, layerMask))
|
||||
if (Physics.Raycast(ray, out hit, _raycastDistance, layerMask))
|
||||
{
|
||||
if (hit.collider.gameObject == gameObject || hit.collider.transform.IsChildOf(transform))
|
||||
isHoveringObject = true;
|
||||
@@ -266,12 +274,22 @@ namespace Unheimlich.Interactions.FloatingWords
|
||||
.setOnComplete(() =>
|
||||
{
|
||||
_hasCompletedMergeOnce = true;
|
||||
UnlockClue();
|
||||
DespawnWords();
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void UnlockClue()
|
||||
{
|
||||
if (_clueToUnlock != null && DetectiveBoardManager.Instance != null)
|
||||
{
|
||||
DetectiveBoardManager.Instance.UnlockClue(_clueToUnlock);
|
||||
Debug.Log($"[ObjectDescription] Unlocked clue: {_clueToUnlock.name} from description merge");
|
||||
}
|
||||
}
|
||||
|
||||
private void UnmergeWords()
|
||||
{
|
||||
if (!_isMerged) return;
|
||||
|
||||
@@ -25,7 +25,6 @@ namespace Unheimlich.Managers
|
||||
public ClueData Data;
|
||||
public bool IsUnlocked;
|
||||
public bool IsEliminated;
|
||||
public Vector2 BoardPosition;
|
||||
}
|
||||
|
||||
public class Connection
|
||||
@@ -37,12 +36,10 @@ namespace Unheimlich.Managers
|
||||
}
|
||||
|
||||
[Header("References")]
|
||||
[SerializeField] private DetectiveBoardSettings _settings;
|
||||
|
||||
private Dictionary<string, ClueState> _clueStates = new Dictionary<string, ClueState>();
|
||||
private List<Connection> _connections = new List<Connection>();
|
||||
|
||||
public DetectiveBoardSettings Settings => _settings;
|
||||
public IReadOnlyDictionary<string, ClueState> ClueStates => _clueStates;
|
||||
public IReadOnlyList<Connection> Connections => _connections;
|
||||
|
||||
@@ -63,8 +60,7 @@ namespace Unheimlich.Managers
|
||||
{
|
||||
Data = clueData,
|
||||
IsUnlocked = true,
|
||||
IsEliminated = false,
|
||||
BoardPosition = clueData.InitialPosition
|
||||
IsEliminated = false
|
||||
};
|
||||
|
||||
OnClueUnlocked?.Invoke(clueData);
|
||||
@@ -124,8 +120,7 @@ namespace Unheimlich.Managers
|
||||
{
|
||||
Data = clueData,
|
||||
IsUnlocked = false,
|
||||
IsEliminated = false,
|
||||
BoardPosition = clueData.InitialPosition
|
||||
IsEliminated = false
|
||||
};
|
||||
}
|
||||
|
||||
@@ -189,17 +184,6 @@ namespace Unheimlich.Managers
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates a clue's position on the board.
|
||||
/// </summary>
|
||||
public void UpdateCluePosition(string clueID, Vector2 newPosition)
|
||||
{
|
||||
if (_clueStates.TryGetValue(clueID, out ClueState state))
|
||||
{
|
||||
state.BoardPosition = newPosition;
|
||||
}
|
||||
}
|
||||
|
||||
public event System.Action<ClueData> OnClueUnlocked;
|
||||
public event System.Action<string, string> OnConnectionCreated;
|
||||
public event System.Action<string> OnClueEliminated;
|
||||
|
||||
@@ -2,6 +2,7 @@ using UnityEngine;
|
||||
using System;
|
||||
using Unheimlich.Core;
|
||||
using Unheimlich.Data.Dialogues;
|
||||
using Unheimlich.Data.DetectiveBoard;
|
||||
using Unheimlich.Gameplay.Player;
|
||||
using Unheimlich.UI;
|
||||
using Unheimlich.Interactions.Dialogues;
|
||||
@@ -150,6 +151,18 @@ namespace Unheimlich.Managers
|
||||
|
||||
UnlockFocus();
|
||||
IsInDialogue = true;
|
||||
|
||||
if (_currentNode.ClueToUnlock != null)
|
||||
{
|
||||
if (DetectiveBoardManager.Instance != null)
|
||||
{
|
||||
DetectiveBoardManager.Instance.UnlockClue(_currentNode.ClueToUnlock);
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.LogWarning($"[DialogueManager] Cannot unlock clue '{_currentNode.ClueToUnlock.ClueID}': DetectiveBoardManager not found");
|
||||
}
|
||||
}
|
||||
|
||||
if(_currentChoiceAnchor == null)
|
||||
{
|
||||
|
||||
@@ -3,22 +3,41 @@ using UnityEngine.UI;
|
||||
|
||||
namespace Unheimlich.UI.DetectiveBoard
|
||||
{
|
||||
public enum LineColorState
|
||||
{
|
||||
Green = 0,
|
||||
Orange = 1,
|
||||
Red = 2
|
||||
}
|
||||
|
||||
[RequireComponent(typeof(RectTransform))]
|
||||
public class ConnectionLine : MonoBehaviour
|
||||
{
|
||||
[Header("References")]
|
||||
[SerializeField] private Image _lineImage;
|
||||
[SerializeField] private Image _outlineImage;
|
||||
[SerializeField] private RectTransform _rectTransform;
|
||||
|
||||
private RectTransform _lineRectTransform;
|
||||
private RectTransform _outlineRectTransform;
|
||||
|
||||
[Header("Settings")]
|
||||
[SerializeField] private float _lineThickness = 3f;
|
||||
[SerializeField] private Color _lineColor = Color.white;
|
||||
[SerializeField] private Color _outlineColor = Color.yellow;
|
||||
|
||||
[Header("Color States")]
|
||||
[SerializeField] private Color _greenColor = Color.green;
|
||||
[SerializeField] private Color _orangeColor = new Color(1f, 0.5f, 0f);
|
||||
[SerializeField] private Color _redColor = Color.red;
|
||||
|
||||
private PostItNode _startNode;
|
||||
private PostItNode _endNode;
|
||||
private LineColorState _colorState = LineColorState.Green;
|
||||
private bool _isHovered;
|
||||
|
||||
public PostItNode StartNode => _startNode;
|
||||
public PostItNode EndNode => _endNode;
|
||||
public LineColorState ColorState => _colorState;
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
@@ -26,20 +45,54 @@ namespace Unheimlich.UI.DetectiveBoard
|
||||
_rectTransform = GetComponent<RectTransform>();
|
||||
|
||||
if (_lineImage == null)
|
||||
_lineImage = GetComponent<Image>();
|
||||
_lineImage = GetComponentInChildren<Image>();
|
||||
|
||||
if (_lineImage != null)
|
||||
{
|
||||
_lineRectTransform = _lineImage.GetComponent<RectTransform>();
|
||||
_lineImage.raycastTarget = true;
|
||||
}
|
||||
|
||||
if (_rectTransform != null)
|
||||
{
|
||||
_rectTransform.anchorMin = new Vector2(0.5f, 0.5f);
|
||||
_rectTransform.anchorMax = new Vector2(0.5f, 0.5f);
|
||||
_rectTransform.pivot = new Vector2(0f, 0.5f);
|
||||
}
|
||||
|
||||
if (_lineRectTransform != null)
|
||||
{
|
||||
_lineRectTransform.anchorMin = new Vector2(0f, 0.5f);
|
||||
_lineRectTransform.anchorMax = new Vector2(0f, 0.5f);
|
||||
_lineRectTransform.pivot = new Vector2(0f, 0.5f);
|
||||
}
|
||||
|
||||
if (_outlineImage == null)
|
||||
{
|
||||
_outlineImage = transform.Find("Outline")?.GetComponent<Image>();
|
||||
}
|
||||
|
||||
if (_outlineImage != null)
|
||||
{
|
||||
_outlineRectTransform = _outlineImage.GetComponent<RectTransform>();
|
||||
if (_outlineRectTransform != null)
|
||||
{
|
||||
_outlineRectTransform.anchorMin = new Vector2(0f, 0.5f);
|
||||
_outlineRectTransform.anchorMax = new Vector2(0f, 0.5f);
|
||||
_outlineRectTransform.pivot = new Vector2(0f, 0.5f);
|
||||
}
|
||||
_outlineImage.raycastTarget = false;
|
||||
_outlineImage.gameObject.SetActive(false);
|
||||
}
|
||||
}
|
||||
|
||||
public void Initialize(PostItNode startNode, PostItNode endNode, Color? color = null)
|
||||
public void Initialize(PostItNode startNode, PostItNode endNode)
|
||||
{
|
||||
_startNode = startNode;
|
||||
_endNode = endNode;
|
||||
_colorState = LineColorState.Green;
|
||||
|
||||
if (color.HasValue)
|
||||
_lineColor = color.Value;
|
||||
|
||||
if (_lineImage != null)
|
||||
_lineImage.color = _lineColor;
|
||||
|
||||
UpdateColorState();
|
||||
UpdateLine();
|
||||
}
|
||||
|
||||
@@ -55,19 +108,57 @@ namespace Unheimlich.UI.DetectiveBoard
|
||||
float distance = direction.magnitude;
|
||||
|
||||
_rectTransform.anchoredPosition = startPos;
|
||||
_rectTransform.sizeDelta = new Vector2(distance, _lineThickness);
|
||||
|
||||
float angle = Mathf.Atan2(direction.y, direction.x) * Mathf.Rad2Deg;
|
||||
_rectTransform.rotation = Quaternion.Euler(0, 0, angle);
|
||||
_rectTransform.localRotation = Quaternion.Euler(0, 0, angle);
|
||||
|
||||
_rectTransform.pivot = new Vector2(0, 0.5f);
|
||||
if (_lineRectTransform != null)
|
||||
{
|
||||
_lineRectTransform.sizeDelta = new Vector2(distance, _lineThickness);
|
||||
}
|
||||
|
||||
if (_outlineRectTransform != null)
|
||||
{
|
||||
_outlineRectTransform.sizeDelta = new Vector2(distance, _lineThickness + 3f);
|
||||
}
|
||||
}
|
||||
|
||||
public void SetColor(Color color)
|
||||
public void CycleColorState()
|
||||
{
|
||||
_lineColor = color;
|
||||
if (_lineImage != null)
|
||||
_lineImage.color = color;
|
||||
_colorState = (LineColorState)(((int)_colorState + 1) % 3);
|
||||
UpdateColorState();
|
||||
}
|
||||
|
||||
public void SetColorState(LineColorState state)
|
||||
{
|
||||
_colorState = state;
|
||||
UpdateColorState();
|
||||
}
|
||||
|
||||
private void UpdateColorState()
|
||||
{
|
||||
if (_lineImage == null)
|
||||
return;
|
||||
|
||||
Color targetColor = _colorState switch
|
||||
{
|
||||
LineColorState.Green => _greenColor,
|
||||
LineColorState.Orange => _orangeColor,
|
||||
LineColorState.Red => _redColor,
|
||||
_ => _greenColor
|
||||
};
|
||||
|
||||
_lineImage.color = targetColor;
|
||||
}
|
||||
|
||||
public void SetHovered(bool hovered)
|
||||
{
|
||||
_isHovered = hovered;
|
||||
|
||||
if (_outlineImage != null)
|
||||
{
|
||||
_outlineImage.gameObject.SetActive(hovered);
|
||||
}
|
||||
}
|
||||
|
||||
public bool ConnectsNodes(PostItNode nodeA, PostItNode nodeB)
|
||||
|
||||
@@ -5,35 +5,85 @@ using System.Linq;
|
||||
using Unheimlich.Data.DetectiveBoard;
|
||||
using Unheimlich.Managers;
|
||||
using Unheimlich.Core;
|
||||
using Unheimlich.Gameplay.Player;
|
||||
|
||||
namespace Unheimlich.UI.DetectiveBoard
|
||||
{
|
||||
public class DetectiveBoardCanvas : Singleton<DetectiveBoardCanvas>
|
||||
{
|
||||
[Header("References")]
|
||||
[SerializeField] private Canvas _canvas;
|
||||
[SerializeField] private RectTransform _boardArea;
|
||||
[Header("Prefabs")]
|
||||
[SerializeField] private GameObject _postItNodePrefab;
|
||||
[SerializeField] private GameObject _connectionLinePrefab;
|
||||
|
||||
[Header("World Space Setup")]
|
||||
[SerializeField] private Camera _targetCamera;
|
||||
[Header("Layout References")]
|
||||
[SerializeField] private RectTransform _boardArea;
|
||||
|
||||
[Header("Connection Settings")]
|
||||
[SerializeField] private Color _connectionColor = Color.white;
|
||||
[SerializeField] private bool _allowFreeConnections = false;
|
||||
[Header("Auto-Placement Settings")]
|
||||
[SerializeField] private float _leftMargin = 50f;
|
||||
[SerializeField] private float _topMargin = 50f;
|
||||
[SerializeField] private float _nodeSpacing = 20f;
|
||||
[SerializeField] private float _randomRotationRange = 10f;
|
||||
|
||||
[Header("Input")]
|
||||
[SerializeField] private Key _connectModeKey = Key.LeftCtrl;
|
||||
[SerializeField] private Key _eliminateKey = Key.X;
|
||||
|
||||
private Canvas _canvas;
|
||||
private Camera _targetCamera;
|
||||
private Dictionary<string, PostItNode> _activeNodes = new Dictionary<string, PostItNode>();
|
||||
private List<ConnectionLine> _activeConnections = new List<ConnectionLine>();
|
||||
private PostItNode _selectedNode;
|
||||
private ConnectionLine _hoveredLine;
|
||||
private bool _isConnectMode;
|
||||
private DetectiveBoardSaveData _saveData;
|
||||
private float _currentColumnYOffset = 0f;
|
||||
|
||||
public bool IsConnectMode => _isConnectMode;
|
||||
public RectTransform BoardArea => _boardArea;
|
||||
|
||||
public void OnLineClicked(ConnectionLine line)
|
||||
{
|
||||
if (line != null)
|
||||
{
|
||||
line.CycleColorState();
|
||||
SaveConnectionColorState(line);
|
||||
}
|
||||
}
|
||||
|
||||
private void SaveConnectionColorState(ConnectionLine line)
|
||||
{
|
||||
if (line.StartNode == null || line.EndNode == null)
|
||||
return;
|
||||
|
||||
ConnectionData startNodeConnection = line.StartNode.NodeData.Connections.Find(c => c.TargetNodeID == line.EndNode.NodeID);
|
||||
if (startNodeConnection != null)
|
||||
{
|
||||
startNodeConnection.ColorState = line.ColorState;
|
||||
}
|
||||
|
||||
ConnectionData endNodeConnection = line.EndNode.NodeData.Connections.Find(c => c.TargetNodeID == line.StartNode.NodeID);
|
||||
if (endNodeConnection != null)
|
||||
{
|
||||
endNodeConnection.ColorState = line.ColorState;
|
||||
}
|
||||
}
|
||||
|
||||
public void SetHoveredLine(ConnectionLine line)
|
||||
{
|
||||
if (_hoveredLine == line)
|
||||
return;
|
||||
|
||||
if (_hoveredLine != null)
|
||||
{
|
||||
_hoveredLine.SetHovered(false);
|
||||
}
|
||||
|
||||
_hoveredLine = line;
|
||||
|
||||
if (_hoveredLine != null)
|
||||
{
|
||||
_hoveredLine.SetHovered(true);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called by DetectiveBoardInteraction when a node is left-clicked.
|
||||
@@ -63,11 +113,13 @@ namespace Unheimlich.UI.DetectiveBoard
|
||||
|
||||
protected override void OnAwake()
|
||||
{
|
||||
if (_canvas == null)
|
||||
_canvas = GetComponent<Canvas>();
|
||||
_canvas = GetComponent<Canvas>();
|
||||
|
||||
if (_canvas != null && _canvas.renderMode == RenderMode.WorldSpace)
|
||||
{
|
||||
if (PlayerController.Instance != null)
|
||||
_targetCamera = PlayerController.Instance.PlayerCamera;
|
||||
|
||||
if (_targetCamera == null)
|
||||
_targetCamera = Camera.main;
|
||||
|
||||
@@ -120,7 +172,6 @@ namespace Unheimlich.UI.DetectiveBoard
|
||||
private void Update()
|
||||
{
|
||||
HandleConnectModeInput();
|
||||
HandleEliminateInput();
|
||||
}
|
||||
|
||||
private void HandleConnectModeInput()
|
||||
@@ -138,17 +189,6 @@ namespace Unheimlich.UI.DetectiveBoard
|
||||
}
|
||||
}
|
||||
|
||||
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))
|
||||
@@ -169,12 +209,8 @@ namespace Unheimlich.UI.DetectiveBoard
|
||||
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);
|
||||
Vector2 autoPlacedPos = CalculateAutoPlacementPosition();
|
||||
nodeData = new PostItNodeData(clueData.ClueID, autoPlacedPos);
|
||||
_saveData.AddOrUpdateNode(nodeData);
|
||||
}
|
||||
|
||||
@@ -191,11 +227,40 @@ namespace Unheimlich.UI.DetectiveBoard
|
||||
|
||||
node.Initialize(clueData, nodeData);
|
||||
|
||||
float savedRotation = nodeData.Rotation;
|
||||
if (savedRotation == 0f)
|
||||
{
|
||||
savedRotation = Random.Range(-_randomRotationRange, _randomRotationRange);
|
||||
nodeData.Rotation = savedRotation;
|
||||
}
|
||||
node.SetRotation(savedRotation);
|
||||
|
||||
_activeNodes.Add(clueData.ClueID, node);
|
||||
|
||||
RestoreConnectionsForNode(node);
|
||||
}
|
||||
|
||||
private Vector2 CalculateAutoPlacementPosition()
|
||||
{
|
||||
Vector2 canvasSize = _boardArea.rect.size;
|
||||
|
||||
float startX = -canvasSize.x * 0.5f + _leftMargin;
|
||||
float startY = canvasSize.y * 0.5f - _topMargin;
|
||||
|
||||
float estimatedNodeHeight = 150f;
|
||||
|
||||
Vector2 position = new Vector2(startX, startY - _currentColumnYOffset);
|
||||
|
||||
_currentColumnYOffset += estimatedNodeHeight + _nodeSpacing;
|
||||
|
||||
if (_currentColumnYOffset > canvasSize.y - _topMargin * 2)
|
||||
{
|
||||
_currentColumnYOffset = 0f;
|
||||
}
|
||||
|
||||
return position;
|
||||
}
|
||||
|
||||
public void RemoveClueNode(string clueID)
|
||||
{
|
||||
if (!_activeNodes.TryGetValue(clueID, out PostItNode node))
|
||||
@@ -229,9 +294,6 @@ namespace Unheimlich.UI.DetectiveBoard
|
||||
|
||||
private void ToggleConnection(PostItNode nodeA, PostItNode nodeB)
|
||||
{
|
||||
if (!CanConnect(nodeA, nodeB))
|
||||
return;
|
||||
|
||||
ConnectionLine existingConnection = _activeConnections.Find(c => c.ConnectsNodes(nodeA, nodeB));
|
||||
|
||||
if (existingConnection != null)
|
||||
@@ -244,20 +306,6 @@ namespace Unheimlich.UI.DetectiveBoard
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
@@ -271,22 +319,34 @@ namespace Unheimlich.UI.DetectiveBoard
|
||||
return;
|
||||
}
|
||||
|
||||
line.Initialize(startNode, endNode, _connectionColor);
|
||||
line.Initialize(startNode, endNode);
|
||||
|
||||
ConnectionData existingConnectionData = startNode.NodeData.Connections.Find(c => c.TargetNodeID == endNode.NodeID);
|
||||
if (existingConnectionData != null)
|
||||
{
|
||||
line.SetColorState(existingConnectionData.ColorState);
|
||||
}
|
||||
|
||||
_activeConnections.Add(line);
|
||||
|
||||
if (!startNode.NodeData.ConnectedNodeIDs.Contains(endNode.NodeID))
|
||||
startNode.NodeData.ConnectedNodeIDs.Add(endNode.NodeID);
|
||||
if (existingConnectionData == null)
|
||||
{
|
||||
startNode.NodeData.Connections.Add(new ConnectionData(endNode.NodeID));
|
||||
}
|
||||
|
||||
if (!endNode.NodeData.ConnectedNodeIDs.Contains(startNode.NodeID))
|
||||
endNode.NodeData.ConnectedNodeIDs.Add(startNode.NodeID);
|
||||
ConnectionData reverseConnectionData = endNode.NodeData.Connections.Find(c => c.TargetNodeID == startNode.NodeID);
|
||||
if (reverseConnectionData == null)
|
||||
{
|
||||
endNode.NodeData.Connections.Add(new ConnectionData(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);
|
||||
connection.StartNode.NodeData.Connections.RemoveAll(c => c.TargetNodeID == connection.EndNode.NodeID);
|
||||
connection.EndNode.NodeData.Connections.RemoveAll(c => c.TargetNodeID == connection.StartNode.NodeID);
|
||||
}
|
||||
|
||||
_activeConnections.Remove(connection);
|
||||
@@ -322,12 +382,12 @@ namespace Unheimlich.UI.DetectiveBoard
|
||||
|
||||
private void RestoreConnectionsForNode(PostItNode node)
|
||||
{
|
||||
if (node.NodeData.ConnectedNodeIDs == null || node.NodeData.ConnectedNodeIDs.Count == 0)
|
||||
if (node.NodeData.Connections == null || node.NodeData.Connections.Count == 0)
|
||||
return;
|
||||
|
||||
foreach (string connectedID in node.NodeData.ConnectedNodeIDs)
|
||||
foreach (ConnectionData connectionData in node.NodeData.Connections)
|
||||
{
|
||||
if (_activeNodes.TryGetValue(connectedID, out PostItNode otherNode))
|
||||
if (_activeNodes.TryGetValue(connectionData.TargetNodeID, out PostItNode otherNode))
|
||||
{
|
||||
bool connectionExists = _activeConnections.Any(c => c.ConnectsNodes(node, otherNode));
|
||||
if (!connectionExists)
|
||||
@@ -366,6 +426,7 @@ namespace Unheimlich.UI.DetectiveBoard
|
||||
if (nodeData != null)
|
||||
{
|
||||
node.SetPosition(nodeData.Position);
|
||||
node.SetRotation(nodeData.Rotation);
|
||||
node.SetEliminated(nodeData.IsEliminated);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ using UnityEngine;
|
||||
using UnityEngine.InputSystem;
|
||||
using UnityEngine.UI;
|
||||
using Unheimlich.Gameplay.DetectiveBoard;
|
||||
using Unheimlich.Gameplay.Player;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine.EventSystems;
|
||||
|
||||
@@ -14,35 +15,38 @@ namespace Unheimlich.UI.DetectiveBoard
|
||||
/// </summary>
|
||||
public class DetectiveBoardInteraction : MonoBehaviour
|
||||
{
|
||||
[Header("References")]
|
||||
[SerializeField] private Camera _camera;
|
||||
[SerializeField] private Canvas _boardCanvas;
|
||||
[SerializeField] private RectTransform _boardArea;
|
||||
[SerializeField] private LayerMask _boardLayer;
|
||||
[SerializeField] private DetectiveBoardController _boardController;
|
||||
|
||||
[Header("Settings")]
|
||||
[SerializeField] private LayerMask _boardLayer;
|
||||
[SerializeField] private float _minInteractionDistance = 0.1f;
|
||||
[SerializeField] private float _maxInteractionDistance = 10f;
|
||||
|
||||
private Camera _camera;
|
||||
private Canvas _boardCanvas;
|
||||
private RectTransform _boardArea;
|
||||
private DetectiveBoardController _boardController;
|
||||
private DetectiveBoardCanvas _canvasManager;
|
||||
private PostItNode _hoveredNode;
|
||||
private PostItNode _selectedNode;
|
||||
private ConnectionLine _hoveredLine;
|
||||
private bool _isDragging;
|
||||
private Plane _canvasPlane;
|
||||
private Vector3 _dragOffset;
|
||||
|
||||
private void Start()
|
||||
{
|
||||
_boardCanvas = GetComponent<Canvas>();
|
||||
_canvasManager = GetComponent<DetectiveBoardCanvas>();
|
||||
_boardController = DetectiveBoardController.Instance;
|
||||
|
||||
if (_canvasManager != null && _canvasManager.BoardArea != null)
|
||||
_boardArea = _canvasManager.BoardArea;
|
||||
|
||||
if (PlayerController.Instance != null)
|
||||
_camera = PlayerController.Instance.PlayerCamera;
|
||||
|
||||
if (_camera == null)
|
||||
_camera = Camera.main;
|
||||
|
||||
if (_boardController == null)
|
||||
_boardController = FindFirstObjectByType<DetectiveBoardController>();
|
||||
|
||||
if (_canvasManager == null)
|
||||
_canvasManager = GetComponent<DetectiveBoardCanvas>();
|
||||
|
||||
UpdateCanvasPlane();
|
||||
}
|
||||
|
||||
@@ -63,6 +67,7 @@ namespace Unheimlich.UI.DetectiveBoard
|
||||
else
|
||||
{
|
||||
HandleHover();
|
||||
HandleLineHover();
|
||||
}
|
||||
|
||||
HandleClick();
|
||||
@@ -70,7 +75,9 @@ namespace Unheimlich.UI.DetectiveBoard
|
||||
|
||||
private bool IsInBoardInteractionMode()
|
||||
{
|
||||
return _boardController != null && _boardController.IsBoardActive;
|
||||
bool isActive = _boardController != null && _boardController.IsBoardActive;
|
||||
|
||||
return isActive;
|
||||
}
|
||||
|
||||
private void ClearInteractionState()
|
||||
@@ -81,6 +88,12 @@ namespace Unheimlich.UI.DetectiveBoard
|
||||
_hoveredNode = null;
|
||||
}
|
||||
|
||||
if (_hoveredLine != null && _canvasManager != null)
|
||||
{
|
||||
_canvasManager.SetHoveredLine(null);
|
||||
_hoveredLine = null;
|
||||
}
|
||||
|
||||
if (_selectedNode != null && _isDragging)
|
||||
{
|
||||
_selectedNode.SetSelected(false);
|
||||
@@ -119,6 +132,59 @@ namespace Unheimlich.UI.DetectiveBoard
|
||||
}
|
||||
}
|
||||
|
||||
private void HandleLineHover()
|
||||
{
|
||||
if (_canvasManager == null || Mouse.current == null)
|
||||
return;
|
||||
|
||||
if (_hoveredNode != null)
|
||||
{
|
||||
if (_hoveredLine != null)
|
||||
{
|
||||
_canvasManager.SetHoveredLine(null);
|
||||
_hoveredLine = null;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
ConnectionLine hitLine = RaycastForLine();
|
||||
|
||||
if (hitLine != _hoveredLine)
|
||||
{
|
||||
_canvasManager.SetHoveredLine(hitLine);
|
||||
_hoveredLine = hitLine;
|
||||
}
|
||||
}
|
||||
|
||||
private ConnectionLine RaycastForLine()
|
||||
{
|
||||
if (_camera == null || Mouse.current == null)
|
||||
return null;
|
||||
|
||||
Vector2 mousePos = Mouse.current.position.ReadValue();
|
||||
|
||||
GraphicRaycaster raycaster = _boardCanvas?.GetComponent<GraphicRaycaster>();
|
||||
if (raycaster != null && EventSystem.current != null)
|
||||
{
|
||||
var pointerEventData = new PointerEventData(EventSystem.current)
|
||||
{
|
||||
position = mousePos
|
||||
};
|
||||
|
||||
var results = new List<RaycastResult>();
|
||||
raycaster.Raycast(pointerEventData, results);
|
||||
|
||||
foreach (var result in results)
|
||||
{
|
||||
ConnectionLine line = result.gameObject.GetComponentInParent<ConnectionLine>();
|
||||
if (line != null)
|
||||
return line;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private void HandleClick()
|
||||
{
|
||||
if (Mouse.current == null)
|
||||
@@ -128,7 +194,18 @@ namespace Unheimlich.UI.DetectiveBoard
|
||||
|
||||
if (Mouse.current.leftButton.wasPressedThisFrame)
|
||||
{
|
||||
HandleLeftClick(hitNode);
|
||||
if (hitNode != null)
|
||||
{
|
||||
HandleLeftClick(hitNode);
|
||||
}
|
||||
else
|
||||
{
|
||||
ConnectionLine hitLine = RaycastForLine();
|
||||
if (hitLine != null && _canvasManager != null)
|
||||
{
|
||||
_canvasManager.OnLineClicked(hitLine);
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (Mouse.current.rightButton.wasPressedThisFrame && hitNode != null && _canvasManager != null)
|
||||
{
|
||||
|
||||
@@ -13,9 +13,11 @@ namespace Unheimlich.UI.DetectiveBoard
|
||||
[SerializeField] private TextMeshProUGUI _titleText;
|
||||
[SerializeField] private TextMeshProUGUI _descriptionText;
|
||||
[SerializeField] private Image _iconImage;
|
||||
[SerializeField] private GameObject _strikethroughLine;
|
||||
[SerializeField] private Image _selectionOutline;
|
||||
|
||||
[Header("Effects")]
|
||||
[SerializeField] private PostItScribbleEffect _scribbleEffect;
|
||||
|
||||
[Header("Connection Points")]
|
||||
[SerializeField] private RectTransform _connectionPoint;
|
||||
|
||||
@@ -27,7 +29,7 @@ namespace Unheimlich.UI.DetectiveBoard
|
||||
|
||||
public string NodeID => _clueData?.ClueID;
|
||||
public RectTransform RectTransform => _rectTransform;
|
||||
public RectTransform ConnectionPoint => _connectionPoint;
|
||||
public RectTransform ConnectionPoint => _rectTransform;
|
||||
public ClueData ClueData => _clueData;
|
||||
public PostItNodeData NodeData => _nodeData;
|
||||
public bool IsEliminated => _isEliminated;
|
||||
@@ -38,8 +40,8 @@ namespace Unheimlich.UI.DetectiveBoard
|
||||
if (_rectTransform == null)
|
||||
_rectTransform = GetComponent<RectTransform>();
|
||||
|
||||
if (_strikethroughLine != null)
|
||||
_strikethroughLine.SetActive(false);
|
||||
if (_scribbleEffect == null)
|
||||
_scribbleEffect = GetComponent<PostItScribbleEffect>();
|
||||
|
||||
if (_selectionOutline != null)
|
||||
_selectionOutline.enabled = false;
|
||||
@@ -72,11 +74,6 @@ namespace Unheimlich.UI.DetectiveBoard
|
||||
_background.color = _originalColor;
|
||||
}
|
||||
|
||||
if (_connectionPoint == null)
|
||||
{
|
||||
_connectionPoint = _rectTransform;
|
||||
}
|
||||
|
||||
SetPosition(nodeData.Position);
|
||||
SetEliminated(nodeData.IsEliminated);
|
||||
}
|
||||
@@ -91,16 +88,28 @@ namespace Unheimlich.UI.DetectiveBoard
|
||||
}
|
||||
}
|
||||
|
||||
public void SetRotation(float rotation)
|
||||
{
|
||||
if (_rectTransform != null)
|
||||
{
|
||||
_rectTransform.localRotation = Quaternion.Euler(0, 0, rotation);
|
||||
if (_nodeData != null)
|
||||
_nodeData.Rotation = rotation;
|
||||
}
|
||||
}
|
||||
|
||||
public float GetRotation()
|
||||
{
|
||||
return _rectTransform != null ? _rectTransform.localEulerAngles.z : 0f;
|
||||
}
|
||||
|
||||
public void SetEliminated(bool eliminated)
|
||||
{
|
||||
_isEliminated = eliminated;
|
||||
|
||||
if (_strikethroughLine != null)
|
||||
_strikethroughLine.SetActive(eliminated);
|
||||
|
||||
if (_background != null)
|
||||
if (_scribbleEffect != null)
|
||||
{
|
||||
_background.color = eliminated ? new Color(0.5f, 0.5f, 0.5f, 0.8f) : _originalColor;
|
||||
_scribbleEffect.SetEliminated(eliminated);
|
||||
}
|
||||
|
||||
if (_nodeData != null)
|
||||
@@ -110,18 +119,13 @@ namespace Unheimlich.UI.DetectiveBoard
|
||||
public void SetSelected(bool selected)
|
||||
{
|
||||
_isSelected = selected;
|
||||
|
||||
if (_selectionOutline != null)
|
||||
{
|
||||
_selectionOutline.enabled = selected;
|
||||
}
|
||||
}
|
||||
|
||||
public void SetHovered(bool hovered)
|
||||
{
|
||||
if (_selectionOutline != null)
|
||||
{
|
||||
_selectionOutline.enabled = hovered || _isSelected;
|
||||
_selectionOutline.enabled = hovered;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,135 @@
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
using Unheimlich.Utility;
|
||||
|
||||
namespace Unheimlich.UI.DetectiveBoard
|
||||
{
|
||||
public class PostItScribbleEffect : MonoBehaviour
|
||||
{
|
||||
[SerializeField] private Material _scribbleMaterialTemplate;
|
||||
[SerializeField] private RectTransform _targetRectTransform;
|
||||
|
||||
private Image _scribbleOverlay;
|
||||
private Material _scribbleMaterialInstance;
|
||||
private bool _overlayCreated = false;
|
||||
private bool _isEliminated = false;
|
||||
|
||||
private static readonly int ProgressID = Shader.PropertyToID("_Progress");
|
||||
private int _scribbleTweenId = -1;
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
if (_targetRectTransform == null)
|
||||
_targetRectTransform = GetComponent<RectTransform>();
|
||||
}
|
||||
|
||||
private void CreateScribbleOverlay()
|
||||
{
|
||||
if (_overlayCreated || _scribbleMaterialTemplate == null || _targetRectTransform == null)
|
||||
return;
|
||||
|
||||
GameObject overlayObject = new GameObject("ScribbleOverlay");
|
||||
overlayObject.transform.SetParent(_targetRectTransform, false);
|
||||
|
||||
_scribbleOverlay = overlayObject.AddComponent<Image>();
|
||||
_scribbleOverlay.raycastTarget = false;
|
||||
|
||||
RectTransform overlayRect = overlayObject.GetComponent<RectTransform>();
|
||||
|
||||
overlayRect.anchorMin = Vector2.zero;
|
||||
overlayRect.anchorMax = Vector2.one;
|
||||
overlayRect.sizeDelta = Vector2.zero;
|
||||
overlayRect.anchoredPosition = Vector2.zero;
|
||||
|
||||
_scribbleMaterialInstance = new Material(_scribbleMaterialTemplate);
|
||||
_scribbleMaterialInstance.SetFloat(ProgressID, 0f);
|
||||
_scribbleOverlay.material = _scribbleMaterialInstance;
|
||||
|
||||
_scribbleOverlay.gameObject.SetActive(false);
|
||||
|
||||
_overlayCreated = true;
|
||||
}
|
||||
|
||||
public void SetEliminated(bool eliminated)
|
||||
{
|
||||
if (_isEliminated == eliminated)
|
||||
return;
|
||||
|
||||
_isEliminated = eliminated;
|
||||
|
||||
if (eliminated)
|
||||
{
|
||||
ApplyScribble();
|
||||
}
|
||||
else
|
||||
{
|
||||
RemoveScribble();
|
||||
}
|
||||
}
|
||||
|
||||
private void ApplyScribble()
|
||||
{
|
||||
CancelActiveTweens();
|
||||
CreateScribbleOverlay();
|
||||
|
||||
if (_scribbleOverlay == null || _scribbleMaterialInstance == null)
|
||||
return;
|
||||
|
||||
_scribbleOverlay.gameObject.SetActive(true);
|
||||
|
||||
_scribbleTweenId = LeanTween.value(gameObject, 0f, 1f, 0.4f)
|
||||
.setEase(LeanTweenType.easeInOutQuad)
|
||||
.setOnUpdate((float val) => {
|
||||
if (_scribbleMaterialInstance != null)
|
||||
_scribbleMaterialInstance.SetFloat(ProgressID, val);
|
||||
})
|
||||
.setOnComplete(() => {
|
||||
_scribbleTweenId = -1;
|
||||
}).id;
|
||||
}
|
||||
|
||||
private void RemoveScribble()
|
||||
{
|
||||
if (!_overlayCreated || _scribbleOverlay == null)
|
||||
return;
|
||||
|
||||
if (!_scribbleOverlay.gameObject.activeSelf)
|
||||
return;
|
||||
|
||||
CancelActiveTweens();
|
||||
|
||||
if (_scribbleMaterialInstance != null)
|
||||
{
|
||||
float currentProgress = _scribbleMaterialInstance.GetFloat(ProgressID);
|
||||
|
||||
_scribbleTweenId = LeanTween.value(gameObject, currentProgress, 0f, 0.3f)
|
||||
.setEase(LeanTweenType.easeOutQuad)
|
||||
.setOnUpdate((float val) => {
|
||||
if (_scribbleMaterialInstance != null)
|
||||
_scribbleMaterialInstance.SetFloat(ProgressID, val);
|
||||
})
|
||||
.setOnComplete(() => {
|
||||
_scribbleTweenId = -1;
|
||||
if (_scribbleOverlay != null)
|
||||
_scribbleOverlay.gameObject.SetActive(false);
|
||||
}).id;
|
||||
}
|
||||
}
|
||||
|
||||
private void CancelActiveTweens()
|
||||
{
|
||||
TweenHelper.CancelTween(ref _scribbleTweenId);
|
||||
}
|
||||
|
||||
private void OnDestroy()
|
||||
{
|
||||
CancelActiveTweens();
|
||||
|
||||
if (_scribbleMaterialInstance != null)
|
||||
Destroy(_scribbleMaterialInstance);
|
||||
|
||||
if (_scribbleOverlay != null)
|
||||
Destroy(_scribbleOverlay.gameObject);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 009173206d77d184d86915685410c7d8
|
||||
@@ -2,11 +2,11 @@ using UnityEngine;
|
||||
using UnityEngine.Rendering;
|
||||
using UnityEngine.Rendering.Universal;
|
||||
using Unheimlich.Core;
|
||||
using Unheimlich.Gameplay.Player;
|
||||
|
||||
namespace Unheimlich.UI
|
||||
{
|
||||
[RequireComponent(typeof(Camera))]
|
||||
public class DialogueBlurEffectURP : Singleton<DialogueBlurEffectURP>
|
||||
public class DialogueBlurEffectURP : Singleton<DialogueBlurEffectURP>
|
||||
{
|
||||
|
||||
[Header("Volume Settings")]
|
||||
@@ -29,6 +29,11 @@ namespace Unheimlich.UI
|
||||
[Tooltip("Layer(s) for dialogue (World Space UI) to render without blur")]
|
||||
[SerializeField] private LayerMask _dialogueLayers;
|
||||
[SerializeField] private bool _autoCreateDialogueCamera = true;
|
||||
|
||||
[Header("Hands Configuration")]
|
||||
[Tooltip("Layer for player hands to render on top of dialogue")]
|
||||
[SerializeField] private LayerMask _handsLayer;
|
||||
[SerializeField] private bool _autoCreateHandsCamera = true;
|
||||
|
||||
private DepthOfField _dof;
|
||||
private ColorAdjustments _color;
|
||||
@@ -42,10 +47,11 @@ namespace Unheimlich.UI
|
||||
|
||||
private Camera _baseCam;
|
||||
private Camera _dialogueCam;
|
||||
private Camera _handsCam;
|
||||
|
||||
protected override void OnAwake()
|
||||
{
|
||||
_baseCam = GetComponent<Camera>();
|
||||
|
||||
|
||||
if (_fadeCurve == null || _fadeCurve.keys.Length == 0)
|
||||
_fadeCurve = AnimationCurve.EaseInOut(0, 0, 1, 1);
|
||||
@@ -96,8 +102,11 @@ namespace Unheimlich.UI
|
||||
|
||||
void Start()
|
||||
{
|
||||
_baseCam = PlayerController.Instance.PlayerCamera;
|
||||
if (_autoCreateDialogueCamera)
|
||||
SetupDialogueOverlayCamera();
|
||||
if (_autoCreateHandsCamera)
|
||||
SetupHandsOverlayCamera();
|
||||
}
|
||||
|
||||
void SetupDialogueOverlayCamera()
|
||||
@@ -113,6 +122,8 @@ namespace Unheimlich.UI
|
||||
Debug.LogWarning("[DialogueBlur] WARNING: dialogueLayers is EMPTY! Ghost words will be blurred!");
|
||||
|
||||
_baseCam.cullingMask &= ~_dialogueLayers;
|
||||
if (_handsLayer.value != 0)
|
||||
_baseCam.cullingMask &= ~_handsLayer;
|
||||
|
||||
var go = new GameObject("Dialogue Camera (Overlay)");
|
||||
go.transform.SetParent(_baseCam.transform);
|
||||
@@ -131,6 +142,38 @@ namespace Unheimlich.UI
|
||||
baseData.cameraStack.Add(_dialogueCam);
|
||||
}
|
||||
|
||||
void SetupHandsOverlayCamera()
|
||||
{
|
||||
if (_handsLayer.value == 0)
|
||||
{
|
||||
Debug.LogWarning("[DialogueBlur] WARNING: handsLayer is EMPTY! Hands camera will not be created.");
|
||||
return;
|
||||
}
|
||||
|
||||
var baseData = _baseCam.GetComponent<UniversalAdditionalCameraData>();
|
||||
if (baseData == null)
|
||||
{
|
||||
Debug.LogError("[DialogueBlur] ERROR: Base camera has no UniversalAdditionalCameraData!");
|
||||
return;
|
||||
}
|
||||
|
||||
var go = new GameObject("Hands Camera (Overlay)");
|
||||
go.transform.SetParent(_baseCam.transform);
|
||||
go.transform.localPosition = Vector3.zero;
|
||||
go.transform.localRotation = Quaternion.identity;
|
||||
|
||||
_handsCam = go.AddComponent<Camera>();
|
||||
_handsCam.CopyFrom(_baseCam);
|
||||
_handsCam.clearFlags = CameraClearFlags.Depth;
|
||||
_handsCam.cullingMask = _handsLayer;
|
||||
|
||||
var overlayData = go.AddComponent<UniversalAdditionalCameraData>();
|
||||
overlayData.renderType = CameraRenderType.Overlay;
|
||||
overlayData.renderPostProcessing = false;
|
||||
|
||||
baseData.cameraStack.Add(_handsCam);
|
||||
}
|
||||
|
||||
void Update()
|
||||
{
|
||||
if (!_fading) return;
|
||||
@@ -186,6 +229,21 @@ namespace Unheimlich.UI
|
||||
|
||||
public void ShowBlur()
|
||||
{
|
||||
if (_dialogueCam == null && _autoCreateDialogueCamera)
|
||||
{
|
||||
if (_baseCam == null && PlayerController.Instance != null)
|
||||
_baseCam = PlayerController.Instance.PlayerCamera;
|
||||
|
||||
if (_baseCam != null)
|
||||
{
|
||||
SetupDialogueOverlayCamera();
|
||||
if (_autoCreateHandsCamera)
|
||||
SetupHandsOverlayCamera();
|
||||
}
|
||||
else
|
||||
Debug.LogWarning("[DialogueBlur] WARNING: Cannot create dialogue camera - PlayerCamera not ready!");
|
||||
}
|
||||
|
||||
if (_fading && _target == 1f) return;
|
||||
|
||||
_start = _current;
|
||||
|
||||
@@ -38,6 +38,7 @@ namespace Unheimlich.UI
|
||||
private bool _shouldFollowPlayerRotation = false;
|
||||
private Transform _currentAnchor;
|
||||
private Vector3 _baseScale;
|
||||
private Vector3 _basePanelScale;
|
||||
private List<ChoiceFocusHandler> _activeChoices = new List<ChoiceFocusHandler>();
|
||||
private float _sortTimer = 0f;
|
||||
private const float SORT_INTERVAL = 0.1f;
|
||||
@@ -59,24 +60,32 @@ namespace Unheimlich.UI
|
||||
if (_dialogueCanvasGroup == null)
|
||||
_dialogueCanvasGroup = _dialoguePanel.AddComponent<CanvasGroup>();
|
||||
}
|
||||
|
||||
if (_dialoguePanel != null)
|
||||
{
|
||||
_basePanelScale = _dialoguePanel.transform.localScale;
|
||||
}
|
||||
}
|
||||
|
||||
private void Update()
|
||||
{
|
||||
if (Player.Instance == null || !ChoiceContainer.activeInHierarchy)
|
||||
if (PlayerController.Instance == null || !ChoiceContainer.activeInHierarchy)
|
||||
return;
|
||||
|
||||
if (_shouldFollowPlayerPosition)
|
||||
ChoiceContainer.transform.position = Player.Instance.transform.position;
|
||||
ChoiceContainer.transform.position = PlayerController.Instance.transform.position;
|
||||
|
||||
if (_shouldFollowPlayerRotation)
|
||||
{
|
||||
float playerYRotation = Player.Instance.transform.eulerAngles.y;
|
||||
float playerYRotation = PlayerController.Instance.transform.eulerAngles.y;
|
||||
ChoiceContainer.transform.rotation = Quaternion.Euler(0f, playerYRotation, 0f);
|
||||
}
|
||||
|
||||
if (_currentAnimationSettings != null && _currentAnimationSettings.EnableDistanceScaling && !_shouldFollowPlayerPosition)
|
||||
{
|
||||
ApplyDistanceScaling();
|
||||
ApplyDistanceScalingToPanel();
|
||||
}
|
||||
|
||||
SortChoicesByDistance();
|
||||
}
|
||||
@@ -188,17 +197,17 @@ namespace Unheimlich.UI
|
||||
_shouldFollowPlayerPosition = followPosition;
|
||||
_shouldFollowPlayerRotation = followRotation;
|
||||
|
||||
if (followPosition && Player.Instance != null)
|
||||
if (followPosition && PlayerController.Instance != null)
|
||||
{
|
||||
Vector3 playerWorldPos = Player.Instance.transform.position;
|
||||
Vector3 playerWorldPos = PlayerController.Instance.transform.position;
|
||||
ChoiceContainer.transform.position = playerWorldPos;
|
||||
}
|
||||
else
|
||||
ChoiceContainer.transform.localPosition = Vector3.zero;
|
||||
|
||||
if (followRotation && Player.Instance != null)
|
||||
if (followRotation && PlayerController.Instance != null)
|
||||
{
|
||||
float playerYRotation = Player.Instance.transform.eulerAngles.y;
|
||||
float playerYRotation = PlayerController.Instance.transform.eulerAngles.y;
|
||||
ChoiceContainer.transform.rotation = Quaternion.Euler(0f, playerYRotation, 0f);
|
||||
}
|
||||
else
|
||||
@@ -215,7 +224,10 @@ namespace Unheimlich.UI
|
||||
SpawnChoices(nodeChoices, animationSettings);
|
||||
|
||||
if (animationSettings != null && animationSettings.EnableDistanceScaling && !_shouldFollowPlayerPosition)
|
||||
{
|
||||
ApplyDistanceScaling();
|
||||
ApplyDistanceScalingToPanel();
|
||||
}
|
||||
|
||||
bool shouldRotate = animationSettings == null || animationSettings.EnableRotation;
|
||||
if (shouldRotate)
|
||||
@@ -275,10 +287,10 @@ namespace Unheimlich.UI
|
||||
/// </summary>
|
||||
private void ApplyDistanceScaling()
|
||||
{
|
||||
if (_currentAnchor == null || Player.Instance == null)
|
||||
if (_currentAnchor == null || PlayerController.Instance == null)
|
||||
return;
|
||||
|
||||
float distance = Vector3.Distance(_currentAnchor.position, Player.Instance.transform.position);
|
||||
float distance = Vector3.Distance(_currentAnchor.position, PlayerController.Instance.transform.position);
|
||||
|
||||
float normalizedDistance = Mathf.InverseLerp(_currentAnimationSettings.MinDistance, _currentAnimationSettings.MaxDistance, distance);
|
||||
|
||||
@@ -286,6 +298,20 @@ namespace Unheimlich.UI
|
||||
|
||||
ChoiceContainer.transform.localScale = _baseScale * finalScale;
|
||||
}
|
||||
|
||||
private void ApplyDistanceScalingToPanel()
|
||||
{
|
||||
if (_currentAnchor == null || PlayerController.Instance == null || _dialoguePanel == null)
|
||||
return;
|
||||
|
||||
float distance = Vector3.Distance(_currentAnchor.position, PlayerController.Instance.transform.position);
|
||||
|
||||
float normalizedDistance = Mathf.InverseLerp(_currentAnimationSettings.MinDistance, _currentAnimationSettings.MaxDistance, distance);
|
||||
|
||||
float finalScale = Mathf.Lerp(_currentAnimationSettings.MinScaleMultiplier, _currentAnimationSettings.MaxScaleMultiplier, normalizedDistance);
|
||||
|
||||
_dialoguePanel.transform.localScale = _basePanelScale * finalScale;
|
||||
}
|
||||
|
||||
private Vector3 GetRotationAxisVector(RotationAxis axis)
|
||||
{
|
||||
@@ -331,7 +357,7 @@ namespace Unheimlich.UI
|
||||
/// </summary>
|
||||
private void SortChoicesByDistance()
|
||||
{
|
||||
if (_activeChoices.Count <= 1 || Player.Instance == null)
|
||||
if (_activeChoices.Count <= 1 || PlayerController.Instance == null)
|
||||
return;
|
||||
|
||||
_sortTimer += Time.deltaTime;
|
||||
|
||||
Reference in New Issue
Block a user