1 Unity原生
1.1 GUI
void OnGUI(){ if(GUI.Button(Rect position, string text)){ //點擊后立即執行 }
1.1 Input
每個手指觸控是通過Input.touches數據結構描述的:
-
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:
相位(狀態)可能是下列之一:
-
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。
事件源:鼠標,觸屏,鍵盤和手柄
事件包括:懸停,按下/抬起,選中/取消選中,點擊,雙擊,拖拽,釋放,文本輸入,tips顯示,滾輪滑動,鍵盤輸入。
EventType:包括3D UI,3D world,2D UI,3D World用於區分UICamera處理UI事件的對象是UI空間還是3D物體。
EventMask:可以過濾到一些不需要接受UI事件的對象。
EventSource:只處理指定事件源,如touch
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; //1、獲取GameObject對應的UIEventListener的組件 static public UIEventListener Get (GameObject go) { UIEventListener listener = go.GetComponent<UIEventListener>(); if (listener == null) listener = go.AddComponent<UIEventListener>(); return listener; } //2、注冊事件委托 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 委托
1、委托定義
在C#中定義一個委托非常簡單,只要包含關鍵詞delegate,其他的與普通方法一致。
public delegate void GreetingDelegate(string name);
委托是一個類,它定義了方法的類型,使得可以將方法當作另一個方法的參數來進行傳遞,這種將方法動態地賦給參數的做法,可以避免在程序中大量使用If-Else(Switch)語句,同時使得程序具有更好的可擴展性。(與Java中的接口非常相似)。
2、方法綁定委托
上面的綁定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