chore: Project Restructuring & Theatre
This commit is contained in:
@@ -0,0 +1,67 @@
|
||||
using UnityEngine;
|
||||
using Unheimlich.Core;
|
||||
using Unheimlich.Core.Utils;
|
||||
using InputManager = Unheimlich.Core.Input.InputManager;
|
||||
|
||||
namespace Unheimlich.Core.Interaction
|
||||
{
|
||||
public class InteractionHandler : Singleton<InteractionHandler>
|
||||
{
|
||||
|
||||
[Header("Settings")]
|
||||
[SerializeField] private float _interactionRange = 1f;
|
||||
[SerializeField] private LayerMask _interactableLayers;
|
||||
[SerializeField] private float _raycastInterval = 0.1f;
|
||||
|
||||
private InteractableBase _currentInteractable;
|
||||
private Camera _playerCamera;
|
||||
private float _raycastTimer;
|
||||
|
||||
protected override void OnAwake()
|
||||
{
|
||||
_playerCamera = GetComponent<Camera>();
|
||||
}
|
||||
|
||||
private bool _didHit;
|
||||
|
||||
private void Update()
|
||||
{
|
||||
_didHit = RaycastHelper.ThrottledRaycastComponent(
|
||||
ref _raycastTimer,
|
||||
_raycastInterval,
|
||||
_playerCamera,
|
||||
_interactionRange,
|
||||
_interactableLayers,
|
||||
out InteractableBase interactable);
|
||||
|
||||
if (_didHit)
|
||||
_currentInteractable = interactable;
|
||||
else if (!_didHit && _raycastTimer <= 0f)
|
||||
_currentInteractable = null;
|
||||
|
||||
HandleInteractionInput();
|
||||
}
|
||||
|
||||
private void HandleInteractionInput()
|
||||
{
|
||||
var interactAction = InputManager.GetInteractAction();
|
||||
if (interactAction != null && interactAction.WasPressedThisFrame())
|
||||
{
|
||||
if (_currentInteractable != null)
|
||||
_currentInteractable.Interact();
|
||||
}
|
||||
}
|
||||
|
||||
private void OnDrawGizmosSelected()
|
||||
{
|
||||
if (_playerCamera != null)
|
||||
{
|
||||
Gizmos.color = _currentInteractable != null ? Color.green : Color.red;
|
||||
|
||||
Vector3 viewportCenter = new Vector3(0.5f, 0.5f, 0f);
|
||||
Ray ray = _playerCamera.ViewportPointToRay(viewportCenter);
|
||||
Gizmos.DrawRay(ray.origin, ray.direction * _interactionRange);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d96ceb0ee513b1e4197e55e9b926269b
|
||||
@@ -0,0 +1,17 @@
|
||||
using UnityEngine;
|
||||
|
||||
namespace Unheimlich.Core.Interaction
|
||||
{
|
||||
/// <summary>
|
||||
/// Base class for all interactable objects in the game.
|
||||
/// Derived classes must implement Interact() to define interaction behavior.
|
||||
/// </summary>
|
||||
public abstract class InteractableBase : MonoBehaviour
|
||||
{
|
||||
[Header("Interaction Settings")]
|
||||
[Tooltip("Text displayed when the player looks at the object")]
|
||||
public string InteractionText = "Interact";
|
||||
|
||||
public abstract void Interact();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c2bd6cb9667f8674294172efbdf8afd3
|
||||
@@ -0,0 +1,384 @@
|
||||
using UnityEngine;
|
||||
using System.Collections.Generic;
|
||||
using Unheimlich.Features.Dialogue.Data;
|
||||
using Unheimlich.Features.DetectiveBoard.Data;
|
||||
using Unheimlich.Features.DetectiveBoard;
|
||||
using Unheimlich.Features.Player;
|
||||
using Unheimlich.Features.Dialogue;
|
||||
using Unheimlich.Features.Dialogue.UI;
|
||||
using OctoCore.Localization;
|
||||
|
||||
namespace Unheimlich.Core.Interaction
|
||||
{
|
||||
public class ObjectDescriptionTrigger : MonoBehaviour
|
||||
{
|
||||
[Header("Description")]
|
||||
[Tooltip("Localized description text (recommended)")]
|
||||
[SerializeField] private OctoLocalizedTextData _descriptionTextLocalized;
|
||||
|
||||
[Tooltip("Fallback description text if localized version is not set")]
|
||||
[SerializeField] private string _descriptionText = "Mysterious Object";
|
||||
|
||||
[Header("Settings")]
|
||||
[SerializeField] private ObjectDescriptionSettings _settings;
|
||||
[SerializeField] private float _raycastDistance = 100f;
|
||||
|
||||
[Header("Anchor")]
|
||||
[SerializeField] private Transform _descriptionAnchor;
|
||||
|
||||
[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>();
|
||||
private GameObject _wordsContainer;
|
||||
private GameObject _wordsRotator;
|
||||
private bool _isActive = false;
|
||||
private bool _isMerged = false;
|
||||
private bool _hasBeenMergedOnce = false;
|
||||
|
||||
// Persists across OnEnable/OnDisable — only cleared by explicit ResetMerge()
|
||||
// or at the start of a new Play Mode session via RuntimeInitializeOnLoadMethod.
|
||||
private bool _hasCompletedMergeOnce = false;
|
||||
private Vector3[] _originalSpawnPositions;
|
||||
private Vector3[] _basePositions;
|
||||
private float _currentConvergenceRadius;
|
||||
private float _sortTimer = 0f;
|
||||
|
||||
private const float SORT_INTERVAL = 0.05f;
|
||||
|
||||
public bool IsAnimationActive => _isActive && (!_settings.MergeOnce || !_hasCompletedMergeOnce);
|
||||
public bool IsInMergeOnceMode => _settings != null && _settings.MergeOnce;
|
||||
|
||||
void Start()
|
||||
{
|
||||
InitializeReferences();
|
||||
}
|
||||
|
||||
void OnEnable()
|
||||
{
|
||||
// With Scene Reload disabled, Start() doesn't run again between Play Mode sessions.
|
||||
// Re-fetch references and reset transient state so animations restart cleanly.
|
||||
InitializeReferences();
|
||||
ResetSessionState();
|
||||
}
|
||||
|
||||
private void InitializeReferences()
|
||||
{
|
||||
_playerTransform = PlayerController.Instance != null ? PlayerController.Instance.transform : null;
|
||||
_mainCamera = Camera.main;
|
||||
|
||||
if (_playerTransform == null)
|
||||
Debug.LogWarning($"[ObjectDescription] No player found for {gameObject.name}");
|
||||
|
||||
if (_descriptionAnchor == null)
|
||||
_descriptionAnchor = transform;
|
||||
|
||||
if (_settings == null)
|
||||
{
|
||||
Debug.LogError($"[ObjectDescription] No settings assigned for {gameObject.name}");
|
||||
enabled = false;
|
||||
}
|
||||
}
|
||||
|
||||
private void ResetSessionState()
|
||||
{
|
||||
// With Domain Reload disabled, static fields and per-instance flags persist between
|
||||
// Play Mode sessions. This method is called from OnEnable() to reset all transient
|
||||
// animation state so merged objects start fresh each session without needing a full reload.
|
||||
if (_isActive)
|
||||
{
|
||||
foreach (var word in _spawnedWords)
|
||||
{
|
||||
if (word != null) Destroy(word.gameObject);
|
||||
}
|
||||
_spawnedWords.Clear();
|
||||
|
||||
if (_wordsContainer != null) Destroy(_wordsContainer);
|
||||
_wordsContainer = null;
|
||||
_wordsRotator = null;
|
||||
}
|
||||
|
||||
_isActive = false;
|
||||
_isMerged = false;
|
||||
_hasBeenMergedOnce = false;
|
||||
_hasCompletedMergeOnce = false; // reset so MergeOnce objects start fresh each session
|
||||
_currentConvergenceRadius = 0f;
|
||||
}
|
||||
|
||||
void Update()
|
||||
{
|
||||
if (_playerTransform == null || _settings == null) return;
|
||||
|
||||
float distanceToPlayer = Vector3.Distance(_playerTransform.position, transform.position);
|
||||
|
||||
if (!_isActive && distanceToPlayer <= _settings.ActivationDistance)
|
||||
{
|
||||
if (!_settings.MergeOnce || !_hasCompletedMergeOnce)
|
||||
SpawnWords();
|
||||
}
|
||||
else if (_isActive && distanceToPlayer >= _settings.DeactivationDistance)
|
||||
DespawnWords();
|
||||
|
||||
if (_isActive)
|
||||
{
|
||||
if (!_settings.MergeOnce || !_hasBeenMergedOnce)
|
||||
{
|
||||
UpdateWordConvergence();
|
||||
UpdateContainerRotation();
|
||||
|
||||
_sortTimer += Time.deltaTime;
|
||||
if (_sortTimer >= SORT_INTERVAL)
|
||||
{
|
||||
_sortTimer = 0f;
|
||||
FloatingWordsSpawner.SortByDistance(_spawnedWords, _mainCamera);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void SpawnWords()
|
||||
{
|
||||
if (_wordPrefab == null)
|
||||
{
|
||||
Debug.LogError($"[ObjectDescription] No word prefab assigned for {gameObject.name}");
|
||||
return;
|
||||
}
|
||||
|
||||
_isActive = true;
|
||||
_isMerged = false;
|
||||
_currentConvergenceRadius = _settings.SpawnRadius;
|
||||
|
||||
_wordsContainer = new GameObject($"{_descriptionText}_Container");
|
||||
_wordsContainer.transform.position = _descriptionAnchor.position;
|
||||
_wordsContainer.transform.rotation = _descriptionAnchor.rotation;
|
||||
|
||||
_wordsRotator = FloatingWordsSpawner.CreateWordsContainer(_descriptionText, _descriptionAnchor, _settings);
|
||||
_wordsRotator.transform.SetParent(_wordsContainer.transform, false);
|
||||
|
||||
ObjectDescriptionTriggerReference triggerRef = _wordsRotator.AddComponent<ObjectDescriptionTriggerReference>();
|
||||
triggerRef.Trigger = this;
|
||||
|
||||
string descText = _descriptionTextLocalized != null
|
||||
? OctoLocalization.GetLocalizedText(_descriptionTextLocalized)
|
||||
: _descriptionText;
|
||||
|
||||
List<DialogueChoiceData> dummyChoices = new List<DialogueChoiceData>();
|
||||
for (int i = 0; i < _settings.WordCount; i++)
|
||||
{
|
||||
dummyChoices.Add(new DialogueChoiceData
|
||||
{
|
||||
ChoiceText = null,
|
||||
ChoiceTextOverride = descText,
|
||||
NextNode = null
|
||||
});
|
||||
}
|
||||
|
||||
_spawnedWords = FloatingWordsSpawner.SpawnByMode(
|
||||
dummyChoices,
|
||||
_wordPrefab,
|
||||
_wordsRotator.transform,
|
||||
_settings,
|
||||
out _originalSpawnPositions
|
||||
);
|
||||
|
||||
_basePositions = new Vector3[_spawnedWords.Count];
|
||||
for (int i = 0; i < _spawnedWords.Count; i++)
|
||||
_basePositions[i] = _spawnedWords[i].transform.localPosition;
|
||||
}
|
||||
|
||||
private void DespawnWords()
|
||||
{
|
||||
_isActive = false;
|
||||
_isMerged = false;
|
||||
|
||||
foreach (var word in _spawnedWords)
|
||||
{
|
||||
if (word != null)
|
||||
Destroy(word.gameObject);
|
||||
}
|
||||
|
||||
_spawnedWords.Clear();
|
||||
|
||||
if (_wordsContainer != null)
|
||||
Destroy(_wordsContainer);
|
||||
|
||||
// Debug.Log($"[ObjectDescription] Despawned words for '{descriptionText}'");
|
||||
}
|
||||
|
||||
private void UpdateWordConvergence()
|
||||
{
|
||||
if (_mainCamera == null || _spawnedWords.Count == 0) return;
|
||||
|
||||
Vector2 screenCenter = new Vector2(Screen.width / 2f, Screen.height / 2f);
|
||||
Ray ray = _mainCamera.ScreenPointToRay(screenCenter);
|
||||
RaycastHit hit;
|
||||
|
||||
int layerMask = ~LayerMask.GetMask("Words", "UI");
|
||||
|
||||
bool isHoveringObject = false;
|
||||
|
||||
if (Physics.Raycast(ray, out hit, _raycastDistance, layerMask))
|
||||
{
|
||||
if (hit.collider.gameObject == gameObject || hit.collider.transform.IsChildOf(transform))
|
||||
isHoveringObject = true;
|
||||
}
|
||||
|
||||
float targetRadius = isHoveringObject ? _settings.MinConvergenceRadius : _settings.SpawnRadius;
|
||||
_currentConvergenceRadius = Mathf.Lerp(_currentConvergenceRadius, targetRadius, Time.deltaTime * _settings.ConvergenceSpeed);
|
||||
|
||||
if (isHoveringObject && !_isMerged && _currentConvergenceRadius <= _settings.MinConvergenceRadius * 5f)
|
||||
MergeWords();
|
||||
else if (!isHoveringObject && _isMerged)
|
||||
{
|
||||
if (!_settings.MergeOnce || !_hasBeenMergedOnce)
|
||||
UnmergeWords();
|
||||
}
|
||||
|
||||
if (_isMerged) return;
|
||||
|
||||
float convergenceFactor = Mathf.InverseLerp(0f, _settings.SpawnRadius, _currentConvergenceRadius);
|
||||
|
||||
for (int i = 0; i < _spawnedWords.Count; i++)
|
||||
{
|
||||
if (_spawnedWords[i] == null) continue;
|
||||
|
||||
Vector3 targetLocalPos = GetConvergenceTargetPosition(_originalSpawnPositions[i], _currentConvergenceRadius);
|
||||
|
||||
_basePositions[i] = Vector3.Lerp(
|
||||
_basePositions[i],
|
||||
targetLocalPos,
|
||||
Time.deltaTime * _settings.ConvergenceSpeed
|
||||
);
|
||||
|
||||
_spawnedWords[i].transform.localPosition = _basePositions[i] + _spawnedWords[i].GetShakeOffset(convergenceFactor);
|
||||
}
|
||||
}
|
||||
|
||||
private Vector3 GetConvergenceTargetPosition(Vector3 originalPosition, float radius)
|
||||
{
|
||||
if (radius < 0.01f) return Vector3.zero;
|
||||
|
||||
switch (_settings.RotationAxis)
|
||||
{
|
||||
case RotationAxis.Forward:
|
||||
Vector2 xyNormalized = new Vector2(originalPosition.x, originalPosition.y - _settings.SpawnHeightOffset).normalized;
|
||||
return new Vector3(
|
||||
xyNormalized.x * radius,
|
||||
xyNormalized.y * radius + _settings.SpawnHeightOffset,
|
||||
originalPosition.z
|
||||
);
|
||||
|
||||
case RotationAxis.Up:
|
||||
Vector2 xzNormalized = new Vector2(originalPosition.x, originalPosition.z).normalized;
|
||||
return new Vector3(
|
||||
xzNormalized.x * radius,
|
||||
originalPosition.y,
|
||||
xzNormalized.y * radius
|
||||
);
|
||||
|
||||
case RotationAxis.Right:
|
||||
Vector2 yzNormalized = new Vector2(originalPosition.y - _settings.SpawnHeightOffset, originalPosition.z).normalized;
|
||||
return new Vector3(
|
||||
originalPosition.x,
|
||||
yzNormalized.x * radius + _settings.SpawnHeightOffset,
|
||||
yzNormalized.y * radius
|
||||
);
|
||||
|
||||
default:
|
||||
return originalPosition.normalized * radius;
|
||||
}
|
||||
}
|
||||
|
||||
private void MergeWords()
|
||||
{
|
||||
if (_isMerged) return;
|
||||
|
||||
_isMerged = true;
|
||||
_hasBeenMergedOnce = true;
|
||||
|
||||
FloatingWordsSpawner.MergeWords(_spawnedWords, _settings, () =>
|
||||
{
|
||||
if (_settings.MergeOnce)
|
||||
{
|
||||
LeanTween.delayedCall(_settings.MergeOnceDisplayDuration, () =>
|
||||
{
|
||||
FadeOutAndComplete();
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void FadeOutAndComplete()
|
||||
{
|
||||
if (_spawnedWords.Count > 0 && _spawnedWords[0] != null)
|
||||
{
|
||||
CanvasGroup canvasGroup = _spawnedWords[0].GetComponent<CanvasGroup>();
|
||||
if (canvasGroup != null)
|
||||
{
|
||||
LeanTween.value(_spawnedWords[0].gameObject, 1f, 0f, _settings.MergeDuration)
|
||||
.setOnUpdate((float val) => { canvasGroup.alpha = val; })
|
||||
.setEaseInQuad()
|
||||
.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;
|
||||
|
||||
_isMerged = false;
|
||||
|
||||
FloatingWordsSpawner.UnmergeWords(_spawnedWords, _settings);
|
||||
}
|
||||
|
||||
private void UpdateContainerRotation()
|
||||
{
|
||||
FloatingWordsSpawner.UpdateContainerRotation(_wordsContainer, _wordsRotator, _playerTransform, _settings);
|
||||
}
|
||||
|
||||
public ObjectDescriptionSettings GetSettings()
|
||||
{
|
||||
return _settings;
|
||||
}
|
||||
|
||||
void OnDrawGizmosSelected()
|
||||
{
|
||||
if (_settings == null) return;
|
||||
|
||||
Transform anchor = _descriptionAnchor != null ? _descriptionAnchor : transform;
|
||||
|
||||
Gizmos.color = Color.yellow;
|
||||
Gizmos.DrawWireSphere(transform.position, _settings.ActivationDistance);
|
||||
|
||||
Gizmos.color = Color.red;
|
||||
Gizmos.DrawWireSphere(transform.position, _settings.DeactivationDistance);
|
||||
|
||||
Gizmos.color = Color.green;
|
||||
Gizmos.DrawWireSphere(anchor.position, _settings.SpawnRadius);
|
||||
|
||||
Gizmos.color = Color.cyan;
|
||||
Gizmos.DrawWireSphere(anchor.position, _settings.MinConvergenceRadius);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a1fe462730ebdae488f89f5868a4c74c
|
||||
@@ -0,0 +1,9 @@
|
||||
using UnityEngine;
|
||||
|
||||
namespace Unheimlich.Core.Interaction
|
||||
{
|
||||
public class ObjectDescriptionTriggerReference : MonoBehaviour
|
||||
{
|
||||
public ObjectDescriptionTrigger Trigger;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 0ddb0acc42d86604c9dbf91872a216d9
|
||||
@@ -0,0 +1,173 @@
|
||||
using System.Collections;
|
||||
using UnityEngine;
|
||||
using UnityEngine.InputSystem;
|
||||
using Unheimlich.Core;
|
||||
using Unheimlich.Features.Items;
|
||||
using Unheimlich.Features.Player;
|
||||
using InputManager = Unheimlich.Core.Input.InputManager;
|
||||
|
||||
namespace Unheimlich.Core.Interaction
|
||||
{
|
||||
/// <summary>
|
||||
/// Handles player item inspection mode.
|
||||
/// </summary>
|
||||
public class PlayerInspectionHandler : Singleton<PlayerInspectionHandler>
|
||||
{
|
||||
|
||||
[Header("Components")]
|
||||
[SerializeField] private Transform _inspectionHoldPoint;
|
||||
|
||||
[Header("State")]
|
||||
public bool IsInspecting { get; private set; } = false;
|
||||
private InspectableItem _currentInspectedItem;
|
||||
|
||||
private Coroutine _interactionListener;
|
||||
private bool _isUsingGamepad = false;
|
||||
|
||||
public void ToggleInspection(InspectableItem item)
|
||||
{
|
||||
if (!IsInspecting)
|
||||
{
|
||||
StartInspection(item);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (_interactionListener != null)
|
||||
StopInspection();
|
||||
}
|
||||
}
|
||||
|
||||
private void StartInspection(InspectableItem item)
|
||||
{
|
||||
IsInspecting = true;
|
||||
_currentInspectedItem = item;
|
||||
|
||||
// Set Inspection context (handles cursor automatically)
|
||||
InputManager.SetContext(InputManager.InputContext.Inspection);
|
||||
|
||||
// Disable OctoPlayer to prevent movement and look
|
||||
if (PlayerController.Instance?.Movement != null)
|
||||
{
|
||||
PlayerController.Instance.Movement.enabled = false;
|
||||
}
|
||||
|
||||
item.PrepareForInspection(_inspectionHoldPoint);
|
||||
|
||||
if (_interactionListener == null)
|
||||
_interactionListener = StartCoroutine(ListenForInteraction());
|
||||
|
||||
}
|
||||
|
||||
private void StopInspection()
|
||||
{
|
||||
if (_interactionListener != null)
|
||||
{
|
||||
StopCoroutine(_interactionListener);
|
||||
_interactionListener = null;
|
||||
}
|
||||
|
||||
StopInspectionInternal();
|
||||
}
|
||||
|
||||
private void StopInspectionInternal()
|
||||
{
|
||||
if (_currentInspectedItem != null)
|
||||
{
|
||||
_currentInspectedItem.Release();
|
||||
_currentInspectedItem = null;
|
||||
|
||||
StartCoroutine(DelayedToggleInput(true));
|
||||
}
|
||||
IsInspecting = false;
|
||||
}
|
||||
|
||||
private IEnumerator DelayedToggleInput(bool enable)
|
||||
{
|
||||
yield return new WaitForEndOfFrame();
|
||||
|
||||
// Re-enable OctoPlayer
|
||||
if (PlayerController.Instance?.Movement != null)
|
||||
{
|
||||
PlayerController.Instance.Movement.enabled = true;
|
||||
}
|
||||
|
||||
// Restore Normal context (handles cursor automatically)
|
||||
InputManager.SetContext(InputManager.InputContext.Normal);
|
||||
|
||||
yield break;
|
||||
}
|
||||
|
||||
|
||||
private IEnumerator ListenForInteraction()
|
||||
{
|
||||
yield return null;
|
||||
|
||||
while (IsInspecting)
|
||||
{
|
||||
var interactAction = InputManager.GetInteractAction();
|
||||
if (interactAction != null && interactAction.WasPressedThisFrame())
|
||||
{
|
||||
StopInspectionInternal();
|
||||
yield break;
|
||||
}
|
||||
|
||||
yield return null;
|
||||
}
|
||||
_interactionListener = null;
|
||||
}
|
||||
|
||||
private void Update()
|
||||
{
|
||||
if (IsInspecting && _currentInspectedItem != null)
|
||||
{
|
||||
HandleObjectRotation();
|
||||
}
|
||||
}
|
||||
|
||||
private void HandleObjectRotation()
|
||||
{
|
||||
if (_currentInspectedItem == null) return;
|
||||
|
||||
Vector2 lookInput = Vector2.zero;
|
||||
bool hasInput = false;
|
||||
|
||||
// Read input directly (not cached)
|
||||
if (Gamepad.current != null)
|
||||
{
|
||||
Vector2 stickInput = Gamepad.current.rightStick.ReadValue();
|
||||
if (stickInput.sqrMagnitude > 0.01f)
|
||||
{
|
||||
lookInput = stickInput;
|
||||
hasInput = true;
|
||||
_isUsingGamepad = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!hasInput && Mouse.current != null)
|
||||
{
|
||||
Vector2 mouseDelta = Mouse.current.delta.ReadValue();
|
||||
if (mouseDelta.sqrMagnitude > 0.01f)
|
||||
{
|
||||
lookInput = mouseDelta;
|
||||
hasInput = true;
|
||||
_isUsingGamepad = false;
|
||||
}
|
||||
}
|
||||
|
||||
// Only rotate if there's actual input
|
||||
if (hasInput)
|
||||
{
|
||||
// Normalize to get constant rotation speed regardless of input magnitude
|
||||
Vector2 direction = lookInput.normalized;
|
||||
|
||||
float rotationMultiplier = _isUsingGamepad ? Time.deltaTime : 0.02f;
|
||||
|
||||
float rotX = direction.y * _currentInspectedItem.RotationSpeed * rotationMultiplier;
|
||||
float rotY = direction.x * _currentInspectedItem.RotationSpeed * rotationMultiplier;
|
||||
|
||||
_currentInspectedItem.transform.Rotate(transform.up, -rotY, Space.World);
|
||||
_currentInspectedItem.transform.Rotate(transform.right, rotX, Space.World);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 464960797e705e84a9eb87ecce4e9311
|
||||
Reference in New Issue
Block a user