Unity事件處理機制與NGUI事件機制

1 Unity原生

1.1 GUI

void OnGUI(){
if(GUI.Button(Rect position, string text)){
    //點擊後當即執行
}

1.1 Input

每一個手指觸控是經過Input.touches數據結構描述的:html

  • fingerId 手指索引
    The unique index for a touch.  觸摸的惟一索引。
  • position 位置
    The screen position of the touch.  觸摸屏幕的位置。
  • deltaPosition 增量位置
    The screen position change since the last frame.
    自最後一幀改變的屏幕位置。
  • deltaTime 增量時間
    Amount of time that has passed since the last state change.
    從最後狀態改變到如今通過的時間
  • tapCount 點擊數
    The iPhone/iPad screen is able to distinguish quick finger taps by the user. This counter will let you know how many times the user has tapped the screen without moving a finger to the sides. Android devices do not count number of taps, this field is always 1.
    iPhone/iPad屏幕可以識別用過的快速點擊, 這個計數器讓你知道用戶點擊屏幕多少次,而不是移動手指。android設備不對點擊計數,這個方法老是返回1
  • phase 相位(狀態)
    Describes so called "phase" or the state of the touch. It can help you determine if the touch just began, if user moved the finger or if he just lifted the finger.
    描述觸摸的狀態,能夠幫助開發者肯定用戶是否剛開始觸摸,是否用戶移動手指,是否用戶剛剛擡起手指。

  Phase can be one of the following:android

  相位(狀態)多是下列之一:ios

    • Began 開始
      A finger just touched the screen.  手指剛剛觸摸屏幕
    • Moved 移動
      A finger moved on the screen.  手指在屏幕上移動
    • Stationary 靜止
      A finger is touching the screen but hasn't moved since the last frame.
      手指觸摸屏幕,可是自最後一幀沒有移動
    • Ended 結束
      A finger was lifted from the screen. This is the final phase of a touch.
      手指從屏幕上擡起,這是觸控的最後狀態。
    • Canceled 取消
      The system cancelled tracking for the touch, as when (for example) the user puts the device to her face or more than five touches happened simultaneously. This is the final phase of a touch.
      系統取消了觸控跟蹤,如,用戶將設備放在了臉上或超過同時超過5個觸摸點。這是觸控的最後狀態。
  • 下面的例子,只要用戶在屏幕上點擊,將射出一條光線:緩存

var particle : GameObject;
function Update () {
    for (var touch : Touch in Input.touches) {
        if (touch.phase == TouchPhase.Began) {
            // Construct a ray from the current touch coordinates
            //從當前的觸摸座標建一條光線
            var ray = Camera.main.ScreenPointToRay (touch.position);
            if (Physics.Raycast (ray)) {
                // Create a particle if hit
                //若是觸摸就建立一個例子
                Instantiate (particle, transform.position, transform.rotation);
            }
        }
    }
}

2 NGUI事件機制

       UICamera主要負責監聽,分發全部此Camera渲染的GameObject。數據結構

       事件源:鼠標,觸屏,鍵盤和手柄app

事件包括:懸停,按下/擡起,選中/取消選中,點擊,雙擊,拖拽,釋放,文本輸入,tips顯示,滾輪滑動,鍵盤輸入。ide

 

EventType:包括3D UI,3D world,2D UI,3D World用於區分UICamera處理UI事件的對象是UI空間仍是3D物體。函數

EventMask:能夠過濾到一些不須要接受UI事件的對象。源碼分析

EventSource:只處理指定事件源,如touchui

Thresholds:是指事件偏差的範圍。

2.1 NGUI事件註冊

2.1.1 直接監聽事件

直接將MonoBehaviour的腳本上的方法綁定至Notifiy上面。這種方法不夠靈活,不推薦。

 

2.1.2 使用SendMessage

過時方式,不建議使用。

2.1.3 UIListener註冊

     在須要接收事件的gameobject上附加Event Listener。添加component->NGUI->Internal->Event Listener。

在任何一個腳本或者類中便可獲得按鈕的點擊事件、把以下代碼放在任意類中或者腳本中。

void Awake () 
    {    
                //獲取須要監聽的按鈕對象
    GameObject button = GameObject.Find("UI Root (2D)/Camera/Anchor/Panel/LoadUI/MainCommon/Button");
                //設置這個按鈕的監聽,指向本類的ButtonClick方法中。
    UIEventListener.Get(button).onClick = ButtonClick;
    }
 
        //計算按鈕的點擊事件
    void ButtonClick(GameObject button)
    {
        Debug.Log("GameObject " + button.name);
    }

3 NGUI事件相關源碼分析

3.1 事件註冊

UIEventListener.Get(gameObject).onClick = ButtonClick;
//一、獲取GameObject對應的UIEventListener的組件
static public UIEventListener Get (GameObject go)
    {
        UIEventListener listener = go.GetComponent<UIEventListener>();
        if (listener == null) listener = go.AddComponent<UIEventListener>();
        return listener;
    }
//二、註冊事件委託

public class UIEventListener : MonoBehaviour
{
    public delegate void VoidDelegate (GameObject go);
        public VoidDelegate onClick;
        void OnClick (){ if (onClick != null) onClick(gameObject); }
}

3.1.1 觀察者模式和事件/委託機制

觀察者模式定義了一種一對多的依賴關係,讓多個觀察者對象同時監聽某一個主題對象,這個主題對象在狀態發生變化時,會通知全部的觀察者對象,使他們可以執行本身的相關行爲。

 

能夠看出,在觀察者模式的結構圖有如下角色:

  • 抽象主題角色(Subject):抽象主題把全部觀察者對象的引用保存在一個列表中,並提供增長和刪除觀察者對象的操做,抽象主題角色又叫作抽象被觀察者角色,通常由抽象類或接口實現。
  • 抽象觀察者角色(Observer):爲全部具體觀察者定義一個接口,在獲得主題通知時更新本身,通常由抽象類或接口實現。
  • 具體主題角色(ConcreteSubject):實現抽象主題接口,具體主題角色又叫作具體被觀察者角色。
  • 具體觀察者角色(ConcreteObserver):實現抽象觀察者角色所要求的接口,以便使自身狀態與主題的狀態相協調。

3.1.1.1            委託

一、委託定義

在C#中定義一個委託很是簡單,只要包含關鍵詞delegate,其餘的與普通方法一致。

public delegate void GreetingDelegate(string name);

委託是一個類,它定義了方法的類型,使得能夠將方法看成另外一個方法的參數來進行傳遞,這種將方法動態地賦給參數的作法,能夠避免在程序中大量使用If-Else(Switch)語句,同時使得程序具備更好的可擴展性。(與Java中的接口很是類似)。

 二、方法綁定委託

上面的綁定onclick便是一個方法綁定。

UIEventListener.Get(gameObject).onClick = ButtonClick;

委託方法能夠綁定多個方法,調用時會一次調用綁定的方法。

UIEventListener.Get(gameObject).onClick += ButtonClick2;

  委託還能夠經過-=的方式解除綁定。

注意:第一次綁定必須用賦值=,若是使用+=將會發生編譯錯誤。

3.2 UICamera事件分發

3.2.1 初始化設置

設置fallThrough爲攝像機節點的UI Root節點或者攝像機節點或者當前自己gameobject。

3.2.2 update獲取事件源

Unity提供了Input接口,可以從觸屏中獲取各種輸入事件。Update的時候查看事件輸入狀況,並經過ProcessTouches處理觸屏事件。

 

ProcessTouches函數

public void ProcessTouches ()
    {
        currentScheme = ControlScheme.Touch;

        for (int i = 0; i < Input.touchCount; ++i)
        {
            Touch touch = Input.GetTouch(i);

           
            currentTouchID = allowMultiTouch ? touch.fingerId : 1;
            //根據TouchID獲取touch事件,若是是begin的則建立新對象加入緩存
            currentTouch = GetTouch(currentTouchID);

            //設置當前輸入的相位
            bool pressed = (touch.phase == TouchPhase.Began) || currentTouch.touchBegan;
            bool unpressed = (touch.phase == TouchPhase.Canceled) || (touch.phase == TouchPhase.Ended);
            currentTouch.touchBegan = false;

            // Although input.deltaPosition can be used, calculating it manually is safer (just in case)
            //若是不是開始按下,就須要計算與上一次的偏移狀況
            currentTouch.delta = pressed ? Vector2.zero : touch.position - currentTouch.pos;
            currentTouch.pos = touch.position;

            // Raycast into the screen
            //經過射線獲取能夠點擊的gameobject
            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
            //ios的屢次點擊,則須要設置時間戳
            if (touch.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 (Input.touchCount == 0)
        {
            if (useMouse) ProcessMouse();
#if UNITY_EDITOR
            else ProcessFakeTouches();
#endif
        }
    }

這裏須要重點介紹Raycast(Vector3 inpos)和ProcessTouch()。

3.2.2.1            Raycast

list中緩存這當前場景中,全部的攝像機,並按照深度來排序。

主要處理World_3D、UI_3D、World_2D和UI_2D4種不一樣類型的。

static public bool Raycast (Vector3 inPos)
    {
        for (int i = 0; i < list.size; ++i)
        {
            UICamera 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
            //UI層和事件層都OK的
            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;
                    //若是父節點上有剛體,則返回
                    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)
                {
                    //碰到多個colider
                    for (int b = 0; b < hits.Length; ++b)
                    {
                        GameObject go = hits[b].collider.gameObject;
                        UIWidget w = go.GetComponent<UIWidget>();

                        //根據UIWidget或者UIRect來判斷,落點是否是在節點裏面
                        if (w != null)
                        {
                            if (!w.isVisible) continue;
                            if (w.hitCheck != null && !w.hitCheck(hits[b].point)) continue;
                        }
                        else
                        {
                            UIRect rect = NGUITools.FindInParents<UIRect>(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<UIWidget>();

                    if (w != null)
                    {
                        if (!w.isVisible) continue;
                        if (w.hitCheck != null && !w.hitCheck(hits[0].point)) continue;
                    }
                    else
                    {
                        UIRect rect = NGUITools.FindInParents<UIRect>(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)
            {
                //用Plane射線獲取
                if (m2DPlane.Raycast(ray, out dist))
                {
                    Vector3 point = ray.GetPoint(dist);
                    Collider2D c2d = Physics2D.OverlapPoint(point, mask);

                    if (c2d)
                    {
                        lastWorldPosition = point;
                        hoveredObject = c2d.gameObject;

                        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<UIWidget>();

                            if (w != null)
                            {
                                if (!w.isVisible) continue;
                                if (w.hitCheck != null && !w.hitCheck(lastWorldPosition)) continue;
                            }
                            else
                            {
                                UIRect rect = NGUITools.FindInParents<UIRect>(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<UIWidget>();

                        if (w != null)
                        {
                            if (!w.isVisible) continue;
                            if (w.hitCheck != null && !w.hitCheck(lastWorldPosition)) continue;
                        }
                        else
                        {
                            UIRect rect = NGUITools.FindInParents<UIRect>(go);
                            if (rect != null && rect.finalAlpha < 0.001f) continue;
                        }

                        if (IsVisible(lastWorldPosition, go))
                        {
                            hoveredObject = go;
                            return true;
                        }
                    }
                }
                continue;
            }
        }
        return false;
    }

3.2.2.2            ProcessTouch

ProcessTouch根據閥值,判斷須要通知的事件類型。直接調用RasyCast返回的GameObject上UIEventListen註冊的事件。

經過直接執行事件委託(onClick),或者SendMessage給返回的GameObject上的相關事件委託。

4 Unity知識

4.1 Camera

遊戲界面中所顯示的內容是攝像機照射到得場景部分。攝像機能夠設置自身的位置、照射方向、照射的面積(相似於顯示中照射廣度)和照射的圖層(只看到本身想看到的層次)。

 

Clear Flags:背景內容,默認爲Skybox

Background:Clear Flag沒設置,將顯示純色

Culling Mask:用於選擇是否顯示某些層,默認爲「EveryThing」

Projection:攝像機類型,透視和正交。正交適合2D,沒有距離感。

Clipping Planes:開始攝像的最近點和最遠點

Viewport Rect:設置顯示區域。多攝像機能夠設置不一樣的區域,來進行分屏顯示。

Depth:深度大的會,畫在小的上面。

Rendering Path:渲染方式。

4.2 Colider

Colider是全部碰撞器的基類,包括BoxColider,SphereColider,CapsuleColider,MeshColider,PhysicMaterial,Rigidbody。

4.2.1 RigidBody剛體

剛體能夠附加物理屬性如物體質量、摩擦力和碰撞係數。

Mass:質量

Drag:阻力

Angular Drag:旋轉阻力,越大旋轉減慢越快

Use Gravity:是否用重力

Is Kinematic:是否受物理的影響

Collision Detection:碰撞檢測

Constraints:凍結,某個方向上的物理效果

4.2.1.1            碰撞

經過sendMessage調用改方法

OnCollisionEnter(Collision collision):開始時,馬上調用

OnCollisionStay(Collision collision):碰撞中,每幀調用

OnCollisionExit(Collision collision):結束時調用

4.2.2 碰撞器

BoxColider(盒子碰撞器),SphereColider(球體碰撞器),CapsuleColider(膠囊碰撞器),MeshColider(自定義模型自身網格決定),WheelMaterial(車輪碰撞器)。

碰撞器之間能夠添加物理材質,用於設定物理碰撞後的效果。如反彈。

4.2.3 射線

射線是3D世界中一個點向一個方向發射的一條無終點的線。在發射的軌跡中,一旦與其餘模型發生碰撞,它將中止發射(也能夠是All)。

建立一個射線首先要知道射線的起點和終點在3D世界中的座標。Ray即爲起點和終點的封裝。ScreenPointToRay,是由攝像機當前位置向鼠標位置發射的射線。

5 參考文獻

http://game.ceeger.com/Manual/Input.html

http://blog.csdn.net/onerain88/article/details/18963539

http://wetest.qq.com

相關文章
相關標籤/搜索