深入浅出OPhone游戏开发(2)

移动开发 游戏开发
Phone 游戏,从字面上就可以理解,肯定是可以运行在OPhone上面的游戏。OPhone是今年国内非常受关注的智能手机,它的游戏性能也是非常强大的,这也极大增强了大家对于OPhone游戏的信心。

开始之前我们先来看看如下一些问题:
OPhone和传统的Java游戏开发一样吗?
OPhone如何设计UI界面呢?UI如何与逻辑之间进行交互呢?
OPhone中如何使用多线程来更新游戏界面呢?
OPhone游戏开发中数据如何存储呢?
OPhone游戏开发中音乐、音效如何控制呢?
如何高效的开发OPhone应用以及如何对OPhone应用进行优化呢?
如果你在开发中也遇到同样的问题,那么本文肯定适合你,不要犹豫,看完之后肯定能一目了然,解决这些问题。开始吧...#t#

在上一篇文章中我们对该游戏的框架进行了架构,确定了一个大的框架采用MVC,同时也完成了游戏菜单的各个界面,如果说上一篇文章你还没有看到游戏究竟是这么开发出来的,那么本文就是专门来实现游戏中的内容的,因为上一篇文章我们总结了OPhone平台开发的重点在于UI的设计,那么本文我们会继续重点介绍UI的设计,至于游戏的逻辑大家都知道很简单,这也是我们之所以选择俄罗斯方块为例子的目的。首先我们就来学习如何完成游戏中的界面设计。

游戏中界面
到这里,我们终于进入了游戏最核心的部分,激动的心情只有一个程序员才会理解,好了废话不多说,直接进入主题。前面的各个界面我们大部分都直接或间接的使用了系统自带的组件来完成界面的设计布局,但是游戏中仅仅使用这些自带的组件是远远不够的(但是整体框架还得使用),这时我们就需要使用自定义视图(View),才能满足每个游戏的不同功能。好了,先说到这里,首先来看看咋们这个游戏的游戏运行界面,如图9所示。

 


大家都知道,前面在设置界面时,我们定义了很多的背景来供用户选择,这些背景就体现在方块区域的渐变颜色。这些颜色肯定不是图片,那么如何实现呢?跟着本文走,后面会详细的介绍。#p#

游戏中UI界面设计

从图9可以看出,界面大体包括了3个部分,分别是:游戏顶部的菜单栏、左边的游戏显示区域,右边的信息和控制区域。不难看出,对于这种结构来说,最合适的就是相对布局(RelativeLayout)了,正确,该游戏界面就是采用的该布局。确定了大的布局,下面我们先来实现比较简单的顶部菜单栏布局。

可以看出菜单栏也是一个相对布局,在这个布局上方式了3个按钮,按钮的堆砌方式分别是:左、中、右,具体实现参见代码清单13所示。

代码清单13菜单栏布局

  1. <RelativeLayout     
  2. android:id="@+id/RelativeLayoutRunMenu"    
  3. android:layout_width="fill_parent"    
  4. android:layout_height="wrap_content"    
  5. android:background="@drawable/titlemenu"    
  6. android:layout_alignParentTop="true"    
  7. android:layout_alignParentLeft="true"    
  8. >      
  9. <Button     
  10. android:id="@+id/ResetGame"    
  11. android:layout_width="wrap_content"    
  12. android:layout_height="wrap_content"    
  13. android:text="@string/runmenu_reset"    
  14. android:textSize="16sp"    
  15. android:layout_alignParentLeft="true"    
  16. android:layout_alignParentTop="true"    
  17. >      
  18. Button>     
  19. <Button     
  20. android:id="@+id/PauseGame"    
  21. android:layout_width="wrap_content"    
  22. android:layout_height="wrap_content"    
  23. android:text="@string/runmenu_pause"    
  24. android:textSize="16sp"    
  25. android:layout_alignParentTop="true"    
  26. android:layout_centerHorizontal="true"    
  27. >      
  28. Button>     
  29. <Button     
  30. android:id="@+id/ExitGame"    
  31. android:layout_width="wrap_content"    
  32. android:layout_height="wrap_content"    
  33. android:text="@string/exit"    
  34. android:textSize="16sp"    
  35. android:layout_alignParentRight="true"    
  36. android:layout_alignParentTop="true"    
  37. >      
  38. Button>     
  39. RelativeLayout>   


完成了菜单栏的布局之后,我们在来看一个比较复杂(只能说比菜单栏稍微复杂点)的右边栏的布局,在怎么复杂的布局也都是由一些简单的布局所组成的,现在我们观察图8右边的部分,我猜测它可能是一个相对布局也可能是一个线性布局,因为这两个布局都很很轻松的实现这样的布局,下面我们选择采用线性布局的方式来完成。确定了整体为线性布局之后,右边的“下一个”、“方块”、“等级”、“分数”都可以直接垂直放置即可。下面操作部分则又需要一个相对布局来实现了,经过前面几个界面布局的练习,这个布局我们就不在过多的讲述了,如代码清单14所示。

