Android 3D 魔方游戲的設(shè)計與開發(fā)
Android 3D 魔方游戲的設(shè)計與開發(fā)
5.1 Feature 定義
魔方是一個有趣的益智游戲,相信很多人都玩過。本次畢業(yè)設(shè)計,欲完成的主要的功能如下:
(1) 開始游戲:開始一個新的游戲
(2) 返回游戲:當游戲已經(jīng)開始,“開始游戲”按鈕將不可用,玩家可通過“返回游戲”按鈕進入先前的游戲界面。
(3) 游戲記錄:保存玩家的游戲記錄,包括排名、玩家姓名、還原魔方所用時間、還原所用的步驟、游戲的日期。
(4) 游戲說明:介紹游戲的操作方法及各個菜單
(5) 退出游戲:結(jié)束游戲
(6) 整體旋轉(zhuǎn):玩家在任一時刻可以同時看到魔方的三個面,玩家可通過旋轉(zhuǎn)按鈕或在魔方外區(qū)域滑動使魔方整體旋轉(zhuǎn),使玩家對魔方整體情況有個了解。
(7) 單層旋轉(zhuǎn):玩家在魔方上滑動可對魔方進行每一層的旋轉(zhuǎn)。
(8) 游戲計時:玩家剛進入游戲時,如果進行整體的翻轉(zhuǎn),則不算時間;如果是單層旋轉(zhuǎn)則開始計時,這時,如果進行整體旋轉(zhuǎn)也算入用時。但游戲期間如果切換到其它界面,則暫停計時。
(9) 開關(guān)按鈕:游戲界面設(shè)置七個圖片按鈕,最左上角的為開關(guān)按鈕,點擊它可以打開或關(guān)閉其它 6 個按鈕。
(10) 菜單按鈕:點擊菜單按鈕可彈出游戲菜單
(11) 放大與縮小按鈕:對魔方的大小進行調(diào)整
(12) 翻轉(zhuǎn)按鈕:由于屏幕大小或玩家的操作習(xí)慣不同,故加入此按鈕,使玩家對魔方的整體翻轉(zhuǎn)有更多的選擇。
(13) 重新開始:將魔方還原到原始的狀態(tài),重新開始游戲,用時及步數(shù)重新開始計算。
(14) 隨機打亂:將一個魔方隨機地轉(zhuǎn)動若干次,旋轉(zhuǎn)次數(shù)可由玩家設(shè)定。
(15) 自動還原:將一個打亂的魔方還原
(16) 回主菜單:返回到游戲的主菜單界面
(17) 游戲設(shè)置:對游戲進行設(shè)置,如整體翻轉(zhuǎn)和單層旋轉(zhuǎn)的速度,隨機打亂的步數(shù)、是否顯示計時欄。
(18) 關(guān)于游戲:關(guān)于游戲的一些信息,如版本號、作者、版權(quán)等
5.2 類的設(shè)計
代碼統(tǒng)計

