李建,乐成数字通信学院 高级讲师。层就职于国内数家SP,CP公司,具有丰富的软件、游戏开发经验。并从事多年教学工作,具有丰富的教学经验。目前主要从事OPhone、
J2ME开发和教学方面的工作。
手机游戏是目前深受广大手机用户喜爱的一种娱乐软件,各大运营商都有自己的手机游戏平台,比如中国移动的百宝箱,MM平台,当然,除了运营商之外,类似QQ游戏平台和一些Free Wap网站都提供手机单机和网络游戏的下载。目前大多数的手机游戏还是以
J2ME平台为主,而OPhone做为一个后起之秀、一个新的手机操作系统,目前上面的软件和游戏还不是很多,但是它有一个很大的优点——也是基于JAVA语言开发。有了这个优点也就意味着所有的
J2ME程序都可以非常快速的迁移到OPhone平台。
笔者曾经有过
J2ME游戏移植到OPHONE平台的经验,现将自己的一些经验写出来与各位分享。由于本文主要针对有过
J2ME开发经验的读者,因此在对
J2ME方面的介绍上稍显笼统,望见谅。下面就从游戏开发的角度将两个平台的相关API做以比较。
平台比较
J2ME:
J2ME(Java 2 Micro Edition)是Java 2的一个组成部分,它与J2SE、J2EE并称。根据Sun的定义:
J2ME是一种高度优化的Java运行环境,主要针对消费类电子设备的,例如蜂窝电话和可视电话、数字机顶盒、汽车导航系统等等。
J2ME技术在1999年的JavaOne Developer Conference大会上正式推出,它将Java语言的与平台无关的特性移植到小型电子设备上,允许移动无线设备之间共享应用程序。
本质上
J2ME是一个平台,需要由操作系统的支持,并依赖于JAVA虚拟机。
OPhone:
为了突破TD终端瓶颈,以及促进手机终端与中国移动的网络及应用服务进行无缝对接,中国移动在谷歌Android操作系统基础上,主导开发了OPhone系统,该系统直接内置了中国移动的服务菜单、音乐随身听、手机导航、号簿管家、139邮箱、飞信、快讯和移动梦网等特色业务。
工程结构比较(源代码,资源文件夹,图片,数据)
J2ME:
res:用来放置图片,声音,数据等资源文件- src:源代码
OPhone:
src:源代码- res\drawable:用来放置图片文件res\raw:用来放置声音文件
- res\values:用来放置字符串
- assets:用来放置数据文件
安装包比较
安装包就是程序最终要生成的可执行文件,手机要先进行程序安装然后才可以实现运行。
J2ME:
OPhone:
代码结构比较
J2ME:
一般要有一个MIDlet做为整个应用程序的入口类
要有一个Canvas类做为应用程序的界面类
OPhone:
一般要有一个Activity做为整个应用程序的入口类Activity
要有一个View/SurfaceView类做为应用程序的界面类
都采用继承的方式,都只有一个MIDlet/Activity,一般都只有一个Canvas/View
全屏设置
用于隐藏手机本身的各种状态栏,尤其是在游戏中可以增大画面的显示区域。
J2ME:
在Canvas类中调用SetFullScreenMode(true)进行全屏设置
OPhone:
在Activity类中调用
//设定全屏显示getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,WindowManager.LayoutParams.FLAG_FULLSCREEN);- /**标题是属于View的,所以窗口所有的修饰部分被隐藏后标题依然有效*/
- requestWindowFeature(Window.FEATURE_NO_TITLE);
Display
J2ME:
J2ME中的屏幕类,用来管理当前屏幕,进行高级界面和低级界面间的跳转,一个MIDlet只有一个Display对象
- Display dis=Display.getDisplay(MIDlet);
OPhone:
没发现其特殊功能,应该和
J2ME作用类似,但一般很少使用。
- Display display = windowManager.getDefaultDisplay();
获得屏幕分辨率
手机和PC不同,每个厂商的手机硬件都各不相同,尤其体现在屏幕的分辨率上面,一款软件要能保证可以运行到绝大数的手机上,因此手机上的开发一般会有一个叫做“适配”的过程。而适配过程的难易主要取决于开发者是否使用了“自适应”的方式来绘制界面。所谓自适应就是根据当前手机的屏幕尺寸来动态的计算出各个按钮、方框、图片等的屏幕坐标。这里最关键的就是获得屏幕的分辨率大小:
J2ME:
Canvas类的getHeight()获得屏幕高度
getWidth()获得屏幕宽度
OPhone:
int screenWidth,screenHeight;- WindowManager windowManager = getWindowManager();Display display = windowManager.getDefaultDisplay();
- screenWidth = display.getWidth();
- screenHeight = display.getHeight();
界面类
顾名思义,所谓界面类就是用来承载游戏中的界面,比如地图,精灵,对话框等所有的游戏界面元素最终都要通过这个界面类来展示出来。一般我们更习惯把界面类称之为画布类。
J2ME:
所有界面都需要绘制到Canvas上面,需要继承这个类。
OPhone:
继承View类,定义构造方法:
public MyView(Context context)- { super(context);
- }
画笔(图形上下文)类
上面我们介绍了画布类,当然光有画布还不行,还要有个能在画布上画图的笔才行,在这两个平台中都提供了具有画笔功能的类,只是名字不同。
J2ME:
Graphics类就是
J2ME中的图形上下文类,或者说是一个画笔类,它提供了诸如绘制直线,方块,文字,图片等方法。
OPhone:
OPhone在这点上和
J2ME有些区别:
J2ME中所有与绘制相关的功能都被定义到了Graphics中,而OPhone则分成了两个类:
Paint:用来设置画笔的属性,比如设置颜色,样式,字体等
Canvas:用来绘制具体的内容,比如绘制多边形,图片,文字等
屏幕绘制方法
有了画笔和画布类,剩下的就是用笔来向画布上绘制内容了。
J2ME:
paint()是Canvas类中的抽象方法,因此只需要实现paint(Graphics)这个抽象方法即可。也就是说,需要把具体的绘制语句写到这个方法里面就是完成绘制的工作。当然这个方法不需要我们手动调用,当Canvas被显示时,此方法会被虚拟机自定调用。举例如下:
protected void paint(Graphics g)- {g.setClip(0,0,screen_w, screen_h);
- g.setFont(font);
- }
OPhone:
Ophone在这点上与
J2ME类似,也是需要把具体的绘制内容写入View类的一个方法,这个方法void onDraw(Canvas g),只不过View不是一个抽象类,在这里是对原有方法的重定义(方法覆盖)。举例如下:
@Override- protected void onDraw(Canvas g){
- //绘制logocanvas.drawBitmap(imgLogo, 0, 0, paint);
- }
图片的创建
J2ME:
一般只支持png格式图片
使用Image类来管理图片
通过Image.createImage(path)获得Image对象
l OPhone:
支持png,jpg,gif等多种格式图片
使用Bitmap类来管理图片
通过BitmapFactory.decodeResource(getResources(),R.drawable.map0);来获取Bitmap对象
可以自己封装方法:
public Bitmap CreateImage(int sImg)- { Bitmap img;
- try {
img = BitmapFactory.decodeResource(getResources(),sImg);return img;- } catch (Exception e)
- {return null;
- }
- }
Font的创建,Font使用,字体设置
J2ME:
J2ME中提供的字体非常有限,只能使用手机支持的几个字体,甚至个别手机只支持一种字体
Font.getFont( )获得字体对象
Graphics类的setFont()方法设置使用的字体
OPhone:
OPhone中支持的字体非常多,甚至可以使用自定义字体,可以采用下面的方法来设置字体属性:
Paint类的setTypeface(null); 用来设置默认字体
Paint类的setTextSize(32); 用来显示字体大小
当然,如果你想使用自定义字体可以采用如下方法:
- Typeface mFace=Typeface.createFromAsset(getContext().getAssets(),"fonts/samplefont.ttf")
此处需要把字体文件放到“assets/fonts”文件目录下面
Paint类的setTypeface(mFace); 用来设置当前字体
图片绘制
J2ME:
drawImage( )来绘制Image对象
OPhone:
- public void drawBitmap(Bitmap bitmap, float left, float top, Paint paint)
多个重载方法请参考API文档,此处不一一列出
使用方法同
J2ME,但注意方法参数数据类型。
字符串绘制
J2ME:
drawString()来绘制字符串
OPhone:
- public native void drawText(String text, float x, float y, Paint paint);
多个重载方法请参考API文档,此处不一一列出
使用方法同
J2ME,但注意方法参数数据类型。
限定屏幕的显示区域
游戏中的某些效果只需要在屏幕的某个地方做显示和刷新,比如类似文字自下而上的滚动效果,当你想把文字的可视区域定位到屏幕的某个范围时就可以使用这样的方式来对屏幕的显示区域大小进行设置:
l
J2ME:
调用setClip()和clipRect()来限定当前屏幕的显示区域
OPhone:
调用下面的方法来实现与
J2ME同样的功能,但是要注意最后一个参数:
- public boolean clipRect(float left, float top, float right, float bottom, Region.Op op)
最后一个参数为:Region.Op.REPLACE
图片旋转
图片旋转是游戏中经常采用的一种技术。所谓图片旋转就是通过代码来对原始图片进行特定角度的旋转,这样做可以有效的减少图片资源的数量,从而在一定程度上减小apk包的体积。
J2ME:
使用Nokia API drawImage()方法
调用drawRegion()方法
使用setClip方法来实现逐个像素翻转
OPhone:
在OPhone中实现对图片的旋转同样是十分简单和方便的,有以下几种方法:
1、调用public void drawBitmap(Bitmap bitmap, Matrix matrix, Paint paint),举例如下:
matix=new Matrix();- matix.setRotate(30);
- @Overrideprotected void onDraw(Canvas canvas)
- {canvas.drawBitmap(img, matix, paint);
- }
2、实现创建好需要翻转的Bitmap对象,在绘制时直接使用翻转后的图片对象,代码如下:
//创建翻转图片- public Bitmap createTransImage(Bitmap img,int trans){
- try {
- int width = img.getWidth();int height = img.getHeight();
int newWidth = 200;- int newHeight = 200;
- // calculate the scale - in this case = 0.4ffloat scaleWidth = ((float) newWidth) / width;
- float scaleHeight = ((float) newHeight) / height;
- // createa matrix for the manipulationMatrix matrix = new Matrix();
- // resize the bit mapmatrix.postScale(scaleWidth, scaleHeight);
- // rotate the Bitmapint degree=0;
Bitmap resizedBitmap=null;- int data[];int buf;
switch(trans)- {case ROTATE_HOR:
- //创建镜像翻转
data=new int [img.getWidth()*img.getHeight()]; - img.getPixels(data, 0, img.getWidth(), 0, 0, img.getWidth(),img.getHeight());//交换数据
- for(int i=0;i<img.getHeight();i++) for(int j=0;j<img.getWidth()/2;j++)
- {
buf=data[i*img.getWidth()+j]; - data[i*img.getWidth()+j]=data[img.getWidth()*(i+1)-(j+1)];data[img.getWidth()*(i+1)-(j+1)]=buf;
- }
resizedBitmap=Bitmap.createBitmap(data, img.getWidth(), - img.getHeight(), Bitmap.Config.ARGB_4444);; return resizedBitmap;
- case ROTATE_VER://创建镜像翻转
data=new int [img.getWidth()*img.getHeight()];img.getPixels(data, 0, img.getWidth(), 0, 0, img.getWidth(),img.getHeight());- //交换数据for(int i=0;i<img.getHeight()/2;i++)
- for(int j=0;j<img.getWidth();j++) {
buf=data[i*img.getWidth()+j];data[i*img.getWidth()+j]=data[(img.getHeight()-i-1)*img.getWidth()+j];- data[(img.getHeight()-i-1)*img.getWidth()+j]=buf; }
resizedBitmap=Bitmap.createBitmap(data, img.getWidth(),- img.getHeight(), Bitmap.Config.ARGB_4444);;
- return resizedBitmap; case ROTATE_90:
- matrix.postRotate(90); // recreate the new Bitmap
resizedBitmap = Bitmap.createBitmap(img, 0, 0, width, height, matrix, true);- return resizedBitmap; case ROTATE_180:
- matrix.postRotate(180);// recreate the new Bitmap
resizedBitmap = Bitmap.createBitmap(img, 0, 0, width, height, matrix, true);- return resizedBitmap; case ROTATE_270:
- matrix.postRotate(270);// recreate the new Bitmap
resizedBitmap = Bitmap.createBitmap(img, 0, 0, width, height, matrix, true);- return resizedBitmap; }
- return resizedBitmap; }
- catch (Exception e) {
- return null; }
- }
3、使用clipRect()逐个像素点绘制(此方法不推荐使用)
绘制矩形
J2ME:
drawRect()绘制空心矩形
fillRect()绘制填充矩形
OPhone:
调用下面方法来绘制矩形,但一般默认为绘制填充矩形
- public void drawRect(float left, float top, float right, float bottom ,Paint paint)
声音处理
J2ME:
创建:
Player coverSound =Manager.createPlayer(InputStream);- coverSound.prepare();
播放:
暂停:
声音设置:
VolumeControl vc = (VolumeControl)Player.getControl(“VolumeControl”);- vc.setLevel(50);
关闭:
OPhone:
创建:
MediaPlayer coverSound =MediaPlayer.create(Activity,R.raw.back);- coverSound.prepare();
播放:
暂停:
声音设置:
AudioManager vc = (AudioManager)Activity.getSystemService(Context.AUDIO_SERVICE);- vc.adjustVolume(AudioManager.ADJUST_LOWER, 1);
关闭:
coverSound.stop();- coverSound.release();
中断处理
在游戏进行的过程中难免会出现电话、短信这样的突发事件,这时,OPhone系统会切出游戏去让用户处理外部事件,因为这些事件要比应用程序的优先界别高,而切出的时候我们势必要将游戏暂停,以便让用户处理完外部事件之后回来继续游戏。
这两个平台都提供了响应外部事件的方法,也就是说系统在切出和返回游戏的同时会调用这些方法,开发人员只需要把游戏暂停的逻辑写入该方法即可。
J2ME:
重写Canvas类中的hideNotify()来响应外部事件
重写Canvas类中的showNotify()来响应从外部事件返回时事件
OPhone:
由于我们并不关心外部事件的种类,只需要判断是否有外部事件,因此只重写下面的方法来处理外部事件,其中参数值代表当前窗口是否有焦点
- public void onWindowFocusChanged(boolean visibility)
按键处理
按键是游戏中必不可少一项功能。在这两个平台中对手机键盘的响应和处理的原理相同:
获得当前的按键键值
根据键值定义不同的处理逻辑
将处理逻辑定义到响应的方法中
J2ME:
重写keyPressed(int key)来处理键盘按下事件
重写keyReleased(int key) 来处理键盘抬起事件
重写keyReapeted(int key)用来处理持续按键
比如:
protected void keyPressed(int keyCode)- {setkeystate(keyConvert(keyCode), true);
- process_key(keyConvert(keyCode));
- }
OPhone:
与
J2ME一样,在OPhone也是通过重写View类中的方法来实现对按键事件的响应,方法如下:
public boolean onKeyDown(int keyCode, KeyEvent msg) 来处理键盘按下事件
public boolean onKeyUp(int keyCode, KeyEvent msg) 来处理键盘抬起事件
比如下面的代码:
@Override- public boolean onKeyDown(int key,KeyEvent event) {
- switch(key) {
- case KeyEvent.KEYCODE_DPAD_UP:
- break; case KeyEvent.KEYCODE_DPAD_DOWN:
- break; return true;
- }
注意:要使按键可以被响应,需要在构造函数中调用this.setFocusable(true);
触摸屏处理
J2ME:
重写pointerPressed(int x,int y)来处理触摸笔按下事件
重写pointerReleased(int x,int y)来处理触摸笔抬起事件
OPhone:
重写下面的方法来处理触屏事件:
public boolean onTouchEvent(MotionEvent me)- { if(me.getAction()==MotionEvent.ACTION_DOWN)
- 。。。。。。 else if(me.getAction()==MotionEvent.ACTION_UP)
- 。。。。。。 return true;
- }
双缓冲的使用
双缓冲用来减少直接向屏幕的绘图速度,消除由于重绘延迟所导致的闪烁现象。
J2ME:
Image bufImage=Image.createImage(bufWidth, bufHeight);- Graphics bufGraphics=bufImage.getGraphics();
OPhone:
Bitmap carBuffer = Bitmap.createBitmap(bufWidth, bufHeight, Bitmap.Config.ARGB_4444);- Canvas carGp = new Canvas(carBuffer);
资源文件的的存放位置及读取
J2ME:
资源文件并没有固定的位置要求,可以放到src,也可以放到res,甚至可以自定义文件夹存放。
OPhone:
不同的资源最好按照要求存在在不同的文件夹下面,如:
图片:
声音:
数据:
asserts- InputStream is=ActivityInstance.getAssets().open(path + ".dat");
屏幕刷新
游戏中需要不断的刷新屏幕来实现动画的绘制和地图的滚动,一般我们要把刷新速度控制在一个适当的范围,既不能太快也不能太慢。实际上,根据前面的介绍,所有与屏幕绘制相关的逻辑都定义到了一个方法中(
J2ME:paint(),OPhone:onDraw()),因此刷新就是对这个方法的再次调用。但是由于某些原因,一般并不是直接调用这两个方法,而是通过下面这种途径来实现:
J2ME:
在线程中调用repaint()方法来实现屏幕更新,也即执行对paint()的调用。
OPhone:
在线程中调用postInvalidate();实现屏幕刷新,也即执行对onDraw ()的调用。
颜色的使用
J2ME:
setColor(int,int ,int)- setColor(int argb)
方法用来设置当前颜色,如果采用ARGB格式,可以省略Alpha通道值。
OPhone:
setColor(0xAARRGGBB); 不能省略Alpha通道值。
所谓ARGB模式就是用用十六进制表示的一种方式,在这种模式中,每个颜色值用两位表示,比如:0XFFFF0000就代表红颜色。其中Alpha通道代表半透明。
数据保存和读取
J2ME:
J2ME不能随意读取手机文件,除非使用JSR75。一般游戏中的数据都需要保存到RMS(Record Manage System)中。
OPhone:
OPhone提供了File,Sql lite,Content Provider和SharedPreferences等多种方式来保存文件,比
J2ME方便的不是一点半点。通常的游戏需要保存的数据比较少,且通常很简单而且不希望外部程序访问,因此用SharedPreferences来处理是比较好的选择。SharedPreferences采用键值对的方式来存储数据,使用方法如下所示:
保存:
SharedPreferences settings = ActivityInstance.getSharedPreferences(String name,int mode);- SharedPreferences.Editor editor = settings.edit();editor.putBoolean("hasRec", true);
- ......editor.putInt("ex",enemy.i_X_abs);
- editor.commit();
读取:
SharedPreferences settings = ActivityInstance.getSharedPreferences(String name,int mode);- this.curFaceDirection=settings.getInt("cd", 0);
这种方法适合保存游戏中的一些用户设置的信息,比如音量大小,联网状态等。如果是RPG,ACT类型的游戏,则要保存英雄的一些信息,这时候用对象序列化的方式将精灵对象序列化之后保存到文件中无疑是比较好的选择。
填充方式
J2ME:
J2ME中用draw***()和fill***()区分绘制空心还是实心多边形
OPhone:
OPhone一般默认为采用实心状态进行绘制,可以通过如下方法进行状态设置:
paint.setStyle(Style.FILL);- paint.setStyle(Style.STROKE);
锚点
在绘制文字和图片的时候,对于屏幕上的左上,右上,左下,右下,中间等一些特殊位置往往采用锚点的方式来绘制,锚点实际上就是在要绘制的字符串或者图片上定义的一个绘制基准点。
J2ME:
水平方向三个点:
Graphics.LEFT- Graphics.RIGHT
- Graphics.HCENTER
垂直方向三个点:
Graphics.TOP- Graphics.BOTTOMGraphics.BASELINE(只能用在字符串绘制中)
- Graphics.VCENTER(只能用在图片绘制中)
一般绘制的时候采用“水平|垂直”这样的方式
OPhone:
通过调用Paint类的setTextAlign(Paint.Align.LEFT);方法来进行设定,具体参数请参考相关API,实际上与
J2ME大同小异。
连接处理
J2ME:
HttpConnection conn = (HttpConnection) Connector.open("www.baidu.com", Connector.READ, true);- conn.setRequestMethod("GET");conn.setRequestProperty("accept", "*/*");
String location = conn.getRequestProperty("location");- int resCode = conn.getResponseCode();
- InputStream stream = conn.openInputStream();
- conn.close();
OPhone:
URL url = new URL("www.baidu.com");
HttpURLConnection conn = (HttpURLConnection) url.openConnection();- conn.setDoInput(true);conn.setConnectTimeout(10000);
- conn.setRequestMethod("GET");conn.setRequestProperty("accept", "*/*");
String location = conn.getRequestProperty("location");- int resCode = conn.getResponseCode();
- InputStream stream = conn.getInputStream();
- conn.disconnect();
总结了一下,在联网方面有以下几点不同之处:
J2ME中的连接从Connector打开,OPhone中从URL对象打开;要设置连接是否可读写,
J2ME中可以直接在Connector.Open()时设置,而在OPhone中必须使用setDoInput(boolean)和setDoOutput(boolean)方法设置。
在
J2ME中可以在Connector.Open()中对连接进行超时设置,在OPhone中使用setConnectTimeout(int)不仅可以对连接超时进行设置,还能设置超时时间,参数为0时忽略连接超时。
在使用这些API时,一定要注意每个参数的意义,比如
J2ME中drawRect()的后两个参数为宽度和高度,而在OPhone中则变成了结束点的坐标,使用时千万不能想当然的随意传参。
对于Override方法的定义,一定别忘了super.的方式来进行回调
总结
上面基本上把
J2ME和OPhone在2D游戏游戏开发中常用的API做了一个比较,了解这些内容后,基本上是可以比较容易地把及
J2ME的游戏游戏平顺地迁移到OPhone平台。当然,此处只限制为游戏,如果你想把一款
J2ME的软件迁移到OPhone平台,此方法并不适用,你需要学习OPhone的控件的使用。