代码清单14右边栏布局

  1. <LinearLayout     
  2. android:id="@+id/LinearLayoutPanel"    
  3. android:layout_width="100px"    
  4. android:layout_height="440px"    
  5. android:orientation="vertical"    
  6. android:layout_below="@+id/RelativeLayoutRunMenu"    
  7. android:layout_toRightOf="@+id/FrameLayoutShowMain"    
  8. >      
  9. <LinearLayout     
  10. android:id="@+id/LinearLayoutNext"    
  11. android:layout_width="wrap_content"    
  12. android:layout_height="130px"    
  13. android:orientation="vertical"    
  14. >      
  15. <TextView     
  16. android:id="@+id/TextViewNext"    
  17. android:layout_width="wrap_content"    
  18. android:layout_height="wrap_content"    
  19. android:text="@string/gamerun_next"    
  20. android:textColor="#ffffffff"    
  21. android:layout_marginTop="10px"    
  22. >      
  23. TextView>     
  24. <ImageView     
  25. android:id="@+id/NextBblock"    
  26. android:layout_width="wrap_content"    
  27. android:layout_height="wrap_content"    
  28. android:layout_gravity="center_horizontal"    
  29. >      
  30. ImageView>     
  31. LinearLayout>     
  32. <LinearLayout     
  33. android:id="@+id/widget284"    
  34. android:layout_width="wrap_content"    
  35. android:layout_height="60px"    
  36. android:orientation="vertical"    
  37. android:textSize="20sp"    
  38. android:layout_marginTop="20px"    
  39. >      
  40. <TextView     
  41. android:id="@+id/TextViewLevel"    
  42. android:textColor="#ffffffff"    
  43. android:layout_width="wrap_content"    
  44. android:layout_height="30px"    
  45. android:text="@string/gamerun_level"    
  46. android:gravity="center"    
  47. >      
  48. TextView>     
  49. <TextView     
  50. android:id="@+id/TextViewScore"    
  51. android:layout_width="wrap_content"    
  52. android:layout_height="30px"    
  53. android:text="@string/gamerun_score"    
  54. android:textColor="#ffffffff"    
  55. android:gravity="center"    
  56. >      
  57. TextView>     
  58. LinearLayout>     
  59.       
  60. <Button     
  61. android:id="@+id/AddScore"    
  62. android:layout_width="wrap_content"    
  63. android:layout_height="wrap_content"    
  64. android:text="+10"    
  65. android:background="@drawable/buttonright"    
  66. android:layout_gravity="center_horizontal"    
  67. android:gravity="center"/>     
  68.       
  69. <LinearLayout     
  70. android:id="@+id/widget341"    
  71. android:layout_width="wrap_content"    
  72. android:layout_height="wrap_content"    
  73. android:orientation="vertical"    
  74. android:layout_marginTop="5px"    
  75. >      
  76. <Button     
  77. android:id="@+id/ButtonUP"    
  78. android:layout_width="wrap_content"    
  79. android:layout_height="wrap_content"    
  80. android:text="@string/gamerun_up"    
  81. android:gravity="center"    
  82. android:background="@drawable/button1"    
  83. >      
  84. Button>     
  85. LinearLayout>     
  86. <RelativeLayout     
  87. android:id="@+id/widget343"    
  88. android:layout_width="wrap_content"    
  89. android:layout_height="wrap_content"    
  90. android:layout_marginTop="10px"    
  91. >      
  92. <Button     
  93. android:id="@+id/ButtonRight"    
  94. android:layout_width="wrap_content"    
  95. android:layout_height="wrap_content"    
  96. android:text="@string/gamerun_right"    
  97. android:gravity="center"    
  98. android:background="@drawable/buttonright"    
  99. android:layout_alignParentTop="true"    
  100. android:layout_alignParentRight="true"    
  101. >      
  102. Button>     
  103. <Button     
  104. android:id="@+id/ButtonLeft"    
  105. android:layout_width="wrap_content"    
  106. android:layout_height="wrap_content"    
  107. android:text="@string/gamerun_left"    
  108. android:gravity="center"    
  109. android:background="@drawable/buttonleft"    
  110. android:layout_alignParentTop="true"    
  111. android:layout_alignParentLeft="true"    
  112. >      
  113. Button>     
  114. RelativeLayout>     
  115. <LinearLayout     
  116. android:id="@+id/widget347"    
  117. android:layout_width="wrap_content"    
  118. android:layout_height="wrap_content"    
  119. android:orientation="vertical"    
  120. android:layout_marginTop="10px"    
  121. >      
  122. <Button     
  123. android:id="@+id/ButtonDown"    
  124. android:layout_width="wrap_content"    
  125. android:layout_height="wrap_content"    
  126. android:text="@string/gamerun_down"    
  127. android:background="@drawable/buttondown"    
  128. android:gravity="center"    
  129. >      
  130. Button>     
  131. LinearLayout>   

这里笔者主要想讲的也就是游戏开发中如何来自定义游戏视图,因为游戏的界面的效果可能需要做得很炫,才能被用户所接受,虽然OPhone的系统组件很强大,但是还是不能满足游戏的布局,所以在开发游戏的过程中必然会使用自定义视图,并且会经常、很多的使用,下面我们开始吧。#p#

自定义View布局

完成了上面两个简单的布局之后,下面我们就要来设计左边的游戏显示区域的布局了,这里我们就需要使用自定义布局。如何来自定义布局呢?下面我们就会来详细的讲解。
在项目的res下的values目录下创建一个attrs.xml文件,文件内容很简单,如代码清单15所示。

