五月天青色头像情侣网名,国产亚洲av片在线观看18女人,黑人巨茎大战俄罗斯美女,扒下她的小内裤打屁股

歡迎光臨散文網(wǎng) 會員登陸 & 注冊

Android 3D 魔方游戲的設(shè)計與開發(fā)

2022-12-12 00:13 作者:ChatGPT云炬學(xué)長  | 我要投稿

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)該會更好。


Android 3D 魔方游戲的設(shè)計與開發(fā)的評論 (共 條)

分享到微博請遵守國家法律
扶绥县| 吴忠市| 泗阳县| 芦溪县| 余干县| 铅山县| 黎城县| 崇阳县| 济阳县| 瑞安市| 什邡市| 宣化县| 普洱| 正安县| 乐山市| 鄂托克旗| 玉环县| 于田县| 白河县| 徐闻县| 长宁区| 南澳县| 孝感市| 红安县| 榆林市| 民县| 西和县| 兴义市| 二连浩特市| 迁西县| 东乡| 佛山市| 肃南| 鹤山市| 镇原县| 鹤峰县| 上栗县| 灌云县| 柞水县| 翁牛特旗| 鹤庆县|