1、Android游戏开发基础Android游戏开发基础作者:宋云 前言:阅读本文之前,最少要有Hello World 的经验,至少知道Android软件开发的过程,知道代码都要布置在什么地方。本文并不是十分细致,但是作为查阅也足够用了,对于没有开发经验的朋友阅读本文要多做练习。游戏开发之路任重而道远,作为个人游戏开发者,其中的艰辛更是比团队开发要辛苦的多。同志们一定要坚持下去,前方会有那个柳暗花明的世外桃源。目录一、绘制矩形 1二、拖拽矩形 3三、控制时间 4四、双缓冲 5五、显示BITMAP 7六、播放声音 8七、全屏与自动调整资源 9八、存储数据 118.1 SharedPreference
2、s 118.2 SQLite 11一、绘制矩形绘制任务最简单的就是在界面上绘制一个矩形。首先了解View类。该类上可以进行任意绘制。继承该类重写OnDraw(Canvas canvas)函数即可实现每次更新界面时的绘制内容。例如下面这个最简单的继承View类GameView:该类最最简单的任务就是绘制了一个红色的矩形。对于不熟悉Android资源使用的人这里解释下颜色资源。在values目录下新建colors.xml既是颜色资源。颜色数据的格式支持#RGB、#ARGB、#RRGGBB、#AARRGGBB。下面的例子就是一个红色的声明。同时会在R文件中自动生成color.red。在使用颜色时用g
3、etResources().getColor(R.color.red)获取。显然这是有些麻烦的,倒不如专门声明一个颜色类管理颜色,每个颜色值都是#AARRGGBB格式,因为在标准的函数中接受的都是这种格式。下面是View类的调用和更新,以下是主Activity 类的代码:这个类也是写的很简单,就是初始化了一个GameView并设置成为主容器试图。如果要更新视图则要添加一个线程,来发送更新消息。线程类代码如下,同样是最简单的一个线程只做了一件事就是发送更新消息。在OnCreate()函数中增加初始线程并运行的代码即可:new Thread(new GameThread(gv).start()运行
4、结果如下,可以在OnDraw函数中添加一些代码,例如渐变颜色red += 0x00000011以验证更新时成功。二、拖拽矩形可以在View的子类中添加OnTouchEvent(MotionEvent event)方法编写触摸屏事件。Event有如下方法需要使用到:getAction():获取Action编码以判断当前是什么事件类型,详细可以查阅API文档。其编码中还含有第几个触摸索引信息(多点触摸),其中MotionEvent.ACTION_DOWN,:对应鼠标事件的按下动作;MotionEvent.ACTION_UP;相当于鼠标抬起动作;MotionEvent.ACTION_MOVE:拖拽动
5、作。贴别的是与MotionEvent.ACTION_MASK(其值为0xff,255)进行与运算可以提取出只含有动作信息的编码。getPointerCount():获取指针索引,自然就是依靠这个方法在软件层实现的多点触摸控制。getX(int index),getY(int index):方法获取索引为index的指针当前动作的位置。现在就可以实现拖拽矩形的代码了,首先得将矩形的RECT定义成全局变量,之后利用offset移动矩形,在更新绘制时即可更新重绘。一下是OnTouchEvent的代码:其中maskAction方法是将action与ACTION_MASK进行与运算,返回运算结果。三、控
6、制时间时间的控制在游戏中极为重要,下面对实现Runnable接口的类填写控制时间代码。System.currentTimeMillis():方法获取系统时间,以毫秒为单位。经过试验在虚拟机中平均一次sleep需要10毫秒,因此基础睡眠时间定为10毫秒为基准。重绘时间定为5个基准单位,数据更新时间定为10个基准单位,每次睡眠获取时间差,减掉时间差之后=0即实现动作。具体代码也非常简单,如下:整个代码主要体现一种思想,绘制和数据更新一定要分开来做,各自实现其功能。确定基准单位时间,并且尽可能的减小时间误差。四、双缓冲双换成技术是游戏开发中必然会用到的技术,使游戏成像流畅的进行。使用SurfaceV
7、iew类可以很容易的实现这个功能。下面将之前写的GameView类和GameRunnalbe类合并成一个类GameSurfaceView类。继承SurfaceView类实现接口SurfaceHolder.Callback,Runnable,一个接口用来处理系统消息,另一个接口用来处理线程。SurfaceHolder用来处理所有的系统消息和绘制,缓冲Canvas也需要用它来实现。在构造函数中实例化一个SurfaceHolder类,并且添加回调函数,以处理系统消息。三个系统消息,需要添加了回调函数才会被调用,分别处理改变大小、创建和销毁时的动作。与GameRunnalbe类的run方法和变量都是一
8、样的,唯一不一样的地方时给GameView发送更新消息改成了,直接调用绘制函数draw(),并且通过变量mLoop来控制线程的终止。开构造函数中出事成true,销毁函数中置为false。绘制函数,与GameView中的基本一样,不同的地方时需要锁定画布、清屏、锁屏显示。由SurfaceHolder实现了双缓存。onTouchEvent与GameView中的一样,这里就不重复了。现在已经实现了双缓存,并且SurfaceView更灵活。五、显示BITMAP下面来介绍Canvas显示Bitmap。显示一个Bitmap的方法有很多,这里介绍最简单的一种。Canvas的drawBitmap方法。在绘制之
9、前需要先获取Bitmap资源,过程是从Drawable资源中获取BitmapDrawable再获取该Drawable的Bitmap。这几行代码获取了7张Bitmap,其中R.drawable.g1这些东西是添加图片的时候程序自动生成的,每一个ID对应一个图片资源。这7个图片连在一起就可以一个小动画,例如设置一个全局变量p,更新数据时p+显示的时候显示bmpp即可。Canvas的drawBitmap方法有一下几种:void drawBitmap(Bitmap bitmap, float left, float top, Paint paint):最基本的绘制方法,需要绘制位置的左上角坐标和要绘制
10、的bitmap但是paint可以为null。void drawBitmap(Bitmap bitmap, Rect src, RectF dst, Paint paint)void drawBitmap(Bitmap bitmap, Rect src, Rect dst, Paint paint):这两个方法类似需要绘制的bitmap;需要绘制bitmap上的哪个矩形区域src的内容;绘制到canvas的哪个矩形位置dst,其中RectF表示浮点型的矩形,同样paint可以为null。void drawBitmap(Bitmap bitmap, Matrix matrix, Paint pai
11、nt):这个方法适合绘制需要经常变动的对象,之前介绍的三种方法都只适合绘制没有太多变化的图片,如果一个图片需要经常旋转,缩放等操作用这个方法在合适不过。setTranslate(x,y)可以设置平移的位置,preTranslate(x,y)累加平移位置。同样有preRotate,setRotate,preScale,setScale等。至于一下三种方法:void drawBitmap(int colors, int offset, int stride, int x, int y, int width, int height, boolean hasAlpha, Paint paint)voi
12、d drawBitmap(int colors, int offset, int stride, float x, float y, int width, int height, boolean hasAlpha, Paint paint)void drawBitmapMesh(Bitmap bitmap, int meshWidth, int meshHeight, float verts, int vertOffset, int colors, int colorOffset, Paint paint)有兴趣的读者可以在一下代码既是在(0,320)位置绘制bmpp图像,这样就显示出了一个动
13、画。同时这样产生一个问题浪费内存,读取图片的时候利用BitmapFactory的decodeStream方法获取bitmap要比从drawable中获取bitmap要节省更多的内存空间。代码如下:InputStream is = this.getResources().openRawResource(id); BitmapFactory.Options options=new BitmapFactory.Options(); options.inJustDecodeBounds = false; /options.inSampleSize = 10; /width,hight设为原来的十分一
14、Bitmap bmp =BitmapFactory.decodeStream(is,null,options); 回收bitmap资源bitmap.recycle(); 再提示系统回收资源system.gc();六、播放声音播放多媒体都可以用MediaPlayer实现,比较方便的实现办法是在资源中新建目录raw,例如:里面有一个声音文件down.wav,加入了声音文件R文件中自动生成起ID,只需要用MediaPlayer的create方法创建一个MediaPlayer对象,播放即可:三行代码实现播放声音,其中seLooping是设置其是否循环。但是在真正使用声音资源的时候,这样是极其低效的。我
15、们可以构造一个常用声音的声音池,预先创建好声音对象,只需要一个函数play(int id)就播放声音,并且节省资源。七、全屏与自动调整资源/无标题requestWindowFeature(Window.FEATURE_NO_TITLE);/全屏模式getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN);/横屏 /setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSC
16、APE);全屏时很好设置的,在Activity的初始化中加入上面代码就可以了。要让程序界面保持一个方向,不随手机方向转动而变化的处理办法: 要在AndroidManifest.xml里面配置一下。加入这一行android:screenOrientation=landscape。例如(landscape是横向,portrait是纵向):Java代码: 另外,android中每次屏幕的切换动会重启Activity,首先要在配置Activity的时候进行如下的配置:android:configChanges=keyboardHidden|orientation,另外需要重写Activity的onCo
17、nfigurationChanged方法就不会再重启了。实现方式如下,不需要做太多的内容:Overridepublic void onConfigurationChanged(Configuration newConfig) super.onConfigurationChanged(newConfig); 有时为了使用与任何大小的屏幕,先获取屏幕信息: float w = this.getWindowManager().getDefaultDisplay().getWidth(); float h = this.getWindowManager().getDefaultDisplay().ge
18、tHeight(); 再计算缩放比例: scaleX = w/STAND_WIDTH; scaleY = h/STAND_HEIGHT; 绘制的时候Matrix先设置scale,再对transX,transY缩放平移即可。但是这样做有些麻烦,可以用Android程序所自提供的功能,准备三种像素的图片素材,在不同分辨率的屏幕上运行,程序会自动选择使用哪种素材,在AndroidManifest.xml中加入如下配置: android:largeScreens=true表示支持大屏幕(480X800);android:largeScreens=true表示支持中等屏幕(320X480);androi
19、d:largeScreens=true表示支持小屏幕(240X320);android:resizeable=true表示可重新调整尺寸;android:anyDensity=true表示支持任何分辨率;资源目录下的结构如下:我们在-hdpi的目录下放大分辨率下需要的资源,-mdpi下方中分辨率需要的资源,-ldpi目录下放低分辨率需要的资源,这样程序就可以自动调整所需要的素材了。 资源用系统自动调整了,那么绘制的偏移坐标,需要我们自己设定,每一个物体的坐标设置三个,高中低三种分辨率的偏移坐标,之后在程序初始化的时候查询一下屏幕分辨率以确定使用哪个坐标。八、存储数据存储数据也是游戏中最基本的功
20、能,介绍完数据的存储,那么制作一个游戏所需要的所有基础就都介绍完了。剩下的就是程序设计,算法,仿真物理等。这些东西就都不是能够速成的了,需要耐心的从基础练起。8.1 SharedPreferencesSharedPreferences主要用来记录简单的参数,和一些简单的变量。以私有方式获得SharedPreferences settings = getPreferences(Activity.MODE_PRIVATE);获得数据(如不存在则返回默认值):int num = Defense_TetrisActivity.settings.getInt(num, 0);编辑数据:Editor ed
21、it = Defense_TetrisActivity.settings.edit();edit.putInt(num, num);mit();/执行保存8.2 SQLite将数据存储在数据库中既方便管理又方便维护,大的小的数据都可以放到数据库中存储。1. 创建和打开数据库openOrCreateDatabase方法实现创建或打开一个数据库,存在则打开不存在则创建一个数据库,返回SQLiteDatabase失败则抛出FileNotFoundException异常,例如:sqliteDB = this.openOrCreateDatabase(“Example_DB”,MODE_PRIVATE,
22、null);创建了一个不存在的或打开了一个存在的Example_DB数据库。2. 创建表execSQL方法执行一个创建表的SQL语句来创建表,例如:String CREATE_TABLE = “CREATE TABLE table1(_id INTEGER PRIMARY KEY,num INTEGER,data TEXT)” sqliteDB.execSQL(CREATE_TALBE); 创建了一个名为table1的表,有3个字段,_id为主键;num为整型数据;data为字符段。3. 向表中添加一条数据ContentValues相当于一个Map,存储了一条数据,例如:ContentValu
23、es cv = new ContentValues();cv.put(TABLE_NUM,1);cv.put(TABLE_DATA,测试数据库数据);sqliteDB.insert(TABLE_NAME,null,cv);或者使用execSQL执行插入数据SQL语句。4从表中删除数据sqliteDB.delete(TABLE_NAME,WHERE _id=0,null)删除了TABLE_NAME表中_id等于0的数据,或者使用execSQL执行删除表中数据的SQL语句。或者使用execSQL执行删除表中数据的SQL语句。5修改表中的数据ContectValues cv = new Conten
24、tValues();cv.put(TALBE_NUM,3);cv.put(TABLE_DATA,”修改后的数据”);sqliteDB.update(“table1”,cv,”num=0”,null);将table1表中num=0的数据修改。或者使用execSQL。6. 关闭数据库sqliteDB.close();7. 删除表SqliteDB.execSQL(“DROP TABLE table1”);8. 删除数据库This.deleteDatabase(DATABASE_NAME)9. 查询表Cursor cur = sqliteDB.rawQuery(“SELECT * FROM table1”,null);if(cur!=null) if(cur.moveToFirst() do int numColum= cur.getColumIndex(“num”); int num = cur.getInt(numColum); while(cur.moveToNext();