[安卓] 12、开源一个基于SurfaceView的飞行射击类小游戏
前言
这款安卓小游戏是基于SurfaceView的飞行射击类游戏,采用Java来写,没有采用游戏引擎,注释详细,条理比较清晰,适合初学者了解游戏状态转化自动机和一些继承与封装的技巧。
效果展示
游戏概述
这里主要涉及的技术有:①SurfaceView框架 ②角色、武器的封装 ③辅助帧动画 ④追踪打击算法 ⑤多武器实现 ⑥敌我升级策略 ⑦模拟手柄。其中SurfaceView游戏框架我在【[安卓] 8、VIEW和SURFACEVIEW游戏框架】有详细介绍,接下来我还将再次分析下;对于角色、武器封装主要涉及本游戏的类与类之间的继承和联系,从敌人、游戏者、子弹、武器等基本元素的封装,最后将这些基本元素在SurfaceView框架中组织起来形成整个游戏逻辑;帧动画主要是用来实现游戏里的一些动画效果,就像我在【[MFC] 高仿Flappy bird 桌面版】中自己封装的计分板一样和真正的Flappy bird的计分板的飞入、退出、计分等达到同样的效果,这里为了使游戏更具游戏的效果,也采用了同样的处理思路,比如子弹爆炸的封装,最终失败时GAME OVER的出现,以及点击小飞机重新游戏的效果;对于追踪打击是我在以前小时候玩的单机游戏中学到的,当时觉得这个武器太棒了,能打一下自动找敌人,太智能了,于是这里就把这个有趣的追踪拿了过来;对于敌我升级策略是比较难考虑的,起初采用过关打Boss的形式,结果发现要设计很多关卡和Boss会导致游戏框架不明显,而且读起来也不容易(虽然,在一定程度上有利于游戏的多样性),但是脑子一转想到了也能采用Flappy bird的形式,我让整个游戏是个无底洞,一直进行,但是发现如果一直进行根本没什么意思,阵列一直重复,玩多了也就不想玩了,于是想到利用主角打怪升级武器的思路让主角来升级,同时根据游戏的进程使敌人的移动速度和出现频率也增加,使敌我双方力量平衡,这样就增添了一点可玩性。这里我把工程的各种java文件放到了GitHub上【https://github.com/beautifulzzzz/Android/tree/master/枪林弹雨】,如果想参考整个工程,请看最后的网盘链接。
3-1、SurfaceView框架
该SurfaceView框架位于文件MySurfaceView.java是整个游戏的运转框架。如下左图:在该框架中首先对游戏中各种资源进行初始化,然后初始化游戏,接着进入一个独立的Run循环,不断接收消息及刷新页面。从右图可以看出:该框架包括构造函数、Created函数、initGame函数、myDraw函数、触屏或是按键监听的函数、logic函数、以及run循环函数。
如下在run函数中其实就是个循环,只有当flag为false或有异常时才会停止;对于其他情况,该循环定时地进行对页面的刷新(调用myDraw)和逻辑变化的处理(调用logic)。所以整个游戏一旦初始化完毕,就进入该循环,然后一直定时执行绘图和逻辑来驱动整个游戏的发展,此外外部点击事件会改变游戏中主角的相应参数,在下一个逻辑被处理,来实现交互的效果。
1 public void run() { 2 while (flag) { 3 long start = System.currentTimeMillis(); 4 myDraw(); 5 logic(); 6 long end = System.currentTimeMillis(); 7 try { 8 if (end - start < 50) {//时间均衡处理 9 Thread.sleep(50 - (end - start)); 10 } 11 } catch (InterruptedException e) { 12 e.printStackTrace(); 13 } 14 } 15 }//run函数
3-2、角色、武器的封装
在该游戏中主要有玩家、敌人和子弹三种类型的元素。这里对玩家的封装请看:Player.java,对敌人的封装请看:Enemy.java,对武器的封装请看:Bullet.java。对于Player主要包含了构造、绘制、逻辑三个基础的函数,分别供在SurfaceView框架中进行对象建立、绘制、逻辑运算,此外还有Player特有的按键监听用来控制主角、设置为无敌状态(因为主角刚诞生一般都是无敌状态,防止一出现就死了,这叫胎死腹中!)、设置和获取主角血量(也可以理解为主角的命,几条命)、获取和设置主角分数、两个碰撞检测(分别是判断主角和敌人是否有碰撞和主角是否和子弹有碰撞)。
对于Enemy还是包含3个基础的函数(构造、绘制、逻辑),此外还有一个碰撞检测(主要是和子弹碰撞)和一个reset函数用于重置数据。对于武器类Bullet就只有3个基础函数了(构造、绘制、逻辑)。
那么在SurfaceView框架中是如何有机地组织这些元素的呢?我们以Enemy为例大致看一下MySurfaceView.java中如何使用这三个对象:
① 这里采用向量来存储所有的Enemy对象 private Vector<Enemy> vcEnemy;
② 在initGame中加载各种Enemy对应的图片资源并实例化Enemy容器 vcEnemy = new Vector<Enemy>(); 此外还调用了Enemy的静态方法reset进行重置数据 Enemy.reset();
③ 在logic中当游戏处于GAMEING状态时要处理背景逻辑、主角逻辑和敌人逻辑:如下,都是分别调用各自封装好的logic函数,对于Enemy还要判断该敌人是否已经死掉,从容器中移除来优化程序。
1 backGround.logic();//背景逻辑 2 player.logic();//主角逻辑 3 //begin-----敌人逻辑 4 for (int i = 0; i < vcEnemy.size(); i++) {//敌人逻辑 5 Enemy en = vcEnemy.elementAt(i); 6 //因为容器不断添加敌人 ,那么对敌人isDead判定, 7 //如果已死亡那么就从容器中删除,对容器起到了优化作用; 8 if (en.isDead) { 9 vcEnemy.removeElementAt(i); 10 } else { 11 en.logic(); 12 } 13 }
然后是生成敌人的相关操作,这里敌人出现的规律采用阵列的形式,何谓“阵列”?简单地讲就像古代打仗排兵布阵一样,对应的兵力要放在哪、什么时候出现等。这里是用enemyArray来存储整列的:见MySurfaceView.java成员函数定义处:private int enemyArray[][] = { { 1, 2,1 }, { 1, 1}, { 1, 3, 1, 2 }, { 1, 2 }, { 2, 3 }, { 3, 1, 3 }, { 2, 2 }, { 1, 2 }, { 2, 2 }, { 1, 3, 1, 1 }, { 2, 1 },{ 1, 3 }, { 2, 1 },{ 1, 3, 1, 1 },{ 3, 3, 3, 3 }};这个enemyArray是个二维数组,其中每一小组表示每一波敌人的类型,如{1,2,1}代表2个第1种敌人和1个第2种敌人组成一波敌人杀过来了,哈哈,想想还挺有趣的吧!只要你稍微改一下这个数组就能实现不同组合的敌人攻击阵列,有一种当大将军的感觉呀!这样下面的生成敌人的代码就能看懂了!
1 //生成敌人 2 count++; 3 if (count % Enemy.createEnemyTime == 0) { 4 for (int i = 0; i < enemyArray[enemyArrayIndex].length; i++) { 5 if (enemyArray[enemyArrayIndex][i] == 1){//章鱼怪 6 int x = random.nextInt(screenW - 100) + 50; 7 vcEnemy.addElement(new Enemy(bmpEnemyFly, 1, x, -50)); 8 } else if (enemyArray[enemyArrayIndex][i] == 2) {//漂浮物左 9 int y = random.nextInt(20); 10 vcEnemy.addElement(new Enemy(bmpEnemyDuck, 2, -50, y)); 11 } else if (enemyArray[enemyArrayIndex][i] == 3) {//漂浮物右 12 int y = random.nextInt(20); 13 vcEnemy.addElement(new Enemy(bmpEnemyDuck, 3, screenW + 50, y)); 14 } else if(enemyArray[enemyArrayIndex][i] == 4){//Boss 15 vcEnemy.addElement(new Enemy(bmpEnemyBoss,4,-100,5)); 16 } 17 }
然后处理敌人与Player碰撞的逻辑,可见:遍历所有的敌人调用Player的碰撞检测函数进行碰撞检测,如果发生碰撞,则Player命减少一个,如果Player没有命了,就游戏结束了。
1 //处理敌人与主角的碰撞 2 for (int i = 0; i < vcEnemy.size(); i++) { 3 if (player.isCollsionWith(vcEnemy.elementAt(i))) { 4 player.setPlayerHp(player.getPlayerHp() - 1);//发生碰撞,主角血量-1 5 if (player.getPlayerHp() <= -1) {//当主角血量小于0,判定游戏失败 6 gameState = GAME_LOST; 7 } 8 } 9 }
接下来处理敌人攻击的逻辑,这里采用每隔一定时间敌人发射子弹,同样的我们还是要遍历Enemy容器,根据敌人的种类不同来设置武器的种类,最后在第19行向子弹容器中加入新产生的子弹(看到这里,大家肯定知道了这个Bullet和Enemy都是采用容器的,所以接下来肯定要判断Buller是否失效,然后从容器中剔除,此外还要像检测Enemy和Player碰撞一样来检测Bullet和Player碰撞,这里就不做说明了!)
1 //每2秒添加一个敌人子弹 2 countEnemyBullet++; 3 if (countEnemyBullet % Enemy.createBulletTime == 0) { 4 for (int i=0;i<vcEnemy.size();i++){ 5 Enemy en=vcEnemy.elementAt(i); 6 int bulletType=0; 7 switch(en.type){//不同类型敌人不同的子弹运行轨迹 8 case Enemy.TYPE_FLY://章鱼怪 9 bulletType = Bullet.BULLET_FLY; 10 break; 11 case Enemy.TYPE_DUCKL://漂浮物 12 case Enemy.TYPE_DUCKR: 13 bulletType = Bullet.BULLET_DUCK; 14 break; 15 case Enemy.TYPE_BOSS://boss的子弹 16 bulletType = Bullet.BULLET_DUCK;//////,,,。,。,。, 17 break; 18 } 19 vcBullet.add(new Bullet(bmpEnemyBullet, en.x + 10, en.y + 20, bulletType)); 20 } 21 }
再接着往后是处理Player的Bullet和Enemy的碰撞情况、Player发射子弹、Player子弹的逻辑、爆炸效果逻辑。
3-3、辅助帧动画
辅助帧动画主要是让交互更加流畅自然,这里包括控制子弹爆炸的类Boom.java;控制开始按钮的类GameMenu.java;控制背景滚动的GameBg.java;控制游戏结束的GameLost.java。其中Boom和GameBg比较相似,包括构造、逻辑、绘制三个基本函数,爆炸主要是几帧的顺序播放一次即可,而背景则是要涉及拼接轮流无缝播放;开始按钮和结束按钮没有逻辑,而是采用触摸监听,然后切换游戏状态。
3-4、追踪打击算法
看下面的代码很容易这里的追踪算法的思想:在子弹的logic()函数中,当判别子弹类型为跟踪弹得时候就计算该跟踪弹前面是否有敌人,如果有就调整方向,如果没有就沿直线前进(特别的这里子弹自身的角度也要调整)
1 double minLength=100000; 2 int findPos=-1; 3 for (int i=0;i<vcEnemy.size();i++){//找离当前子弹最近的敌人下标[在子弹前面的敌人算] 4 if(vcEnemy.elementAt(i).y<bulletY){ 5 double curLength=vcEnemy.elementAt(i).getLength(bulletX, bulletY); 6 if(curLength<minLength){ 7 minLength=curLength; 8 findPos=i; 9 } 10 } 11 } 12 if(findPos!=-1){//有目标算出x方向的速度 13 double tan=1.0*(vcEnemy.elementAt(findPos).x-bulletX) 14 /(vcEnemy.elementAt(findPos).y-bulletY); 15 angle=-(int)(Math.atan(tan)*180/3.1415926); 16 if(tan<0)speedX=-speed*2; 17 else speedX=speed*2; 18 }else{//没有目标直着前进 19 speedX=0; 20 angle=0; 21 }
3-5、多武器实现
因为武器采用类的封装,通过kind区分不同武器并作出不同处理,所以主框架下采用vector容器来存储各种武器,想要实现不同的打击效果一方面可以通过现有武器的组合另一方面可以在封装的武器类里继续扩展。本游戏一方面采用组合的方式构造单发、双发、多发模式,另一方面又有扩展追踪子弹,同时又将扩展子弹再和原有子弹组合达到种类繁多的效果。
1 case 6://5发2跟踪 2 vcBulletPlayer.add(new Bullet(bmpBullet, player.x + 3, player.y - 20, Bullet.BULLET_PLAYER)); 3 vcBulletPlayer.add(new Bullet(bmpBullet, player.x + 9, player.y - 22, Bullet.BULLET_PLAYER)); 4 vcBulletPlayer.add(new Bullet(bmpBullet, player.x + 15, player.y - 25, Bullet.BULLET_PLAYER)); 5 vcBulletPlayer.add(new Bullet(bmpBullet, player.x + 21, player.y - 22, Bullet.BULLET_PLAYER)); 6 vcBulletPlayer.add(new Bullet(bmpBullet, player.x + 27, player.y - 20, Bullet.BULLET_PLAYER)); 7 if(Bullet.num<=2) 8 vcBulletPlayer.add(new Bullet(bmpBullet, player.x + 15, player.y - 25, Bullet.BULLET_PLAYER1)); 9 break;
3-6、敌我升级策略
这里没有采用传统的积分->进入商城->升级装备的模式,也没有采用打击->出现奖励->吃奖励升级的模式,为了简单这里将根据主角的积分值自动划分主角战斗力。另一方面,如果敌人的实力不相应的增强,玩家很快也会丧失兴趣,这里敌人采取梯队的攻击模式,当整个梯队攻击一轮之后,敌人的速度和攻击频率都会相应的增强。
1 //当整个战斗梯队都过一遍之后就提升难度[这里是交替地将敌人生成频率和敌人攻击频率提升] 2 enemyArrayIndex=enemyArrayIndex+1;//15组出现效果....一轮过去提升难度 3 if(enemyArrayIndex>=15){ 4 enemyArrayIndex=0; 5 if(Enemy.createBulletTime>5 6 && Enemy.createBulletTime>=Enemy.createEnemyTime) 7 Enemy.createBulletTime-=5; 8 else if(Enemy.createEnemyTime>5 9 && Enemy.createBulletTime<=Enemy.createEnemyTime) 10 Enemy.createEnemyTime-=5; 11 }
3-7、模拟手柄
这个模拟手柄是单独被封装好的类Control.java,其中主要包括:构造、重置、绘制、触摸监听。其原理是根据小圆圆心和大圆圆心的夹角判断所在区域并改变主角的移动方向。这里要特别注意的是:在触摸监听里当手指按住屏幕时,就计算小圆该出现的位置并重绘小圆,当手指离开时小圆归位。
1 //根据夹角设置主角移动方向 [0不动,1up,2,left,3,down,4,right] 2 angle=angle/Math.PI*180;//将弧度转换为角度[控制] 3 if(angle>=-150 && angle<=-30)player.setDirect(1); 4 else if(angle>=30 && angle<=150)player.setDirect(3); 5 if(Math.abs(angle)>=120)player.setDirect(2); 6 else if(Math.abs(angle)<=60)player.setDirect(4);
后记
整个项目比较简单,适合安卓初学者拿来玩玩,如果您觉得还不错,请点个赞分享给更多需要的人~谢啦!!☆⌒(*^-゜)v
链接
本文链接:http://www.cnblogs.com/zjutlitao/p/4233536.html
更多精彩:http://www.cnblogs.com/zjutlitao/
GitHub链接:https://github.com/beautifulzzzz/Android/tree/master/枪林弹雨
安卓工程链接:http://pan.baidu.com/s/1pJHS5V9
APK试玩:http://pan.baidu.com/s/1qWK8rbY