// Interact Object Appearance|Interactables|35030 namespace VRTK { using UnityEngine; using System.Collections; using System.Collections.Generic; /// /// Event Payload /// /// The GameObject that is being affected. /// The Interactable Object that is being monitored. /// The type of interaction initiating the event. public struct InteractObjectAppearanceEventArgs { public GameObject affectingObject; public GameObject objectToIgnore; public VRTK_InteractableObject monitoringObject; public VRTK_InteractableObject.InteractionType interactionType; } /// /// Event Payload /// /// this object /// public delegate void InteractObjectAppearanceEventHandler(object sender, InteractObjectAppearanceEventArgs e); /// /// Determine whether the `Object To Affect` should be visible or hidden by default or on interaction (near touch, touch, grab, use). /// /// /// **Required Components:** /// * `VRTK_InteractableObject` - The Interactable Object component to detect interactions on. This must be applied on the same GameObject as this script if one is not provided via the `Object To Monitor` parameter. /// /// **Script Usage:** /// * Place the `VRTK_InteractObjectAppearance` script on either: /// * The GameObject of the Interactable Object to detect interactions on. /// * Any other scene GameObject and provide a valid `VRTK_InteractableObject` component to the `Object To Monitor` parameter of this script. /// * Optionally provide a GameObject to the `Object To Affect` parameter to determine which GameObject to affect the appearance of. /// /// /// `VRTK/Examples/008_Controller_UsingAGrabbedObject` shows that the controller can be hidden when touching, grabbing and using an object. /// [AddComponentMenu("VRTK/Scripts/Interactions/Interactables/VRTK_InteractObjectAppearance")] public class VRTK_InteractObjectAppearance : VRTK_InteractableListener { /// /// The valid interacting object. /// public enum ValidInteractingObject { /// /// Any GameObject is considered a valid interacting object. /// Anything, /// /// Only a game controller is considered a valid interacting objcet. /// EitherController, /// /// Any GameObject except a game controller is considered a valid interacting object. /// NeitherController, /// /// Only the left game controller is considered a valid interacting objcet. /// LeftControllerOnly, /// /// Only the right game controller is considered a valid interacting objcet. /// RightControllerOnly } [Header("General Settings")] [Tooltip("The GameObject to affect the appearance of. If this is null then then the interacting object will be used (usually the controller).")] public GameObject objectToAffect; [SerializeField] [Tooltip("The Interactable Object to monitor for the interaction event (near touch/touch/grab/use).")] protected VRTK_InteractableObject objectToMonitor; [Header("Default Appearance Settings")] [Tooltip("If this is checked then the `Object To Affect` will be an active GameObject when the script is enabled. If it's unchecked then it will be disabled. This only takes effect if `Affect Interacting Object` is unticked.")] public bool gameObjectActiveByDefault = true; [Tooltip("If this is checked then the `Object To Affect` will have visible renderers when the script is enabled. If it's unchecked then it will have it's renderers disabled. This only takes effect if `Affect Interacting Object` is unticked.")] public bool rendererVisibleByDefault = true; [Header("Near Touch Appearance Settings")] [Tooltip("If this is checked then the `Object To Affect` will be an active GameObject when the `Object To Monitor` is near touched. If it's unchecked then it will be disabled on near touch.")] public bool gameObjectActiveOnNearTouch = true; [Tooltip("If this is checked then the `Object To Affect` will have visible renderers when the `Object To Monitor` is near touched. If it's unchecked then it will have it's renderers disabled on near touch.")] public bool rendererVisibleOnNearTouch = true; [Tooltip("The amount of time to wait before the near touch appearance settings are applied after the near touch event.")] public float nearTouchAppearanceDelay = 0f; [Tooltip("The amount of time to wait before the previous appearance settings are applied after the near untouch event.")] public float nearUntouchAppearanceDelay = 0f; [Tooltip("Determines what type of interacting object will affect the appearance of the `Object To Affect` after the near touch and near untouch event.")] public ValidInteractingObject validNearTouchInteractingObject = ValidInteractingObject.Anything; [Header("Touch Appearance Settings")] [Tooltip("If this is checked then the `Object To Affect` will be an active GameObject when the `Object To Monitor` is touched. If it's unchecked then it will be disabled on touch.")] public bool gameObjectActiveOnTouch = true; [Tooltip("If this is checked then the `Object To Affect` will have visible renderers when the `Object To Monitor` is touched. If it's unchecked then it will have it's renderers disabled on touch.")] public bool rendererVisibleOnTouch = true; [Tooltip("The amount of time to wait before the touch appearance settings are applied after the touch event.")] public float touchAppearanceDelay = 0f; [Tooltip("The amount of time to wait before the previous appearance settings are applied after the untouch event.")] public float untouchAppearanceDelay = 0f; [Tooltip("Determines what type of interacting object will affect the appearance of the `Object To Affect` after the touch/untouch event.")] public ValidInteractingObject validTouchInteractingObject = ValidInteractingObject.Anything; [Header("Grab Appearance Settings")] [Tooltip("If this is checked then the `Object To Affect` will be an active GameObject when the `Object To Monitor` is grabbed. If it's unchecked then it will be disabled on grab.")] public bool gameObjectActiveOnGrab = true; [Tooltip("If this is checked then the `Object To Affect` will have visible renderers when the `Object To Monitor` is grabbed. If it's unchecked then it will have it's renderers disabled on grab.")] public bool rendererVisibleOnGrab = true; [Tooltip("The amount of time to wait before the grab appearance settings are applied after the grab event.")] public float grabAppearanceDelay = 0f; [Tooltip("The amount of time to wait before the previous appearance settings are applied after the ungrab event.")] public float ungrabAppearanceDelay = 0f; [Tooltip("Determines what type of interacting object will affect the appearance of the `Object To Affect` after the grab/ungrab event.")] public ValidInteractingObject validGrabInteractingObject = ValidInteractingObject.Anything; [Header("Use Appearance Settings")] [Tooltip("If this is checked then the `Object To Affect` will be an active GameObject when the `Object To Monitor` is used. If it's unchecked then it will be disabled on use.")] public bool gameObjectActiveOnUse = true; [Tooltip("If this is checked then the `Object To Affect` will have visible renderers when the `Object To Monitor` is used. If it's unchecked then it will have it's renderers disabled on use.")] public bool rendererVisibleOnUse = true; [Tooltip("The amount of time to wait before the use appearance settings are applied after the use event.")] public float useAppearanceDelay = 0f; [Tooltip("The amount of time to wait before the previous appearance settings are applied after the unuse event.")] public float unuseAppearanceDelay = 0f; [Tooltip("Determines what type of interacting object will affect the appearance of the `Object To Affect` after the use/unuse event.")] public ValidInteractingObject validUseInteractingObject = ValidInteractingObject.Anything; /// /// Emitted when the GameObject on the `Object To Affect` is enabled. /// public event InteractObjectAppearanceEventHandler GameObjectEnabled; /// /// Emitted when the GameObject on the `Object To Affect` is disabled. /// public event InteractObjectAppearanceEventHandler GameObjectDisabled; /// /// Emitted when the Renderers on the `Object To Affect` are enabled. /// public event InteractObjectAppearanceEventHandler RenderersEnabled; /// /// Emitted when the Renderers on the `Object To Affect` are disabled. /// public event InteractObjectAppearanceEventHandler RenderersDisabled; protected Dictionary currentRenderStates = new Dictionary(); protected Dictionary currentGameObjectStates = new Dictionary(); protected Dictionary affectingRoutines = new Dictionary(); protected HashSet nearTouchingObjects = new HashSet(); protected HashSet touchingObjects = new HashSet(); public virtual void OnGameObjectEnabled(InteractObjectAppearanceEventArgs e) { if (GameObjectEnabled != null) { GameObjectEnabled(this, e); } } public virtual void OnGameObjectDisabled(InteractObjectAppearanceEventArgs e) { if (GameObjectDisabled != null) { GameObjectDisabled(this, e); } } public virtual void OnRenderersEnabled(InteractObjectAppearanceEventArgs e) { if (RenderersEnabled != null) { RenderersEnabled(this, e); } } public virtual void OnRenderersDisabled(InteractObjectAppearanceEventArgs e) { if (RenderersDisabled != null) { RenderersDisabled(this, e); } } protected virtual void OnEnable() { currentRenderStates.Clear(); currentGameObjectStates.Clear(); affectingRoutines.Clear(); nearTouchingObjects.Clear(); touchingObjects.Clear(); EnableListeners(); if (objectToAffect != null) { ToggleState(objectToAffect, gameObjectActiveByDefault, rendererVisibleByDefault, VRTK_InteractableObject.InteractionType.None); } } protected virtual void OnDisable() { DisableListeners(); CancelRoutines(); } protected override bool SetupListeners(bool throwError) { objectToMonitor = (objectToMonitor == null ? GetComponentInParent() : objectToMonitor); if (objectToMonitor != null) { objectToMonitor.InteractableObjectDisabled += InteractableObjectDisabled; objectToMonitor.SubscribeToInteractionEvent(VRTK_InteractableObject.InteractionType.NearTouch, InteractableObjectNearTouched); objectToMonitor.SubscribeToInteractionEvent(VRTK_InteractableObject.InteractionType.NearUntouch, InteractableObjectNearUntouched); objectToMonitor.SubscribeToInteractionEvent(VRTK_InteractableObject.InteractionType.Touch, InteractableObjectTouched); objectToMonitor.SubscribeToInteractionEvent(VRTK_InteractableObject.InteractionType.Untouch, InteractableObjectUntouched); objectToMonitor.SubscribeToInteractionEvent(VRTK_InteractableObject.InteractionType.Grab, InteractableObjectGrabbed); objectToMonitor.SubscribeToInteractionEvent(VRTK_InteractableObject.InteractionType.Ungrab, InteractableObjectUngrabbed); objectToMonitor.SubscribeToInteractionEvent(VRTK_InteractableObject.InteractionType.Use, InteractableObjectUsed); objectToMonitor.SubscribeToInteractionEvent(VRTK_InteractableObject.InteractionType.Unuse, InteractableObjectUnused); return true; } else if (throwError) { VRTK_Logger.Error(VRTK_Logger.GetCommonMessage(VRTK_Logger.CommonMessageKeys.REQUIRED_COMPONENT_MISSING_NOT_INJECTED, "VRTK_InteractObjectAppearance", "VRTK_InteractableObject", "objectToMonitor", "current or parent")); } return false; } protected override void TearDownListeners() { if (objectToMonitor != null) { RestoreDefaults(); objectToMonitor.InteractableObjectDisabled -= InteractableObjectDisabled; objectToMonitor.UnsubscribeFromInteractionEvent(VRTK_InteractableObject.InteractionType.Touch, InteractableObjectTouched); objectToMonitor.UnsubscribeFromInteractionEvent(VRTK_InteractableObject.InteractionType.Untouch, InteractableObjectUntouched); objectToMonitor.UnsubscribeFromInteractionEvent(VRTK_InteractableObject.InteractionType.Grab, InteractableObjectGrabbed); objectToMonitor.UnsubscribeFromInteractionEvent(VRTK_InteractableObject.InteractionType.Ungrab, InteractableObjectUngrabbed); objectToMonitor.UnsubscribeFromInteractionEvent(VRTK_InteractableObject.InteractionType.Use, InteractableObjectUsed); objectToMonitor.UnsubscribeFromInteractionEvent(VRTK_InteractableObject.InteractionType.Unuse, InteractableObjectUnused); } } protected virtual InteractObjectAppearanceEventArgs SetPayload(GameObject affectingObject, VRTK_InteractableObject.InteractionType interactionType) { InteractObjectAppearanceEventArgs e; e.affectingObject = affectingObject; e.monitoringObject = objectToMonitor; e.objectToIgnore = ObjectToIgnore(); e.interactionType = interactionType; return e; } protected virtual void RestoreDefaults() { if (objectToMonitor != null && objectToMonitor.IsTouched()) { foreach (GameObject touchingObject in new HashSet(touchingObjects)) { ToggleState(touchingObject, gameObjectActiveByDefault, rendererVisibleByDefault, VRTK_InteractableObject.InteractionType.None); } foreach (GameObject nearTouchingObject in new HashSet(nearTouchingObjects)) { ToggleState(nearTouchingObject, gameObjectActiveByDefault, rendererVisibleByDefault, VRTK_InteractableObject.InteractionType.None); } } } protected virtual GameObject ObjectToIgnore() { return (objectToAffect == null ? objectToMonitor.gameObject : null); } protected virtual void EmitRenderEvent(GameObject objectToToggle, bool rendererShow, VRTK_InteractableObject.InteractionType interactionType) { if (rendererShow) { OnRenderersEnabled(SetPayload(objectToToggle, interactionType)); } else { OnRenderersDisabled(SetPayload(objectToToggle, interactionType)); } } protected virtual void EmitGameObjectEvent(GameObject objectToToggle, bool gameObjectShow, VRTK_InteractableObject.InteractionType interactionType) { if (gameObjectShow) { OnGameObjectEnabled(SetPayload(objectToToggle, interactionType)); } else { OnGameObjectDisabled(SetPayload(objectToToggle, interactionType)); } } protected virtual void ToggleState(GameObject objectToToggle, bool gameObjectShow, bool rendererShow, VRTK_InteractableObject.InteractionType interactionType) { if (objectToToggle != null) { if (!currentRenderStates.ContainsKey(objectToToggle) || currentRenderStates[objectToToggle] != rendererShow) { VRTK_ObjectAppearance.ToggleRenderer(rendererShow, objectToToggle, ObjectToIgnore()); EmitRenderEvent(objectToToggle, rendererShow, interactionType); } if (!currentGameObjectStates.ContainsKey(objectToToggle) || currentGameObjectStates[objectToToggle] != gameObjectShow) { objectToToggle.SetActive(gameObjectShow); EmitGameObjectEvent(objectToToggle, gameObjectShow, interactionType); } VRTK_SharedMethods.AddDictionaryValue(currentRenderStates, objectToToggle, rendererShow, true); VRTK_SharedMethods.AddDictionaryValue(currentGameObjectStates, objectToToggle, gameObjectShow, true); } } protected virtual IEnumerator ToggleStateAfterTime(GameObject objectToToggle, bool gameObjectShow, bool rendererShow, float delayTime, VRTK_InteractableObject.InteractionType interactionType) { yield return new WaitForSeconds(delayTime); ToggleState(objectToToggle, gameObjectShow, rendererShow, interactionType); } protected virtual void CancelRoutines(GameObject currentAffectingObject = null) { if (currentAffectingObject != null) { Coroutine currentAffectingRoutine = VRTK_SharedMethods.GetDictionaryValue(affectingRoutines, currentAffectingObject); if (currentAffectingRoutine != null) { StopCoroutine(currentAffectingRoutine); } } else { foreach (KeyValuePair affectingRouting in affectingRoutines) { if (currentAffectingObject == affectingRouting.Key && affectingRouting.Value != null) { StopCoroutine(affectingRouting.Value); } } } } protected virtual GameObject GetActualController(GameObject givenObject) { VRTK_ControllerReference controllerReference = VRTK_ControllerReference.GetControllerReference(givenObject); if (VRTK_ControllerReference.IsValid(controllerReference)) { return controllerReference.actual; } else { return givenObject; } } protected virtual void InteractableObjectDisabled(object sender, InteractableObjectEventArgs e) { if (objectToMonitor != null && !objectToMonitor.gameObject.activeInHierarchy) { RestoreDefaults(); } } protected virtual bool IsValidInteractingObject(GameObject givenObject, ValidInteractingObject givenInteractingObjectValidType) { if (gameObject.activeInHierarchy) { VRTK_ControllerReference controllerReference = VRTK_ControllerReference.GetControllerReference(givenObject); switch (givenInteractingObjectValidType) { case ValidInteractingObject.Anything: return true; case ValidInteractingObject.EitherController: return VRTK_ControllerReference.IsValid(controllerReference); case ValidInteractingObject.NeitherController: return !VRTK_ControllerReference.IsValid(controllerReference); case ValidInteractingObject.LeftControllerOnly: return (VRTK_ControllerReference.IsValid(controllerReference) && controllerReference.hand == SDK_BaseController.ControllerHand.Left); case ValidInteractingObject.RightControllerOnly: return (VRTK_ControllerReference.IsValid(controllerReference) && controllerReference.hand == SDK_BaseController.ControllerHand.Right); } } return false; } protected virtual void InteractableObjectNearTouched(object sender, InteractableObjectEventArgs e) { if (IsValidInteractingObject(e.interactingObject, validNearTouchInteractingObject)) { GameObject nearTouchAffectedObject = (objectToAffect == null ? GetActualController(e.interactingObject) : objectToAffect); CancelRoutines(nearTouchAffectedObject); nearTouchingObjects.Add(nearTouchAffectedObject); VRTK_SharedMethods.AddDictionaryValue(affectingRoutines, nearTouchAffectedObject, StartCoroutine(ToggleStateAfterTime(nearTouchAffectedObject, gameObjectActiveOnNearTouch, rendererVisibleOnNearTouch, nearTouchAppearanceDelay, VRTK_InteractableObject.InteractionType.NearTouch)), true); } } protected virtual void InteractableObjectNearUntouched(object sender, InteractableObjectEventArgs e) { if (IsValidInteractingObject(e.interactingObject, validNearTouchInteractingObject)) { GameObject nearTouchAffectedObject = (objectToAffect == null ? GetActualController(e.interactingObject) : objectToAffect); CancelRoutines(nearTouchAffectedObject); nearTouchingObjects.Remove(nearTouchAffectedObject); VRTK_SharedMethods.AddDictionaryValue(affectingRoutines, nearTouchAffectedObject, StartCoroutine(ToggleStateAfterTime(nearTouchAffectedObject, gameObjectActiveByDefault, rendererVisibleByDefault, nearUntouchAppearanceDelay, VRTK_InteractableObject.InteractionType.NearUntouch)), true); } } protected virtual void InteractableObjectTouched(object sender, InteractableObjectEventArgs e) { if (IsValidInteractingObject(e.interactingObject, validTouchInteractingObject)) { GameObject touchAffectedObject = (objectToAffect == null ? GetActualController(e.interactingObject) : objectToAffect); CancelRoutines(touchAffectedObject); touchingObjects.Add(touchAffectedObject); VRTK_SharedMethods.AddDictionaryValue(affectingRoutines, touchAffectedObject, StartCoroutine(ToggleStateAfterTime(touchAffectedObject, gameObjectActiveOnTouch, rendererVisibleOnTouch, touchAppearanceDelay, VRTK_InteractableObject.InteractionType.Touch)), true); } } protected virtual void InteractableObjectUntouched(object sender, InteractableObjectEventArgs e) { if (IsValidInteractingObject(e.interactingObject, validTouchInteractingObject)) { GameObject touchAffectedObject = (objectToAffect == null ? GetActualController(e.interactingObject) : objectToAffect); CancelRoutines(touchAffectedObject); touchingObjects.Remove(touchAffectedObject); if (objectToMonitor.IsNearTouched()) { VRTK_SharedMethods.AddDictionaryValue(affectingRoutines, touchAffectedObject, StartCoroutine(ToggleStateAfterTime(touchAffectedObject, gameObjectActiveOnNearTouch, rendererVisibleOnNearTouch, untouchAppearanceDelay, VRTK_InteractableObject.InteractionType.NearTouch)), true); } else { VRTK_SharedMethods.AddDictionaryValue(affectingRoutines, touchAffectedObject, StartCoroutine(ToggleStateAfterTime(touchAffectedObject, gameObjectActiveByDefault, rendererVisibleByDefault, untouchAppearanceDelay, VRTK_InteractableObject.InteractionType.Untouch)), true); } } } protected virtual void InteractableObjectGrabbed(object sender, InteractableObjectEventArgs e) { if (IsValidInteractingObject(e.interactingObject, validGrabInteractingObject)) { GameObject grabAffectedObject = (objectToAffect == null ? GetActualController(e.interactingObject) : objectToAffect); CancelRoutines(grabAffectedObject); VRTK_SharedMethods.AddDictionaryValue(affectingRoutines, grabAffectedObject, StartCoroutine(ToggleStateAfterTime(grabAffectedObject, gameObjectActiveOnGrab, rendererVisibleOnGrab, grabAppearanceDelay, VRTK_InteractableObject.InteractionType.Grab)), true); } } protected virtual void InteractableObjectUngrabbed(object sender, InteractableObjectEventArgs e) { if (IsValidInteractingObject(e.interactingObject, validGrabInteractingObject)) { GameObject grabAffectedObject = (objectToAffect == null ? GetActualController(e.interactingObject) : objectToAffect); CancelRoutines(grabAffectedObject); if (objectToMonitor.IsUsing()) { VRTK_SharedMethods.AddDictionaryValue(affectingRoutines, grabAffectedObject, StartCoroutine(ToggleStateAfterTime(grabAffectedObject, gameObjectActiveOnUse, rendererVisibleOnUse, ungrabAppearanceDelay, VRTK_InteractableObject.InteractionType.Ungrab)), true); } else if (objectToMonitor.IsTouched()) { VRTK_SharedMethods.AddDictionaryValue(affectingRoutines, grabAffectedObject, StartCoroutine(ToggleStateAfterTime(grabAffectedObject, gameObjectActiveOnTouch, rendererVisibleOnTouch, ungrabAppearanceDelay, VRTK_InteractableObject.InteractionType.Ungrab)), true); } else if (objectToMonitor.IsNearTouched()) { VRTK_SharedMethods.AddDictionaryValue(affectingRoutines, grabAffectedObject, StartCoroutine(ToggleStateAfterTime(grabAffectedObject, gameObjectActiveOnNearTouch, rendererVisibleOnNearTouch, ungrabAppearanceDelay, VRTK_InteractableObject.InteractionType.NearTouch)), true); } else { VRTK_SharedMethods.AddDictionaryValue(affectingRoutines, grabAffectedObject, StartCoroutine(ToggleStateAfterTime(grabAffectedObject, gameObjectActiveByDefault, rendererVisibleByDefault, ungrabAppearanceDelay, VRTK_InteractableObject.InteractionType.Ungrab)), true); } } } protected virtual void InteractableObjectUsed(object sender, InteractableObjectEventArgs e) { if (IsValidInteractingObject(e.interactingObject, validUseInteractingObject)) { GameObject useAffectedObject = (objectToAffect == null ? GetActualController(e.interactingObject) : objectToAffect); CancelRoutines(useAffectedObject); VRTK_SharedMethods.AddDictionaryValue(affectingRoutines, useAffectedObject, StartCoroutine(ToggleStateAfterTime(useAffectedObject, gameObjectActiveOnUse, rendererVisibleOnUse, useAppearanceDelay, VRTK_InteractableObject.InteractionType.Use)), true); } } protected virtual void InteractableObjectUnused(object sender, InteractableObjectEventArgs e) { if (IsValidInteractingObject(e.interactingObject, validUseInteractingObject)) { GameObject useAffectedObject = (objectToAffect == null ? GetActualController(e.interactingObject) : objectToAffect); CancelRoutines(useAffectedObject); if (objectToMonitor.IsGrabbed()) { VRTK_SharedMethods.AddDictionaryValue(affectingRoutines, useAffectedObject, StartCoroutine(ToggleStateAfterTime(useAffectedObject, gameObjectActiveOnGrab, rendererVisibleOnGrab, unuseAppearanceDelay, VRTK_InteractableObject.InteractionType.Unuse)), true); } else if (objectToMonitor.IsTouched()) { VRTK_SharedMethods.AddDictionaryValue(affectingRoutines, useAffectedObject, StartCoroutine(ToggleStateAfterTime(useAffectedObject, gameObjectActiveOnTouch, rendererVisibleOnTouch, unuseAppearanceDelay, VRTK_InteractableObject.InteractionType.Unuse)), true); } else if (objectToMonitor.IsNearTouched()) { VRTK_SharedMethods.AddDictionaryValue(affectingRoutines, useAffectedObject, StartCoroutine(ToggleStateAfterTime(useAffectedObject, gameObjectActiveOnNearTouch, rendererVisibleOnNearTouch, unuseAppearanceDelay, VRTK_InteractableObject.InteractionType.NearTouch)), true); } else { VRTK_SharedMethods.AddDictionaryValue(affectingRoutines, useAffectedObject, StartCoroutine(ToggleStateAfterTime(useAffectedObject, gameObjectActiveByDefault, rendererVisibleByDefault, unuseAppearanceDelay, VRTK_InteractableObject.InteractionType.Unuse)), true); } } } } }