代码清单15attrs.xml

  1. xml version="1.0" encoding="utf-8"?>     
  2. <resources>     
  3. <declare-styleable name="TetrisView">     
  4. <attr name="imgBackground" format="integer" />     
  5. declare-styleable>     
  6. resources>    


其中“”指定了自定义视图类的名字,””描述了该自定义视图的背景,格式(类型)为Intrger。

接下来我们就需要来编写这个自定义视图类TetrisView了,首先在项目中建立一个类TetrisView继承自View类,因为View类包含了3个构造方法,这里我们实现其中一个构造函数,如代码清单16所示。

代码清单16构造方法

  1. public TetrisView(Context context, AttributeSet attrs)     
  2. {     
  3.       super(context, attrs);     
  4.       TypedArray params = context.obtainStyledAttributes(attrs, R.styleable.TetrisView);     
  5.       int backgroudId = params.getResourceId(R.styleable.TetrisView_imgBackground, 0);     
  6.       if (backgroudId != 0) setBackgroundResource(backgroudId);     
  7. }    

当我们的程序在用setContentView函数设置显示一个布局文件就会调用该构造函数,我们在上面的代码中通过obtainStyledAttributes方法得到一个布局文件,然后通过TypedArra的getResourceId方法取得布局文件中所定义的背景,然后通过setBackgroundResource方法来设置该布局的背景。当然View类还有其他两个构造方法,如果我们的游戏界面全部是通过2D图形图像绘制出来的,那么就可以选择实现TetrisView(Contextcontext)这个只带一个参数的构造函数,然后在setContentView时就直接使用TetrisView类的对象,而不使用布局文件,代码如下:

  1. TetrisView mTetrisView = new TetrisView();     
  2. setContentView(mTetrisView);   

接下来我们就需要在游戏中界面布局文件tetris.xml中来加入一段我们自定义的View作为游戏界面的左边游戏显示区域。如代码清单17所示。

代码清单17自定义视图布局

  1. <com.yarin.OPhone.Tetris.TetrisView     
  2. android:id="@+id/iTetrisView"    
  3. android:layout_width="fill_parent"    
  4. android:layout_height="fill_parent"/>  

这里我们没有使用“imgBackground”参数,因为我们将背景作为一个ImageView另外来布局了,他是一个需要渐变的背景,而自定义视图则布满整个屏幕,方便我们游戏的图形绘制。背景布局如代码清单18所示。

代码清单18背景设置

  1. <ImageView     
  2. android:id="@+id/FrameLayoutShowMain"    
  3. android:layout_width="220px"    
  4. android:layout_height="480px"    
  5. android:layout_below="@+id/RelativeLayoutRunMenu"    
  6. android:layout_alignParentLeft="true"/>   

这样我们就布局好了整个游戏界面的视图,在Activity类中直接调用setContentView(R.layout.tetris)即可看到我们所布局的界面了,但是此时的界面还没有具体的游戏数据,因为我们还没有将这些内同绘制在自定义的试图上。下面我们就来学习如何在自定义视图上绘制所需要的一系列信息。#p#

自定义视图绘制

我们都知道TetrisView类继承自View类,这时我们需要重写View类中的onDraw、onKeyDown、onTouchEvent等方法。
onDraw:主要用来绘制图形
onKeyDown\onKeyUP:用于处理按键事件
onTouchEvent:用于处理触笔事件

在重写了这些方法之后,当我们想绘制图形时,就在onDraw方法中进行即可。当有事件触发时,系统会自动调用我们所重载事件处理函数。就这么简单,我们就实现了自定义视图的框架。剩下的就是如何将图形绘制和事件的处理了。首先我们,来看看事件的处理。

自定义视图事件处理
首先是按键事件的处理,当键盘按键按下时会触发onKeyDown函数,按键弹起时会触发onKeyUP函数。从这两个函数的参数中,我们可以得到一个整型的按键键值(keyCode),然后我们在这个函数中根据键值的不同来进行不同的处理即可。示例如代码清单19所示。

代码清单19按键事件处理

  1. public boolean onKeyDown(int keyCode, KeyEvent event)     
  2. {     
  3.       switch (keyCode)     
  4.       {     
  5.       case KeyEvent.KEYCODE_BACK://按下返回键     
  6.               break;     
  7.       case KeyEvent.KEYCODE_MENU://按下菜单键     
  8.               break;     
  9.       case KeyEvent.KEYCODE_DPAD_UP://按下上方向键     
  10.               break;     
  11.       case KeyEvent.KEYCODE_DPAD_DOWN:     
  12.               break;     
  13.       case KeyEvent.KEYCODE_DPAD_LEFT:     
  14.               break;     
  15.       case KeyEvent.KEYCODE_DPAD_RIGHT:     
  16.               break;     
  17.       }     
  18.       return false;     
  19. }    

键盘上每一个键都会对应一个键值,更多键值请参见OPhonesdk文档,在这里就不一一列举了,接下来我们来学习如何处理触笔事件。
触笔事件是通过onTouchEvent方法来触发的,通过该方法我们会得到一个MotionEvent类型的事件,我们可以通过其方法getAction()来得到触笔事件的类型,比如:按下(MotionEvent.ACTION_DOWN)、释放(MotionEvent.ACTION_UP)、拖动(MotionEvent.ACTION_MOVE)等。同时我们可以通过getX和getY方法来得到触笔事件发生时所处的X,Y的坐标值,然后可以根据这个坐标值来进行处理。示例如代码清单20所示。

