上次說到了珠子的消除、落下和生成,接下來就要進入珠子的移動與交換,以下是這次的目標。
使用滑鼠或手指按著珠子,接著拖曳珠子移動可以與其他位置的珠子交換,直到放開珠子或指定時間到時結束。
珠子交換本文中會使用collider,所以在珠子的預設物件上新增2D Collider。
這裡使用Circle Collider 2D,也就是2D的圓形碰撞器,後續會用到觸發函式,所以將Is Trigger打勾。
如果是盤面珠子數量固定的情況,可以直接將偏移量(Offset)和半徑(Radius)設定好數值,至於可調整盤面的情況,需要對Orb腳本做以下修改。
using UnityEngine; using System.Collections.Generic; using UnityEngine.UI; public class Orb : MonoBehaviour { . . . public float width = 120; public float height = 120; void Start() { . . . GetComponent<CircleCollider2D>().offset = new Vector2(width / 2, height / 2); GetComponent<CircleCollider2D>().radius = width / 2 - 2; } }在Start中重新設定偏移量,由於珠子的支點在左下角,則偏移量寬高的1/2,半徑基本上為寬或高的1/2,因為想讓珠子之間留一點距離,減去一些固定數值。
接下來新增物件Finger和物件下元件。
新增Image物件Finger在Canvas之下,作為玩家滑鼠或手指的代理物件,物件下新增2D Collider、2D Rigidbody和腳本Finger。
Finger物件的RectTransform如下圖所示。
座標中心與盤面背景相同為左下角,位置與寬高會在Finger腳本中初始化。
以下是Finger腳本的內容,首先是需要的變數和初始化。using UnityEngine; using System.Collections; using UnityEngine.UI; public class Finger : MonoBehaviour { public PuzzleSystem puzzleSystem;////(1) private RectTransform rect;////(2) public Vector2 initPos;////(3) private Image image;////(4) private Color display = new Color(1, 1, 1, 0.5f);////(5) private Color hide = new Color(1, 1, 1, 0);////(6) public Orb nowOrb;////(7) public bool moveState;////(8) void OnTriggerEnter2D(Collider2D other){}////(9) public void FingerInit()////(11) { rect.anchoredPosition = initPos;////(12) image.color = hide;////(13) nowOrb = null;////(14) moveState = false;////(15) } void Start() { rect = GetComponent<RectTransform>(); rect.sizeDelta = new Vector2(puzzleSystem.BGRect.sizeDelta.x / puzzleSystem.columnCount, puzzleSystem.BGRect.sizeDelta.y / puzzleSystem.rowCount);////(10) image = GetComponent<Image>(); FingerInit(); } }(1)移動時需要盤面上珠子的資料,所以宣告puzzleSystem用來抓資料。
(2)Finger的RectTransform。
(3)Finger初始化的位置。
(4)Finger的Image。
(5)點擊珠子時Finger的透明度。
(6)放開珠子時Finger的透明度。
(7)儲存被點擊珠子的資料。
(8)Finger是否在移動中。
(9)當Finger被觸發時做珠子交換,內容後續會提到。
(10)初始化Finger的寬高,與珠子大小相同。
(11)宣告FingerInit函式並在Start中呼叫,包含會重複初始化的內容。
(12)初始化Finger的位置。
(13)圖片預設為隱藏。
(14)點擊珠子預設為null。
(15)移動狀態為false。
接下來是OnTriggerEnter2D函式的內容。
void OnTriggerEnter2D(Collider2D other) { if (other.tag == "Orb")////(1) { if (nowOrb == null || nowOrb == other.GetComponent<Orb>())////(2) { other.GetComponent<Image>().color = hide;////(3) nowOrb = other.GetComponent<Orb>();////(4) } else { moveState = true;////(5) ////(6) Orb.OrbsType temp = other.GetComponent<Orb>().type; other.GetComponent<Orb>().type = puzzleSystem.orbs[nowOrb.number].type; puzzleSystem.orbs[nowOrb.number].type = temp; ////(7) other.GetComponent<Orb>().ChangeImage(); other.GetComponent<Image>().color = hide; puzzleSystem.orbs[nowOrb.number].ChangeImage(); puzzleSystem.orbs[nowOrb.number].GetComponent<Image>().color = Color.white; ////(8) nowOrb = other.GetComponent<Orb>(); } ////(9) image.sprite = Resources.Load<Sprite>("Image/" + other.GetComponent<Orb>().type); image.color = display; } }(1)當觸發物件標籤為Orb,也就是珠子時才做移動和交換。
(2)當nowOrb為null或等於觸發物件,也就是點擊珠子但還沒開始移動交換珠子。
(3)隱藏點擊的珠子。
(4)將nowOrb設為觸發物件。
(5)當(2)不成立時,代表目前觸發到的珠子與nowOrb不同,也就是開始移動交換珠子,所以把moveState設為true。
(6)做nowOrb與觸發珠子的屬性交換。
(7)呼叫珠子底下的函式ChangeImage換圖,並重新設定顯示顏色。
(8)將nowOrb設為觸發物件。
(9)將Finger的圖片設為目前珠子的屬性圖片,並修改顯示顏色。
到這裡我們做完了觸發到珠子物件時要做的交換,接下來要處理手指移動與移動狀態。
新增空物件PuzzleControl並新增腳本PuzzleControl,如果要顯示狀態和秒數文字也可以順便新增。
以下是PuzzleControl腳本內容。
using UnityEngine; using System.Collections; using UnityEngine.UI; public class PuzzleControl : MonoBehaviour { public enum PuzzleState { Standby, Start, Move, End };////(1) public PuzzleState state;////(2) public RectTransform BGRect;////(3) public RectTransform fingerRect;////(4) public Finger finger;////(5) public float puzzleTime = 4;////(6) public float timer = 0;////(7) public Text stateText;////(8) public Text timerText;////(9) Vector2 WorldToRect()////(10) { Vector2 screenPoint = RectTransformUtility.WorldToScreenPoint(Camera.main, Input.mousePosition); Vector2 Pos; RectTransformUtility.ScreenPointToLocalPointInRectangle(BGRect, screenPoint, Camera.main, out Pos); return Pos; } ////(11) void StandbyControl(){} void StartControl(){} void MoveControl(){} void EndControl(){} void Start () { state = PuzzleState.Standby;////(12) } void Update() { ////(13) stateText.text = "狀態 " + state; timerText.text = "剩餘時間:" + (puzzleTime - timer) + "秒"; ////(14) switch (state) { case PuzzleState.Standby: StandbyControl(); break; case PuzzleState.Start: StartControl(); break; case PuzzleState.Move: MoveControl(); break; case PuzzleState.End: EndControl(); break; } } }(1)列舉轉珠時的狀態,分別為待機(Standby)、開始點擊(Start)、移動中(Move)、轉珠結束(End)。
(2)儲存當前的轉珠狀態。
(3)背景物件的RectTransform,這裡用來做滑鼠點擊位置轉換到UI位置的基準。(上圖BGRect物件不小心拉錯,這裡要帶入的是背景圖物件Background。)
(4)Finger物件的RectTransform。
(5)Finger物件的腳本。
(6)轉珠時間。
(7)轉珠過程的計時器。
(8)顯示轉珠狀態的文字。
(9)顯示剩餘秒數的文字。
(10)將滑鼠點擊位置轉換成UI位置的函式,會回傳一個Vector2數值。
(11)個狀態的控制函式,除了EndControl以外,其他會在下面提到。
(12)轉珠狀態初始化為待機。
(13)顯示文字。
(14)依state數值執行狀態函式。
接下來是個狀態的內容,這裡只提滑鼠點擊,觸控會暫時略過。
首先是StandbyControl。
void StandbyControl() { if (Input.GetMouseButtonDown(0))////(1) { Vector2 fingerPos = WorldToRect();////(2) if (fingerPos.x < BGRect.sizeDelta.x && fingerPos.x > 0 && fingerPos.y < BGRect.sizeDelta.y && fingerPos.y > 0)////(3) state = PuzzleState.Start;////(4) } }(1)當滑鼠左鍵按下時。
(2)儲存轉換的點擊位置。
(3)當點擊的範圍在下方轉珠的區塊時。
(4)把轉珠狀態切換成開始點擊(Start)。
接著是StartControl。
void StartControl() { if (Input.GetMouseButton(0))////(1) { Vector2 fingerPos = WorldToRect();////(2) fingerRect.anchoredPosition = new Vector2(Mathf.Clamp(fingerPos.x, 0, BGRect.sizeDelta.x), Mathf.Clamp(fingerPos.y, 0, BGRect.sizeDelta.y));////(3) if (finger.moveState)////(4) state = PuzzleState.Move; } else if (Input.GetMouseButtonUp(0))////(5) { GameObject.Find("PuzzleSystem").GetComponent<PuzzleSystem>().orbs[finger.nowOrb.number].GetComponent<Image>().color = Color.white;////(6) finger.FingerInit();////(7) state = PuzzleState.Standby;////(8) } }(1)當點著滑鼠左鍵時改變finger物件位置。
(2)儲存轉換的點擊位置。
(3)將移動範圍限制在下方的轉珠區塊並帶入finger物件位置。
(4)當finger開始移動時,把轉珠狀態切換成移動。
(5)當放開滑鼠左鍵時。
(6)將當前點擊的珠子顯示顏色修改回來。
(7)初始化finger物件數值。
(8)將轉珠狀態設回待機。
最後是MoveControl。
void MoveControl() { ////(1) if (Input.GetMouseButton(0)) { Vector2 fingerPos = WorldToRect(); fingerRect.anchoredPosition = new Vector2(Mathf.Clamp(fingerPos.x, 0, BGRect.sizeDelta.x), Mathf.Clamp(fingerPos.y, 0, BGRect.sizeDelta.y)); } ////(2) else if (Input.GetMouseButtonUp(0)) { GameObject.Find("PuzzleSystem").GetComponent<PuzzleSystem>().orbs[finger.nowOrb.number].GetComponent<Image>().color = Color.white; finger.FingerInit(); state = PuzzleState.End; } ////(3) timer += Time.deltaTime; if (timer >= puzzleTime) { GameObject.Find("PuzzleSystem").GetComponent<PuzzleSystem>().orbs[finger.nowOrb.number].GetComponent<Image>().color = Color.white; finger.FingerInit(); type = PuzzleState.End; timer = 0; } }(1)與StartControl相同,點擊時改變finger物件位置。
(2)滑鼠放開時做finger初始化等等動作,轉珠狀態切換為結束(End)
(3)除了滑鼠方開外,還要做計時並判斷是否超時的動作,超時同樣要切換成結束狀態。
沒意外的話執行後可以看到以下結果。
以上珠子移動與交換的說明,製作過程中遇到一些難點,基本上都是有關判斷的問題,使用UI物件或許不算是好方式,但硬著頭皮還是做出來了。
MoveControl那段寫得太累贅,滑鼠放開和超時的內容可以在合併。
另外StandbyControl存在一個不小的bug,會產生沒點到珠子但轉珠狀態卻換成開始的問題,物理上的解決方式可以調整Collider,或是追加判斷的變數。
最後在EndControl中做Part.2到Part.4的內容,轉珠系統的本體就完成的差不多了,最後就是追加上害計算和動畫。
這次就到這裡。
請問一下 裡面提到的nowOrb.number 是在Orb的裡面的哪一個參數??
回覆刪除number是int變數,前面文章好像漏掉。
刪除在生成珠子的時候依序給編號就可以了。
請問最後說的在「EndControl中做Part.2到Part.4的內容」是甚麼意思不太了解
回覆刪除還有在Finger物件上做了2D Rigidbody後,雖然一開始執行遊戲時不會掉落
可是移動珠子過後卻會往下掉落不會消失
非常感謝教學分享!!!