452 lines
16 KiB
C#
452 lines
16 KiB
C#
using System.Collections;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using UnityEngine;
|
|
using UnityEngine.Events;
|
|
|
|
namespace BNG {
|
|
public class SnapZone : MonoBehaviour {
|
|
|
|
[Header("Starting / Held Item")]
|
|
[Tooltip("The currently held item. Set this in the editor to equip on Start().")]
|
|
public Grabbable HeldItem;
|
|
|
|
[Tooltip("TSet this in the editor to equip on Start().")]
|
|
public Grabbable StartingItem;
|
|
|
|
[Header("Options")]
|
|
public bool AutoGrab = false;
|
|
/// <summary>
|
|
/// If false, Item will Move back to inventory space if player drops it.
|
|
/// </summary>
|
|
[Tooltip("If false, Item will Move back to inventory space if player drops it.")]
|
|
public bool CanDropItem = true;
|
|
|
|
/// <summary>
|
|
/// If false the snap zone cannot have it's content replaced.
|
|
/// </summary>
|
|
[Tooltip("If false the snap zone cannot have it's content replaced.")]
|
|
public bool CanSwapItem = true;
|
|
|
|
/// <summary>
|
|
/// If false the item inside the snap zone may not be removed
|
|
/// </summary>
|
|
[Tooltip("If false the snap zone cannot have it's content replaced.")]
|
|
public bool CanRemoveItem = true;
|
|
|
|
/// <summary>
|
|
/// Multiply Item Scale times this when in snap zone.
|
|
/// </summary>
|
|
[Tooltip("Multiply Item Scale times this when in snap zone.")]
|
|
public float ScaleItem = 1f;
|
|
private float _scaleTo;
|
|
|
|
public bool DisableColliders = true;
|
|
List<Collider> disabledColliders = new List<Collider>();
|
|
|
|
[Tooltip("If true the item inside the SnapZone will be duplicated, instead of removed, from the SnapZone.")]
|
|
public bool DuplicateItemOnGrab = false;
|
|
|
|
/// <summary>
|
|
/// Only snap if Grabbable was dropped maximum of X seconds ago
|
|
/// </summary>
|
|
[Tooltip("Only snap if Grabbable was dropped maximum of X seconds ago")]
|
|
public float MaxDropTime = 0.1f;
|
|
|
|
/// <summary>
|
|
/// Last Time.time this item was snapped into
|
|
/// </summary>
|
|
[HideInInspector]
|
|
public float LastSnapTime;
|
|
|
|
[Header("Filtering")]
|
|
/// <summary>
|
|
/// If not empty, can only snap objects if transform name contains one of these strings
|
|
/// </summary>
|
|
[Tooltip("If not empty, can only snap objects if transform name contains one of these strings")]
|
|
public List<string> OnlyAllowNames;
|
|
|
|
/// <summary>
|
|
/// Do not allow snapping if transform contains one of these names
|
|
/// </summary>
|
|
[Tooltip("Do not allow snapping if transform contains one of these names")]
|
|
public List<string> ExcludeTransformNames;
|
|
|
|
[Header("Audio")]
|
|
public AudioClip SoundOnSnap;
|
|
public AudioClip SoundOnUnsnap;
|
|
|
|
|
|
[Header("Events")]
|
|
/// <summary>
|
|
/// Optional Unity Event to be called when something is snapped to this SnapZone. Passes in the Grabbable that was attached.
|
|
/// </summary>
|
|
public GrabbableEvent OnSnapEvent;
|
|
|
|
/// <summary>
|
|
/// Optional Unity Event to be called when something has been detached from this SnapZone. Passes in the Grabbable is being detattached.
|
|
/// </summary>
|
|
public GrabbableEvent OnDetachEvent;
|
|
|
|
GrabbablesInTrigger gZone;
|
|
|
|
Rigidbody heldItemRigid;
|
|
bool heldItemWasKinematic;
|
|
Grabbable trackedItem; // If we can't drop the item, track it separately
|
|
|
|
// Closest Grabbable in our trigger
|
|
[HideInInspector]
|
|
public Grabbable ClosestGrabbable;
|
|
|
|
SnapZoneOffset offset;
|
|
|
|
void Start() {
|
|
gZone = GetComponent<GrabbablesInTrigger>();
|
|
_scaleTo = ScaleItem;
|
|
|
|
// Auto Equip item by moving it into place and grabbing it
|
|
if (StartingItem != null) {
|
|
StartingItem.transform.position = transform.position;
|
|
GrabGrabbable(StartingItem);
|
|
}
|
|
// Can also use HeldItem (retains backwards compatibility)
|
|
else if (HeldItem != null) {
|
|
HeldItem.transform.position = transform.position;
|
|
GrabGrabbable(HeldItem);
|
|
}
|
|
}
|
|
|
|
void Update() {
|
|
|
|
ClosestGrabbable = getClosestGrabbable();
|
|
|
|
// Can we grab something
|
|
if (HeldItem == null && ClosestGrabbable != null) {
|
|
float secondsSinceDrop = Time.time - ClosestGrabbable.LastDropTime;
|
|
if (secondsSinceDrop < MaxDropTime || AutoGrab) {
|
|
GrabGrabbable(ClosestGrabbable);
|
|
}
|
|
}
|
|
|
|
// Keep snapped to us or drop
|
|
if (HeldItem != null) {
|
|
|
|
// Something picked this up or changed transform parent
|
|
if (HeldItem.BeingHeld || HeldItem.transform.parent != transform) {
|
|
ReleaseAll();
|
|
}
|
|
else {
|
|
// Scale Item while inside zone.
|
|
HeldItem.transform.localScale = Vector3.Lerp(HeldItem.transform.localScale, HeldItem.OriginalScale * _scaleTo, Time.deltaTime * 30f);
|
|
|
|
// Make sure this can't be grabbed from the snap zone
|
|
if (HeldItem.enabled || (disabledColliders != null && disabledColliders.Count > 0 && disabledColliders[0] != null && disabledColliders[0].enabled)) {
|
|
disableGrabbable(HeldItem);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Can't drop item. Lerp to position if not being held
|
|
if (!CanDropItem && trackedItem != null && HeldItem == null) {
|
|
if (!trackedItem.BeingHeld) {
|
|
GrabGrabbable(trackedItem);
|
|
}
|
|
}
|
|
}
|
|
|
|
Grabbable getClosestGrabbable() {
|
|
|
|
Grabbable closest = null;
|
|
float lastDistance = 9999f;
|
|
|
|
if (gZone == null || gZone.NearbyGrabbables == null) {
|
|
return null;
|
|
}
|
|
|
|
foreach (var g in gZone.NearbyGrabbables) {
|
|
|
|
// Collider may have been disabled
|
|
if (g.Key == null) {
|
|
continue;
|
|
}
|
|
|
|
float dist = Vector3.Distance(transform.position, g.Value.transform.position);
|
|
if (dist < lastDistance) {
|
|
|
|
// Not allowing secondary grabbables such as slides
|
|
if (g.Value.OtherGrabbableMustBeGrabbed != null) {
|
|
continue;
|
|
}
|
|
|
|
// Don't allow SnapZones in SnapZones
|
|
if (g.Value.GetComponent<SnapZone>() != null) {
|
|
continue;
|
|
}
|
|
|
|
// Don't allow InvalidSnapObjects to snap
|
|
if (g.Value.CanBeSnappedToSnapZone == false) {
|
|
continue;
|
|
}
|
|
|
|
// Must contain transform name
|
|
if (OnlyAllowNames != null && OnlyAllowNames.Count > 0) {
|
|
string transformName = g.Value.transform.name;
|
|
bool matchFound = false;
|
|
for (int x = 0; x < OnlyAllowNames.Count; x++) {
|
|
string name = OnlyAllowNames[x];
|
|
if (transformName.Contains(name)) {
|
|
matchFound = true;
|
|
}
|
|
}
|
|
|
|
// Not a valid match
|
|
if (!matchFound) {
|
|
continue;
|
|
}
|
|
}
|
|
|
|
// Check for name exclusion
|
|
if (ExcludeTransformNames != null) {
|
|
string transformName = g.Value.transform.name;
|
|
bool matchFound = false;
|
|
for (int x = 0; x < ExcludeTransformNames.Count; x++) {
|
|
// Not a valid match
|
|
if (transformName.Contains(ExcludeTransformNames[x])) {
|
|
matchFound = true;
|
|
}
|
|
}
|
|
// Exclude this
|
|
if (matchFound) {
|
|
continue;
|
|
}
|
|
}
|
|
|
|
// Only valid to snap if being held or recently dropped
|
|
if (g.Value.BeingHeld || (Time.time - g.Value.LastDropTime < MaxDropTime)) {
|
|
closest = g.Value;
|
|
lastDistance = dist;
|
|
}
|
|
}
|
|
}
|
|
|
|
return closest;
|
|
}
|
|
|
|
public virtual void GrabGrabbable(Grabbable grab) {
|
|
|
|
// Grab is already in Snap Zone
|
|
if (grab.transform.parent != null && grab.transform.parent.GetComponent<SnapZone>() != null) {
|
|
return;
|
|
}
|
|
|
|
if (HeldItem != null) {
|
|
ReleaseAll();
|
|
}
|
|
|
|
HeldItem = grab;
|
|
heldItemRigid = HeldItem.GetComponent<Rigidbody>();
|
|
|
|
// Mark as kinematic so it doesn't fall down
|
|
if (heldItemRigid) {
|
|
heldItemWasKinematic = heldItemRigid.isKinematic;
|
|
heldItemRigid.isKinematic = true;
|
|
}
|
|
else {
|
|
heldItemWasKinematic = false;
|
|
}
|
|
|
|
// Set the parent of the object
|
|
grab.transform.parent = transform;
|
|
|
|
// Set scale factor
|
|
// Use SnapZoneScale if specified
|
|
if (grab.GetComponent<SnapZoneScale>()) {
|
|
_scaleTo = grab.GetComponent<SnapZoneScale>().Scale;
|
|
}
|
|
else {
|
|
_scaleTo = ScaleItem;
|
|
}
|
|
|
|
// Is there an offset to apply?
|
|
SnapZoneOffset off = grab.GetComponent<SnapZoneOffset>();
|
|
if (off) {
|
|
offset = off;
|
|
}
|
|
else {
|
|
offset = grab.gameObject.AddComponent<SnapZoneOffset>();
|
|
offset.LocalPositionOffset = Vector3.zero;
|
|
offset.LocalRotationOffset = Vector3.zero;
|
|
}
|
|
|
|
// Lock into place
|
|
if (offset) {
|
|
HeldItem.transform.localPosition = offset.LocalPositionOffset;
|
|
HeldItem.transform.localEulerAngles = offset.LocalRotationOffset;
|
|
}
|
|
else {
|
|
HeldItem.transform.localPosition = Vector3.zero;
|
|
HeldItem.transform.localEulerAngles = Vector3.zero;
|
|
}
|
|
|
|
// Disable the grabbable. This is picked up through a Grab Action
|
|
disableGrabbable(grab);
|
|
|
|
// Call Grabbable Event from SnapZone
|
|
if (OnSnapEvent != null) {
|
|
OnSnapEvent.Invoke(grab);
|
|
}
|
|
|
|
// Fire Off Events on Grabbable
|
|
GrabbableEvents[] ge = grab.GetComponents<GrabbableEvents>();
|
|
if (ge != null) {
|
|
for (int x = 0; x < ge.Length; x++) {
|
|
ge[x].OnSnapZoneEnter();
|
|
}
|
|
}
|
|
|
|
if (SoundOnSnap) {
|
|
// Only play the sound if not just starting the scene
|
|
if (Time.timeSinceLevelLoad > 0.1f) {
|
|
VRUtils.Instance.PlaySpatialClipAt(SoundOnSnap, transform.position, 0.75f);
|
|
}
|
|
}
|
|
|
|
LastSnapTime = Time.time;
|
|
}
|
|
|
|
void disableGrabbable(Grabbable grab) {
|
|
|
|
if (DisableColliders) {
|
|
disabledColliders = grab.GetComponentsInChildren<Collider>(false).ToList();
|
|
for (int x = 0; x < disabledColliders.Count; x++) {
|
|
disabledColliders[x].enabled = false;
|
|
}
|
|
}
|
|
|
|
// Disable the grabbable. This is picked up through a Grab Action
|
|
grab.enabled = false;
|
|
}
|
|
|
|
/// <summary>
|
|
/// This is typically called by the GrabAction on the SnapZone
|
|
/// </summary>
|
|
/// <param name="grabber"></param>
|
|
public virtual void GrabEquipped(Grabber grabber) {
|
|
|
|
if (grabber != null) {
|
|
if (HeldItem) {
|
|
|
|
// Not allowed to be removed
|
|
if (!CanBeRemoved()) {
|
|
return;
|
|
}
|
|
|
|
var g = HeldItem;
|
|
if (DuplicateItemOnGrab) {
|
|
|
|
ReleaseAll();
|
|
|
|
// Position next to grabber if somewhat far away
|
|
if (Vector3.Distance(g.transform.position, grabber.transform.position) > 0.2f) {
|
|
g.transform.position = grabber.transform.position;
|
|
}
|
|
|
|
// Instantiate the object before it is grabbed
|
|
GameObject go = Instantiate(g.gameObject, transform.position, Quaternion.identity) as GameObject;
|
|
Grabbable grab = go.GetComponent<Grabbable>();
|
|
|
|
// Ok to attach it to snap zone now
|
|
this.GrabGrabbable(grab);
|
|
|
|
// Finish Grabbing the desired object
|
|
grabber.GrabGrabbable(g);
|
|
}
|
|
else {
|
|
ReleaseAll();
|
|
|
|
// Position next to grabber if somewhat far away
|
|
if (Vector3.Distance(g.transform.position, grabber.transform.position) > 0.2f) {
|
|
g.transform.position = grabber.transform.position;
|
|
}
|
|
|
|
// Do grab
|
|
grabber.GrabGrabbable(g);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
public virtual bool CanBeRemoved() {
|
|
// Not allowed to be removed
|
|
if (!CanRemoveItem) {
|
|
return false;
|
|
}
|
|
|
|
// Not a valid grab if we just snapped this item in an it's a toggle type
|
|
if (HeldItem.Grabtype == HoldType.Toggle && (Time.time - LastSnapTime < 0.1f)) {
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Release everything snapped to us
|
|
/// </summary>
|
|
public virtual void ReleaseAll() {
|
|
|
|
// No need to keep checking
|
|
if (HeldItem == null) {
|
|
return;
|
|
}
|
|
|
|
// Still need to keep track of item if we can't fully drop it
|
|
if (!CanDropItem && HeldItem != null) {
|
|
trackedItem = HeldItem;
|
|
}
|
|
|
|
HeldItem.ResetScale();
|
|
|
|
if (DisableColliders && disabledColliders != null) {
|
|
foreach (var c in disabledColliders) {
|
|
if (c) {
|
|
c.enabled = true;
|
|
}
|
|
}
|
|
}
|
|
disabledColliders = null;
|
|
|
|
// Reset Kinematic status
|
|
if (heldItemRigid) {
|
|
heldItemRigid.isKinematic = heldItemWasKinematic;
|
|
}
|
|
|
|
HeldItem.enabled = true;
|
|
HeldItem.transform.parent = null;
|
|
|
|
// Play Unsnap sound
|
|
if (HeldItem != null) {
|
|
if (SoundOnUnsnap) {
|
|
if (Time.timeSinceLevelLoad > 0.1f) {
|
|
VRUtils.Instance.PlaySpatialClipAt(SoundOnUnsnap, transform.position, 0.75f);
|
|
}
|
|
}
|
|
|
|
// Call event
|
|
if (OnDetachEvent != null) {
|
|
OnDetachEvent.Invoke(HeldItem);
|
|
}
|
|
|
|
// Fire Off Grabbable Events
|
|
GrabbableEvents[] ge = HeldItem.GetComponents<GrabbableEvents>();
|
|
if (ge != null) {
|
|
for (int x = 0; x < ge.Length; x++) {
|
|
ge[x].OnSnapZoneExit();
|
|
}
|
|
}
|
|
}
|
|
|
|
HeldItem = null;
|
|
}
|
|
}
|
|
} |