About: 游戲的關(guān)于頁面
Color: 顏色類,用于給魔方上色
Cube: 立方體,一個三階魔方由 27 個小立方體組成
DataBaseAdapter: 數(shù)據(jù)庫接口
DataBaseBean: 封裝好的數(shù)據(jù)庫操作類
Face: 面,每個立方體都有 6 個面
FancyCube: 程序的入口點,負責繪制主菜單界面
GameMain: 游戲的主類,主要負責游戲場景的構(gòu)建、坐標的轉(zhuǎn)換、魔方旋轉(zhuǎn)、保存設(shè)置與加載設(shè)置、游戲狀態(tài)的判斷等
GameRecord: 顯示、保存與刪除游戲戲記錄
Gesture: 手勢判斷
Help: 游戲說明
Layer: 層,一個三階魔方由 9 個層構(gòu)成
Matrix: 矩陣,點與矩陣的乘法、矩陣與矩陣的乘法
MatrixGrabber 、 MatrixStack 、 MatrixTrackingGL: 這三個類就是為了實現(xiàn)一個功能、攔截當前的模型矩陣
Point: 平面上的點,主要是進行數(shù)學(xué)運算
Quadrilateral: 平面四邊形類,進行點相對四邊形的位置的判定
Render:Renderer 接口的實現(xiàn)類,負責圖形渲染
SelfReduction: 魔方自動還原類,首先得到當前的魔方狀態(tài),然后進行一系列的計算與判斷,最后算出還原的步驟
Settings: 游戲設(shè)置
Shape: 形狀類, Cube 的父類
TimingCounter: 計時器、根據(jù)游戲的狀態(tài)進行計時
Triangle: 三角形,判斷一個點是在三角形內(nèi)還是三角形外
Vertex: 頂點,每個立方體都有 8 個頂點
World: 游戲場景類,保存魔方頂點坐標、顏色、紋理坐標、索引數(shù)據(jù)
5.3 UI 設(shè)計
( 1 )主菜單:從圖 5-1 中可以看到按鈕有三種狀態(tài):正常狀態(tài)、不可選狀態(tài)、點擊狀態(tài)。根據(jù)按鈕的狀態(tài)。會自動選擇按鈕的背景圖片、文字顏色。

圖5-1 主菜單界面
( 2 )游戲記錄:在某條記錄上長按會彈出刪除對話框,如圖 5-3 所示。游戲的排名是根據(jù)用時、用時相同根據(jù)步數(shù)、步數(shù)相同則根據(jù)日期 , 日期越新越靠前,如圖 5-2 所示。


?3 )游戲說明:介紹游戲的操作及各個游戲菜單,如圖 5-4 所示。

圖5-4 游戲說明
( 4 )游戲界面:玩家可選擇是否顯示計時欄及控制按鈕、可對魔方的大小進行調(diào)整。


( 5 )游戲成功界面:當玩家還原魔方成功的時候會提示保存游戲記錄。


( 6 )游戲菜單:當按下鍵盤上的菜單鍵或屏幕上方的菜單按鈕時、就會彈出如圖 5-7 所示的游戲菜單。

( 7 )游戲設(shè)置:可以對游戲的旋轉(zhuǎn)速度、打亂步驟、計時欄狀態(tài)等進行設(shè)置。


( 8 )關(guān)于游戲 : 關(guān)于本游戲的一些信息,如版本、作者、版權(quán)等。如圖 5-10所示。

