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")]
///
/// If false, Item will Move back to inventory space if player drops it.
///
[Tooltip("If false, Item will Move back to inventory space if player drops it.")]
public bool CanDropItem = true;
///
/// If false the snap zone cannot have it's content replaced.
///
[Tooltip("If false the snap zone cannot have it's content replaced.")]
public bool CanSwapItem = true;
///
/// If false the item inside the snap zone may not be removed
///
[Tooltip("If false the snap zone cannot have it's content replaced.")]
public bool CanRemoveItem = true;
///
/// Multiply Item Scale times this when in snap zone.
///
[Tooltip("Multiply Item Scale times this when in snap zone.")]
public float ScaleItem = 1f;
private float _scaleTo;
public bool DisableColliders = true;
List disabledColliders = new List();
[Tooltip("If true the item inside the SnapZone will be duplicated, instead of removed, from the SnapZone.")]
public bool DuplicateItemOnGrab = false;
///
/// Only snap if Grabbable was dropped maximum of X seconds ago
///
[Tooltip("Only snap if Grabbable was dropped maximum of X seconds ago")]
public float MaxDropTime = 0.1f;
///
/// Last Time.time this item was snapped into
///
[HideInInspector]
public float LastSnapTime;
[Header("Filtering")]
///
/// If not empty, can only snap objects if transform name contains one of these strings
///
[Tooltip("If not empty, can only snap objects if transform name contains one of these strings")]
public List OnlyAllowNames;
///
/// Do not allow snapping if transform contains one of these names
///
[Tooltip("Do not allow snapping if transform contains one of these names")]
public List ExcludeTransformNames;
[Header("Audio")]
public AudioClip SoundOnSnap;
public AudioClip SoundOnUnsnap;
[Header("Events")]
///
/// Optional Unity Event to be called when something is snapped to this SnapZone. Passes in the Grabbable that was attached.
///
public GrabbableEvent OnSnapEvent;
///
/// Optional Unity Event to be called when something has been detached from this SnapZone. Passes in the Grabbable is being detattached.
///
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();
_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) {
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() != 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() != null) {
return;
}
if (HeldItem != null) {
ReleaseAll();
}
HeldItem = grab;
heldItemRigid = HeldItem.GetComponent();
// 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()) {
_scaleTo = grab.GetComponent().Scale;
}
else {
_scaleTo = ScaleItem;
}
// Is there an offset to apply?
SnapZoneOffset off = grab.GetComponent();
if (off) {
offset = off;
}
else {
offset = grab.gameObject.AddComponent();
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();
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(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;
}
///
/// This is typically called by the GrabAction on the SnapZone
///
///
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();
// 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;
}
///
/// Release everything snapped to us
///
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();
if (ge != null) {
for (int x = 0; x < ge.Length; x++) {
ge[x].OnSnapZoneExit();
}
}
}
HeldItem = null;
}
}
}