代码清单20触笔事件处理

  1. public synchronized boolean onTouchEvent(MotionEvent event)     
  2. {     
  3.       int iAction = event.getAction();     
  4.       if (iAction == MotionEvent.ACTION_CANCEL ||      
  5.           iAction == MotionEvent.ACTION_DOWN ||      
  6. iAction == MotionEvent.ACTION_MOVE)      
  7. {      
  8.  //这里我们只处理了触笔释放时的事件     
  9. return false;      
  10. }     
  11.       int x = (int) event.getX();     
  12.       int y = (int) event.getY();     
  13.       
  14.       return false;     
  15. }    


到这里,我们已经明白了如何处理事件了,接下来我们就需要学习如何绘制图形?#p#

游戏图形绘制

该游戏中图形绘制的地方很少,主要是绘制方块,和网格。正好我们能够学习如何绘制2D几何图形和图片。首先我们来看看如何绘制2D几个和图形吧,开始之前我们需要先学习一下Paint这个类。

Paint大家可以理解为是一直画笔,既然是画笔,我们就可以设置这个画笔的属性了,包括:颜色、风格、字体大小等等。Paint类的使用如代码清单21所示。

代码清单21Paint类的使用

  1. Paint mPaint = new Paint(); //实例化Paint     
  2. mPaint.setColor(0xffcccccc); //设置颜色     
  3. mPaint.setStyle(Style.FILL); //设置风格(完全填充)     
  4. mPaint.setAlpha(255);         //设置Alpha值     
  5. mPaint.setTextSize(15);       //设置字体尺寸     
  6. 关于Paint的属性还有很多,这里不一一列举了,请参见OPhone sdk文档。准备好了画笔,  
  7. 下面我们就需要准备画刷,通过刷子来绘制不同的图形。     
  8. OPhone中的画刷则相当于Canvas类,该类提供了很多图形绘制的方法,  
  9. 我们可以看出View类的onDraw方法的参数就是Canvas,  
  10. 我们便可以通过这个Canvas来绘制图形,示例如代码清单22所示。     
  11. 代码清单22 Canvas的使用     
  12. canvas.drawLine(0, 100, 20, 100, mPaint); //绘制一条线     
  13. canvas.drawText("绘制字符串", 0, 10, mPaint);//绘制字符串     
  14. canvas.drawRect(0, 0, 50, 100, mPaint);//绘制空心矩形     
  15. mPaint.setStyle(Style.FILL);     
  16. canvas.drawRect(0, 0, 50, 100, mPaint);//绘制实心矩形   

当然Canvas类中还有很多可以方法能够绘制出各种图形,比如:椭圆、圆形、颜色等等。更多内容请参见Ophonesdk文档。下面讲述如何将图片绘制到自定义视图上。
细心的朋友可能已经发现Canvas类中的drawBitmap方法了,就是用来绘制一个图片的,在OPhone中我们需要将图片实例化为一个Bitmap对象,Bitmap有点像J2me中的Image类,如何实例化图片然后绘制呢?我们来看看下面一段代码,代码清单23所示。

代码清单23图像绘制

  1. public static void drawImage(Canvas canvas, int x, int y, int w, int h, int bx, int by)     
  2. {     
  3.       Bitmap mBlockBitmap =   
  4. BitmapFactory.decodeResource  
  5. (mTetrisView.GetContext().getResources(), R.drawable.block);     
  6.       
  7.       Rect src = new Rect();     
  8.       Rect dst = new Rect();     
  9.       src.left = bx;     
  10.       src.top = by;     
  11.       src.right = bx + w;     
  12.       src.bottom = by + h;     
  13.       dst.left = x;     
  14.       dst.top = y;     
  15.       dst.right = x + w;     
  16.       dst.bottom = y + h;     
  17.       canvas.drawBitmap(mBlockBitmap, src, dst, null);     
  18. }    


代码清单23中BitmapFactory.decodeResource方法可以装载一个图片文件到Bitmap中去,然后通过canvas.drawBitmap方法将图片按指定的裁剪矩形区域绘制到指定的举行区域上去。到这里你已经学会了该游戏中所有的图形绘制方法,更多关于图形绘制的方法还得参考OPhonesdk文档。绘制好了图形,但是还是没有显示在屏幕之上啊,这就是我们下面将要学习的通过多线程来更新UI界面的显示。#p#

多线程更新游戏界面

说白了,游戏就是在不停的更新着界面,展示给用户不同的界面,那么在OPhone平台上如何通过多线程来更新UI界面呢?
OPhone平台关于界面的更新有两种方式:invalidate和postInvalidate。Invalidate方法是不能在主线程中进行更新的,必须通过OPhone提供的Handler来处理异步消息,Handler就是OPhone提供的一套专门处理异步消息的方案,Handler采用了消息机制,也就是说,我们在线程中达到某个条件之后就通过Handler发送一个消息(Handler.sendMessage),当Handler接收到消息(Handler.handleMessage)之后进行相应的处理(这里则是进行UI界面的更新),代码如下:

  1. mHandler = new Handler() {      
  2.       public void handleMessage(Message msg) {      
  3.       super.handleMessage(msg);      
  4.       invalidate();      
  5.       }      
  6. };      
  7. public void run()     
  8. {     
  9.       while(true)     
  10.       {     
  11.               try {      
  12.                        Thread.sleep(2000);      
  13.               } catch (InterruptedException e){}      
  14.               mHandler.sendMessage(mHandler.obtainMessage());      
  15.       }     
  16. }    

