?游戲展示
拼圖演示
資源:?
鏈接:https://pan.baidu.com/s/1BGeSmRCO_WZRUyl3MxefGw?
提取碼:0n4a
一、玩法介紹
排列拼圖碎片,拼出最后的圖案??梢渣c(diǎn)住碎片的任意位置拖動(dòng);點(diǎn)擊"重來"按鈕,可以回到最初狀態(tài)重新開始。
二、流暢的拖拽操作
有很多電腦游戲的原型來自于現(xiàn)實(shí)世界中的玩具,拼圖游戲就是其中的一個(gè)代表。
本文我們介紹的拼圖游戲雖然是一款玩法比較簡單的游戲,不過這并不意味著開發(fā)也非常簡單。
相對于其他游戲通過操作鍵盤或移動(dòng)鼠標(biāo)來控制角色的運(yùn)動(dòng)方向,拼圖游戲通過鼠標(biāo)的拖拽直接移動(dòng)拼圖的碎片。
游戲的核心在于流暢的拖拽操作。
除了拖拽操作外,我們也可以借此機(jī)會思考一下諸如"當(dāng)碎片移動(dòng)到正確的位置附近時(shí)會被吸附到正確的位置"等觸屏游戲的小細(xì)節(jié)。
三、點(diǎn)住碎片的任意位置拖動(dòng)
Unity可以很容易判斷出"某個(gè)對象受到了點(diǎn)擊",不過如果要實(shí)現(xiàn)自然流暢的操作,我們?nèi)孕柘鹿Ψ?。這里,為了讓鼠標(biāo)的拖拽操作更急接近"用手指摁住移動(dòng)"的效果,我們需要考慮一下如何才能點(diǎn)住碎片的任意位置拖動(dòng)。
1、透視變換和逆透視變換
鼠標(biāo)的光標(biāo)位于屏幕上時(shí),其位置坐標(biāo)位于二維坐標(biāo)系內(nèi)。而拼圖碎片位于3D空間內(nèi),所以其位置坐標(biāo)自然有三個(gè)維度。為了比較鼠標(biāo)光標(biāo)和拼圖碎片的位置,必須將它們放入相同的坐標(biāo)系。因此,我們使用逆透視變換的方法,將鼠標(biāo)光標(biāo)的坐標(biāo)變換至三維坐標(biāo)系
2、被點(diǎn)擊處即為光標(biāo)的位置
通過逆透視變換將鼠標(biāo)光標(biāo)和拼圖碎片的坐標(biāo)統(tǒng)一到相同的坐標(biāo)系后,我們就該嘗試通過拖拽使拼圖移動(dòng)了,只需要在點(diǎn)擊按鍵的瞬間,將鼠標(biāo)光標(biāo)的坐標(biāo)復(fù)制到拼圖碎片的坐標(biāo)即可。這種方法確實(shí)非常簡單,不過它有個(gè)缺點(diǎn):鼠標(biāo)光標(biāo)總是顯示在拼圖碎片的中心。
在本游戲中,拼圖碎片被點(diǎn)擊的位置并不影響游戲的玩法。不過,對于某些游戲而言,點(diǎn)擊位置的不同可能會改變角色的朝向,或者是游戲角色以光標(biāo)為中心擺動(dòng),這些情況下在何處點(diǎn)擊就變得很重要了。
而且,即使不影響游戲的核心玩法,點(diǎn)擊的瞬間拼圖碎片會突然移動(dòng)一下這種體驗(yàn)也很糟糕。盡管有些時(shí)候這種機(jī)制可能會更好,但是為了應(yīng)對不同的要求,我們還是需要掌握如何能點(diǎn)住碎片的任意處拖動(dòng)。
在本游戲中,碎片的點(diǎn)擊判斷都是通過Unity的網(wǎng)格碰撞器實(shí)現(xiàn)的。網(wǎng)格碰撞器采用網(wǎng)格進(jìn)行碰撞檢測,點(diǎn)擊拼圖碎片的任何部位都將發(fā)生碰撞。對于玩家來說點(diǎn)擊碎片的哪個(gè)位置都可以,這反映到程序中就是"不用關(guān)心碎片的何處受到了點(diǎn)擊"。
點(diǎn)擊的瞬間,鼠標(biāo)光標(biāo)不一定位于碎片的中心。兩者的坐標(biāo)存在一定的差距,我們將這種坐標(biāo)的差距稱為偏移。
之前我們把光標(biāo)的坐標(biāo)原原本本地復(fù)制到碎片坐標(biāo)時(shí),因?yàn)閮蓚€(gè)坐標(biāo)值相同所以差距為0,這種坐標(biāo)差的急劇變化正是導(dǎo)致拼圖碎片突然移動(dòng)的原因。
知道了坐標(biāo)偏移值的變化是問題所在后,我們來考慮如何固定這個(gè)偏移值。首先,要在鼠標(biāo)點(diǎn)擊拼圖碎片的瞬間,也就是開始拖動(dòng)的時(shí)候,計(jì)算出鼠標(biāo)光標(biāo)和碎片中心的坐標(biāo)差,得到的值就是偏移值。
偏移=碎片的位置-鼠標(biāo)光標(biāo)的位置
拖動(dòng)的過程中則與之相反,用鼠標(biāo)光標(biāo)的位置加上偏移值就可以得到碎片的位置
碎片的位置=鼠標(biāo)光標(biāo)位置+偏移值
這樣一來,鼠標(biāo)光標(biāo)距離碎片中心總是保持一定的距離,這樣就保證了鼠標(biāo)點(diǎn)擊瞬間的位置就是碎片被拖拽的位置。
下面來看看實(shí)際的代碼
private void begin_dragging()
{
do {
// 將光標(biāo)坐標(biāo)變換為3D空間內(nèi)的世界坐標(biāo)
Vector3 world_position;
if(!this.unproject_mouse_position(out world_position, Input.mousePosition)) {
break;
}
if(PieceControl.IS_ENABLE_GRAB_OFFSET) {
// 求出偏移值(點(diǎn)擊位置距離碎片的中心有多遠(yuǎn))
this.grab_offset = this.transform.position - world_position;
}
} while(false);
}
private void do_dragging()
{
do {
// 將光標(biāo)坐標(biāo)變換為3D空間內(nèi)的世界坐標(biāo)
Vector3 world_position;
if(!this.unproject_mouse_position(out world_position, Input.mousePosition)) {
break;
}
// 加上光標(biāo)坐標(biāo)(3D)的偏移值,計(jì)算出碎片的中心坐標(biāo)
this.transform.position = world_position + this.grab_offset;
} while(false);
}
四、打亂拼圖碎片
商店里售賣紙質(zhì)拼圖游戲時(shí)一般會將拼圖碎片打亂順序后放入包裝盒中。雖然也有些是已經(jīng)拼好的狀態(tài),不過玩家在開始游戲之前還是要將各碎片的順序打亂。
有很多事情都是"人類做起來很簡單,計(jì)算機(jī)處理起來卻很難",比如將拼圖碎片全部打亂這件事就是一個(gè)例子。
Unity提供了取得隨機(jī)數(shù)的方法,不過單純使用該方法似乎并不能達(dá)到打亂碎片順序的目的。
這里我們不妨來分析一下如何隨機(jī)打亂各拼圖碎片的順序。
1、設(shè)置拼圖碎片的坐標(biāo)為隨機(jī)數(shù)
最簡單的隨機(jī)打亂拼圖碎片的方法是,直接將隨機(jī)數(shù)代入各個(gè)碎片的坐標(biāo)。只要控制好隨機(jī)數(shù)的范圍,就能讓各個(gè)拼圖碎片隨機(jī)分布在畫面上。
但這種方式的弊端也很明顯,就是有很多拼圖碎片可能疊在一起。雖然這樣也未嘗不可,不過可以的話最好還是將各碎片均勻分散開。如果很多碎片重疊在一起,就可能導(dǎo)致下面的碎片被覆蓋而無法看見。
2、改進(jìn)策略
首先我們整理一下拼圖碎片隨機(jī)散開的要求,即需求分析
- 碎片之間彼此互不重疊
- 碎片散開分布到整個(gè)畫面上
- 隨機(jī)分散各個(gè)碎片
需求基本上就是這樣。如果拼圖碎片的數(shù)量有所增加,可能還需要追加一項(xiàng)"能夠控制游戲的難易度"。
接下來我們對實(shí)現(xiàn)方法進(jìn)行說明。首先簡單熟悉一下整體流程
- 將拼圖碎片分配到網(wǎng)格中
- 打亂拼圖碎片的排列順序
- 在網(wǎng)格內(nèi)通過隨機(jī)坐標(biāo)調(diào)整碎片的位置
- 將整個(gè)拼圖隨機(jī)旋轉(zhuǎn)一定角度
我們可以選擇任意圖片,將其分割成幾塊。這里我們選擇一個(gè)"貓頭鷹"圖片,將其分割成8塊。
首先,將所有的拼圖碎片從左上角開始依次放入網(wǎng)格中。
該網(wǎng)格的行數(shù)和列數(shù)相同,并且網(wǎng)格總數(shù)達(dá)于拼圖碎片數(shù)量。"貓頭鷹"拼圖碎片數(shù)量為8,我們就搞一個(gè)3??3的網(wǎng)格,空出來的格子不用理會。根據(jù)碎片數(shù)量的不同,有時(shí)候剩余的格子會比較多,這種情況下可以調(diào)整網(wǎng)格的行數(shù)和列數(shù)。
所有網(wǎng)格塊都為正方形,且都應(yīng)當(dāng)能確保能夠容納下拼圖碎片。另外,因?yàn)楹罄m(xù)步驟在網(wǎng)格內(nèi)移動(dòng)拼圖碎片,所以還需要在確保整體網(wǎng)格不溢出畫面的前提下適當(dāng)放大網(wǎng)格的尺寸。
之所以想這樣把拼圖碎片紡織到網(wǎng)格中,是為了避免出現(xiàn)碎片之間彼此重疊的狀況。
接下來隨機(jī)打亂個(gè)碎片的排列位置
在第一個(gè)步驟中,我們將碎片從左上角開始依次放入了網(wǎng)格中。而第二個(gè)步驟就是打亂各個(gè)碎片的排列順序。利用隨機(jī)數(shù)選出兩個(gè)網(wǎng)格,案后交換其中的碎片空白的網(wǎng)格也可以參與交換。
做到這里,前面我們做出的需求分析中,"碎片之間彼此互不重疊","碎片分散于整個(gè)畫面"和"隨機(jī)分散各個(gè)碎片"就已經(jīng)基本得到了實(shí)現(xiàn)。不過從程序?qū)嶋H情況來看,很容易發(fā)現(xiàn)拼圖碎片被規(guī)則地排列在了網(wǎng)格上。我們得想辦法讓這種隨機(jī)分散的效果更真實(shí)。
在第三個(gè)步驟中,我們讓拼圖碎片在網(wǎng)格中隨機(jī)移動(dòng)。
最初的步驟中增加網(wǎng)格尺寸的用意就在于為這里的碎片移動(dòng)做準(zhǔn)備。如果網(wǎng)格的尺寸太小,將無法移動(dòng)碎片,反之如果太大,則會令碎片之間過于松散。我們需要結(jié)合拼圖碎片的大小和畫面整體的尺寸,調(diào)整網(wǎng)格尺寸為最佳值,
最后,為了不讓玩家看出碎片排列的規(guī)律性,稍微將拼圖網(wǎng)格整體旋轉(zhuǎn)一定的角度
雖然旋轉(zhuǎn)了整體的網(wǎng)格,但是需要保持拼圖碎片自身的角度不變。文章來源:http://www.zghlxwxcb.cn/news/detail-634677.html
下面,我們結(jié)合實(shí)際代碼來看看這一流程文章來源地址http://www.zghlxwxcb.cn/news/detail-634677.html
private void shuffle_pieces()
{
#if true
// 將碎片按照網(wǎng)格順序排列
int[] piece_index = new int[this.shuffle_grid_num*this.shuffle_grid_num];
for(int i = 0;i < piece_index.Length;i++) {
if(i < this.all_pieces.Length) {
piece_index[i] = i;
} else {
piece_index[i] = -1;
}
}
// 隨機(jī)選取兩個(gè)碎片,交換位置
for(int i = 0;i < piece_index.Length - 1;i++) {
int j = Random.Range(i + 1, piece_index.Length);
int temp = piece_index[j];
piece_index[j] = piece_index[i];
piece_index[i] = temp;
}
// 通過位置的索引變換為實(shí)際的坐標(biāo)來進(jìn)行配置
Vector3 pitch;
pitch = this.shuffle_zone.size/(float)this.shuffle_grid_num;
for(int i = 0;i < piece_index.Length;i++) {
if(piece_index[i] < 0) {
continue;
}
PieceControl piece = this.all_pieces[piece_index[i]];
Vector3 position = piece.finished_position;
int ix = i%this.shuffle_grid_num;
int iz = i/this.shuffle_grid_num;
position.x = ix*pitch.x;
position.z = iz*pitch.z;
position.x += this.shuffle_zone.center.x - pitch.x*(this.shuffle_grid_num/2.0f - 0.5f);
position.z += this.shuffle_zone.center.z - pitch.z*(this.shuffle_grid_num/2.0f - 0.5f);
position.y = piece.finished_position.y;
piece.start_position = position;
}
// 逐步(網(wǎng)格的格子內(nèi))隨機(jī)移動(dòng)位置
Vector3 offset_cycle = pitch/2.0f;
Vector3 offset_add = pitch/5.0f;
Vector3 offset = Vector3.zero;
for(int i = 0;i < piece_index.Length;i++) {
if(piece_index[i] < 0) {
continue;
}
PieceControl piece = this.all_pieces[piece_index[i]];
Vector3 position = piece.start_position;
position.x += offset.x;
position.z += offset.z;
piece.start_position = position;
//
offset.x += offset_add.x;
if(offset.x > offset_cycle.x/2.0f) {
offset.x -= offset_cycle.x;
}
offset.z += offset_add.z;
if(offset.z > offset_cycle.z/2.0f) {
offset.z -= offset_cycle.z;
}
}
// 使全體旋轉(zhuǎn)
foreach(PieceControl piece in this.all_pieces) {
Vector3 position = piece.start_position;
position -= this.shuffle_zone.center;
position = Quaternion.AngleAxis(this.pazzle_rotation, Vector3.up)*position;
position += this.shuffle_zone.center;
piece.start_position = position;
}
this.pazzle_rotation += 90;
#else
// 簡單地使用隨機(jī)數(shù)來決定坐標(biāo)時(shí)的情況
foreach(PieceControl piece in this.all_pieces) {
Vector3 position;
Bounds piece_bounds = piece.GetBounds(Vector3.zero);
position.x = Random.Range(this.shuffle_zone.min.x - piece_bounds.min.x, this.shuffle_zone.max.x - piece_bounds.max.x);
position.z = Random.Range(this.shuffle_zone.min.z - piece_bounds.min.z, this.shuffle_zone.max.z - piece_bounds.max.z);
position.y = piece.finished_position.y;
piece.start_position = position;
}
#endif
}
到了這里,關(guān)于Unity小游戲——迷你拼圖的文章就介紹完了。如果您還想了解更多內(nèi)容,請?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!