上次說到了珠子的消除、落下和生成,接下來就要進入珠子的移動與交換,以下是這次的目標。
使用滑鼠或手指按著珠子,接著拖曳珠子移動可以與其他位置的珠子交換,直到放開珠子或指定時間到時結束。
珠子交換本文中會使用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後,雖然一開始執行遊戲時不會掉落
可是移動珠子過後卻會往下掉落不會消失
非常感謝教學分享!!!