这里需要说明一下,在一条线程中Handler对于消息(Message)并不是并发的,而是按顺序处理完成第一个消息之后接着处理第二个消息,所以当我们需要同时处理两个的消息,就需要在另外开启一个Handler。但是该游戏者选择了比较简单的postInvalidate,可以直接在游戏的主线程内来更新视图的显示。所示我们需要将游戏视图类TetrisView继承View并且实现Runnable接口,然后在接口的Run方法你来更新视图显示,具体实现如代码清单24所示。

代码清单24视图更新

  1. public void run()     
  2. {     
  3.       while (mBLoop)     
  4.       {     
  5.               try    
  6.               {     
  7.                        Thread.sleep(yarin.GAME_LOOP);     
  8.               }     
  9.               catch (InterruptedException ex)     
  10.               {     
  11.                        ex.printStackTrace();     
  12.               }     
  13.               if ( !mBPause && !mBGameOver)     
  14.               {     
  15.                        this.postInvalidate();     
  16.               }     
  17.       }     
  18. }    

到这里,游戏差不多可以运行了,但是大家想想还有一个什么功能要讲呢?那就是我们在游戏设置里提供了背景选择项,当背景很多时,我们又不能使用图片来完成,这样的话游戏开发出来的程序包就很大,所以OPhone平台为我们提供了shape用来对图形进行渲染。#p#

Shape图形渲染

Shape就是专门用来渲染图形的,比如我们要实现一个渐变色,虚线框等各种常用图形,当然通过硬编码我们也能实现,但是这会很复杂,要是使用OPhone平台的shape就变得相当简单,shape的使用也非常简单,我们就不多说,看下面的资历,代码清单25所示。

代码清单25shape的使用

  1. >     
  2. <shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="line">     
  3.     <stroke android:width="1dp" android:color="#FF000000"    
  4.             android:dashWidth="1dp" android:dashGap="2dp" />     
  5.     <size android:height="5dp" />     
  6. shape>     
  7. >     
  8. <shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle">     
  9.     <gradient android:startColor="#FFe79c97" android:endColor="#ffe3e196"    
  10.             android:angle="270"/>     
  11.     <padding android:left="7dp" android:top="7dp"    
  12.             android:right="7dp" android:bottom="7dp" />     
  13.     <corners android:radius="1dp" />     
  14. shape>    
  15.  

其中gradient表示产生颜色渐变,android:angle代表从哪个角度开始变,android:shape="rectangle"图形为长方形,solid表示为实心的,stroke表示描边的方式,android:color表示所采用的颜色。startColor="#FFe79c97"表示渐变色开始的颜色,android:endColor="#ffe3e196"表示渐变色结束的颜色(注意:这里的颜色必须采用8位),padding表示填充的区域。Corners表示圆角矩形的角度。这些XML文件的就相当于是一张图片,但是可能会比图片更节约资源,同时也让UI界面的设计变得更加灵活。笔者比较推荐这种方式。该游戏中的每一个背景都是这样定义的一个XML文件。在使用时直接设置布局的背景为这个资源文件即可,或者通过android:src属性来指向这个文件,比如我们在游戏中,先要在设置界面确定用户选择的背景,然后在来在游戏中设置用户所选择的哪一个背景。

关于游戏的主体框架又介绍到这里,下面的一些功能就是用来辅助游戏的,比如游戏的音乐、音效、数据存储等。

碰撞检测

碰撞检测是一个游戏必不可少的模块,只是根据游戏和平台的不同,我们所采用的方式不同而已,比如J2ME的游戏我们可以选择矩形碰撞检测和像素碰撞检测,其实碰撞检测就是判断游戏中的任何两个元素之间的关系,就拿咋们这个俄罗斯方块游戏来说吧,我们需要检测下落的方块是否和屏幕上的方块发生碰撞,如果发生碰撞,那么下落的方块就停止下落,然后检测是否可以消除每一行全部存在的方块,首先咋们这个游戏是将屏幕上方块的区域定义为一个数组,然后根据数组中每个元素的不同来绘制不同的方块,我们都知道每一个下落的方块最多就是4*4的一个数组,所以我们就需要检测这个4*4的数组与其下一行是否存在方块,考虑到游戏的效率,我们就不需要检测整个屏幕数组了,下面是我们检测方块是否可以下落的代码:

  1. public boolean checkDown(boolean kyouseiflag)     
  2. {     
  3.       boolean check = true;     
  4.       /* 分别扫描下坠物的4行,从最下面的那行开始 */    
  5.       for (int i = 0; i < 4; i++)     
  6.       {     
  7.               int row = 3;     
  8.               while (row >= 0)     
  9.               {     
  10.                        if (blockpattern[rot][row][i] == 1)     
  11.                        {     
  12.                                 if (map.get(x + i, y + row+1 ) != 0)     
  13.                                 {     
  14.                                          check = false;     
  15.                                 }     
  16.                                 row = -1; /* 终止循环 */    
  17.                        }     
  18.                        else    
  19.                        {     
  20.                                 row--;     
  21.                        }     
  22.               }     
  23.       }     
  24.       return check;     
  25. }    

