NEIL的Unity學習筆記(3) - 轉珠寫法簡易入門

寫了一個轉珠的遊戲,順道也寫一下教學,有興趣的話可以下載看看

https://play.google.com/store/apps/details?id=com.FruitsPuzzle.CF

這個簡易寫法跟一些主流遊戲的差別在於珠子的移動判斷。
我分享的這個簡易入門版只是進行了珠子的位置互換,而主流遊戲如龍族拼圖,它可能是使用了類似a star演算法。更具體一點來說,這個簡易版的缺憾就是,如果你移動太快,會偵測不到移動路徑中的珠子,會直接與最後位置的珠子互換。而龍族拼圖會把你最後的位置進行A* 尋路演算,與路徑中的每一顆珠子互換。

另外這個寫法是原地重新,非主流遊戲的往下/往上掉落。消珠的方式也有差異。
所以這算是一個概念式的入門分享,更進階的請自己努力去想一想吧。

首先,我們會利用2D array的寫法去建立5*6的架構

建立一個public static DATA的公用資料庫,然後進行下列定義

public static int row=5;    //這裡 row 和 column的名字我倒轉了, 別在意, 看懂就好
public static int column=6;
public static int[,] _Matrix = new int[column,row];

注意一下,在unity C sharp的寫法是 int[ a,b], 而不是int[a][b]。

定義好之後,我們利用for loop去快速的幫我們在各個位置設定好數值

for(int b=0;b<column;b++){
for(int a=0;a<row;a++){
_Matrix[b,a]=Random.Range(0,5);
}
}
因為public static DATA只作資料存取之用,不用也不會依附在物件上面,程式只會在呼叫時才會運用,所以我們要建立一個主運行的script,我把他叫作GlobalControllor。

然後在裡面建立一個產生珠子的function
void respawn(){
//預先建立一個空物件供珠子依附,如果要共用function的話多做一個動作把珠子移除,避免重複產生
balls.BroadcastMessage ("_Destroy",SendMessageOptions.DontRequireReceiver);
int i=0,r=0; // i 是用作給予珠子編號, r 是記錄架構位置的數值
GameObject[] objs=new GameObject[30];// 預先建立位置, 5*6 共30顆珠子
GameObject prefab= Resources.Load ("Prefabs/apple")as GameObject;;
GameObject apple = Resources.Load ("Prefabs/apple")as GameObject;
GameObject blueberry = Resources.Load ("Prefabs/blueberry")as GameObject;
GameObject watermelon = Resources.Load ("Prefabs/watermelon")as GameObject;
GameObject pineapple = Resources.Load ("Prefabs/pineapple")as GameObject;
GameObject mangosteen = Resources.Load ("Prefabs/mangosteen")as GameObject;

for(int a=0;a<Data.row;a++){
for(int b=0;b<Data.column;b++){
r=Data._Matrix[b,a];
switch(r){
case 0:
prefab=apple;
break;
case 1:
prefab=blueberry;
break;
case 2:
prefab=watermelon;
break;
case 3:
prefab=pineapple;
break;
case 4:
prefab=mangosteen;
break;
}

objs[i]=Instantiate(prefab,new Vector3(b,a,0),Quaternion.identity)as GameObject;
objs[i].transform.Rotate(0,180,0);
objs[i].transform.SetParent(balls.transform);
//layer 8是自己建立的camera mask
objs[i].layer=8;
//這一句是把ball 這個script 附加上去
objs[i].AddComponent<ball>();
//再把數值傳過去在ball裡面的一個叫 assign的function
objs[i].SendMessage("assign",r);
i++;
}
}
}


產生出來差不多就是這個樣子。

