2012-2013第一学期期末作业报告
课程名称: 移动终端游戏开发 学 院: 软件学院 专 业: 软件工程
班 级: 学 号: 姓 名: 左杭 成 绩:
20xx年 12 月 29 日
基于Android的移动终端游戏设计与实现
1. 游戏概述
桌球游戏是一个充满操作性的游戏,桌面足球又是其中之一,以其独特的魅力吸引了大批玩家,但是随着社会的发展与进步,桌球游戏也逐渐的消失在了人们的视野之中,为了重新体验桌球游戏的独特魅力,准备开发一个桌球游戏-超级足球。
超级足球主要是模仿了现实生活中的桌面足球,在一定的区域内实验对于足球的控制,完成进球得分,得分高着获得胜利。
2. 游戏架构分析
本游戏根据现实生活中的桌面足球的来,游戏会分为两家对战,对与操作杆的控制来击打小球,使得小球射进球门,为此我们需要设界面的显示模块和计小球的运动模块,对于游戏来说需要一些的奖励,所以增加一个物品奖励的模块,所以根据构思可以分为界面模块、运动的控制模块和物品奖励的模块。
游戏的界面模块:主要显示游戏的主界面,大致可以分为加载界面和游戏主界面和菜单的界面,通过这三个界面来展现游戏。
游戏的控制模块:主要包括了足球,玩家球员,电脑的控制,并且要实现碰撞检测,并且对键盘进行监听,并且对游戏的奖励模块也要有一些控制。 游戏的奖励物品模块:在游戏中应该有物品的奖励模块来提高玩家对游戏的兴趣,物品奖励模块应该遵循一定的规则和一定的不确定性。
总架构如下所示:
3. 游戏详细设计与实现
对于游戏的开发,觉得对于每个模块一个部分一个部分的完成,并且不断的测试,并且不断的完善。
1.首先应该对游戏的界面部分进行设计:
创建一个主类FootBalGameActivity,FootBalGameActivity继承至Activity,然后在创建一个类WelcomeGame,这个类主要作为开始的显示,并且继承至surfaceView,游戏需要等待,于是需要创建一个等待的视图,创建LodingGame,继承至surfaceView,然后需要对游戏主界面的显示,所以创建一个类MainGame,也继承至SurfaceView。并且创建游戏的足球类Ball,以及实现球队的CustomTeam。
2.然后对于游戏的控制模块进行设计:
游戏中有小球电脑玩家手机玩家等需要控制的类,这些类需要继承Thread类,来取得各自的运行,小球类需要自己的运行模块,既需要自己一个线程类来控制,创建一个BallThread,继承至Thread,玩家Player需要自己的逻辑类,并且需要对小球的控制,既创建PlayerThread,来对小球进行控制。在各自的线程中做好自己的控制工作,来对游戏进行监听,从而控制游戏。
3.最后奖励物品模块的设计:
由上面的总体架构可以知道,奖励物品Award,奖励物品有两个,一个是IceAward和LargerAward这两个类,一个是冰冻效果。另外一个是增宽球门,在游戏中也需要一个类来对奖励的物品进行控制,创建一个类AwardManager,这个类独自的一个线程,所以应该继承至Thread。
之后就要准备游戏的资源了,首先是球队的图片,本游戏准备选取中超联赛的十六只球队球队作为游戏的可选球队,大概如下所示:
截取以上的球队资料并且保存图片如下所示:
然后就是声音文件了,奖励物品有两个声音,一个是冰冻的效果,另外一个是增宽球门的效果,根据设想还有足球射进球门的声音和失败的声音和胜利的声音,当然足球滚动也应该有一个声音,背景音乐也应该有一个声音,于是准备了声音如下所示:
重里面截取需要的音乐,截取完如下所示:
游戏经历了上面的大致规划后,就开始进行了详细设计的阶段了,首先是对界面显示模块的详细设计:
1.1 FootballActivity这个主类,这个类的主要功能是运行程序时对界面加载显示,游戏需要显示界面和播放声音,所以需要获取图片资源,和播放声音文件,本游戏播放音乐使用Mediaplay,并且创建相行的Mediaplay,由于在足球运动中球员有十人,也分别为前中后三个位置,我们需要创建前中后三个矩形框,来去的对前中后场人数的设置,并且根据这前中后三个位置来设定游戏的画面,可以通过对人数的分配来变化不同的阵形:
boolean wantSound = true; //判断声音是否播放的标志位
MediaPlayer mpWelcomeMusic; //打开游戏时登录的声音
MediaPlayer mpKick; //足球运动的声音
MediaPlayer mpCheerForWin; //玩家胜利的声音
MediaPlayer mpCheerForLose; //玩家失败的声音
MediaPlayer mpCheerForGoal; //玩家进球的庆祝的声音
MediaPlayer mpIce; //足球撞击冰块的声音
MediaPlayer mpLargerGoal; //球门增大的声音
Rect [] rectPlus; //表示增加了球员的矩形框
Rect [] rectMinus; //表示减少了球员的矩形框
Rect rectSound; //表示是否播放声音的矩形框
Rect rectStart; //表示开始的矩形框
Rect rectQuit; //表示退出的矩形框
Rect rectGallery; //表示gallery的矩形框
//下面是获取图片资源并且保存在imagIDs里面
int [] imageIDs ={
R.drawable.club_1,
R.drawable.club_2,
R.drawable.club_3,
R.drawable.club_4,
R.drawable.club_5,
R.drawable.club_6,
R.drawable.club_7,
R.drawable.club_8,
R.drawable.club_9,
R.drawable.club_10,
R.drawable.club_11,
R.drawable.club_12,
R.drawable.club_13,
R.drawable.club_14,
R.drawable.club_16,
};
int clubID = imageIDs[0]; //表示当前的选取图片的id为0
游戏开始的时候欢迎界面,必须有一定的加载,创建函数initWelcomGame(),然后就必须初始化rect的资源,于是创建initRect(),并且之后需要对游戏里面的声音进行加载,创建函数inintSound()来取得资源文件对象;
public void initWelcomeGameSound(Context context){
mpWelcomeMusic = MediaPlayer.create(context, R.raw.music);
}
public void initRects(){
rectPlus = new Rect[3]; //表示前中后场球员数的加号
rectMinus = new Rect[3]; //表示前中后球员数的减号
for(int i=0;i<3;i++){
rectPlus[i] = new Rect(244,200+40*i,280,236+40*i);
rectMinus[i] = new Rect(280,200+40*i,316,236+40*i);
}
rectSound = new Rect(135,370,185,420);
rectStart = new Rect(205,425,295,475);
rectQuit = new Rect(25,425,115,475);
rectGallery = new Rect(10,10,310,110);
}
public void initSound(){
mpKick = MediaPlayer.create(this, R.raw.kick);
updateProgressView();//更新游戏Loding进度条
mpCheerForWin = MediaPlayer.create(this, R.raw.cheer_win);
updateProgressView();//更新游戏Loding进度条
mpCheerForLose = MediaPlayer.create(this, R.raw.cheer_lose);
updateProgressView();//更新游戏Loding进度条
mpCheerForGoal = MediaPlayer.create(this, R.raw.cheer_goal);
updateProgressView();//更新游戏Loding进度条
mpLargerGoal = MediaPlayer.create(this, R.raw.lager_goal);
updateProgressView();//更新游戏Loding进度条
mpIce = MediaPlayer.create(this, R.raw.ice);
updateProgressView();//更新游戏Loding进度条
}
在界面显示中,必学对前中后场球员进行检查,因为前面已经提到了前中后场球员只能有十个人员,创建函数checkLayout():
public boolean checkLayout(int [] layout){
int sum=0;
for(int i=0;i<layout.length;i++){ //遍历取得球员的占位
if(layout[i]<0){ //确定某一个位置上的人数不能大于0 return false;
}
else{
sum+=layout[i]; //将各个阵线上的球员个数相加
}
}
if(sum == 10){ //人员和为十即是占位合法的 return true;
}
else{
return false;
}
}
1.2 大概模型已经构建成功,由于需要自己勾践gallery,所交创建CustomTeam,这个类显示了球队的列表,提供给玩家选择球队,并且这个类可以实现android提供的gallery类似的功能,可以对于玩家的操作做出相应的视觉反映,创建CustomTeam:
要实现与gallery类似的功能需要对于用户的操作做出具体的分析,并且对其进行准确的判断,所以需要创建一个Bitmap的数组来取得图片集,并且设置好图片集的数目和图片在游戏中的位置,也需要取得图片的宽度和高度: Bitmap [] bmpContent; int length; //要现实的图片集 //图片集的个数
int currIndex; int startX; int startY; int cellWidth; int cellHeight; //当前被选择图片的Id //gallery在游戏中左上角在屏幕中的x的坐标 // gallery在游戏中左上角在屏幕中的y的坐标 //图片的宽度 //图片的高度
针对玩家的操作,做出相应的判断,选择相应的图片显示,创建setCurrent(),并且还需要创建一个函数drawrect()来描绘gallery中的图片,并且通过drawrect()函数来对图片进行一些特性的描绘
public void setCurrent(int index){ //设置当前显示的图片
if(index >=0 && index < length){
this.currIndex = index;
}
}
public void drawGallery(Canvas canvas,Paint paint){//绘制图片
Paint paintBack = new Paint();
paintBack.setARGB(220, 99, 99, 99);
//创建边框的画笔
Paint paintBorder = new Paint();
paintBorder.setStyle(Paint.Style.STROKE);
paintBorder.setStrokeWidth(4.5f);
paintBorder.setARGB(255, 150, 150, 150);
//画左边的图片
if(currIndex >0){
canvas.drawRect(startX, startY, startX+cellWidth, startY+cellHeight, paintBack); //背景
canvas.drawBitmap(bmpContent[currIndex-1], startX, startY, paint); //贴图片
canvas.drawRect(startX, startY, startX+cellWidth, startY+cellHeight, paintBorder); //画左边图片的边框
}
//画被选中的图片
canvas.drawRect(startX+cellWidth, startY, startX+cellWidth*2,
startY+cellHeight, paintBack); //背景
canvas.drawBitmap(bmpContent[currIndex], startX+cellWidth, startY, paint);
//画右边的图片
if(currIndex<length-1){
canvas.drawRect(startX+cellWidth*2, startY, startX+cellWidth*3, startY+cellHeight, paintBack); //背景
canvas.drawBitmap(bmpContent[currIndex+1], startX+cellWidth*2, startY, paint);
paintBorder.setARGB(255, 150, 150, 150); //画右边图片的边框
canvas.drawRect(startX+cellWidth*2, startY, startX+cellWidth*3, startY+cellHeight, paintBorder);
}
//画选中的边框
paintBorder.setColor(Color.RED);
canvas.drawRect(startX+cellWidth, startY, startX+cellWidth*2,
startY+cellHeight, paintBorder);
}
当然还需要一个对于事件的相应函数gallerytouchevent(),通过这个函数可以对传进来的坐标进行判断,并且产生相应的操作,判断点击的位置是在当前图片的左端还是在当前图片的又断并且做出响应:
public void galleryTouchEvnet(int x,int y){
if(x>startX && x<startX+cellWidth){ //点在了左边那张图片
if(currIndex > 0){ //判断当前图片的左边还有没有图片
currIndex --; //设置当前图片为左边的图片
}
}
else if(x>startX+cellWidth*2 && x<startX+cellWidth*3){ //点在了右边那张图片
if(currIndex < length-1){//判断当前图片的右边还有没有图片
currIndex++; //设置当前图片为右边的图片 }
}
}
1.3 当CustomTeam创建好之后,就可以着手创建了Welcomgame这个类了,这个类是游戏的欢迎界面,所以这个类应该继承至surfaceView,并且实现callback的接口,这个个欢迎类可以在不同的状态绘制不同的类容,这些类容可以设置起开场动画的透明度,并且对主界面进行一些初始化,所以需要的到主界面的类的引用,并且需要获取一些图片资源比背景图片和Customteam等,并且需要设置一个int型的数用来表示加载的不同状态:
FootBalGameActivity father; //主界面的引用
int index = 0; //开场3个动画帧的索引
int status = -1; //0代表足球动画,1代表背景转进来,2代表全部渐显,3代表待命
int alpha = 255; //透明度,初始为255,即不透明
int [] layout = {3,3,4}; //玩家球员的站位数组,3个值分别代表前场、中场、后场
CustomTeam cg; //自定义的Gallery类,用来选择玩家自己喜欢的球队 Bitmap [] bmpLayout; //代表前场、中场、后场3个阵线的图片数组 Bitmap bmpPlus; //加号图片
Bitmap bmpMinus; //减号图片
Bitmap bmpPlayer; //玩家图片
Bitmap [] bmpSound; //声音开关图片数组
Bitmap bmpStart; //开始按钮图片
Bitmap bmpQuit; //退出按钮图片
Bitmap [] bmpGallery; //存储Gallery对象要显示图片集
Bitmap [] bmpAnimaition; //存储欢迎动画帧的数组
Bitmap bmpBack; //背景图片
Matrix matrix; //Matrix对象,用来旋转背景图
在welcomGame这个类中,由于是对于图片的描绘,所以在这个类中的doDraw()函数就是用来描绘的,dodraw()函数可以对欢迎界面的状态的改变而绘制不同的函数,可以通过switch()函数来通过对status的改变来达到绘制不同界面的要求,在绘制完开始之后,就要开始绘制游戏的主界面了,通过对FootballActivity的引用,来去的游戏开始的一些资源并且传进dodraw()函数之中,来绘制CustomTeam(),还绘制出足球和相应的界面的图标: public void doDraw(Canvas canvas) {
Paint paint = new Paint();
switch(status){
case 0:
canvas.drawBitmap(bmpAnimaition[index], 0, 0, null);
break;
case 1://旋转背景图片
canvas.drawColor(Color.BLACK); //清屏幕
Bitmap bmpTemp = Bitmap.createBitmap(bmpBack, 0, 0,
bmpBack.getWidth(), bmpBack.getHeight(), matrix, true);//旋转背景图片
canvas.drawBitmap(bmpTemp, 0, 0, null); //绘制背景图 break;
case 2://全场透明
case 3:
canvas.drawColor(Color.BLACK); //通过清屏来重新绘制 paint.setAlpha(alpha); //设置透明度
canvas.drawBitmap(bmpBack, 0, 0, paint);//绘制背景
cg.drawGallery(canvas,paint); //绘制出自己定制的gallery for(int i=0;i<layout.length;i++){ //对于球场上各个阵线上的信息进行绘制
canvas.drawBitmap(bmpLayout[i], 0, 200+40*i, paint);//绘制阵线名称即前场、中场、后场
for(int j=0;j<layout[i];j++)
//绘制出主界面的开始退出声音万剑图标
canvas.drawBitmap(bmpPlayer, 65+j*18, 205+40*i, paint); }
canvas.drawBitmap(bmpPlus, 244, 200+40*i, paint); canvas.drawBitmap(bmpMinus, 280, 200+40*i, paint); }
canvas.drawBitmap(bmpSound[father.wantSound?0:1], 135, 370, paint);
canvas.drawBitmap(bmpStart, 205, 425, paint); canvas.drawBitmap(bmpQuit, 25, 425, paint); break;
}
}
1.4 在WelcomeGame中需要旋转图片,所以可以创建一个WelcomeThread()类来控制图片的旋转,这个类继承至Thread,这个类通过对Welcomegame的status的检测来获得不同的状态,来进行不同的处理,当欢迎界面完成后,这个也就跟着消亡了:
public void run(){
while(isWelcoming){
switch(father.status){//获取现在的状态
case 0:
animationCounter++; //换帧计数器自加
if(animationCounter == 4){ //计数器达到4时换帧
father.index ++;
if(father.index == 3){ //判断是否播放完毕所有帧 father.status = 1; //转入下一状态
}
animationCounter = 0; //清空计数器
}
break;
case 1:
father.matrix.postRotate(rotateAngle); //设置旋转角度 rotateCounter++; //计数器自加 if(rotateCounter == 6){//旋转计数器到了6
father.status = 2;//设置状态
father.alpha = 0; //设置alpha值,用于菜单渐显
}
break;
case 2:
father.alpha +=51; //alpha值增加
if(father.alpha >= 255){
father.status = 3;//进入待命状态,此状态玩家可以选择菜单选项 }
break;
case 3:
this.isWelcoming = false;
break;
}
try{
Thread.sleep(sleepSpan ); //休眠一段时间
}
catch(Exception e){
e.printStackTrace();
}
}
}
在主界面中由于要不断的重绘界面,既是需要不断的调用dodraw()函数,所以可以在开发一个线程WelcomeDrawThread类,这个类来进行对于welcome的重绘,这个类继承至Thread,起的run方法不断的调用WelcomGame的dodraw()函数,通过对游戏的状态的监听来判断是否继续运行:
public void run(){ Canvas canvas = null; while(isGameOn){
画布
} } try{ canvas = surfaceHolder.lockCanvas(null); //获得画布并且锁住 synchronized(surfaceHolder){ father.doDraw(canvas); //重新绘制画布 } } catch (Exception e) { e.printStackTrace(); } finally{ if(canvas != null){ //解锁画布 surfaceHolder.unlockCanvasAndPost(canvas); } } try{ Thread.sleep(sleepSpan); //休眠一段时间 } catch(Exception e){ e.printStackTrace(); }
在WelcomeGame因为实现了callback接口,所以可以对其中的回调函数进行一定的修改来启动WelcomeThread和WelcomeDrawThread:
public void surfaceCreated(SurfaceHolder holder) {
if(!wt.isAlive()){ //wt是welcomThread,这个是不断的修改当前数据的线程
wt.start();
}
if(!wdt.isAlive()){ //wdt是WelcomeDrawThread线程,这个线程是重绘界面显示的线程
wdt.start();
}
}
界面到此时类大部分已经完成,所以可以在主要类的oncreate()方法中来设置游戏开始时的显示,可以采用以下的方式来设置全屏和设置标题: requestWindowFeature(Window.FEATURE_NO_TITLE);
getWindow().setFlags(
WindowManager.LayoutParams.FLAG_FULLSCREEN,
WindowManager.LayoutParams.FLAG_FULLSCREEN
);
1.5 之后就是进入游戏的主界面了,由于进入游戏主界面需要加载一些资源,所以需要创建一个lodingGame的类,来对游戏的资源进行加载,在加载完成之后进入游戏主界面。 因为lodingGame这个类重绘游戏界面,所以这个类继承至surfaceview,还必须添加一个callback实现,在lodingGame中需要绘
制一个进度条,在资源加载的不同阶段来绘制不同的进度,可以通过创建另外一个线程类来对lodingGame的不听的刷新和判断绘制,当资源加载完后可以提示玩家加载已经完成,可以通过一些操作来进入游戏,这个类与前面的Welcomegame类似,采用了相同的原理进行刷屏:
FootBalGameActivity father; //Activity的引用
Bitmap bmpProgress; //进度条的图片
Bitmap [] bmpProgSign; //进度条上的标志物
Bitmap bmpLoad; //进度条图片对象
int progress=0; //进度,0到100
int progY = 330; //进度条的Y坐标
LoadingDrawThread lt; //LoadingView的刷屏线程 、
??
public void doDraw(Canvas canvas) {
canvas.drawColor(Color.BLACK); //清屏幕 canvas.drawBitmap(bmpLoad, 10, 100, null); //画加载时图片 canvas.drawBitmap(bmpProgress, 5, progY, null); //画进度条图片
Paint p = new Paint();
p.setColor(Color.BLACK); //设置画笔颜色
int temp = (int)((progress/100.0)*320); //将进度值换算成屏幕上的长度
canvas.drawRect(temp, progY, 315, progY+20, p); //画遮盖物挡住进度条图片
for(int i=0;i<3;i++){
canvas.drawBitmap(bmpProgSign[i], 140*i, progY-10, null); }
if(progress == 100){ //绘制进度条已满的提示文字 p.setTextSize(13.5f);
p.setColor(Color.GREEN);
canvas.drawText("单击屏幕开始游戏...", 100, progY+50, p); }else{ //绘制进度条未满的提示文字 p.setTextSize(13.5f);
p.setColor(Color.RED);
canvas.drawText("加载中,请稍后....", 120, progY+50, p); }
}
??
创建lodingThread类,来进行刷屏:
public void run(){
Canvas canvas=null;
while(flag){
try{
canvas = surfaceHolder.lockCanvas(null);//获得画布并锁住画布 synchronized(surfaceHolder){
father.doDraw(canvas); //重新绘制
}
} } } catch(Exception e){ e.printStackTrace(); } finally{ if(canvas != null){ surfaceHolder.unlockCanvasAndPost(canvas);//对画布解锁 } } try{ Thread.sleep(sleepSpan); //休眠一段时间 } catch(Exception e){ e.printStackTrace(); }
1.6 当在lodingGame完成之后,就是游戏的主界面了,创建一个类MainGame,这个类要显示界面同样采用继承surfaceview实现calback接口的方式来创建。在游戏的主界面是玩家的主要界面,所以需要虚拟一个玩家来进行游戏的对战所以应该创建一个玩家类player,player来模拟玩家与电脑对战,假设已经开发好了ball和player,记在MainGame里面需要创建各自的实列,由于需要进行碰撞检测所以需要得到球场的边界和桌球里面每个运动员之间的距离,也必须得到每个运动员各自的大小,才能进行碰撞检测,在游戏过程中,玩家和电脑控制小球进行射门,应该对其设置不同的难度来提高可玩性,但是难度有一个上限来控制,玩家的电脑的得分也应该有一个记录,得分的坐标也应该固定在游戏的某个位置,由于可以对球门进行增加,就应该对球门的坐标有一个记录。在游戏结束之后,不能由于结束了就结束了而没有一个记录,可以设置一个状态位来判断是否胜利了来播放胜利的音乐,当进球时也应该设置一个标志位,来播放进球的音乐。所以大概有以下的参数:
Bitmap bmpMyPlayer; //玩家的球员图片
Bitmap bmpAIPlayer; //AI的球员图片 Bitmap bmpGoalBanner; //进球后显示的图片 Bitmap [] bmpScores; //表示比分的数字图片 Bitmap bmpWinBanner; //游戏胜利的图片 Bitmap bmpWinText; //游戏胜利的文字 Bitmap bmpLoseBanner; //游戏失败的图片 Bitmap bmpLoseText; //游戏失败的文字 Bitmap bmpPassAll; //打赢所有比赛提示的图片 Bitmap bmpMyClub; //记录玩家所选的俱乐部 Bitmap bmpAIClub; //记录AI的俱乐部 Bitmap bmpMenu; //菜单图片 Bitmap bmpDialog; //对话框图片 Bitmap bmpBackField; //球场背景图片 //Rect对象,用来与点击事件发生的位置进行匹配 Rect rectMenu = new Rect(134,0,186,60); //代表菜单的矩形框, Rect rectYesToDialog = new Rect(75,271,100,292); //代表返回主菜单对话框
中的是按钮
Rect rectNoToDialog = new Rect(223,271,248,292); //代表返回主菜单对话框中的否按钮
int clubID;//存放俱乐部logo的图片ID
boolean isScoredAGoal; //若进了球则暂时会置为true
boolean isGameOver; //一局比赛结束
boolean isGameWin; //是否赢了
boolean isShowDialog; //是否显示对话框
boolean isGamePassAll; //是否打赢了所有等级的比赛
Ball ball; //小球对象
int fieldLeft=27; //球场的左边界在屏幕中的位置
int fieldRight=293; //球场的右边界在屏幕中的位置
int fieldUp = 68; //球场的上边界在屏幕中的位置
int fieldDown = 470; //球场的下边界在屏幕中的位置
int barDistance = 50; //两个摇杆的间距
int playerSize = 18; //运动员图片的大小
int maxLeftPosition = 115; //守门员最左能走到的地方=
int maxRightPosition = 205; //守门员最右能走到的地方=
int aiDirection = -1; //记录AI的运动方向,4为左,12为右= int level=1; //游戏的难度
int MaxLevel = 3; //最高等级
int [] scores = {0,0}; //比分,前我后敌
int scoreLeft = 80; //绘制左边比分时的参考X坐标
int scoreRight = 240; //绘制右边比分时的参考X坐标
int timeCounter=0; //计时器,用于显示
int winPoint = 8; //获胜点,谁的进球数先达到该值获胜
int AIGoalLeft; //AI球门左边=
int AIGoalRight; //AI球门右边=
int myGoalLeft; //玩家的球门左边=
int myGoalRight; //玩家的球门右边=
然后就应该在界面中画出小球玩家球场等界面了,并且在玩家运动中不断刷屏来显示游戏的进行,这个函数绘制了很多的东西,也对参数进行了不断的设置与重置:
protected void doDraw(Canvas canvas) { canvas.drawBitmap(bmpBackField, 0, 0, null); //画出小球 ball.drawSelf(canvas); //画出双方玩家 drawBothPlayers(canvas); //画出比分 drawScores(canvas); //检查并绘制活的Bonus checkAndDrawBonus(canvas); //画出双方的logo drawLogo(canvas); //画出菜单 //画球场背景
canvas.drawBitmap(bmpMenu, 134, 0, null);
if(isScoredAGoal){ //如果进了一球,绘制进球图片
canvas.drawBitmap(bmpGoalBanner,
(fieldLeft+fieldRight)/2-bmpGoalBanner.getWidth()/2,
(fieldUp+fieldDown)/2-bmpGoalBanner.getHeight()/2, null);
timeCounter++; //每画一次,计时器增加
if(timeCounter > 50){ //如果计时器增加到50
isScoredAGoal = false; //设置进球标志位为false
initRound(); //初始化游戏回合
timeCounter = 0; //清空计时器
ball.isPlaying = true; //小球运动标志位设为true }
}
if(isGameOver){//一场比赛结束,根据胜负,绘制相应图片
if(isGamePassAll){ //是否打赢了最强的AI
canvas.drawBitmap(bmpPassAll,(fieldLeft+fieldRight)/2-bmpPassAll.getWidth()/2, (fieldUp+fieldDown)/2-bmpPassAll.getHeight()/2, null);
timeCounter++;//每画一次,计时器增加
if(timeCounter >50){//如果计时器增加到50
isGameOver = false; //设置游戏结束标志位为false
isShowDialog = true; //设置显示对话框为true
}
}
else if(isGameWin){ //获得胜利
canvas.drawBitmap(bmpLoseBanner,
(fieldLeft+fieldRight)/2-bmpWinBanner.getWidth()/2,
(fieldUp+fieldDown)/2-bmpWinBanner.getHeight()/2-20, null);
canvas.drawBitmap(bmpWinText,
(fieldLeft+fieldRight)/2-bmpWinText.getWidth()/2,
(fieldUp+fieldDown)/2-bmpWinText.getHeight()/2, null);
timeCounter++;//每画一次,计时器增加
if(timeCounter >50){//如果计时器增加到50
isGameOver = false;//设置游戏结束标志位为false
initGame(); //初始化游戏
levelUp(); //升级
timeCounter = 0; //计时器清零
ball.isPlaying = true; //足球运动标志位为true
}
}
else{
canvas.drawBitmap(bmpLoseBanner,
(fieldLeft+fieldRight)/2-bmpWinBanner.getWidth()/2,
(fieldUp+fieldDown)/2-bmpWinBanner.getHeight()/2-20, null);
canvas.drawBitmap(bmpLoseText,
(fieldLeft+fieldRight)/2-bmpWinText.getWidth()/2,
(fieldUp+fieldDown)/2-bmpWinText.getHeight()/2, null);
timeCounter++;//每画一次,计时器增加
if(timeCounter >50){//如果计时器增加到50
isGameOver = false; //设置游戏结束标志位为false
isShowDialog = true; //设置显示对话框为true
}
}
}
if(isShowDialog){
canvas.drawBitmap(bmpDialog, 60, 200, null); //画出返回主菜单对话框
}
}
2,1 之后就开始创建玩家这个类了,玩家这个类很简单,也很好控制,主要有几个参数来表示玩家的状态和游戏的状态:
??
int x,y;
int movingDirection=-1;
int movingSpan = 1;
int attackDirection;
int power=10; //球员中心的x和y的坐标 //运动员的运动方向,12左4右 //移动步进 //进攻方向,0上8下 //踢球时给球的速度大小
??
玩家也应该有一个thread类来判断玩家的操作轨迹,于此在游戏中产生相应的效果,这个类应该拥有一个footballactivity这个类,用来控制全局的数据,也应该有一个线程标志位来控制自己的实现:
while(outerFlag){
while(flag){
//修改玩家和AI运动员的位置 if(father.current == father.gv){ if(myMoving){ //如果FieldView是当前屏幕 //如果玩家的球员是可移动的 //键盘状态为向右 int key = father.keyState; //读取键盘监听状态 if((key & 1) == 1){ father.gv.movePlayers(father.gv.alMyPlayer, 4);//调用方法向右移动球员 } else if((key & 2) == 2){ //键盘状态为向左 father.gv.movePlayers(father.gv.alMyPlayer, 12);//调用方法向左移动球员 } else{ //将direction设置为静止-1 father.gv.movePlayers(father.gv.alMyPlayer, -1);
} } //判断AI是否可以移动
//if(aiMoving){
读取AI球员的运动方向
} AI运动员的位置 int d = father.gv.aiDirection; father.gv.movePlayers(father.gv.alAIPlayer, d); //修改 } try{ } catch(Exception e){ e.printStackTrace(); } Thread.sleep(sleepSpan); //线程休眠一段时间 //打印并捕获异常
2.2 然后就可以创建ball对象了,运动员可以对小球进行控制,及修改小球的一些信息,又因为小球可以自己运动和判断自己的位置,即是小球需要拥有自己的线程操作,来对其自身的控制,小球继承至thread,小球在游戏中应该有自己的坐标x、y,小球的速度也会因为在玩家击打过后增加,并且在一定时间之后再度的减小,并且为了增加可玩性,可以对小球进行一个随机方向的滚动, 这些都可以通过小球的线程来实现监听与控制,对于小球来说,小球的碰撞检测是游戏的重点,我们可以在小球类里面构造这样一个函数来检测小球与边界的碰撞与否:
public void checkForBorders(){ int d = direction; //左右是不是出边界了 if(x <= father.fieldLeft){ //撞了左边界 if(d>8 && d<16 && d!=12){ //如果不是正撞到左边界 if(Math.random() < changeOdd){ direction = 16 - direction; } else{ direction = (direction>12?1:5) + (int)(Math.random()*100)%3; } } else if(d == 12){ //如果是正撞到左边界 if(Math.random() < 0.4){ direction = 4; } else{ direction = (Math.random() > 0.5?3:5); } } }
else if(x > father.fieldRight){ //撞到右边界 if(d >0 && d<8 && d!=4){ if(Math.random() < changeOdd){ direction = 16-direction; } else{ direction = (direction>4?9:13) + (int)(Math.random()*100)%3; } } else if(d == 4){ //如果是正撞到右边界 if(Math.random() < 0.4){ direction = 12; } else{ direction = (Math.random()>0.5?11:13); } } } d = direction; //判断是否撞到上边界 if(y < father.fieldUp){ //不是正撞 if(d>0 && d<4 || d>12&&d<16){ if(Math.random() < changeOdd){ direction = (d>12?24:8) - d; } else{ direction = (d>12?9:5) + (int)(Math.random()*100)%3; } } else if(d == 0){ //正撞到上边界 if(Math.random() < 0.4){ direction = 8; } else{ direction = (Math.random() < 0.5?7:9); } } } //判断是否撞到下边界 else if(y > father.fieldDown){ //不是正撞 if(d >4 && d<12 && d!=8){ if(Math.random() < changeOdd){ direction = (d>8?24:8) - d;
}
}
} else{ //随机变向 direction = (d>8?13:1) +(int)(Math.random()*100)%3; } }
else if(d == 8){ //正撞到下边界 if(Math.random() < 0.4){ //正常变向 direction = 0; } else{ //随机变向 direction = (Math.random()>0.5?1:15); } }
在小球的thread类中,可以设置一个标志位,来判断小球的走向,从而来控制电脑的运动方向,这个很容易实现:
public void run(){ while(flag){ int d = father.ball.direction; if(d >0 && d<8){ father.aiDirection = 4; } else if(d>8 && d<15){ father.aiDirection = 12; } try{ Thread.sleep(sleepSpan); } catch(Exception e){ e.printStackTrace(); } }
//获取足球运动方向 //如果足球方向偏左
//AI运动方向改为向左 //如果足球方向偏右 //AI运动方向改为向右
//休眠一段时间
//打印并捕获异常
2.3 在界面显示中,需要对屏幕进行监听,来去的用户的操作,又因为在ontouch()方法中如果返回了false,android系统不会对游戏进行任何的处理,只有返回了ture,android系统才会对其进行处理,重写ontouch()方法,来取得对玩家对于屏幕的点击,玩家也可以通过键盘的方式来取得对游戏的操作,越是重写onkeydown()和onkeyup()函数:
public boolean onKeyUp(int keyCode, KeyEvent event) { switch(keyCode){ case 21: //左 keyState = keyState & 0xffffffd; break; case 22: //右 keyState = keyState & 0xfffffffe; break;
default: break; } return true;
}
public boolean onKeyDown(int keyCode, KeyEvent event) {//处理键盘按下事件 switch(keyCode){
case 21: //左
keyState = keyState | 2;
keyState = keyState & 0xfffffffe;
break;
case 22: //右
keyState = keyState | 1;
keyState = keyState & 0xffffffd;
break;
default:
break;
}
return true;
}
public boolean onTouchEvent(MotionEvent event) {//重写onTouchEvent方法 if(event.getAction()== MotionEvent.ACTION_UP){//判断事件类型
int x = (int)event.getX(); //获得点击处的X坐标
int y = (int)event.getY(); //获得点击处的Y坐标 if(current == welcome){//如果当前界面是欢迎界面
if(rectGallery.contains(x, y)){ //用户点击的是Gallery welcome.cg.galleryTouchEvnet(x, y); //CustomGame来处理相应的事件 }
else if(rectSound.contains(x, y)){ /
this.wantSound = !this.wantSound; //更改声音选项 return true;
}
else if(rectStart.contains(x, y)){ //点下开始键
if(checkLayout(welcome.layout)){
layoutArray = welcome.layout;
lv = new LodingGame(this); //创建读取进度View this.setContentView(lv); //设置当前的view为lodingGame
this.current = lv; //记录当前View lv.lt.start(); //启动自己的刷屏线程
new Thread(){
public void run(){
Looper.prepare();
if(wantSound){
initSound();//初始化声音
}
gv = new
MainGame(FootBalGameActivity.this,imageIDs[welcome.cg.currIndex]); lv.progress = 100;
welcome = null;
}
}.start();
}
}
else if(rectQuit.contains(x,y)){ //按下退出键。程序则退出 System.exit(0);
}
else{ //检查是否按下了修改队员站位的加号和减号按钮
for(int i=0;i<3;i++){
if(rectPlus[i].contains(x,y)){ //如果有加号按钮点下,就增加对应进攻防守线上人数
if(welcome.layout[0]+welcome.layout[1]+welcome.layout[2] <10){
welcome.layout[i]++;
}
break;
}
if(rectMinus[i].contains(x, y)){//如果有减号按钮点下,就减少相应人数
if(welcome.layout[i] > 0){
welcome.layout[i]--;
}
break;
}
}
}
}
else if(current == gv){ //如果当前显示的View为GameView if(gv.rectMenu.contains(x,y)){ //如果点下了菜单按钮
gv.isShowDialog = true; //设置显示对话框
gv.ball.isPlaying = false; //足球停止移动
pmt.flag = false; //使PlayerMoveThread空转 }
else if(gv.rectYesToDialog.contains(x,y)){ //如果点下的是对话框中的”是“按钮
if(gv.isShowDialog){ //检查对话框是不是正在显示
welcome = new WelcomGame(this); //新建一个WelcomeView
setContentView(welcome); //设置当前屏幕为WelcomeView
待命状态 屏幕
welcome.status = 3; current = welcome;
//直接设为//记录当前
gv = null;
if(wantSound && mpWelcomeMusic!=null){ //如需要,播放声音 mpWelcomeMusic.start(); } } }
??
之后就是奖励物品的开发,奖励物品可以创建一个基类award,基类里面有一些基本的方法和属性,基类有两个子类,iceaward和largeraward,他们共同组成了奖励物品:
//下面是一个奖励物品,在时间到了之后可以消除奖励物品
public void setTimeout(int timeout){ timer = new Timer(); timer.schedule(new TimerTask(){ public void run() { Award.this.status = Award.DEAD; //杀死Bonus Award.this.undoJob(); //调用undoJob方法 Award.this.owner.remove(Award.this); //从其所属的集合中移除自己 father.balDelete.add(Award.this); //将自己添加到待删除集合中 } }, timeout);
}
//当然奖励物品也应该拥有绘制的功能如下所示 public void drawSelf(Canvas canvas){ canvas.drawBitmap(bmpSelf[(selfIndex++)%selfFrameNumber], x-bonusSize/2, y-bonusSize/2, null);
}
奖励物品有一样的特性,重要的是奖励物品的管理类,这个类拥有对奖励物品的管理功能,AwardManager这个类继承了Thread,拥有自己的标志位,通过Randow函数来随机创建出一种奖励物品,再起run函数里面也对奖励物品作产生做出了一定的要求:
while(flag){ //只在游戏正常状态下才产生Bonus if((!father.isGameOver) && (!father.isScoredAGoal) &&(!father.isShowDialog)){ generateBonus(); //调用generateBonus方法产生Bonus }
try{ Thread.sleep(sleepSpan); } catch(Exception e){ e.printStackTrace(); } } //休眠一段时间
4. 游戏运行结果
首先是游戏的开始界面,既是进入游戏的界面:
这个是Welcomegame,当进入游戏时会有欢迎界面,在这个界面时会根据加载的资源来变化状态数从而达到了资源的监测,
这个是游戏的主界面,由WelcomeGame这个类绘制而成,当欢迎动画完成过后,资源加载完毕,即开始对这个界面的绘制,其中的按钮可以点击。
这个是通过点击+号来实现对球员的分配
这个是对球队的选择,通过自己实现的gallery来绘制
这个是进入游戏主界面的加载界面,已经加载完成,用过lodingGame这个来来实现绘制,并且通过LodingThread这个类来控制对游戏主界面资源加载的监听
这个是游戏的主界面,在lodingGame之后,资源已经完毕,在这个界面玩家可以控制键盘来对游戏的控制,通过击打小球来改变小球的路线
这个是奖励物品,通过IceAward这个类来实现,通过AwardManager这个类来对于奖励物品的管理。
5. 总结与展望
本游戏实现了现实生活中的桌面足球,使的玩家怀念过去玩耍桌面足球的快乐与满足,本游戏难度适中,易学易会,不伤脑不伤手,是一款有趣的休闲游戏,可以打发无聊的时间和怀念童年的快乐,适合不同年龄段的玩家。但是此游戏的不足之处就是界面不是很华丽,游戏设计太过简陋,是一款玩“三分钟“的游戏,这些都是还有待完善的地方,相信在以后的某一天我可以把这个游戏修改成为一个让玩家爱不释手的游戏,让这个游戏让更多的人享受到其中的乐趣。
手游体验报告之《崩坏学园2》编者:何健锋基本信息游戏名称:崩坏学园2(简称:崩坏2)游戏类型:横版动作ACG手游游戏标签:横版、射…
湖南科技大学游戏程序设计报告游戏名称自助游科大指导教师专业班级学号姓名自助游科大一游戏程序设计目的我把这个游戏定位于小型3D游戏关…
龙之谷游戏体验报告及中东市场推广摘要本文通过对游戏龙之谷的体验写出相关的体验报告并结合自身对中东市场的一些看法对该游戏的中东市场推…
20xx20xx第一学期期末作业报告课程名称移动终端游戏开发学院软件学院专业软件工程班级学号姓名左杭成绩20xx年12月29日基于…
科技有限公司关于手机网络游戏的自行审核报告游戏软件是本公司自主研发的手机游戏软件并于年月日原始取得了中华人民共和国国家版权局颁发的…
调查目的:了解幼儿园游戏活动的现状,分析其影响因素调查时间:20xx年x月~6月调查地点:花儿朵朵幼儿园调查方法:访问法、谈话法调…
写测评的时候,脑袋要清醒,自己要写什么,不需要写什么,不要为了完成任务而写测试文章,你们所做的作品都是要给别人看的。写测试报告的时…
20xx年***学校学生健康体检和体质健康检测报告本校共有学生196人,其中男生102人,女生94人。我校联系镇医院对学生进行了健…
健康体检结果汇总分析报告我校遵照成都市教育局成都市卫生局成都市物价局关于在全市中小学开展健康体检工作通知及成都市中小学健康体检管理…
健康体检结果汇总分析报告我校遵照成都市教育局成都市卫生局成都市物价局关于在全市中小学开展健康体检工作通知及成都市中小学健康体检管理…
20xx年度中国电脑游戏产业报告《大众软件》杂志20xx年01期发表了名为“20xx年度中国电脑游戏产业报告”的文章,这也是该杂志…