feat (interaction): Add PressurePlateButton and WallInteractButton classes with interaction logic

This commit is contained in:
Thibault Pouch
2026-03-06 18:15:08 +01:00
parent 56be31e534
commit d7dae868a9
7 changed files with 347 additions and 0 deletions

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 303f7f8e61ca68143b9597e9d31f0e02
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -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<Collider> m_validCollidersOnPlate = new HashSet<Collider>();
private bool m_isPressed;
private void Reset()
{
Collider col = GetComponent<Collider>();
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<PlayerMovement>() != null)
return true;
}
if (allowRigidbodies && other.attachedRigidbody != null)
return true;
return false;
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 69374de63bb12844d97acb7a794b7b40

View File

@@ -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<Renderer>();
}
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);
}
}
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: b93699191d7b58146b2c38540cbed40f

View File

@@ -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<Collider>();
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<PlayerMovement>() != null;
}
private void UpdatePrompt(bool visible)
{
if (promptText == null)
return;
promptText.text = promptMessage;
promptText.gameObject.SetActive(visible);
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 96a3f628ff18ea34788167cc398180ca