珠子要先進行預製物prefab的操作,加上collider,用來作觸控的偵測。
以下是觸控的部份,可以先從官網找到touch裡面的範例,然後把下列更換上去
switch (touch.phase) {
case TouchPhase.Began:
if(state==0){// state是我自己多加的一個key, 確定觸控的狀態
ray = _cam.ScreenPointToRay(Input.mousePosition);
hit = Physics.RaycastAll(ray.GetPoint(0.1f), transform.forward, 100);
if(hit.Length>0){
//這裡先把第一顆珠子位置資料記錄下來
touchTarget=hit[0].transform.gameObject;
touchTargetPos=hit[0].transform.position;
touchTarget.transform.position=new Vector3(touchTarget.transform.position.x,touchTarget.transform.position.y,-2);
}
}
break;


case TouchPhase.Moved:
state=1;
if(touchTarget){
ray = _cam.ScreenPointToRay(Input.mousePosition);
if(ray.origin.y>4){
ray.origin=new Vector2(ray.origin.x,4);
hit = Physics.RaycastAll(new Vector3(ray.origin.x,ray.origin.y,-6), transform.forward, 100);
}
else{
hit = Physics.RaycastAll(ray.GetPoint(0.1f), transform.forward, 100);
}
touchTarget.transform.position=new Vector3(ray.origin.x,ray.origin.y,-2);
//這裡進行珠子的位置交換
if(hit.Length>1){// exchange postion 
if(touchTarget!=hit[0].transform.gameObject){
touchTarget2=hit[0].transform.gameObject;
Vector3 temp=touchTarget2.transform.position;
touchTarget2.transform.position=touchTargetPos;
touchTargetPos=temp;
}
else if(touchTarget!=hit[1].transform.gameObject){
touchTarget2=hit[1].transform.gameObject;
Vector3 temp=touchTarget2.transform.position;
touchTarget2.transform.position=touchTargetPos;
touchTargetPos=temp;
}
}
}
break;

在moved 裡面一段
if(ray.origin.y>4){
ray.origin=new Vector2(ray.origin.x,4);
hit = Physics.RaycastAll(new Vector3(ray.origin.x,ray.origin.y,-6), transform.forward, 100);
}
是用作防止珠子根據觸控的位置超出應有的範圍,當超過時重新給予定向。

case TouchPhase.Ended:
state=0;
if(touchTarget){
touchEnd();
}
touchTarget=null;
break;

void touchEnd(){
if(touchTarget){
// 把觸控的珠子放好
touchTarget.transform.position=new Vector3(touchTargetPos.x,touchTargetPos.y,0);
}
touchTarget=null;//清空
sendTarget.BroadcastMessage("set_position");
//因為位置改變了,所以要重新填入數值,在之前已經給珠子附在一個父物件上,所以用這一句可以向每一個它的子物件傳送指示
state=0;
Data.check_combo ();// 這裡是檢查消珠,隨後說明。
}
在script<Ball>裡建立一個function
void set_position(){
column = (int)transform.position.x;
row = (int)transform.position.y;
Data._Matrix[column,row]=typeNUM;
//這個typeNUM是之前傳過去的r,請自己定義typeNUM
}
因為物件位置是直接用架構位置設定的,所以直接把XY轉回int就可以設定。

回到DATA,我們建立一個進行消珠的function
先定義兩個參數用來儲存可消除的珠子位置
public static Vector2[] buffer = new Vector2[300];
public static int buffer_count=0
public static void check_combo(){
combo = 0;
isDestroy = false;
int count = 0;
for(int b=0;b<column;b++){
for(int a=0;a<row;a++){
//先從行(直)進行檢測
if(_Matrix[b,a]!=-1){
if(a<row-2){
//因為每3顆才成立消除條件,當倒數第3也不成立的時候,之後那兩顆也就不用檢查
if(_Matrix[b,a]==_Matrix[b,a+1]){
if(_Matrix[b,a]==_Matrix[b,a+2]){
if(a<=1){
if(_Matrix[b,a]==_Matrix[b,a+3]){
if(a==0){
if(_Matrix[b,a]==_Matrix[b,a+4]){
buffer[buffer_count]=new Vector2(b,a+4);
buffer_count++;
}
}
buffer[buffer_count]=new Vector2(b,a+3);
buffer_count++;
}
}
buffer[buffer_count]=new Vector2(b,a);
buffer_count++;
buffer[buffer_count]=new Vector2(b,a+1);
buffer_count++;
buffer[buffer_count]=new Vector2(b,a+2);
buffer_count++;
//成立的珠子我們先不去改變數值,先進行儲存
}
}
}
if(b<column-2){//進行列(橫)檢查
if(_Matrix[b,a]==_Matrix[b+1,a]){
if(_Matrix[b,a]==_Matrix[b+2,a]){
if(b<=2){
if(_Matrix[b,a]==_Matrix[b+3,a]){
if(b<=1){
if(_Matrix[b,a]==_Matrix[b+4,a]){
if(b==0){
if(_Matrix[b,a]==_Matrix[b+5,a]){
buffer[buffer_count]=new Vector2(b+5,a);
buffer_count++;
}
}
buffer[buffer_count]=new Vector2(b+4,a);
buffer_count++;
}
}
buffer[buffer_count]=new Vector2(b+3,a);
buffer_count++;
}
}
buffer[buffer_count]=new Vector2(b,a);
buffer_count++;
buffer[buffer_count]=new Vector2(b+1,a);
buffer_count++;
buffer[buffer_count]=new Vector2(b+2,a);
buffer_count++;
}
}
}
}
}
}
//最後一次性地把所有珠子的數值改變
for(int i=0;i<buffer_count;i++){
if(_Matrix[(int)buffer[i].x,(int)buffer[i].y]!=-1){
_Matrix[(int)buffer[i].x,(int)buffer[i].y]=-1;
}
}
}

再回到ball 去做位置數值檢查,如果是 -1 就Destroy(gameobject);
再進行一次位置檢查,把-1的地方重新生成物件

分享到這邊,完

留言

  1. 不好意思問一下,版大所有的script檔有哪些?
    一開始的DATA公用資料庫是一個script嗎?
    哪些又該寫在start ,哪些又該寫在update呢?

    回覆刪除
  2. 1.基本兩個:data,move
    2.data是static ,沒start and update
    3.move 部份直接寫在update
    建議先熟練程式結構,這篇不適合初學者直接上手

    回覆刪除

張貼留言

這個網誌中的熱門文章

21-5-2021 時空之門CrossGate/魔力寶貝 主線 劇情 - 4000年輪迴的終結

2020-6-19 《XENOBLADE異度神劍》- 善意地阻止你踩雷

2019-10-21 《Dragon Quest勇者鬥惡龍XI S》 評測 - 平淡而完滿