5.4 功能設(shè)計
5.4.1 3D 魔方場景的構(gòu)建
在 GameMain 類中調(diào)用 makeWorld 方法生成 27 個小立方體。對于每一個小立方體,它有八個頂點及六個面。 Cube 類繼承于 Shape 類, Shape 類有一個 addVertex和 addFace 的方法, Cube 類繼承了這兩個方法,在生成每一個小立方的過程中,同時保存頂點和面及索引的數(shù)據(jù),然后給魔方的每個面上色,最后通過 addShape方法將這 27 個小立方體添加到世界場景中,并創(chuàng)建魔方的 9 個層及分配每一層的小立方體,這時就得到了生成整個魔方所需要的所有數(shù)據(jù)。再通過 Render (渲染器)調(diào)用 World 類的 draw 方法進行圖形的繪制。
5.4.2 魔方單個層的旋轉(zhuǎn)
在 OpenGL ES 中,旋轉(zhuǎn)這個操作的底層實現(xiàn)是通過維護一個 4*4 的矩陣來實現(xiàn)的。根據(jù)旋轉(zhuǎn)軸的不同,生成不同的矩陣,然后將生成的矩陣與點的坐標相乘得到另一個坐標,該坐標即為旋轉(zhuǎn)后的新坐標。
public void setAngle( float angle)
{
float twopi = ( float ) Math. PI * 2f ;
while (angle >= twopi)
angle -= twopi;
while (angle < 0f )
angle += twopi;
float sin = ( float ) Math.sin (angle);
float cos = ( float ) Math.cos (angle);
float [][] m = mTransform . matrix ;
switch ( mAxis )
{
case kAxisX :
m[1][1] = cos;
m[1][2] = sin;
m[2][1] = -sin;
m[2][2] = cos;
m[0][0] = 1f ;
m[0][1] = m[0][2] = m[1][0] = m[2][0] = 0f ;
break ;
case kAxisY :
m[0][0] = cos;
m[0][2] = sin;
m[2][0] = -sin;
m[2][2] = cos;
m[1][1] = 1f ;
m[0][1] = m[1][0] = m[1][2] = m[2][1] = 0f ;
break ;
case kAxisZ :
m[0][0] = cos;
m[0][1] = sin;
m[1][0] = -sin;
m[1][1] = cos;
m[2][2] = 1f ;
m[2][0] = m[2][1] = m[0][2] = m[1][2] = 0f ;
break ;
}
for ( int i = 0; i < mShapes . length ; i++)
{
Shape shape = mShapes [i];
if (shape != null )
shape.animateTransform( mTransform );
}
5.4.3 魔方整體的旋轉(zhuǎn)
魔方的整體旋轉(zhuǎn)可分解為三個層的同時旋轉(zhuǎn)
5.4.4 OpenGL 3D 鼠標拾取的實現(xiàn)
( 1 ) OpenGL 圖形管線

圖 5-11 演示了 OpenGL 中關(guān)于坐標系統(tǒng)的一系列變換。在 OpenGL 中頂點坐標稱作模型坐標。模型視圖矩陣將這些坐標變換成視覺坐標。投影矩陣將視覺坐標變換成裁剪坐標。透視除法將裁剪坐標變換成規(guī)格化設(shè)備坐標。視口變換最終將這些坐標變換成窗口坐標。模型坐標,視覺坐標和裁剪坐標都為 4 維值,分別為 x,y,z和 w 坐標。因此,模型視圖矩陣和物體矩陣為 4*4 矩陣 [13]。
( 2 )模型視圖矩陣及投影矩陣的獲取
在 OpenGL 中可以直接使用 glGetFloatv 這個方法獲得 OpenGL 管線層的模型視圖矩陣及投影矩陣 [14]。但在 OpenGL ES 中刪除了這個方法,所以得自己手動攔截這兩個矩陣。使用谷歌 ApiDemos 中的 MatrixGrabber 、 MatrixStack 、MatrixTrackingGL 這三個類就可以實現(xiàn)攔截。
( 3 )模型坐標與屏幕坐標的轉(zhuǎn)換
攔截到模型視圖矩陣與投影矩陣后,再加上一個視口坐標(通常就是屏幕的寬度和高度) , 根據(jù)上圖的變換流程或使用系統(tǒng)提供的轉(zhuǎn)換函數(shù)就能得到一個平面坐標 [15]。
GLU.gluProject(mVisibleVertexs[i].x, mVisibleVertexs[i].y, mVisibleVertexs[i].z, mMatrixGrabber.mModelView, 0, mMatrixGrabber.mProjection, 0, mRender.viewPort, 0, temp, 0);
在游戲視角下,玩家可見的魔方頂點一共有 37 個,通過 gluProject 方法可將這37 個三維坐標轉(zhuǎn)換成 37 個平面坐標,這 37 個平面點又構(gòu)成了 27 個不規(guī)則四邊形, 當玩家手指在屏幕上滑動的時候,會得到兩個點(起點與終點)的平面坐標。根據(jù)起點所在區(qū)域決定魔方是進行整體的翻轉(zhuǎn)還是單層的旋轉(zhuǎn)。根據(jù)終點所在區(qū)域決定魔方的轉(zhuǎn)動方向。
另外,因為 OpenGL 的坐標與 Windows 窗口的坐標不一樣,所以進行判斷之前還需要再進行一次坐標轉(zhuǎn)換。 OpenGL 中的 (0,0) 點是在屏幕左下角,而 Windows的 (0,0) 點是在屏幕的左上角,所以 X 軸的坐標保持不變,但 Y 軸的坐標 y=height-y; height 是在渲染器的 onSurfaceChanged 中設(shè)定的,通常都會設(shè)定為屏幕的高度 [16]。
/* 當窗口大小發(fā)生改變時調(diào)用,在程序開始時至少運行一次,設(shè)置場景的大小 */
public void onSurfaceChanged(GL10 gl, int width, int height)
{
this . width = width;
this . height = height;
viewPort = new int [] { 0, 0, width, height };
float ratio = ( float ) width / height;
// 設(shè)置場景的大小
gl.glViewport(0, 0, width, height);
// 設(shè)置投影矩陣
gl.glMatrixMode(GL10. GL_PROJECTION );
// 重置投影矩陣
gl.glLoadIdentity();
// 設(shè)置視口的大小
gl.glFrustumf(-ratio, ratio, -1, 1, 2, 12);
// 得到投影矩陣
mGameMain . mMatrixGrabber .getCurrentProjection(gl);
// 選擇模型觀察矩陣
gl.glMatrixMode(GL10. GL_MODELVIEW );
// 重置當前模型觀察矩陣
gl.glLoadIdentity();
}
( 4 )點與直線 (AB) 距離
// 點與直線 AB 的距離
public double getDistance(Point A, Point B)
{
// 分子
double numerator = Math.abs ((B. y - A. y ) * this . x + (A. x - B. x ) * this . y + B. x * A. y - A. x * B. y );
// 分母
double denominator = Math.sqrt ((A. x - B. x ) * (A. x - B. x ) + (A. y - B. y ) * (A. y - B. y ));
return numerator / denominator;
}
( 5 )兩點之間的距離
// 兩點之間的距離
public double getDistance(Point another)
{
return Math.sqrt (( x - another. x ) * ( x - another. x ) + ( y - another. y ) * ( y - another. y ));
}
( 6 )點與四邊形的關(guān)系判斷
連接點與四邊形的四個點可得到四個角度,如果這 4 個角度相加為 360 度或者這 4 個角度中出現(xiàn)了 0 的情況,則點在四邊形內(nèi)或在四邊形的邊上。
/***********************************
******* A(p1) D(p4) *******
******* -------------- *******
******* \ O / *******
******* \ * / *******
******* \ / *******
******* ------ *******
******* B(p2) C(p3) *******
***********************************/
public boolean inQuadrilateral(Quadrilateral q)
{
double ab = q. p1 .getDistance(q. p2 );
double bc = q. p2 .getDistance(q. p3 );
double cd = q. p3 .getDistance(q. p4 );
double da = q. p4 .getDistance(q. p1 );
double oa = getDistance(q. p1 );
double ob = getDistance(q. p2 );
double oc = getDistance(q. p3 );
double od = getDistance(q. p4 );
double cosAOB = (oa * oa + ob * ob - ab * ab) / (2 * oa * ob);
double cosBOC = (ob * ob + oc * oc - bc * bc) / (2 * ob * oc);
double cosCOD = (oc * oc + od * od - cd * cd) / (2 * oc * od);
double cosDOA = (od * od + oa * oa - da * da) / (2 * oa * od);
double AOB = Math.acos (cosAOB);
double BOC = Math.acos (cosBOC);
double COD = Math.acos (cosCOD);
double DOA = Math.acos (cosDOA);
if (Math.abs (AOB + BOC + COD + DOA - Math. PI * 2) < exp )
return true ;
if (Math.abs (AOB) < exp || Math.abs (BOC) < exp || Math.abs(COD) < exp || Math.abs (DOA) < exp )
return true ;
return false ;
}
( 7 )點 (P) 與有向直線 (AB) 的關(guān)系判斷(左邊或右邊或直線上)
PAB 組成一個三角形,利用三角形面積計算公式(如圖 5-12 所示):

這個表達式的正負數(shù)值,可以區(qū)分 P 位于 AB 直線的哪一側(cè)。如果 s 是正的,表示三點逆時針排列,否則是順時針排列,等于 0 是共線的。

// 判斷點在有向直線(由點 A 與點 B 構(gòu)成,方向為由 A 指向 B )的哪一側(cè),返回 true表示在 AB 左側(cè),返回 false 表示 AB 在右側(cè)
public boolean inLeftSide(Point A, Point B)
{
float a, b, c, d;
a = B. y - A. y ;
b = A. x - B. x ;
c = B. x * A. y - A. x * B. y ;
d = a * this . x + b * this . y + c;
// 小于 0 為左側(cè),大于 0 為右側(cè),等于 0 為在直線上
if (d < 0)
return true ;
else
return false ;
}
( 8 )點與三角形關(guān)系的判斷
沿著三角形的順時針或逆時針順序,可依次得到三條有向直線,如果點都在這三條有向直線的同一側(cè),則在三角形中。

// 判斷點是否在三角形內(nèi)
public boolean inTriangle(Triangle mTriangle)
{
boolean b1 = this .inLeftSide(mTriangle. p1 , mTriangle. p2 );
boolean b2 = this .inLeftSide(mTriangle. p2 , mTriangle. p3 );
boolean b3 = this .inLeftSide(mTriangle. p3 , mTriangle. p1 );
if ((b1 && b2 && b3) || (!b1 && !b2 && !b3))
{
return true ;
}
else
return false ;
}
( 9 )翻轉(zhuǎn)方向的確定
若觸點在魔方區(qū)域外,則進行魔方的整體旋轉(zhuǎn)。將游戲界面劃分為 6 個區(qū)域,分別為左上、上、右上、左、右、左下、下、右下。對每一個區(qū)域進行如下判斷:沿著每個區(qū)域作一條與魔方邊緣平行的直線 AB (由 A 指向 B ),過該點( A )作直線( AB )的法線。這時候形成了一個垂直的十字架,共四個區(qū)域,然后分別作這四個區(qū)域的角平分線,形成了八個區(qū)域,如圖 5-15 所示。然后根據(jù)點與有向直線的關(guān)系來判斷手勢終點所在的區(qū)域。

/**
* 點 A 和點 B 構(gòu)成一條直線,當前點在直線 AB 外
* 過當前點作一條 AB 的法線,再作一條平行線
* 此時當前點與這兩條新直線形成了四個區(qū)域
* 在每一個區(qū)域作一條角平分線,這時候就形成了八個區(qū)域
* 求 des 這個點在這八個區(qū)域中的哪一個,并返回區(qū)域號
* 求解過程:
* 根據(jù)直線 AB 和當前點,能算出平行線方程,在當前點右邊取一點 P1 ,算出它的坐標
* 根據(jù)直線 AB 和當前點,能算出法線方程,在當前點上方取一點 P2 ,算出它的坐標
*/
public int getAreaID(Point A, Point B, Point des)
{
Point p1 = new Point( this . x + 1, (A. y - B. y ) / (A. x - B. x ) + this . y );
Point p2 = new Point( this . x + 1, (B. x - A. x ) / (A. y - B. y ) + this . y );
// 0 、 1 、 2 、 3 區(qū)域
if (des.inLeftSide( this , p1))
{
// 0 、 1 區(qū)域
if (des.inLeftSide( this , p2))
{
// 區(qū)域 0
if (des.getDistance( this , p1) < des.getDistance( this, p2))
{
return 0;
}
// 區(qū)域 1
else
{
return 1;
}
}
// 2 、 3 區(qū)域
else
{
// 區(qū)域 3
if (des.getDistance( this , p1) < des.getDistance( this, p2))
{
return 3;
}
// 區(qū)域 2
else
{
return 2;
}
}
}
// 4 、 5 、 6 、 7 區(qū)域
else
{
// 6 、 7 區(qū)域
if (des.inLeftSide( this , p2))
{
// 區(qū)域 7
if (des.getDistance( this , p1) < des.getDistance( this, p2))
{
return 7;
}
// 區(qū)域 6
else
{
return 6;
}
}
// 4 、 5 區(qū)域
else
{
// 區(qū)域 4
if (des.getDistance( this , p1) < des.getDistance( this, p2))
{
return 4;
}
// 區(qū)域 5
else
{
return 5;
}
}
}
}
( 10 )旋轉(zhuǎn)方向的確定
若起點在魔方區(qū)域內(nèi),則進行魔方的單層旋轉(zhuǎn),主要的情況如圖 5-16 所示:

a . 終點在魔方上,而且與起點處于同一個四邊形內(nèi)
將起點與四邊形的四個點連接起來,形成四個小三角形,然后判斷終點落在哪個三角形內(nèi),點與三角形關(guān)系的判斷前文已經(jīng)提及。

b . 終點在魔方上,而且與起點處于同一個層
因為處于同一個層,所以層號可以確定,但仍需判斷起點與終點的順序,從而確定旋轉(zhuǎn)的方向。
c . 終點在魔方上,但與起點不處于同一個四邊形而且也不處于同一個層。判斷方法與情況 d 一樣。
d . 終點在魔方外
將起點所在四邊形的四條邊延伸出去,然后在四個角區(qū)域作角平分線,形成圖5-18 所示情形。然后判斷終點在哪一個區(qū)域。判斷方法就是根據(jù)點與有向直線的關(guān)系。如果終點落在四個角區(qū)域,則需要再多加一次判斷點是位于角平分線的哪一側(cè),根據(jù)點與直線的距離即可得到結(jié)果。

5.4.5 游戲記錄
Android 內(nèi)置了一個 SQLite 數(shù)據(jù)庫,提供了數(shù)據(jù)庫的接口。當游戲成功完成時,記錄下玩家的步數(shù)、用時、當前日期、讓玩家輸入姓名,然后保存到數(shù)據(jù)庫中。
數(shù)據(jù)庫字段: _id , name , time , step , date
記錄的刪除:在游戲記錄界面里,其實不只五列,在排名的左邊還有一個隱藏列,記錄的就是 _id ,玩家長按記錄時,通過 _id 這個主鍵在數(shù)據(jù)庫查找對應(yīng)的記錄并刪除。
5.4.6 游戲計時
Java 里面提供了 Timer 、 TimerTask 、 Handler 類。游戲的計時就是這三個類的運用。 Timer 每隔 0.1 秒就發(fā)送一個響應(yīng), TimerTask 收到響應(yīng)后獲得當前游戲的狀態(tài)、并將狀態(tài)信息保存到消息中,交由 Handler 來處理, Handler 接收到后根據(jù)游戲狀態(tài)來進行相關(guān)操作(如正在游戲,則毫秒加 1 ,暫停游戲?qū)⒉蛔魅魏翁幚恚?/p>
5.4.7 放大縮小
OpenGL 中提供了一個縮放函數(shù) glScalef() ,放大與縮小只需要設(shè)置一個變量scale ,然后每次響應(yīng)到按鈕點擊的時候給 scale 一個增量即可。
// 設(shè)置三方向的縮放系數(shù)
gl.glScalef( scale , scale , scale );
5.4.8 隨機打亂
Java 提供了一個 Random 類,能夠生成指定范圍的偽隨機整數(shù)和布爾值,從而確定魔方轉(zhuǎn)動的層 ID 與方向。
5.4.9 自動還原
首先得到魔方的當前狀態(tài),記錄下當前每個面的顏色數(shù)據(jù),保存為一個二維數(shù)組。魔方的每次轉(zhuǎn)動都會改變這個數(shù)組,所以需要定義 18 個轉(zhuǎn)換函數(shù),分別對應(yīng)魔方 6 個層(中間三個層除外)的順、逆時針旋轉(zhuǎn)及魔方整體旋轉(zhuǎn)( X 、 Y 、 Z的順、逆時針翻轉(zhuǎn))的 6 種情況。每進行一次旋轉(zhuǎn),對新的狀態(tài)進行判斷,得到下一步的還原步驟。具體的還原方法為層先法。
5.4.10 游戲設(shè)置
當玩家按下“游戲設(shè)置”菜單時,首先得到當前的設(shè)置數(shù)據(jù),并封裝在一個intent 中,然后啟動游戲設(shè)置的 Activity , 同時要求返回一個結(jié)果集(startActivityForResult )。在游戲設(shè)置的 Activity 中,接收來自主游戲界面?zhèn)鬟f過來的設(shè)置數(shù)據(jù),對單選及復(fù)選控件進行初始化,最后玩家點擊確定按鈕的時候,得到玩家的新設(shè)置信息,然后返回給主游戲界面的 Activity ,如果點擊的是“取消”按鈕。則將主游戲界面?zhèn)鬟f過來的設(shè)置信息原封不動地返回。
5.4.11 游戲成功判定
當玩家進行了層轉(zhuǎn)動的操作后,對魔方的 Front 、 Right 、 Top 這三個面的顏色進行檢測,如果這三個面的每一個面的顏色都相同,則可確定還原成功。
在游戲的過程中,玩家每轉(zhuǎn)動(包括整體翻轉(zhuǎn)與單層旋轉(zhuǎn))一次魔方,小立方體的朝向?qū)淖?。在游戲剛開始時,魔方處于規(guī)則狀態(tài),每個面的朝向也是規(guī)律的,給每個小立方體的每個面增加一個索引值,當每轉(zhuǎn)動一次魔方時,更新相關(guān)小立方體的面索引。舉例:當玩家由左向右旋轉(zhuǎn)了魔方的 Top 層,對于 Top 層的九個小立方體,它們原來的 Front 面變成了 Right 面, Right 面變成了 Back 面, Back面變成了 Left 面。 Left 面變成了 Front 面。
算法思路:首先得到旋轉(zhuǎn)層的 9 個小立方體,根據(jù)旋轉(zhuǎn)的方向可以得到一個轉(zhuǎn)換序列 T ,如 Front 、 Right 、 Back 、 Left 。對于每一個小立方體,都有一個面數(shù)組,遍歷這 6 個面,從序列 T 中查找當前面索引是否存在于 T 中,若存在,則將 T中的下一個值賦于當前面的索引變量。
public void updateFaceIndices( int k1, int k2, int k3, int k4)
{
int i = 0;
int j = 0;
int k = 0;
int [] src = new int [6];
int [] des = new int [6];
int [] transform = new int [] { k1, k2, k3, k4 };
Iterator<Face> iter1 = mFaceList .iterator();
while (iter1.hasNext())
{
src[i++] = iter1.next(). index ;
}
for (i = 0; i < src. length ; i++)
{
boolean NotFound = true ;
for (k = 0; k < transform. length ; k++)
{
if (src[i] == transform[k])
{
NotFound = false ;
if (k == 3)
des[j++] = transform[0];
else
des[j++] = transform[k + 1];
break ;
}
}
if (NotFound)
des[j++] = src[i];
}
i = 0;
Iterator<Face> iter2 = mFaceList .iterator();
while (iter2.hasNext())
{
iter2.next(). index = des[i++];
}
}
6 游戲測試
(1) 在真機上測試時,發(fā)現(xiàn)當手機屏幕的方向改變,魔方也會自動改變,而且會重新調(diào)用 OnCreate 方法相當于重新開始游戲。解決方法:設(shè)定為游戲的布局為為某一固定方向即可。
(2) 游戲計時:當重新開始游戲時、發(fā)現(xiàn)計時的速度加快,再重新開始,速度又再次加快。原因:游戲開始時會啟動一個單獨的線程來進行計時、點擊“重新開始”按鈕會又生成了一個線程,兩個線程同時發(fā)出響應(yīng),所以速度越來越快。解決方法:在 Activity 的 onCreate 方法中生成計時對象,在onDestroy 方法中銷毀計時對象。在 onResume 方法中啟動計時。
(3) 整體翻轉(zhuǎn)時的萬向節(jié)死鎖問題。當魔方繞某一個軸翻轉(zhuǎn)時,假設(shè)為 X 軸并向右上方翻轉(zhuǎn),則翻轉(zhuǎn)完畢后模型坐標系的 Y 軸與 Z 軸同時也發(fā)生了改變。所以再次點翻轉(zhuǎn)按鈕的時候發(fā)現(xiàn)和預(yù)想中的完全不一樣。解決方法:系統(tǒng)的 glRotatef 方法雖然能完成旋轉(zhuǎn)的功能,但是同時也改變了模型視圖矩陣,也加重了坐標轉(zhuǎn)換的計算量,所以通過自己維護一個旋轉(zhuǎn)矩陣來實現(xiàn)旋轉(zhuǎn)的功能,而又不改變坐標軸的方向。
(4) 放大與縮?。寒斈Х娇s小到一定程度時突然就消失,而且再點擊放大按鈕都無任何反應(yīng)。原因: scale 的值自減到了負數(shù)。解決方法:給 scale 設(shè)定一個下限。
(5) 游戲狀態(tài)的判斷錯誤:當玩家返回到主菜單并再次切換游戲界面的時候游戲狀態(tài)應(yīng)該與之前的相同,但在日志里發(fā)現(xiàn)狀態(tài)和想像中的不一致。原因:當 Activity 調(diào)用 onPause 方法的時候?qū)⒛Х降臓顟B(tài)設(shè)置為暫停,但返回時候得不到之前魔方的狀態(tài)值。解決方法:設(shè)置一個臨時的狀態(tài)變量,當暫停的時候?qū)斍坝螒驙顟B(tài)賦值給臨時變量,返回的時候從臨時狀態(tài)變量中得到之前的狀態(tài)。
(6) 游戲步數(shù)的計算出錯:原因,玩家成功還原魔方后,沒有將步數(shù)重置為 0,所以出現(xiàn)了累加的現(xiàn)象。解決方法:游戲成功后置 0
(7) 游戲排名出錯:舉例:玩家 A 用時 0.0.12 .9 ,玩家 B 用時 0.0.8.6 。結(jié)果 A排名靠前。原因:游戲的排名是根據(jù)用時來判斷的,用時在數(shù)據(jù)庫中的數(shù)據(jù)類型為文本型,所以默認情況下是進行字符串的比較。字符串是按 ASCII碼來進行比較的,所以會出現(xiàn)上述錯誤。解決方法,當用時的每一部分不足兩位數(shù)時,加一個前導(dǎo) 0. 同理,步數(shù)也是文本型,也會出現(xiàn)相同的情況,所以重新設(shè)置步數(shù)的字段為整形。
(8) 當魔方正在隨機打亂時,如果按下旋轉(zhuǎn)按鈕或重新開始菜單,出現(xiàn)整個魔方的變形,如圖 5-19 所示 。同樣在魔方整體旋轉(zhuǎn)時,如果又檢測到新的手勢,造成前一次的旋轉(zhuǎn)未結(jié)束,又開始了新的旋轉(zhuǎn)。解決方法,設(shè)置一個布爾值來判斷魔方是否正在旋轉(zhuǎn),直到旋轉(zhuǎn)完成后再開始新的操作。

(9) 當點擊“自動還原”按鈕時,發(fā)現(xiàn)游戲畫面一直卡住不動。原因:在自動還原類的某些方法中,有許多地方將當前應(yīng)該完成的旋轉(zhuǎn)動作移交給了下一步,在下一步的時候又移交給第三步,使程序陷入了死循環(huán)。解決方法:判斷完當前的狀態(tài),將可以完成的旋轉(zhuǎn)馬上完成,而不是一直往后推遲。
7 不足與改進
( 1 )原計劃中有紋理貼圖的功能,但最后因為時間的關(guān)系未能完成。
( 2 )將游戲視圖固定死了,不能任意角度來旋轉(zhuǎn)魔方。
( 3 )魔方的觸控操作還有一些小問題,當手指在屏幕邊緣滑動,有時候會進行層的旋轉(zhuǎn),到現(xiàn)在還沒找到真正的原因。
( 4 )魔方的自動還原使用步數(shù)過多,如果加入人工智能的話效果應(yīng)該會更好。

