/* ******************************************************************************** *Copyright(C),coolae.net *Author: chenbin *Version: 2.0 *Date: 2017-01-09 *Description: 在NGUI的uicamera的基础上作了修改,这样支持在3d场景中单独控制操作控制 *Others: *History: ********************************************************************************* */ using UnityEngine; using System.Collections.Generic; #if UNITY_EDITOR using UnityEditor; #endif /// /// This script should be attached to each camera that's used to draw the objects with /// UI components on them. This may mean only one camera (main camera or your UI camera), /// or multiple cameras if you happen to have multiple viewports. Failing to attach this /// script simply means that objects drawn by this camera won't receive UI notifications: /// /// * OnHover (isOver) is sent when the mouse hovers over a collider or moves away. /// * OnPress (isDown) is sent when a mouse button gets pressed on the collider. /// * OnSelect (selected) is sent when a mouse button is first pressed on an object. Repeated presses won't result in an OnSelect(true). /// * OnClick () is sent when a mouse is pressed and released on the same object. /// UICamera.currentTouchID tells you which button was clicked. /// * OnDoubleClick () is sent when the click happens twice within a fourth of a second. /// UICamera.currentTouchID tells you which button was clicked. /// /// * OnDragStart () is sent to a game object under the touch just before the OnDrag() notifications begin. /// * OnDrag (delta) is sent to an object that's being dragged. /// * OnDragOver (draggedObject) is sent to a game object when another object is dragged over its area. /// * OnDragOut (draggedObject) is sent to a game object when another object is dragged out of its area. /// * OnDragEnd () is sent to a dragged object when the drag event finishes. /// /// * OnTooltip (show) is sent when the mouse hovers over a collider for some time without moving. /// * OnScroll (float delta) is sent out when the mouse scroll wheel is moved. /// * OnKey (KeyCode key) is sent when keyboard or controller input is used. /// namespace Coolape { [RequireComponent (typeof(Camera))] public class MyMainCamera : MonoBehaviour { /// /// The isbe controled.是否是被ui控制,需要和CLUIDrag4World接合起来用 /// public bool isbeControled = false; //add by chenbin public enum ControlScheme { Mouse, Touch, Controller, } /// /// Whether the touch event will be sending out the OnClick notification at the end. /// public enum ClickNotification { None, Always, BasedOnDelta, } /// /// Ambiguous mouse, touch, or controller event. /// public class MouseOrTouch { public Vector2 pos; // Current position of the mouse or touch event public Vector2 lastPos; // Previous position of the mouse or touch event public Vector2 delta; // Delta since last update public Vector2 totalDelta; // Delta since the event started being tracked public Camera pressedCam; // Camera that the OnPress(true) was fired with public GameObject last; // Last object under the touch or mouse public GameObject current; // Current game object under the touch or mouse public GameObject pressed; // Last game object to receive OnPress public GameObject dragged; // Game object that's being dragged public float pressTime = 0f; // When the touch event started public float clickTime = 0f; // The last time a click event was sent out public ClickNotification clickNotification = ClickNotification.Always; public bool touchBegan = true; public bool pressStarted = false; public bool dragStarted = false; /// /// Delta time since the touch operation started. /// public float deltaTime { get { return RealTime.time - pressTime; } } /// /// Returns whether this touch is currently over a UI element. /// public bool isOverUI { get { return current != null && current != fallThrough && NGUITools.FindInParents (current) != null; } } } /// /// Camera type controls how raycasts are handled by the UICamera. /// public enum EventType : int { World_3D, // Perform a Physics.Raycast and sort by distance to the point that was hit. UI_3D, // Perform a Physics.Raycast and sort by widget depth. World_2D, // Perform a Physics2D.OverlapPoint UI_2D, // Physics2D.OverlapPoint then sort by widget depth } /// /// List of all active cameras in the scene. /// static public BetterList list = new BetterList (); public delegate bool GetKeyStateFunc (KeyCode key); public delegate float GetAxisFunc (string name); /// /// GetKeyDown function -- return whether the specified key was pressed this Update(). /// static public GetKeyStateFunc GetKeyDown = Input.GetKeyDown; /// /// GetKeyDown function -- return whether the specified key was released this Update(). /// static public GetKeyStateFunc GetKeyUp = Input.GetKeyUp; /// /// GetKey function -- return whether the specified key is currently held. /// static public GetKeyStateFunc GetKey = Input.GetKey; /// /// GetAxis function -- return the state of the specified axis. /// static public GetAxisFunc GetAxis = Input.GetAxis; public delegate void OnScreenResize (); /// /// Delegate triggered when the screen size changes for any reason. /// Subscribe to it if you don't want to compare Screen.width and Screen.height each frame. /// static public OnScreenResize onScreenResize; /// /// Event type -- use "UI" for your user interfaces, and "World" for your game camera. /// This setting changes how raycasts are handled. Raycasts have to be more complicated for UI cameras. /// public EventType eventType = EventType.UI_3D; /// /// By default, events will go to rigidbodies when the Event Type is not UI. /// You can change this behaviour back to how it was pre-3.7.0 using this flag. /// public bool eventsGoToColliders = false; /// /// Which layers will receive events. /// public LayerMask eventReceiverMask = -1; /// /// If 'true', currently hovered object will be shown in the top left corner. /// public bool debug = false; /// /// Whether the mouse input is used. /// public bool useMouse = true; /// /// Whether the touch-based input is used. /// public bool useTouch = true; /// /// Whether multi-touch is allowed. /// public bool allowMultiTouch = true; /// /// Whether the keyboard events will be processed. /// public bool useKeyboard = true; /// /// Whether the joystick and controller events will be processed. /// public bool useController = true; [System.Obsolete ("Use new OnDragStart / OnDragOver / OnDragOut / OnDragEnd events instead")] public bool stickyPress { get { return true; } } /// /// Whether the tooltip will disappear as soon as the mouse moves (false) or only if the mouse moves outside of the widget's area (true). /// public bool stickyTooltip = true; /// /// How long of a delay to expect before showing the tooltip. /// public float tooltipDelay = 1f; /// /// If enabled, a tooltip will be shown after touch gets pressed on something and held for more than "tooltipDelay" seconds. /// public bool longPressTooltip = false; /// /// How much the mouse has to be moved after pressing a button before it starts to send out drag events. /// public float mouseDragThreshold = 4f; /// /// How far the mouse is allowed to move in pixels before it's no longer considered for click events, if the click notification is based on delta. /// public float mouseClickThreshold = 10f; /// /// How much the mouse has to be moved after pressing a button before it starts to send out drag events. /// public float touchDragThreshold = 40f; /// /// How far the touch is allowed to move in pixels before it's no longer considered for click events, if the click notification is based on delta. /// public float touchClickThreshold = 40f; /// /// Raycast range distance. By default it's as far as the camera can see. /// public float rangeDistance = -1f; /// /// Name of the axis used for scrolling. /// public string scrollAxisName = "Mouse ScrollWheel"; /// /// Name of the axis used to send up and down key events. /// public string verticalAxisName = "Vertical"; /// /// Name of the axis used to send left and right key events. /// public string horizontalAxisName = "Horizontal"; /// /// Simulate a right-click on OSX when the Command key is held and a left-click is used (for trackpad). /// public bool commandClick = true; /// /// Various keys used by the camera. /// public KeyCode submitKey0 = KeyCode.Return; public KeyCode submitKey1 = KeyCode.JoystickButton0; public KeyCode cancelKey0 = KeyCode.Escape; public KeyCode cancelKey1 = KeyCode.JoystickButton1; public delegate void OnCustomInput (); /// /// Custom input processing logic, if desired. For example: WP7 touches. /// Use UICamera.current to get the current camera. /// static public OnCustomInput onCustomInput; /// /// Whether tooltips will be shown or not. /// static public bool showTooltips = true; /// /// Position of the last touch (or mouse) event. /// static public Vector2 lastTouchPosition = Vector2.zero; /// /// Position of the last touch (or mouse) event in the world. /// static public Vector3 lastWorldPosition = Vector3.zero; /// /// Last raycast hit prior to sending out the event. This is useful if you want detailed information /// about what was actually hit in your OnClick, OnHover, and other event functions. /// Note that this is not going to be valid if you're using 2D colliders. /// static public RaycastHit lastHit; /// /// UICamera that sent out the event. /// static public MyMainCamera current = null; /// /// Last camera active prior to sending out the event. This will always be the camera that actually sent out the event. /// static public Camera currentCamera = null; /// /// Current control scheme. Set automatically when events arrive. /// static public ControlScheme currentScheme = ControlScheme.Controller; /// /// ID of the touch or mouse operation prior to sending out the event. Mouse ID is '-1' for left, '-2' for right mouse button, '-3' for middle. /// static public int currentTouchID = -100; /// /// Key that triggered the event, if any. /// static public KeyCode currentKey = KeyCode.None; /// /// Ray projected into the screen underneath the current touch. /// static public Ray currentRay { get { return (currentCamera != null && currentTouch != null) ? currentCamera.ScreenPointToRay (currentTouch.pos) : new Ray (); } } /// /// Current touch, set before any event function gets called. /// static public MouseOrTouch currentTouch = null; /// /// Whether an input field currently has focus. /// static public bool inputHasFocus = false; // Obsolete, kept for backwards compatibility. static GameObject mGenericHandler; /// /// If set, this game object will receive all events regardless of whether they were handled or not. /// [System.Obsolete ("Use delegates instead such as UICamera.onClick, UICamera.onHover, etc.")] static public GameObject genericEventHandler { get { return mGenericHandler; } set { mGenericHandler = value; } } /// /// If events don't get handled, they will be forwarded to this game object. /// static public GameObject fallThrough; public delegate void MoveDelegate (Vector2 delta); public delegate void VoidDelegate (GameObject go); public delegate void BoolDelegate (GameObject go, bool state); public delegate void FloatDelegate (GameObject go, float delta); public delegate void VectorDelegate (GameObject go, Vector2 delta); public delegate void ObjectDelegate (GameObject go, GameObject obj); public delegate void KeyCodeDelegate (GameObject go, KeyCode key); /// /// These notifications are sent out prior to the actual event going out. /// static public VoidDelegate onClick; static public VoidDelegate onDoubleClick; static public BoolDelegate onHover; static public BoolDelegate onPress; static public BoolDelegate onSelect; static public FloatDelegate onScroll; static public VectorDelegate onDrag; static public VoidDelegate onDragStart; static public ObjectDelegate onDragOver; static public ObjectDelegate onDragOut; static public VoidDelegate onDragEnd; static public ObjectDelegate onDrop; static public KeyCodeDelegate onKey; static public BoolDelegate onTooltip; static public MoveDelegate onMouseMove; // Selected widget (for input) static GameObject mCurrentSelection = null; // Mouse events static MouseOrTouch[] mMouse = new MouseOrTouch[] { new MouseOrTouch (), new MouseOrTouch (), new MouseOrTouch () }; // The last object to receive OnHover static GameObject mHover; // Joystick/controller/keyboard event static public MouseOrTouch controller = new MouseOrTouch (); // Used to ensure that joystick-based controls don't trigger that often static float mNextEvent = 0f; /// /// List of all the active touches. /// static public List activeTouches = new List (); // Used internally to store IDs of active touches static List mTouchIDs = new List (); // Used to detect screen dimension changes static int mWidth = 0; static int mHeight = 0; // Tooltip widget (mouse only) GameObject mTooltip = null; // Mouse input is turned off on iOS Camera mCam = null; float mTooltipTime = 0f; float mNextRaycast = 0f; /// /// Helper function that determines if this script should be handling the events. /// bool handlesEvents { get { return eventHandler == this; } } /// /// Caching is always preferable for performance. /// #if UNITY_4_3 || UNITY_4_5 || UNITY_4_6 public Camera cachedCamera { get { if (mCam == null) mCam = camera; return mCam; } } #else public Camera cachedCamera { get { if (mCam == null) mCam = GetComponent (); return mCam; } } #endif /// /// Set to 'true' just before OnDrag-related events are sent. No longer needed, but kept for backwards compatibility. /// static public bool isDragging = false; /// /// The object hit by the last Raycast that was the result of a mouse or touch event. /// static public GameObject hoveredObject; /// /// Whether the last raycast was over the UI. /// static public bool isOverUI { get { if (currentTouch != null) return currentTouch.isOverUI; if (hoveredObject == null) return false; if (hoveredObject == fallThrough) return false; return NGUITools.FindInParents (hoveredObject) != null; } } /// /// Option to manually set the selected game object. /// static public GameObject selectedObject { get { if (mCurrentSelection) return mCurrentSelection; return null; } set { if (mCurrentSelection == value) return; bool shouldRestore = false; if (currentTouch == null) { shouldRestore = true; currentTouchID = -100; currentTouch = controller; currentScheme = ControlScheme.Controller; } inputHasFocus = false; if (onSelect != null) onSelect (selectedObject, false); Notify (mCurrentSelection, "OnSelect", false); mCurrentSelection = value; if (mCurrentSelection != null) { if (shouldRestore) { MyMainCamera cam = (mCurrentSelection != null) ? FindCameraForLayer (mCurrentSelection.layer) : MyMainCamera.list [0]; if (cam != null) { current = cam; currentCamera = cam.cachedCamera; } } inputHasFocus = (mCurrentSelection.activeInHierarchy && mCurrentSelection.GetComponent () != null); if (onSelect != null) onSelect (mCurrentSelection, true); Notify (mCurrentSelection, "OnSelect", true); } if (shouldRestore) { current = null; currentCamera = null; currentTouch = null; currentTouchID = -100; } } } /// /// Returns 'true' if any of the active touch, mouse or controller is currently holding the specified object. /// static public bool IsPressed (GameObject go) { for (int i = 0; i < 3; ++i) if (mMouse [i].pressed == go) return true; for (int i = 0, imax = activeTouches.Count; i < imax; ++i) { MouseOrTouch touch = activeTouches [i]; if (touch.pressed == go) return true; } if (controller.pressed == go) return true; return false; } [System.Obsolete ("Use either 'CountInputSources()' or 'activeTouches.Count'")] static public int touchCount { get { return CountInputSources (); } } /// /// Number of active touches from all sources. /// Note that this will include the sum of touch, mouse and controller events. /// If you want only touch events, use activeTouches.Count. /// static public int CountInputSources () { int count = 0; for (int i = 0, imax = activeTouches.Count; i < imax; ++i) { MouseOrTouch touch = activeTouches [i]; if (touch.pressed != null) ++count; } for (int i = 0; i < mMouse.Length; ++i) if (mMouse [i].pressed != null) ++count; if (controller.pressed != null) ++count; return count; } /// /// Number of active drag events from all sources. /// static public int dragCount { get { int count = 0; for (int i = 0, imax = activeTouches.Count; i < imax; ++i) { MouseOrTouch touch = activeTouches [i]; if (touch.dragged != null) ++count; } for (int i = 0; i < mMouse.Length; ++i) if (mMouse [i].dragged != null) ++count; if (controller.dragged != null) ++count; return count; } } /// /// Convenience function that returns the main HUD camera. /// static public Camera mainCamera { get { MyMainCamera mouse = eventHandler; return (mouse != null) ? mouse.cachedCamera : null; } } /// /// Event handler for all types of events. /// static public MyMainCamera eventHandler { get { for (int i = 0; i < list.size; ++i) { // Invalid or inactive entry -- keep going MyMainCamera cam = list.buffer [i]; if (cam == null || !cam.enabled || !NGUITools.GetActive (cam.gameObject)) continue; return cam; } return null; } } /// /// Static comparison function used for sorting. /// static int CompareFunc (MyMainCamera a, MyMainCamera b) { if (a.cachedCamera.depth < b.cachedCamera.depth) return 1; if (a.cachedCamera.depth > b.cachedCamera.depth) return -1; return 0; } struct DepthEntry { public int depth; public RaycastHit hit; public Vector3 point; public GameObject go; } static DepthEntry mHit = new DepthEntry (); static BetterList mHits = new BetterList (); /// /// Find the rigidbody on the parent, but return 'null' if a UIPanel is found instead. /// The idea is: send events to the rigidbody in the world, but to colliders in the UI. /// static Rigidbody FindRootRigidbody (Transform trans) { while (trans != null) { if (trans.GetComponent () != null) return null; #if UNITY_4_3 || UNITY_4_5 || UNITY_4_6 Rigidbody rb = trans.rigidbody; #else Rigidbody rb = trans.GetComponent (); #endif if (rb != null) return rb; trans = trans.parent; } return null; } /// /// Find the 2D rigidbody on the parent, but return 'null' if a UIPanel is found instead. /// static Rigidbody2D FindRootRigidbody2D (Transform trans) { while (trans != null) { if (trans.GetComponent () != null) return null; #if UNITY_4_3 || UNITY_4_5 || UNITY_4_6 Rigidbody2D rb = trans.rigidbody2D; #else Rigidbody2D rb = trans.GetComponent (); #endif if (rb != null) return rb; trans = trans.parent; } return null; } /// /// Returns the object under the specified position. /// static public bool Raycast (Vector3 inPos) { for (int i = 0; i < list.size; ++i) { MyMainCamera cam = list.buffer [i]; // Skip inactive scripts if (!cam.enabled || !NGUITools.GetActive (cam.gameObject)) continue; // Convert to view space currentCamera = cam.cachedCamera; Vector3 pos = currentCamera.ScreenToViewportPoint (inPos); if (float.IsNaN (pos.x) || float.IsNaN (pos.y)) continue; // If it's outside the camera's viewport, do nothing if (pos.x < 0f || pos.x > 1f || pos.y < 0f || pos.y > 1f) continue; // Cast a ray into the screen Ray ray = currentCamera.ScreenPointToRay (inPos); // Raycast into the screen int mask = currentCamera.cullingMask & (int)cam.eventReceiverMask; float dist = (cam.rangeDistance > 0f) ? cam.rangeDistance : currentCamera.farClipPlane - currentCamera.nearClipPlane; if (cam.eventType == EventType.World_3D) { if (Physics.Raycast (ray, out lastHit, dist, mask)) { lastWorldPosition = lastHit.point; hoveredObject = lastHit.collider.gameObject; if (!list [0].eventsGoToColliders) { Rigidbody rb = FindRootRigidbody (hoveredObject.transform); if (rb != null) hoveredObject = rb.gameObject; } return true; } continue; } else if (cam.eventType == EventType.UI_3D) { RaycastHit[] hits = Physics.RaycastAll (ray, dist, mask); if (hits.Length > 1) { for (int b = 0; b < hits.Length; ++b) { GameObject go = hits [b].collider.gameObject; UIWidget w = go.GetComponent (); if (w != null) { if (!w.isVisible) continue; if (w.hitCheck != null && !w.hitCheck (hits [b].point)) continue; } else { UIRect rect = NGUITools.FindInParents (go); if (rect != null && rect.finalAlpha < 0.001f) continue; } mHit.depth = NGUITools.CalculateRaycastDepth (go); if (mHit.depth != int.MaxValue) { mHit.hit = hits [b]; mHit.point = hits [b].point; mHit.go = hits [b].collider.gameObject; mHits.Add (mHit); } } mHits.Sort (delegate(DepthEntry r1, DepthEntry r2) { return r2.depth.CompareTo (r1.depth); }); for (int b = 0; b < mHits.size; ++b) { #if UNITY_FLASH if (IsVisible(mHits.buffer[b])) #else if (IsVisible (ref mHits.buffer [b])) #endif { lastHit = mHits [b].hit; hoveredObject = mHits [b].go; lastWorldPosition = mHits [b].point; mHits.Clear (); return true; } } mHits.Clear (); } else if (hits.Length == 1) { GameObject go = hits [0].collider.gameObject; UIWidget w = go.GetComponent (); if (w != null) { if (!w.isVisible) continue; if (w.hitCheck != null && !w.hitCheck (hits [0].point)) continue; } else { UIRect rect = NGUITools.FindInParents (go); if (rect != null && rect.finalAlpha < 0.001f) continue; } if (IsVisible (hits [0].point, hits [0].collider.gameObject)) { lastHit = hits [0]; lastWorldPosition = hits [0].point; hoveredObject = lastHit.collider.gameObject; return true; } } continue; } else if (cam.eventType == EventType.World_2D) { if (m2DPlane.Raycast (ray, out dist)) { Vector3 point = ray.GetPoint (dist); Collider2D c2d = Physics2D.OverlapPoint (point, mask); if (c2d) { lastWorldPosition = point; hoveredObject = c2d.gameObject; if (!cam.eventsGoToColliders) { Rigidbody2D rb = FindRootRigidbody2D (hoveredObject.transform); if (rb != null) hoveredObject = rb.gameObject; } return true; } } continue; } else if (cam.eventType == EventType.UI_2D) { if (m2DPlane.Raycast (ray, out dist)) { lastWorldPosition = ray.GetPoint (dist); Collider2D[] hits = Physics2D.OverlapPointAll (lastWorldPosition, mask); if (hits.Length > 1) { for (int b = 0; b < hits.Length; ++b) { GameObject go = hits [b].gameObject; UIWidget w = go.GetComponent (); if (w != null) { if (!w.isVisible) continue; if (w.hitCheck != null && !w.hitCheck (lastWorldPosition)) continue; } else { UIRect rect = NGUITools.FindInParents (go); if (rect != null && rect.finalAlpha < 0.001f) continue; } mHit.depth = NGUITools.CalculateRaycastDepth (go); if (mHit.depth != int.MaxValue) { mHit.go = go; mHit.point = lastWorldPosition; mHits.Add (mHit); } } mHits.Sort (delegate(DepthEntry r1, DepthEntry r2) { return r2.depth.CompareTo (r1.depth); }); for (int b = 0; b < mHits.size; ++b) { #if UNITY_FLASH if (IsVisible(mHits.buffer[b])) #else if (IsVisible (ref mHits.buffer [b])) #endif { hoveredObject = mHits [b].go; mHits.Clear (); return true; } } mHits.Clear (); } else if (hits.Length == 1) { GameObject go = hits [0].gameObject; UIWidget w = go.GetComponent (); if (w != null) { if (!w.isVisible) continue; if (w.hitCheck != null && !w.hitCheck (lastWorldPosition)) continue; } else { UIRect rect = NGUITools.FindInParents (go); if (rect != null && rect.finalAlpha < 0.001f) continue; } if (IsVisible (lastWorldPosition, go)) { hoveredObject = go; return true; } } } continue; } } return false; } static Plane m2DPlane = new Plane (Vector3.back, 0f); /// /// Helper function to check if the specified hit is visible by the panel. /// static bool IsVisible (Vector3 worldPoint, GameObject go) { UIPanel panel = NGUITools.FindInParents (go); while (panel != null) { if (!panel.IsVisible (worldPoint)) return false; panel = panel.parentPanel; } return true; } /// /// Helper function to check if the specified hit is visible by the panel. /// #if UNITY_FLASH static bool IsVisible (DepthEntry de) #else static bool IsVisible (ref DepthEntry de) #endif { UIPanel panel = NGUITools.FindInParents (de.go); while (panel != null) { if (!panel.IsVisible (de.point)) return false; panel = panel.parentPanel; } return true; } /// /// Whether the specified object should be highlighted. /// static public bool IsHighlighted (GameObject go) { if (MyMainCamera.currentScheme == MyMainCamera.ControlScheme.Mouse) return (MyMainCamera.hoveredObject == go); if (MyMainCamera.currentScheme == MyMainCamera.ControlScheme.Controller) return (MyMainCamera.selectedObject == go); return false; } /// /// Find the camera responsible for handling events on objects of the specified layer. /// static public MyMainCamera FindCameraForLayer (int layer) { int layerMask = 1 << layer; for (int i = 0; i < list.size; ++i) { MyMainCamera cam = list.buffer [i]; Camera uc = cam.cachedCamera; if ((uc != null) && (uc.cullingMask & layerMask) != 0) return cam; } return null; } /// /// Using the keyboard will result in 1 or -1, depending on whether up or down keys have been pressed. /// static int GetDirection (KeyCode up, KeyCode down) { if (GetKeyDown (up)) return 1; if (GetKeyDown (down)) return -1; return 0; } /// /// Using the keyboard will result in 1 or -1, depending on whether up or down keys have been pressed. /// static int GetDirection (KeyCode up0, KeyCode up1, KeyCode down0, KeyCode down1) { if (GetKeyDown (up0) || GetKeyDown (up1)) return 1; if (GetKeyDown (down0) || GetKeyDown (down1)) return -1; return 0; } /// /// Using the joystick to move the UI results in 1 or -1 if the threshold has been passed, mimicking up/down keys. /// static int GetDirection (string axis) { float time = RealTime.time; if (mNextEvent < time && !string.IsNullOrEmpty (axis)) { float val = GetAxis (axis); if (val > 0.75f) { mNextEvent = time + 0.25f; return 1; } if (val < -0.75f) { mNextEvent = time + 0.25f; return -1; } } return 0; } static int mNotifying = 0; /// /// Generic notification function. Used in place of SendMessage to shorten the code and allow for more than one receiver. /// static public void Notify (GameObject go, string funcName, object obj) { if (mNotifying > 10) return; if (NGUITools.GetActive (go)) { ++mNotifying; //NGUIDebug.Log(funcName + "(" + obj + ") on " + (go != null ? go.name : "") + "; " + currentTouchID + ", " + Input.touchCount); go.SendMessage (funcName, obj, SendMessageOptions.DontRequireReceiver); if (mGenericHandler != null && mGenericHandler != go) mGenericHandler.SendMessage (funcName, obj, SendMessageOptions.DontRequireReceiver); --mNotifying; } } /// /// Get the details of the specified mouse button. /// static public MouseOrTouch GetMouse (int button) { return mMouse [button]; } /// /// Get or create a touch event. If you are trying to iterate through a list of active touches, use activeTouches instead. /// static public MouseOrTouch GetTouch (int id) { if (id < 0) return GetMouse (-id - 1); for (int i = 0, imax = mTouchIDs.Count; i < imax; ++i) if (mTouchIDs [i] == id) return activeTouches [i]; MouseOrTouch touch = new MouseOrTouch (); touch.pressTime = RealTime.time; touch.touchBegan = true; activeTouches.Add (touch); mTouchIDs.Add (id); return touch; } /// /// Remove a touch event from the list. /// static public void RemoveTouch (int id) { for (int i = 0, imax = mTouchIDs.Count; i < imax; ++i) { if (mTouchIDs [i] == id) { mTouchIDs.RemoveAt (i); activeTouches.RemoveAt (i); return; } } } /// /// Add this camera to the list. /// void Awake () { mWidth = Screen.width; mHeight = Screen.height; if (Application.platform == RuntimePlatform.Android || Application.platform == RuntimePlatform.IPhonePlayer || Application.platform == RuntimePlatform.WP8Player || Application.platform == RuntimePlatform.BlackBerryPlayer) { useTouch = true; useMouse = false; useKeyboard = false; useController = false; } else if (Application.platform == RuntimePlatform.PS3 || Application.platform == RuntimePlatform.XBOX360) { useMouse = false; useTouch = false; useKeyboard = false; useController = true; } // Save the starting mouse position mMouse [0].pos = Input.mousePosition; for (int i = 1; i < 3; ++i) { mMouse [i].pos = mMouse [0].pos; mMouse [i].lastPos = mMouse [0].pos; } lastTouchPosition = mMouse [0].pos; } /// /// Sort the list when enabled. /// void OnEnable () { list.Add (this); list.Sort (CompareFunc); } /// /// Remove this camera from the list. /// void OnDisable () { list.Remove (this); } /// /// We don't want the camera to send out any kind of mouse events. /// void Start () { if (eventType != EventType.World_3D && cachedCamera.transparencySortMode != TransparencySortMode.Orthographic) cachedCamera.transparencySortMode = TransparencySortMode.Orthographic; if (Application.isPlaying) { // Always set a fallthrough object if (fallThrough == null) { UIRoot root = NGUITools.FindInParents (gameObject); if (root != null) { fallThrough = root.gameObject; } else { Transform t = transform; fallThrough = (t.parent != null) ? t.parent.gameObject : gameObject; } } cachedCamera.eventMask = 0; } if (handlesEvents) NGUIDebug.debugRaycast = debug; } #if UNITY_EDITOR void OnValidate () { Start (); } #endif /// /// Check the input and send out appropriate events. /// public void Update () { // Only the first UI layer should be processing events #if UNITY_EDITOR if (!Application.isPlaying || !handlesEvents) return; #else if (!handlesEvents) return; #endif current = this; // Process touch events first if (useTouch) ProcessTouches (); else if (useMouse) ProcessMouse (); // Custom input processing if (onCustomInput != null) onCustomInput (); // Clear the selection on the cancel key, but only if mouse input is allowed if (useMouse && mCurrentSelection != null) { if (cancelKey0 != KeyCode.None && GetKeyDown (cancelKey0)) { currentScheme = ControlScheme.Controller; currentKey = cancelKey0; selectedObject = null; } else if (cancelKey1 != KeyCode.None && GetKeyDown (cancelKey1)) { currentScheme = ControlScheme.Controller; currentKey = cancelKey1; selectedObject = null; } } // If nothing is selected, input focus is lost if (mCurrentSelection == null) { inputHasFocus = false; } else if (!mCurrentSelection || !mCurrentSelection.activeInHierarchy) { inputHasFocus = false; mCurrentSelection = null; } // Update the keyboard and joystick events if ((useKeyboard || useController) && mCurrentSelection != null) ProcessOthers (); // If it's time to show a tooltip, inform the object we're hovering over if (useMouse && mHover != null) { float scroll = !string.IsNullOrEmpty (scrollAxisName) ? GetAxis (scrollAxisName) : 0f; if (scroll != 0f) { if (onScroll != null) onScroll (mHover, scroll); Notify (mHover, "OnScroll", scroll); } if (showTooltips && mTooltipTime != 0f && (mTooltipTime < RealTime.time || GetKey (KeyCode.LeftShift) || GetKey (KeyCode.RightShift))) { mTooltip = mHover; currentTouch = mMouse [0]; currentTouchID = -1; ShowTooltip (true); } } current = null; currentTouchID = -100; } /// /// Keep an eye on screen size changes. /// public void LateUpdate () { #if UNITY_EDITOR if (!Application.isPlaying || !handlesEvents) return; #else if (!handlesEvents) return; #endif int w = Screen.width; int h = Screen.height; if (w != mWidth || h != mHeight) { mWidth = w; mHeight = h; UIRoot.Broadcast ("UpdateAnchors"); if (onScreenResize != null) onScreenResize (); } } /// /// Update mouse input. /// public void ProcessMouse () { // Is any button currently pressed? bool isPressed = false; bool justPressed = false; for (int i = 0; i < 3; ++i) { if (Input.GetMouseButtonDown (i)) { currentScheme = ControlScheme.Mouse; justPressed = true; isPressed = true; } else if (Input.GetMouseButton (i)) { currentScheme = ControlScheme.Mouse; isPressed = true; } } // We're currently using touches -- do nothing if (currentScheme == ControlScheme.Touch) return; // Update the position and delta Vector2 pos = Input.mousePosition; Vector2 delta = pos - mMouse [0].pos; float sqrMag = delta.sqrMagnitude; bool posChanged = false; if (currentScheme != ControlScheme.Mouse) { if (sqrMag < 0.001f) return; // Nothing changed and we are not using the mouse -- exit currentScheme = ControlScheme.Mouse; posChanged = true; } else if (sqrMag > 0.001f) posChanged = true; lastTouchPosition = pos; // Propagate the updates to the other mouse buttons for (int i = 0; i < 3; ++i) { mMouse [i].pos = pos; mMouse [i].delta = delta; } // No need to perform raycasts every frame if (isPressed || posChanged || mNextRaycast < RealTime.time) { mNextRaycast = RealTime.time + 0.02f; if (!Raycast (Input.mousePosition)) hoveredObject = fallThrough; if (hoveredObject == null) hoveredObject = mGenericHandler; for (int i = 0; i < 3; ++i) mMouse [i].current = hoveredObject; } bool highlightChanged = (mMouse [0].last != mMouse [0].current); if (highlightChanged) currentScheme = ControlScheme.Mouse; currentTouch = mMouse [0]; currentTouchID = -1; if (isPressed) { // A button was pressed -- cancel the tooltip mTooltipTime = 0f; } else if (posChanged && (!stickyTooltip || highlightChanged)) { if (mTooltipTime != 0f) { // Delay the tooltip mTooltipTime = RealTime.time + tooltipDelay; } else if (mTooltip != null) { // Hide the tooltip ShowTooltip (false); } } // Generic mouse move notifications if (posChanged && onMouseMove != null) { onMouseMove (currentTouch.delta); currentTouch = null; } // The button was released over a different object -- remove the highlight from the previous if ((justPressed || !isPressed) && mHover != null && highlightChanged) { if (mTooltip != null) ShowTooltip (false); if (onHover != null) onHover (mHover, false); Notify (mHover, "OnHover", false); mHover = null; } // Process all 3 mouse buttons as individual touches for (int i = 0; i < 3; ++i) { bool pressed = Input.GetMouseButtonDown (i); bool unpressed = Input.GetMouseButtonUp (i); if (pressed || unpressed) currentScheme = ControlScheme.Mouse; currentTouch = mMouse [i]; #if UNITY_STANDALONE_OSX || UNITY_EDITOR_OSX if (commandClick && i == 0 && (Input.GetKey (KeyCode.LeftControl) || Input.GetKey (KeyCode.RightControl))) { currentTouchID = -2; currentKey = KeyCode.Mouse1; } else #endif { currentTouchID = -1 - i; currentKey = KeyCode.Mouse0 + i; } // We don't want to update the last camera while there is a touch happening if (pressed) { currentTouch.pressedCam = currentCamera; currentTouch.pressTime = RealTime.time; } else if (currentTouch.pressed != null) currentCamera = currentTouch.pressedCam; // Process the mouse events ProcessTouch (pressed, unpressed); currentKey = KeyCode.None; } // If nothing is pressed and there is an object under the touch, highlight it if (!isPressed && highlightChanged) { currentScheme = ControlScheme.Mouse; mTooltipTime = RealTime.time + tooltipDelay; mHover = mMouse [0].current; currentTouch = mMouse [0]; currentTouchID = -1; if (onHover != null) onHover (mHover, true); Notify (mHover, "OnHover", true); } currentTouch = null; // Update the last value mMouse [0].last = mMouse [0].current; for (int i = 1; i < 3; ++i) mMouse [i].last = mMouse [0].last; } static bool mUsingTouchEvents = true; public class Touch { public int fingerId; public TouchPhase phase = TouchPhase.Began; public Vector2 position; public int tapCount = 0; } public delegate int GetTouchCountCallback (); public delegate Touch GetTouchCallback (int index); static public GetTouchCountCallback GetInputTouchCount; static public GetTouchCallback GetInputTouch; /// /// Update touch-based events. /// int oldTouchCount = 0; // add by chenbin public void ProcessTouches () { int count = (GetInputTouchCount == null) ? Input.touchCount : GetInputTouchCount (); for (int i = 0; i < count; ++i) { int fingerId; TouchPhase phase; Vector2 position; int tapCount; if (GetInputTouch == null) { UnityEngine.Touch touch = Input.GetTouch (i); phase = touch.phase; fingerId = touch.fingerId; position = touch.position; tapCount = touch.tapCount; } else { Touch touch = GetInputTouch (i); phase = touch.phase; fingerId = touch.fingerId; position = touch.position; tapCount = touch.tapCount; } currentTouchID = allowMultiTouch ? fingerId : 1; currentTouch = GetTouch (currentTouchID); bool pressed = (phase == TouchPhase.Began) || currentTouch.touchBegan; bool unpressed = (phase == TouchPhase.Canceled) || (phase == TouchPhase.Ended); currentTouch.touchBegan = false; // Assume touch-based control currentScheme = ControlScheme.Touch; // Although input.deltaPosition can be used, calculating it manually is safer (just in case) // currentTouch.delta = pressed ? Vector2.zero : position - currentTouch.pos; #region modify by chenbin if (Input.touchCount == oldTouchCount) { currentTouch.delta = pressed ? Vector2.zero : position - currentTouch.pos; } else { currentTouch.delta = Vector2.zero; } #endregion currentTouch.pos = position; // Raycast into the screen if (!Raycast (currentTouch.pos)) hoveredObject = fallThrough; if (hoveredObject == null) hoveredObject = mGenericHandler; currentTouch.last = currentTouch.current; currentTouch.current = hoveredObject; lastTouchPosition = currentTouch.pos; // We don't want to update the last camera while there is a touch happening if (pressed) currentTouch.pressedCam = currentCamera; else if (currentTouch.pressed != null) currentCamera = currentTouch.pressedCam; // Double-tap support if (tapCount > 1) currentTouch.clickTime = RealTime.time; // Process the events from this touch ProcessTouch (pressed, unpressed); // If the touch has ended, remove it from the list if (unpressed) RemoveTouch (currentTouchID); currentTouch.last = null; currentTouch = null; // Don't consider other touches if (!allowMultiTouch) break; } if (count == 0) { // Skip the first frame after using touch events if (mUsingTouchEvents) { mUsingTouchEvents = false; return; } if (useMouse) ProcessMouse (); #if UNITY_EDITOR else if (GetInputTouch == null) ProcessFakeTouches (); #endif } else mUsingTouchEvents = true; oldTouchCount = count; // add by chenbin } /// /// Process fake touch events where the mouse acts as a touch device. /// Useful for testing mobile functionality in the editor. /// void ProcessFakeTouches () { bool pressed = Input.GetMouseButtonDown (0); bool unpressed = Input.GetMouseButtonUp (0); bool held = Input.GetMouseButton (0); if (pressed || unpressed || held) { currentTouchID = 1; currentTouch = mMouse [0]; currentTouch.touchBegan = pressed; if (pressed) currentTouch.pressTime = RealTime.time; Vector2 pos = Input.mousePosition; currentTouch.delta = pressed ? Vector2.zero : pos - currentTouch.pos; currentTouch.pos = pos; // Raycast into the screen if (!Raycast (currentTouch.pos)) hoveredObject = fallThrough; if (hoveredObject == null) hoveredObject = mGenericHandler; currentTouch.last = currentTouch.current; currentTouch.current = hoveredObject; lastTouchPosition = currentTouch.pos; // We don't want to update the last camera while there is a touch happening if (pressed) currentTouch.pressedCam = currentCamera; else if (currentTouch.pressed != null) currentCamera = currentTouch.pressedCam; // Process the events from this touch ProcessTouch (pressed, unpressed); // If the touch has ended, remove it from the list if (unpressed) RemoveTouch (currentTouchID); currentTouch.last = null; currentTouch = null; } } /// /// Process keyboard and joystick events. /// public void ProcessOthers () { currentTouchID = -100; currentTouch = controller; bool submitKeyDown = false; bool submitKeyUp = false; if (submitKey0 != KeyCode.None && GetKeyDown (submitKey0)) { currentKey = submitKey0; submitKeyDown = true; } if (submitKey1 != KeyCode.None && GetKeyDown (submitKey1)) { currentKey = submitKey1; submitKeyDown = true; } if (submitKey0 != KeyCode.None && GetKeyUp (submitKey0)) { currentKey = submitKey0; submitKeyUp = true; } if (submitKey1 != KeyCode.None && GetKeyUp (submitKey1)) { currentKey = submitKey1; submitKeyUp = true; } if (submitKeyDown) currentTouch.pressTime = RealTime.time; if (submitKeyDown || submitKeyUp) { currentScheme = ControlScheme.Controller; currentTouch.last = currentTouch.current; currentTouch.current = mCurrentSelection; ProcessTouch (submitKeyDown, submitKeyUp); currentTouch.last = null; } int vertical = 0; int horizontal = 0; if (useKeyboard) { if (inputHasFocus) { vertical += GetDirection (KeyCode.UpArrow, KeyCode.DownArrow); horizontal += GetDirection (KeyCode.RightArrow, KeyCode.LeftArrow); } else { vertical += GetDirection (KeyCode.W, KeyCode.UpArrow, KeyCode.S, KeyCode.DownArrow); horizontal += GetDirection (KeyCode.D, KeyCode.RightArrow, KeyCode.A, KeyCode.LeftArrow); } } if (useController) { if (!string.IsNullOrEmpty (verticalAxisName)) vertical += GetDirection (verticalAxisName); if (!string.IsNullOrEmpty (horizontalAxisName)) horizontal += GetDirection (horizontalAxisName); } // Send out key notifications if (vertical != 0) { currentScheme = ControlScheme.Controller; KeyCode key = vertical > 0 ? KeyCode.UpArrow : KeyCode.DownArrow; if (onKey != null) onKey (mCurrentSelection, key); Notify (mCurrentSelection, "OnKey", key); } if (horizontal != 0) { currentScheme = ControlScheme.Controller; KeyCode key = horizontal > 0 ? KeyCode.RightArrow : KeyCode.LeftArrow; if (onKey != null) onKey (mCurrentSelection, key); Notify (mCurrentSelection, "OnKey", key); } if (useKeyboard && GetKeyDown (KeyCode.Tab)) { currentKey = KeyCode.Tab; currentScheme = ControlScheme.Controller; if (onKey != null) onKey (mCurrentSelection, KeyCode.Tab); Notify (mCurrentSelection, "OnKey", KeyCode.Tab); } // Send out the cancel key notification if (cancelKey0 != KeyCode.None && GetKeyDown (cancelKey0)) { currentKey = cancelKey0; currentScheme = ControlScheme.Controller; if (onKey != null) onKey (mCurrentSelection, KeyCode.Escape); Notify (mCurrentSelection, "OnKey", KeyCode.Escape); } if (cancelKey1 != KeyCode.None && GetKeyDown (cancelKey1)) { currentKey = cancelKey1; currentScheme = ControlScheme.Controller; if (onKey != null) onKey (mCurrentSelection, KeyCode.Escape); Notify (mCurrentSelection, "OnKey", KeyCode.Escape); } currentTouch = null; currentKey = KeyCode.None; } /// /// Process the press part of a touch. /// void ProcessPress (bool pressed, float click, float drag) { // Send out the press message if (pressed) { if (mTooltip != null) ShowTooltip (false); currentTouch.pressStarted = true; if (onPress != null && currentTouch.pressed) onPress (currentTouch.pressed, false); Notify (currentTouch.pressed, "OnPress", false); currentTouch.pressed = currentTouch.current; currentTouch.dragged = currentTouch.current; currentTouch.clickNotification = ClickNotification.BasedOnDelta; currentTouch.totalDelta = Vector2.zero; currentTouch.dragStarted = false; if (onPress != null && currentTouch.pressed) onPress (currentTouch.pressed, true); Notify (currentTouch.pressed, "OnPress", true); if (mTooltip != null) ShowTooltip (false); selectedObject = currentTouch.pressed; } else if (currentTouch.pressed != null && (currentTouch.delta.sqrMagnitude != 0f || currentTouch.current != currentTouch.last)) { // Keep track of the total movement currentTouch.totalDelta += currentTouch.delta; float mag = currentTouch.totalDelta.sqrMagnitude; bool justStarted = false; // If the drag process hasn't started yet but we've already moved off the object, start it immediately if (!currentTouch.dragStarted && currentTouch.last != currentTouch.current) { currentTouch.dragStarted = true; currentTouch.delta = currentTouch.totalDelta; // OnDragOver is sent for consistency, so that OnDragOut is always preceded by OnDragOver isDragging = true; if (onDragStart != null) onDragStart (currentTouch.dragged); Notify (currentTouch.dragged, "OnDragStart", null); if (onDragOver != null) onDragOver (currentTouch.last, currentTouch.dragged); Notify (currentTouch.last, "OnDragOver", currentTouch.dragged); isDragging = false; } else if (!currentTouch.dragStarted && drag < mag) { // If the drag event has not yet started, see if we've dragged the touch far enough to start it justStarted = true; currentTouch.dragStarted = true; currentTouch.delta = currentTouch.totalDelta; } // If we're dragging the touch, send out drag events if (currentTouch.dragStarted) { if (mTooltip != null) ShowTooltip (false); isDragging = true; bool isDisabled = (currentTouch.clickNotification == ClickNotification.None); if (justStarted) { if (onDragStart != null) onDragStart (currentTouch.dragged); Notify (currentTouch.dragged, "OnDragStart", null); if (onDragOver != null) onDragOver (currentTouch.last, currentTouch.dragged); Notify (currentTouch.current, "OnDragOver", currentTouch.dragged); } else if (currentTouch.last != currentTouch.current) { if (onDragStart != null) onDragStart (currentTouch.dragged); Notify (currentTouch.last, "OnDragOut", currentTouch.dragged); if (onDragOver != null) onDragOver (currentTouch.last, currentTouch.dragged); Notify (currentTouch.current, "OnDragOver", currentTouch.dragged); } if (onDrag != null) onDrag (currentTouch.dragged, currentTouch.delta); Notify (currentTouch.dragged, "OnDrag", currentTouch.delta); currentTouch.last = currentTouch.current; isDragging = false; if (isDisabled) { // If the notification status has already been disabled, keep it as such currentTouch.clickNotification = ClickNotification.None; } else if (currentTouch.clickNotification == ClickNotification.BasedOnDelta && click < mag) { // We've dragged far enough to cancel the click currentTouch.clickNotification = ClickNotification.None; } } } } /// /// Process the release part of a touch. /// #region add by chenbin public float dragThreshold { get { bool isMouse = (currentScheme == ControlScheme.Mouse); float drag = isMouse ? mouseDragThreshold : touchDragThreshold; return drag; } } public void ProcessRelease () { bool isMouse = (currentScheme == ControlScheme.Mouse); float drag = isMouse ? mouseDragThreshold : touchDragThreshold; // So we can use sqrMagnitude below drag *= drag; ProcessRelease (isMouse, drag); } #endregion void ProcessRelease (bool isMouse, float drag) { // Send out the unpress message if (currentTouch == null) return; currentTouch.pressStarted = false; //if (mTooltip != null) ShowTooltip(false); if (currentTouch.pressed != null) { // If there was a drag event in progress, make sure OnDragOut gets sent if (currentTouch.dragStarted) { if (onDragOut != null) onDragOut (currentTouch.last, currentTouch.dragged); Notify (currentTouch.last, "OnDragOut", currentTouch.dragged); if (onDragEnd != null) onDragEnd (currentTouch.dragged); Notify (currentTouch.dragged, "OnDragEnd", null); } // Send the notification of a touch ending if (onPress != null) onPress (currentTouch.pressed, false); Notify (currentTouch.pressed, "OnPress", false); // Send a hover message to the object if (isMouse) { if (onHover != null) onHover (currentTouch.current, true); Notify (currentTouch.current, "OnHover", true); } mHover = currentTouch.current; // If the button/touch was released on the same object, consider it a click and select it if (currentTouch.dragged == currentTouch.current || (currentScheme != ControlScheme.Controller && currentTouch.clickNotification != ClickNotification.None && currentTouch.totalDelta.sqrMagnitude < drag)) { // If the touch should consider clicks, send out an OnClick notification if (currentTouch.clickNotification != ClickNotification.None && currentTouch.pressed == currentTouch.current) { float time = RealTime.time; if (onClick != null) onClick (currentTouch.pressed); Notify (currentTouch.pressed, "OnClick", null); if (currentTouch.clickTime + 0.35f > time) { if (onDoubleClick != null) onDoubleClick (currentTouch.pressed); Notify (currentTouch.pressed, "OnDoubleClick", null); } currentTouch.clickTime = time; } } else if (currentTouch.dragStarted) { // The button/touch was released on a different object // Send a drop notification (for drag & drop) if (onDrop != null) onDrop (currentTouch.current, currentTouch.dragged); Notify (currentTouch.current, "OnDrop", currentTouch.dragged); } } currentTouch.dragStarted = false; currentTouch.pressed = null; currentTouch.dragged = null; } /// /// Process the events of the specified touch. /// public void ProcessTouch (bool pressed, bool released) { // Whether we're using the mouse bool isMouse = (currentScheme == ControlScheme.Mouse); float drag = isMouse ? mouseDragThreshold : touchDragThreshold; float click = isMouse ? mouseClickThreshold : touchClickThreshold; // So we can use sqrMagnitude below drag *= drag; click *= click; if (currentTouch.pressed != null) { if (released) ProcessRelease (isMouse, drag); ProcessPress (pressed, click, drag); // Hold event = show tooltip if (currentTouch.pressed == currentTouch.current && currentTouch.clickNotification != ClickNotification.None && !currentTouch.dragStarted && currentTouch.deltaTime > tooltipDelay) { currentTouch.clickNotification = ClickNotification.None; if (longPressTooltip) { mTooltip = currentTouch.pressed; ShowTooltip (true); } Notify (currentTouch.current, "OnLongPress", null); } } else if (isMouse || pressed || released) { ProcessPress (pressed, click, drag); if (released) ProcessRelease (isMouse, drag); } //modify by chenbin if (isbeControled && released) { enabled = false; } } /// /// Show or hide the tooltip. /// public void ShowTooltip (bool val) { mTooltipTime = 0f; if (onTooltip != null) onTooltip (mTooltip, val); Notify (mTooltip, "OnTooltip", val); if (!val) mTooltip = null; } } }