diff --git a/Assets/Code/Scripts/Interaction.meta b/Assets/Code/Scripts/Interaction.meta new file mode 100644 index 0000000..60c2048 --- /dev/null +++ b/Assets/Code/Scripts/Interaction.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 303f7f8e61ca68143b9597e9d31f0e02 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Code/Scripts/Interaction/PressurePlateButton.cs b/Assets/Code/Scripts/Interaction/PressurePlateButton.cs new file mode 100644 index 0000000..21c4c1c --- /dev/null +++ b/Assets/Code/Scripts/Interaction/PressurePlateButton.cs @@ -0,0 +1,76 @@ +using System.Collections.Generic; +using UnityEngine; +using UnityEngine.Events; + +[RequireComponent(typeof(Collider))] +public class PressurePlateButton : MonoBehaviour +{ + [Header("Detection")] + [Tooltip("Layers that can activate the plate. Keep default to allow everything.")] + [SerializeField] private LayerMask detectionMask = ~0; + + [Tooltip("If true, rigidbody objects can activate the plate.")] + [SerializeField] private bool allowRigidbodies = true; + + [Tooltip("If true, the Player can activate the plate (tag Player or PlayerMovement component).")] + [SerializeField] private bool allowPlayer = true; + + [Header("Events")] + public UnityEvent OnPressed; + public UnityEvent OnReleased; + + private readonly HashSet m_validCollidersOnPlate = new HashSet(); + private bool m_isPressed; + + private void Reset() + { + Collider col = GetComponent(); + col.isTrigger = true; + } + + private void OnTriggerEnter(Collider other) + { + if (!IsValidActivator(other)) + return; + + m_validCollidersOnPlate.Add(other); + + if (!m_isPressed) + { + m_isPressed = true; + OnPressed?.Invoke(); + } + } + + private void OnTriggerExit(Collider other) + { + if (!m_validCollidersOnPlate.Remove(other)) + return; + + if (m_validCollidersOnPlate.Count == 0 && m_isPressed) + { + m_isPressed = false; + OnReleased?.Invoke(); + } + } + + private bool IsValidActivator(Collider other) + { + if (((1 << other.gameObject.layer) & detectionMask) == 0) + return false; + + if (allowPlayer) + { + if (other.CompareTag("Player")) + return true; + + if (other.GetComponentInParent() != null) + return true; + } + + if (allowRigidbodies && other.attachedRigidbody != null) + return true; + + return false; + } +} diff --git a/Assets/Code/Scripts/Interaction/PressurePlateButton.cs.meta b/Assets/Code/Scripts/Interaction/PressurePlateButton.cs.meta new file mode 100644 index 0000000..8e375e2 --- /dev/null +++ b/Assets/Code/Scripts/Interaction/PressurePlateButton.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 69374de63bb12844d97acb7a794b7b40 \ No newline at end of file diff --git a/Assets/Code/Scripts/Interaction/TestBlock.cs b/Assets/Code/Scripts/Interaction/TestBlock.cs new file mode 100644 index 0000000..776260a --- /dev/null +++ b/Assets/Code/Scripts/Interaction/TestBlock.cs @@ -0,0 +1,148 @@ +using UnityEngine; +using UnityEngine.Events; + +public class TestBlock : MonoBehaviour +{ + [Header("Visual")] + [SerializeField] private Renderer targetRenderer; + [SerializeField] private Color offColor = Color.red; + [SerializeField] private Color onColor = Color.green; + + [Header("Material")] + [SerializeField] private string colorPropertyName = "_BaseColor"; + + [Header("Optional Auto Wiring")] + [Tooltip("If assigned, block turns ON while plate is pressed and OFF when released.")] + [SerializeField] private PressurePlateButton[] pressurePlates; + + [Tooltip("If assigned, block toggles every time the wall button is interacted with.")] + [SerializeField] private WallInteractButton[] wallButtons; + + private MaterialPropertyBlock m_propertyBlock; + private bool m_isOn; + + private void Awake() + { + if (targetRenderer == null) + { + targetRenderer = GetComponentInChildren(); + } + + m_propertyBlock = new MaterialPropertyBlock(); + } + + private void Start() + { + SetOff(); + } + + private void OnEnable() + { + RegisterEvents(); + } + + private void OnDisable() + { + UnregisterEvents(); + } + + public void SetOn() + { + m_isOn = true; + Debug.Log($"{gameObject.name} turned ON"); + ApplyColor(onColor); + } + + public void SetOff() + { + m_isOn = false; + Debug.Log($"{gameObject.name} turned OFF"); + ApplyColor(offColor); + } + + public void Toggle() + { + if (m_isOn) + SetOff(); + else + SetOn(); + } + + // Generic interaction entry point for UnityEvents from any interactable. + public void TriggerInteraction() + { + Toggle(); + } + + public void Activate() + { + SetOn(); + } + + public void Deactivate() + { + SetOff(); + } + + private void ApplyColor(Color color) + { + if (targetRenderer == null) + return; + + targetRenderer.GetPropertyBlock(m_propertyBlock); + m_propertyBlock.SetColor(colorPropertyName, color); + targetRenderer.SetPropertyBlock(m_propertyBlock); + } + + private void RegisterEvents() + { + if (pressurePlates != null) + { + for (int i = 0; i < pressurePlates.Length; i++) + { + if (pressurePlates[i] == null) + continue; + + pressurePlates[i].OnPressed.AddListener(SetOn); + pressurePlates[i].OnReleased.AddListener(SetOff); + } + } + + if (wallButtons != null) + { + for (int i = 0; i < wallButtons.Length; i++) + { + if (wallButtons[i] == null) + continue; + + wallButtons[i].OnInteract.AddListener(Toggle); + } + } + } + + private void UnregisterEvents() + { + if (pressurePlates != null) + { + for (int i = 0; i < pressurePlates.Length; i++) + { + if (pressurePlates[i] == null) + continue; + + pressurePlates[i].OnPressed.RemoveListener(SetOn); + pressurePlates[i].OnReleased.RemoveListener(SetOff); + } + } + + if (wallButtons != null) + { + for (int i = 0; i < wallButtons.Length; i++) + { + if (wallButtons[i] == null) + continue; + + wallButtons[i].OnInteract.RemoveListener(Toggle); + } + } + } +} diff --git a/Assets/Code/Scripts/Interaction/TestBlock.cs.meta b/Assets/Code/Scripts/Interaction/TestBlock.cs.meta new file mode 100644 index 0000000..4744716 --- /dev/null +++ b/Assets/Code/Scripts/Interaction/TestBlock.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: b93699191d7b58146b2c38540cbed40f \ No newline at end of file diff --git a/Assets/Code/Scripts/Interaction/WallInteractButton.cs b/Assets/Code/Scripts/Interaction/WallInteractButton.cs new file mode 100644 index 0000000..66fba89 --- /dev/null +++ b/Assets/Code/Scripts/Interaction/WallInteractButton.cs @@ -0,0 +1,109 @@ +using TMPro; +using UnityEngine; +using UnityEngine.Events; +using UnityEngine.InputSystem; + +[RequireComponent(typeof(Collider))] +public class WallInteractButton : MonoBehaviour +{ + [Header("Interaction")] + [Tooltip("Optional Input Action. If empty, fallback key will be used.")] + [SerializeField] private InputActionReference interactAction; + + [Tooltip("Used only if no Input Action is assigned.")] + [SerializeField] private Key fallbackKey = Key.E; + + [SerializeField] private bool oneShot = false; + + [Header("Prompt")] + [SerializeField] private TMP_Text promptText; + [SerializeField] private string promptMessage = "Press E to interact"; + + [Header("Events")] + public UnityEvent OnInteract; + + private bool m_playerInRange; + private bool m_hasInteracted; + + private void Reset() + { + Collider col = GetComponent(); + col.isTrigger = true; + } + + private void OnEnable() + { + if (interactAction != null) + { + interactAction.action.Enable(); + } + } + + private void OnDisable() + { + if (interactAction != null) + { + interactAction.action.Disable(); + } + } + + private void Start() + { + UpdatePrompt(false); + } + + private void Update() + { + if (!m_playerInRange) + return; + + if (oneShot && m_hasInteracted) + return; + + if (WasInteractPressed()) + { + m_hasInteracted = true; + OnInteract?.Invoke(); + } + } + + private void OnTriggerEnter(Collider other) + { + if (!IsPlayer(other)) + return; + + m_playerInRange = true; + UpdatePrompt(true); + } + + private void OnTriggerExit(Collider other) + { + if (!IsPlayer(other)) + return; + + m_playerInRange = false; + UpdatePrompt(false); + } + + private bool WasInteractPressed() + { + if (interactAction != null) + return interactAction.action.WasPressedThisFrame() || interactAction.action.WasPerformedThisFrame(); + + return Keyboard.current != null && Keyboard.current[fallbackKey].wasPressedThisFrame; + } + + private bool IsPlayer(Collider other) + { + return other.CompareTag("Player") || other.GetComponentInParent() != null; + } + + private void UpdatePrompt(bool visible) + { + if (promptText == null) + return; + + promptText.text = promptMessage; + promptText.gameObject.SetActive(visible); + } +} diff --git a/Assets/Code/Scripts/Interaction/WallInteractButton.cs.meta b/Assets/Code/Scripts/Interaction/WallInteractButton.cs.meta new file mode 100644 index 0000000..88b259f --- /dev/null +++ b/Assets/Code/Scripts/Interaction/WallInteractButton.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 96a3f628ff18ea34788167cc398180ca \ No newline at end of file