// // ©2015 - 2025 Candy Smith // // All rights reserved // // Redistribution of this software is strictly not allowed. // // Copy of this software can be obtained from unity asset store only. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // // FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE // // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // // THE SOFTWARE. using System; using System.Collections; using UnityEngine; using UnityEngine.InputSystem.LowLevel; using UnityEngine.InputSystem.UI; using UnityEngine.UI; namespace WordsToolkit.Scripts.Gameplay { public class VirtualMouse : MonoBehaviour { private VirtualMouseInput virtualMouseInput; private bool isInitialized = false; [SerializeField] private Canvas canvas; private RectTransform canvasRectTransform; private Camera uiCamera; [SerializeField] private float cursorHideDelay = 1.0f; // Time in seconds before hiding cursor [SerializeField] private float fadeSpeed = 3.0f; // How fast the cursor fades in/out [SerializeField] private float minCursorAlpha = 0f; // Minimum alpha when hidden [SerializeField] private float maxCursorAlpha = .5f; // Maximum alpha when visible private float idleTimer = 0f; private Vector2 lastPosition; public Image cursorImage; // Reference to cursor image private float targetAlpha; private float currentAlpha; private void Awake() { virtualMouseInput = GetComponent(); if (virtualMouseInput == null) { Debug.LogError("VirtualMouseInput component not found! Please add it to the same GameObject."); return; } canvasRectTransform = canvas.GetComponent(); if (canvas.renderMode == RenderMode.ScreenSpaceCamera || canvas.renderMode == RenderMode.WorldSpace) { uiCamera = canvas.worldCamera; } targetAlpha = maxCursorAlpha; currentAlpha = maxCursorAlpha; } private void Start() { StartCoroutine(WaitForVirtualMouseInitialization()); } private IEnumerator WaitForVirtualMouseInitialization() { float timeout = 2.0f; float timer = 0f; while (timer < timeout) { if (virtualMouseInput != null && virtualMouseInput.virtualMouse != null) { lastPosition = virtualMouseInput.virtualMouse.position.value; isInitialized = true; Debug.Log("Virtual mouse initialized successfully."); yield break; } timer += 0.1f; yield return new WaitForSeconds(0.1f); } Debug.LogError("Failed to initialize virtualMouse within timeout period."); } private void Update() { if (!isInitialized || virtualMouseInput == null || virtualMouseInput.virtualMouse == null) { return; // Skip if not initialized } transform.SetAsLastSibling(); if (canvas.renderMode == RenderMode.ScreenSpaceOverlay || canvas.renderMode == RenderMode.ScreenSpaceCamera) { transform.localScale = new Vector3(1f / canvas.scaleFactor, 1f / canvas.scaleFactor, 1f); } // Check if cursor moved - with null checks try { Vector2 currentPosition = virtualMouseInput.virtualMouse.position.value; if (Vector2.Distance(currentPosition, lastPosition) > 0.5f) // Small threshold to detect real movement { idleTimer = 0f; targetAlpha = maxCursorAlpha; // Set target to maximum alpha rather than 1.0 lastPosition = currentPosition; } else { // No movement, increment timer idleTimer += Time.deltaTime; // Start fading cursor after delay if (idleTimer >= cursorHideDelay) { targetAlpha = minCursorAlpha; // Set target to minimum alpha } } // Smoothly fade the cursor if (cursorImage != null) { // Gradually interpolate current alpha toward target alpha currentAlpha = Mathf.Lerp(currentAlpha, targetAlpha, Time.deltaTime * fadeSpeed); // Apply the alpha to the cursor image Color cursorColor = cursorImage.color; cursorColor.a = currentAlpha; cursorImage.color = cursorColor; } } catch (Exception e) { Debug.LogWarning("Error accessing virtual mouse position: " + e.Message); } } private void LateUpdate() { if (!isInitialized || virtualMouseInput == null || virtualMouseInput.virtualMouse == null) { return; // Skip if not initialized } try { // Get the current virtual mouse position var virtualMousePosition = virtualMouseInput.virtualMouse.position.value; // Clamp to screen boundaries virtualMousePosition.x = Mathf.Clamp(virtualMousePosition.x, 0, Screen.width); virtualMousePosition.y = Mathf.Clamp(virtualMousePosition.y, 0, Screen.height); if (canvas != null) { if (canvas.renderMode == RenderMode.ScreenSpaceOverlay) { float scaleFactor = canvas.scaleFactor; Rect canvasRect = canvasRectTransform.rect; float canvasWidth = canvasRect.width * scaleFactor; float canvasHeight = canvasRect.height * scaleFactor; float xOffset = (Screen.width - canvasWidth) / 2; float yOffset = (Screen.height - canvasHeight) / 2; virtualMousePosition.x = Mathf.Clamp(virtualMousePosition.x, xOffset, xOffset + canvasWidth); virtualMousePosition.y = Mathf.Clamp(virtualMousePosition.y, yOffset, yOffset + canvasHeight); } else if (canvas.renderMode == RenderMode.ScreenSpaceCamera && uiCamera != null) { Vector3[] corners = new Vector3[4]; canvasRectTransform.GetWorldCorners(corners); Vector2 min = new Vector2(float.MaxValue, float.MaxValue); Vector2 max = new Vector2(float.MinValue, float.MinValue); for (int i = 0; i < 4; i++) { Vector2 screenPos = RectTransformUtility.WorldToScreenPoint(uiCamera, corners[i]); min.x = Mathf.Min(min.x, screenPos.x); min.y = Mathf.Min(min.y, screenPos.y); max.x = Mathf.Max(max.x, screenPos.x); max.y = Mathf.Max(max.y, screenPos.y); } virtualMousePosition.x = Mathf.Clamp(virtualMousePosition.x, min.x, max.x); virtualMousePosition.y = Mathf.Clamp(virtualMousePosition.y, min.y, max.y); } } InputState.Change(virtualMouseInput.virtualMouse.position, virtualMousePosition); } catch (Exception e) { Debug.LogWarning("Error in virtual mouse LateUpdate: " + e.Message); } } } }