using System; using UnityEngine; using UnityEngine.Events; using Logger = ARLocation.Utils.Logger; namespace ARLocation { [AddComponentMenu("AR+GPS/Hotspot")] [HelpURL("https://http://docs.unity-ar-gps-location.com/guide/#hotspot")] [DisallowMultipleComponent] public class Hotspot : MonoBehaviour { [Serializable] public class OnHotspotActivatedUnityEvent : UnityEvent { } [Serializable] public class OnHotspotActivatedWithIdUnityEvent : UnityEvent { } [Serializable] public class OnHotspotLeaveUnityEvent : UnityEvent { } [Serializable] public class OnHotspotLeaveWithIdUnityEvent : UnityEvent { } [Serializable] public enum PositionModes { HotspotCenter, CameraPosition }; [Serializable] public class HotspotSettingsData { [Tooltip("The prefab/GameObject that will be instantiated by the Hotspot.")] public GameObject Prefab; [Tooltip("The positioning mode. 'HotspotCenter' means the object will be instantiated at the Hotpot's center geo-location. 'CameraPosition' means it will be positioned at the front of the camera.")] public PositionModes PositionMode; [Tooltip("The distance from the center that the user must be located to activate the Hotspot.")] public float ActivationRadius = 4.0f; [Tooltip("If true, align the instantiated object to face the camera (horizontally).")] public bool AlignToCamera = true; [Tooltip("If 'PositionMode' is set to 'CameraPosition', how far from the camera the instantiated object should be placed.")] public float DistanceFromCamera = 3.0f; [Tooltip("If true, use raw GPS data, instead of the filtered GPS data.")] public bool UseRawLocation = true; [Tooltip("If true, deactivate the hotspot when leaving the radius after entering it. After deactivattion, the hotspot will be available for activation againg when re-entering the radius.")] public bool DeactivateOnLeave = false; [Tooltip("An optional integer ID which is passed to the \"On Hotspot Activated With Id\" and \"On Hotspot Leave With Id\" events.")] [HideInInspector] public int Id = 0; } [Serializable] public class StateData { public bool Activated; public GameObject Instance; public Location Location; public bool EmittedLeaveHotspotEvent; } public PlaceAtLocation.LocationSettingsData LocationSettings = new PlaceAtLocation.LocationSettingsData(); public HotspotSettingsData HotspotSettings = new HotspotSettingsData(); [Space(4.0f)] [Header("Debug")] [Tooltip("When debug mode is enabled, this component will print relevant messages to the console. Filter by 'Hotspot' in the log output to see the messages.")] public bool DebugMode; [Space(4.0f)] [Header("Events")] [Tooltip("If true, monitor distance to emit the 'OnLeaveHotspot' event. If you don't need it, keep this disabled for better performance.")] public bool UseOnLeaveHotspotEvent; [Tooltip("Event for the Hotspot is activated.")] public OnHotspotActivatedUnityEvent OnHotspotActivated = new OnHotspotActivatedUnityEvent(); [Tooltip("This event will be emited only once, when the user leaves the hotspot area after it is activated.")] public OnHotspotLeaveUnityEvent OnHotspotLeave = new OnHotspotLeaveUnityEvent(); [Tooltip("This event will be emited only once, when the user leaves the hotspot area after it is activated.")] [HideInInspector] public OnHotspotLeaveWithIdUnityEvent OnHotspotLeaveWithId = new OnHotspotLeaveWithIdUnityEvent(); [Tooltip("Event for the Hotspot is activated.")] [HideInInspector] public OnHotspotActivatedWithIdUnityEvent OnHotspotActivatedWithId = new OnHotspotActivatedWithIdUnityEvent(); private readonly StateData state = new StateData(); private Transform root; private Camera arCamera; private double currentDistance; // ReSharper disable once UnusedMember.Global public GameObject Instance => state.Instance; // ReSharper disable once MemberCanBePrivate.Global public Location Location { // ReSharper disable once UnusedMember.Global get => state.Location; set => state.Location = value; } /// /// /// Returns the Hotspot's current position, both before and after /// activation. Before activation it will return a Horizontal position, /// i.e., without altiture (that is, with y = 0). /// /// public Vector3 Position { get { if (state.Activated) { return state.Instance.transform.position; } else { var arLocMngr = ARLocationManager.Instance; var arLocPrvdr = ARLocationProvider.Instance; var pos = Location.GetGameObjectPositionForLocation( arLocMngr.gameObject.transform, arLocMngr.Camera.transform.position, arLocPrvdr.CurrentLocation.ToLocation(), state.Location, true ); pos.y = 0; return pos; } } } /// /// Returns the current user distance to the Hotspot center. /// public float CurrentDistance => (float)currentDistance; void Start() { var arLocationProvider = ARLocationProvider.Instance; if (HotspotSettings.UseRawLocation) { arLocationProvider.Provider.LocationUpdatedRaw += Provider_LocationUpdated; } else { arLocationProvider.Provider.LocationUpdated += Provider_LocationUpdated; } root = ARLocationManager.Instance.gameObject.transform; if (state.Location == null) { state.Location = LocationSettings.GetLocation(); } arCamera = ARLocationManager.Instance.MainCamera; if (arLocationProvider.IsEnabled) { Provider_LocationUpdated(arLocationProvider.CurrentLocation, arLocationProvider.LastLocation); } } public void OnDisable() { var arLocationProvider = ARLocationProvider.Instance; if (!arLocationProvider) return; if (HotspotSettings.UseRawLocation) { arLocationProvider.Provider.LocationUpdatedRaw -= Provider_LocationUpdated; } else { arLocationProvider.Provider.LocationUpdated -= Provider_LocationUpdated; } } public void Restart() { Destroy(state.Instance); state.Instance = null; state.Activated = false; state.EmittedLeaveHotspotEvent = false; } private void Provider_LocationUpdated(LocationReading currentLocation, LocationReading lastLocation) { if (state.Activated) return; Logger.LogFromMethod("Hotspot", "LocationUpdatedRaw", $"({gameObject.name}): New device location {currentLocation}", DebugMode); currentDistance = Location.HorizontalDistance(currentLocation.ToLocation(), state.Location); var delta = Location.HorizontalVectorFromTo(currentLocation.ToLocation(), state.Location); if (currentDistance <= HotspotSettings.ActivationRadius) { ActivateHotspot(new Vector3((float)delta.x, 0, (float)delta.y)); } else { Logger.LogFromMethod("Hotspot", "LocationUpdatedRaw", $"({gameObject.name}): No activation - distance = {currentDistance}", DebugMode); } } private void ActivateHotspot(Vector3 delta) { Logger.LogFromMethod("Hotspot", "ActivateHotspot", $"({gameObject.name}): Activating hotspot...", DebugMode); if (HotspotSettings.Prefab == null) return; state.Instance = Instantiate(HotspotSettings.Prefab, HotspotSettings.AlignToCamera ? gameObject.transform : root); switch (HotspotSettings.PositionMode) { case PositionModes.HotspotCenter: state.Instance.transform.position = arCamera.transform.position + delta; break; case PositionModes.CameraPosition: var transform1 = arCamera.transform; var forward = transform1.forward; forward.y = 0; state.Instance.transform.position = transform1.position + forward * HotspotSettings.DistanceFromCamera; break; } if (HotspotSettings.AlignToCamera) { state.Instance.transform.LookAt(arCamera.transform); } state.Instance.AddComponent(); state.Instance.name = name + " (Hotspot)"; state.Activated = true; Logger.LogFromMethod("Hotspot", "ActivateHotspot", $"({name}): Hotspot activated", DebugMode); OnHotspotActivated?.Invoke(state.Instance); OnHotspotActivatedWithId?.Invoke(state.Instance, HotspotSettings.Id); } public static Hotspot AddHotspotComponent(GameObject go, Location location, HotspotSettingsData settings) { var hotspot = go.AddComponent(); hotspot.Location = location.Clone(); hotspot.HotspotSettings = settings; return hotspot; } public static Hotspot AddHotspotComponent(GameObject go, Location location, HotspotSettingsData settings, int id = 0) { settings.Id = id; return AddHotspotComponent(go, location, settings); } public static GameObject CreateHotspotGameObject(Location location, HotspotSettingsData settings, string name = "GPS Hotspot") { var go = new GameObject(name); AddHotspotComponent(go, location, settings); return go; } public static GameObject CreateHotspotGameObject(Location location, HotspotSettingsData settings, string name = "GPS Hotspot", int id = 0) { settings.Id = id; return CreateHotspotGameObject(location, settings, name); } void Update() { if (UseOnLeaveHotspotEvent && state.Activated && !state.EmittedLeaveHotspotEvent) { var distance = Vector3.Distance(arCamera.transform.position, state.Instance.transform.position); if (distance >= HotspotSettings.ActivationRadius) { OnHotspotLeave?.Invoke(state.Instance); OnHotspotLeaveWithId?.Invoke(state.Instance, HotspotSettings.Id); state.EmittedLeaveHotspotEvent = true; if (HotspotSettings.DeactivateOnLeave) { Restart(); } } } } } }