代码中“blockpattern[rot][row][i]==1”用于检测某一行是否存在方块,而“map.get(x+i,y+row+1”则检测下一行是否存在方块,如果存在方块,则说明下落的方块已经和屏幕上已有的方块发生了碰撞。该游戏的碰撞检测就如此简单,其实不管好复杂的游戏碰撞也会很简单,你就去想着是判断两个物体是否相交嘛,比如简单的,我们判断两个矩形是否相交,大家都会,那么复杂的游戏还不是可以由很多个这种简单的碰撞组成。当然现在也只是空说,要看具体的游戏,在来确定所采用的碰撞方式,有机会的话,我们可以单独一片文章介绍碰撞检测的相关内容。#p#

游戏音乐、音效

我们都知道,一款成功的游戏,音乐、音效必定少不了,那么在OPhone平台上如何实现音乐和音效的播放呢。该游戏主要实现了游戏的背景音乐,和当方块变形和消除方块时音效。背景音乐很简单,我们可以通过一个线程来播放,当然这里笔者直接采用了OPhone平台提供的MediaPlayer类来比方背景音乐,因此我们首先将这些音乐文件放到项目的res下的raw文件中,然后调用MediaPlayer.create方法来实例化一个音乐文件,这些简单的东西我们就不多说了,代码清单26是该游戏播放背景音乐的两个函数。

代码清单26播放背景音乐

  1. public void playBackGround(boolean bloop)     
  2. {     
  3.       stopBackGround();     
  4.       try    
  5.       {     
  6.               mMediaPlayer = MediaPlayer.create(mContext, R.raw.background);     
  7.               //设置时候循环     
  8.               mMediaPlayer.setLooping(bloop);     
  9.               //开始播放     
  10.               mMediaPlayer.start();     
  11.       }     
  12.       catch (Exception e)     
  13.       {     
  14.               e.printStackTrace();     
  15.               stopBackGround();     
  16.       }     
  17.       
  18. }     
  19. // 停止音乐播放     
  20. public void stopBackGround()     
  21. {     
  22.       if (mMediaPlayer != null)     
  23.       {     
  24.               //停止播放,释放资源     
  25.               mMediaPlayer.stop();     
  26.               mMediaPlayer.release();     
  27.               mMediaPlayer = null;     
  28.       }     
  29. }  

到这里就出现问题了,背景音乐是可以正常的播放,那么音效又如何来播放呢?毕竟MediaPlayer不支持同时播放两手及其以上的音乐。这时的考虑OPhone为我们提供的SoundPool了。下面我们来学习如何使用SoundPool来同时播放多个音效。

首先,我们需定义一下几个变量:

  1. // 音效的音量     
  2. int   streamVolume;     
  3. // 定义SoundPool 对象     
  4. private SoundPool soundPool;     
  5. // 定义HASH表     
  6. private HashMap<Integer, Integer> soundPoolMap;     
  7. 然后,初始化SoundPool和HashMap,  
  8. 如代码清单27所示。     
  9. 代码清单27 初始化SoundPool和HashMap     
  10. private void initSounds()     
  11. {     
  12.       // 实例化soundPool对象,5是允许5个声音流同时播放,AudioManager.STREAM_MUSIC是声音类型,100是声音的品质     
  13.       soundPool = new SoundPool(5, AudioManager.STREAM_MUSIC, 100);     
  14.       // 初始化HASH表     
  15.       soundPoolMap = new HashMap  
  16. <Integer, Integer>();     
  17.       // 获得声音设备和设备音量     
  18.       AudioManager mgr = (AudioManager) mContext     
  19.               .getSystemService(Context.AUDIO_SERVICE);     
  20.       streamVolume = mgr.getStreamVolume(AudioManager.STREAM_MUSIC);     
  21. }     
  22.     最后,将所有的音效,按顺序装到HashMap中,代码如下:     
  23. // 把资源中的音效加载到指定的ID(播放时直接使用ID即可)     
  24. soundPoolMap.put  
  25. (ID, soundPool.load(mContext, raw, ID));     
  26. 接下来我们就可以在需要播放的地方来播放对应的音效即可,  
  27. 播放函数代码如下:     
  28. //sound为声音的ID,uLoop为循环的次数(从0开始)     
  29. public void play(int sound, int uLoop)     
  30. {     
  31.       soundPool.play(soundPoolMap.get(sound), streamVolume, streamVolume, 1,uLoop, 1f);     
  32. }   

大家或许在想,我们为什么不把背景音乐也放到这里来播放呢?因为SoundPool只能用于播放音效,音效总是很短小的音乐片段,时间比较段,如果比方时间比较长的音乐,SoundPool就会出现一些意想不到的问题。游戏音乐音效的问题得以解决,最后我们来学习OPhone平台中如何存储数据呢?#p#

游戏数据存储

数据存储也是游戏开发中比不可少的部分,OPhone平台为我们提供的数据存储方式很多,分别是:系统配置(Preference),文件(File),数据库(DataBase),数据共享(ContentProvider)。这些存储方式各有特色,Preference只适合存储数据量比较小的轻量级数据,File是将数据保存到文件中,需要时在读取出来,DataBase是一个关系数据库,则适合数据量较大的数据,如果我们的数据需要被其他的程序共享那么我们就需要使用ContentProvider。由于篇幅关系,我们就不在一一介绍了。

一般小型的单机游戏,使用得最多的也就是Preference和File了,首先我们介绍一下File的方式来存储数据吧,在使用文件存储数据时,我们可以先将所有要存储的数据打包到一个Properties中,然后在将Properties一起存放到文件中,存储数据代码如下:

  1. boolean save(Activity activity)     
  2. {     
  3.       Properties properties = new Properties();     
  4.       properties.put("music", String.valueOf(mBMusic));     
  5.       try {     
  6.               FileOutputStream stream = activity.openFileOutput(     
  7.                                 "Tetris.cfg", Context.MODE_WORLD_WRITEABLE);     
  8.               properties.store(stream, "");     
  9.       } catch (FileNotFoundException e) {     
  10.               return false;     
  11.       } catch (IOException e) {     
  12.               return false;     
  13.       }     
  14.       return true;     
  15. }    

该代码中,我们将一个boolean数据mBMusic先放置到Properties中,然后通过openFileOutput方法来打开一个文件,“Context.MODE_WORLD_WRITEABLE”代表打开文件的模式,比如:只读、可写可读。如果存储成功则返回true,否则返回false。存储好了,那么在我们要使用数据时又如何来读取出来呢,那么,我们还是需要使用Properties,首先将文件的所有内容读取到一个Properties中,然后通过其get方法来分别取出指定的数据,并转换数据类型,具体读取代码如下:

  1. void load(Activity activity)     
  2. {     
  3.       Properties properties = new Properties();     
  4.       try    
  5.       {     
  6.               FileInputStream stream = activity.openFileInput("Tetris.cfg");     
  7.               properties.load(stream);     
  8.       }     
  9.       catch (FileNotFoundException e)     
  10.       {     
  11.               return;     
  12.       }     
  13.       catch (IOException e)     
  14.       {     
  15.               return;     
  16.       }     
  17.       mBMusic = Byte.valueOf(properties.get("music").toString());     
  18. }    

该游戏中,我们需要存储的都是一些比较小的配置信息,比如:是否开启音效、分数等(即游戏设置和高分榜中的内容)。因此我们选择了Preference来存储。这种存储方式,文件会以XML文件的形式存放在/data/data/PACKAGE_NAME/shared_prefs目录下。下面我们来分析如何存储,首先我们可以通过getSharedPreferences方法来取得SharedPreferences对象,然后通过SharedPreferences.edit()方法来取得SharedPreferences.Editor对象。最后通过editor.put...方法来存入不同类型的数据,当存放完毕时,需要使用editor.commit()来保存。完整的保存数据实现如代码清单28所示。

代码清单28保存数据

  1. public boolean SavaConfig()     
  2. {     
  3.       try    
  4.       {     
  5.               SharedPreferences config = mContext.getSharedPreferences(PACK_NAME, android.content.Context.MODE_PRIVATE);     
  6.               SharedPreferences.Editor editor = config.edit();     
  7.               editor.putBoolean("music", mBMusic);     
  8.               editor.putBoolean("grid", mBGrid);     
  9.               editor.putInt("level", mBLevel);     
  10.               editor.putInt("background", mBBackGround);     
  11.               editor.putInt("score1", mIscore1);     
  12.               editor.putInt("score2", mIscore2);     
  13.               editor.putInt("score3", mIscore3);     
  14.               editor.commit();     
  15.       }     
  16.       catch (Exception e)     
  17.       {     
  18.               return false;     
  19.       }     
  20.       return true;     
  21. }    

保存好了数据,在使用数据时我们有如何来读取出这些数据呢?同样我们首先通过getSharedPreferences方法来得到SharedPreferences对象,然后通过SharedPreferences.get...方法来读取不同类型的数据。具体实现见代码清单29所示。

代码清单29读取数据

  1. public boolean LoadConfig()     
  2. {     
  3.       try    
  4.       {     
  5.               SharedPreferences config = mContext.getSharedPreferences(PACK_NAME, android.content.Context.MODE_PRIVATE);     
  6.               mBMusic = config.getBoolean("music", false);     
  7.               mBGrid = config.getBoolean("grid", true);     
  8.               mBLevel= (byte)(config.getInt("level", 2));     
  9.               mBBackGround= (byte)(config.getInt("background", 1));     
  10.                    
  11.               mIscore1 = config.getInt("score1", 0);     
  12.               mIscore2 = config.getInt("score2", 0);     
  13.               mIscore3 = config.getInt("score3", 0);     
  14.       }     
  15.       catch (Exception e)     
  16.       {     
  17.               return false;     
  18.       }     
  19.       return true;     
  20. }    

注意:在使用getSharedPreferences方法时一定要通过Context得到,不能使用Activity,如果在某些特定的情况下跨越使用Activity,因为使用Activity数据就只能类该Activity所访问,其他程序都不能访问。#p#

打包应用程序(准备发布)

游戏做出来了,最主要的就是要找到各种渠道进行销售,移动的MM就是一个很好的渠道,不管是个人开发者还是企业开发都同时接纳,这里我们主要讲述一下应用的打包,即发布前的准备工作,至于如何发布到MM上去,请查看MM的开发者社区的帮助,同时还会有很多热心的MM客服,帮助你,相信你能成功。下面我们来学习如何签名打包应用程序。

发布前首先应该回答如下最主要而且必要的问题:
应用是否开发完成?
应用是否存在BUG?
产品和适配机型的问题?

应用程序签名

其实要注意的问题会根据不同的应用有不同的问题,这里我们列举的是3个通用的且经常容易出现的问题,首先确保应用开发完整,不会出现严重的BUG。然后就是记性适配问题,这也一直是一个老问题,主要包括分辨率的适配、软件的版本,手机系统的版本(比如:OPhone1.5开发的程序不修改肯定不能在OPhone1.0上运行),一些特殊的机型需要进行特殊的处理,比如:索爱K700系列的J2me程序在按键处理上不支持getGameAction方法的问题,最后就是应用程序签名的问题,因为这个问题在MM上呗很多个人开发者碰到过,下面我们会讲述如何来签名应用程序。

打包程序

确保这些问题都无误之后,在Eclipse的“PackageExplorer”中右键点击工程,选择“OphoneTools(OPhone1.0中应该是“AndroidTools”)”下面的“Export...”,其中“Signed”是在打包的同时进行签名,“UnSigned”则是指打包不签名,当然“Signed”是在OPhone1.5中才出现的,但是目前大部分机型是OPhone1.0的,所以这里我们就将打包和签名分开来讲解,或许对大家的帮助会比较大,我们选择“UnSigned”开头的,如图10所示,然后,指定打包后的文件存放的路径,完成即可。这是就会在你所指定的目录下产生一个apk类型的文件,但是这个文件我们只是打包了,没有签名。下面我们就来为该程序签名。

 

签名程序

首先确保你的电脑上安装了JDK,因为签名,我们将使用JDK自带的创建和管理数字证书的工具Keytool。进入命令行中,输入如下命令:

  1. keytool -genkey -v -keystore ophone.keystore -alias ophone -keyalg RSA -validity 20000     

其中,-keystoreophone.keystore表示生成的证书,可以加上路径(默认在用户主目录下);-aliasophone表示证书的别名是ophone;-keyalgRSA表示采用的RSA算法;-validity20000表示证书的有效期是20000天。MM上的应用程序证书有效期必须大于1年哦。回车之后会提示你输入相关的信息,大家安自己的信息填写吧,需要注意的是在输入国家代码的时候,“CN”代表中国,然后就在当电电脑的用户主目录产生一个ophone.keystore文件,这就是我们生成的证书,然后我们在进入刚才生成apk文件的目录(笔者将ophone.keystore文件也一起拷贝到了apk文件所在目录),输入如下命令:

  1. jarsigner -verbose -keystore ophone.keystore Tetris.apk ophone   

其中“ophone.keystore”是我们的证书(如果没有拷贝过来的话就需要指定路径),“Tetris.apk”即是我们要签名的apk文件,最后的“ophone”则是证书的别名(创建证书时填写的),这样就可以完成对应用的签名,最后我们可以使用如下命令来检测签名是否成功:
jarsigner-verifyTetris.apk

如果成功则会显示“jar已验证”。
当然,这是1.0中打包和签名的方法,在OPhone1.5中就不需要输入命令了,ODT插件提供了在Eclipse中可视化界面签名(即我们前面所说的选择“Signed”开头的,然后填写相关资料即可,有兴趣的,大家可以试试,后面有机会我们在继续讲)。

打包、签名都做好了,下一步你就去MM开发者社区注册账号,准备审核通过,然后上传程序,有MM负责测试你的程序,通过之后就可以上线销售了。

小结

到这里,我们该项目就算快结束了,下面我们简单的说说通过该项目得到的一些经验总结,该项目的题材很简单,但是游戏本身从各个角度来说都很经典,也基本涉及到了OPhone游戏开发的方方面面,从上一篇的菜单界面到本文的游戏中界面以及最后的音乐、音效、存档和简单的碰撞检测等问题,我们都彻底的实践了一次,这里笔者还要强调OPhone游戏开发的重点应该在UI界面设计和框架的布局上,同时也借台湾高老师(高焕堂)的一句话,“Android其实本身就是一个框架”,所以只要把握好了这个框架,就不怕应用开发中所出现的难题。期待大家的应用都能在MM上去的好的下载量,受用户的喜爱。

责任编辑:chenqingxiang 来源: ophonesdn
相关推荐

2010-07-26 12:57:12

OPhone游戏开发

2021-03-16 08:54:35

AQSAbstractQueJava

2011-07-04 10:39:57

Web

2012-05-21 10:06:26

FrameworkCocoa

2019-01-07 15:29:07

HadoopYarn架构调度器

2021-07-20 15:20:02

FlatBuffers阿里云Java

2017-07-02 18:04:53

块加密算法AES算法

2022-09-26 09:01:15

语言数据JavaScript

2012-02-07 14:45:52

Android核心组件Service

2022-12-02 09:13:28

SeataAT模式

2018-11-09 16:24:25

物联网云计算云系统

2009-11-18 13:30:37

Oracle Sequ

2009-11-30 16:46:29

学习Linux

2019-12-04 10:13:58

Kubernetes存储Docker

2019-11-11 14:51:19

Java数据结构Properties

2022-01-11 07:52:22

CSS 技巧代码重构

2021-04-27 08:54:43

ConcurrentH数据结构JDK8

2012-02-21 13:55:45

JavaScript

2022-11-09 08:06:15

GreatSQLMGR模式

2022-10-31 09:00:24

Promise数组参数
点赞
收藏

51CTO技术栈公众号