[安卓] 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)。所以整个游戏一旦初始化完毕,就进入该循环,然后一直定时执行绘图和逻辑来驱动整个游戏的发展,此外外部点击事件会改变游戏中主角的相应参数,在下一个逻辑被处理,来实现交互的效果。
public void run() {
while (flag) {
long start = System.currentTimeMillis();
myDraw();
logic();
long end = System.currentTimeMillis();
try {
if (end - start < 50) {//时间均衡处理
Thread.sleep(50 - (end - start));
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}//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还要判断该敌人是否已经死掉,从容器中移除来优化程序。
backGround.logic();//背景逻辑
player.logic();//主角逻辑
//begin-----敌人逻辑
for (int i = 0; i < vcEnemy.size(); i++) {//敌人逻辑
Enemy en = vcEnemy.elementAt(i);
//因为容器不断添加敌人 ,那么对敌人isDead判定,
//如果已死亡那么就从容器中删除,对容器起到了优化作用;
if (en.isDead) {
vcEnemy.removeElementAt(i);
} else {
en.logic();
}
}
然后是生成敌人的相关操作,这里敌人出现的规律采用阵列的形式,何谓“阵列”?简单地讲就像古代打仗排兵布阵一样,对应的兵力要放在哪、什么时候出现等。这里是用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种敌人组成一波敌人杀过来了,哈哈,想想还挺有趣的吧!只要你稍微改一下这个数组就能实现不同组合的敌人攻击阵列,有一种当大将军的感觉呀!这样下面的生成敌人的代码就能看懂了!
//生成敌人
count++;
if (count % Enemy.createEnemyTime == 0) {
for (int i = 0; i < enemyArray[enemyArrayIndex].length; i++) {
if (enemyArray[enemyArrayIndex][i] == 1){//章鱼怪
int x = random.nextInt(screenW - 100) + 50;
vcEnemy.addElement(new Enemy(bmpEnemyFly, 1, x, -50));
} else if (enemyArray[enemyArrayIndex][i] == 2) {//漂浮物左
int y = random.nextInt(20);
vcEnemy.addElement(new Enemy(bmpEnemyDuck, 2, -50, y));
} else if (enemyArray[enemyArrayIndex][i] == 3) {//漂浮物右
int y = random.nextInt(20);
vcEnemy.addElement(new Enemy(bmpEnemyDuck, 3, screenW + 50, y));
} else if(enemyArray[enemyArrayIndex][i] == 4){//Boss
vcEnemy.addElement(new Enemy(bmpEnemyBoss,4,-100,5));
}
}
然后处理敌人与Player碰撞的逻辑,可见:遍历所有的敌人调用Player的碰撞检测函数进行碰撞检测,如果发生碰撞,则Player命减少一个,如果Player没有命了,就游戏结束了。
//处理敌人与主角的碰撞
for (int i = 0; i < vcEnemy.size(); i++) {
if (player.isCollsionWith(vcEnemy.elementAt(i))) {
player.setPlayerHp(player.getPlayerHp() - 1);//发生碰撞,主角血量-1
if (player.getPlayerHp() <= -1) {//当主角血量小于0,判定游戏失败
gameState = GAME_LOST;
}
}
}
接下来处理敌人攻击的逻辑,这里采用每隔一定时间敌人发射子弹,同样的我们还是要遍历Enemy容器,根据敌人的种类不同来设置武器的种类,最后在第19行向子弹容器中加入新产生的子弹(看到这里,大家肯定知道了这个Bullet和Enemy都是采用容器的,所以接下来肯定要判断Buller是否失效,然后从容器中剔除,此外还要像检测Enemy和Player碰撞一样来检测Bullet和Player碰撞,这里就不做说明了!)
//每2秒添加一个敌人子弹
countEnemyBullet++;
if (countEnemyBullet % Enemy.createBulletTime == 0) {
for (int i=0;i<vcEnemy.size();i++){
Enemy en=vcEnemy.elementAt(i);
int bulletType=0;
switch(en.type){//不同类型敌人不同的子弹运行轨迹
case Enemy.TYPE_FLY://章鱼怪
bulletType = Bullet.BULLET_FLY;
break;
case Enemy.TYPE_DUCKL://漂浮物
case Enemy.TYPE_DUCKR:
bulletType = Bullet.BULLET_DUCK;
break;
case Enemy.TYPE_BOSS://boss的子弹
bulletType = Bullet.BULLET_DUCK;//////,,,。,。,。,
break;
}
vcBullet.add(new Bullet(bmpEnemyBullet, en.x + 10, en.y + 20, bulletType));
}
}
再接着往后是处理Player的Bullet和Enemy的碰撞情况、Player发射子弹、Player子弹的逻辑、爆炸效果逻辑。
3-3、辅助帧动画
辅助帧动画主要是让交互更加流畅自然,这里包括控制子弹爆炸的类Boom.java;控制开始按钮的类GameMenu.java;控制背景滚动的GameBg.java;控制游戏结束的GameLost.java。其中Boom和GameBg比较相似,包括构造、逻辑、绘制三个基本函数,爆炸主要是几帧的顺序播放一次即可,而背景则是要涉及拼接轮流无缝播放;开始按钮和结束按钮没有逻辑,而是采用触摸监听,然后切换游戏状态。

3-4、追踪打击算法
看下面的代码很容易这里的追踪算法的思想:在子弹的logic()函数中,当判别子弹类型为跟踪弹得时候就计算该跟踪弹前面是否有敌人,如果有就调整方向,如果没有就沿直线前进(特别的这里子弹自身的角度也要调整)
double minLength=100000;
int findPos=-1;
for (int i=0;i<vcEnemy.size();i++){//找离当前子弹最近的敌人下标[在子弹前面的敌人算]
if(vcEnemy.elementAt(i).y<bulletY){
double curLength=vcEnemy.elementAt(i).getLength(bulletX, bulletY);
if(curLength<minLength){
minLength=curLength;
findPos=i;
}
}
}
if(findPos!=-1){//有目标算出x方向的速度
double tan=1.0*(vcEnemy.elementAt(findPos).x-bulletX)
/(vcEnemy.elementAt(findPos).y-bulletY);
angle=-(int)(Math.atan(tan)*180/3.1415926);
if(tan<0)speedX=-speed*2;
else speedX=speed*2;
}else{//没有目标直着前进
speedX=0;
angle=0;
}
3-5、多武器实现
因为武器采用类的封装,通过kind区分不同武器并作出不同处理,所以主框架下采用vector容器来存储各种武器,想要实现不同的打击效果一方面可以通过现有武器的组合另一方面可以在封装的武器类里继续扩展。本游戏一方面采用组合的方式构造单发、双发、多发模式,另一方面又有扩展追踪子弹,同时又将扩展子弹再和原有子弹组合达到种类繁多的效果。
case 6://5发2跟踪
vcBulletPlayer.add(new Bullet(bmpBullet, player.x + 3, player.y - 20, Bullet.BULLET_PLAYER));
vcBulletPlayer.add(new Bullet(bmpBullet, player.x + 9, player.y - 22, Bullet.BULLET_PLAYER));
vcBulletPlayer.add(new Bullet(bmpBullet, player.x + 15, player.y - 25, Bullet.BULLET_PLAYER));
vcBulletPlayer.add(new Bullet(bmpBullet, player.x + 21, player.y - 22, Bullet.BULLET_PLAYER));
vcBulletPlayer.add(new Bullet(bmpBullet, player.x + 27, player.y - 20, Bullet.BULLET_PLAYER));
if(Bullet.num<=2)
vcBulletPlayer.add(new Bullet(bmpBullet, player.x + 15, player.y - 25, Bullet.BULLET_PLAYER1));
break;
3-6、敌我升级策略
这里没有采用传统的积分->进入商城->升级装备的模式,也没有采用打击->出现奖励->吃奖励升级的模式,为了简单这里将根据主角的积分值自动划分主角战斗力。另一方面,如果敌人的实力不相应的增强,玩家很快也会丧失兴趣,这里敌人采取梯队的攻击模式,当整个梯队攻击一轮之后,敌人的速度和攻击频率都会相应的增强。
//当整个战斗梯队都过一遍之后就提升难度[这里是交替地将敌人生成频率和敌人攻击频率提升]
enemyArrayIndex=enemyArrayIndex+1;//15组出现效果....一轮过去提升难度
if(enemyArrayIndex>=15){
enemyArrayIndex=0;
if(Enemy.createBulletTime>5
&& Enemy.createBulletTime>=Enemy.createEnemyTime)
Enemy.createBulletTime-=5;
else if(Enemy.createEnemyTime>5
&& Enemy.createBulletTime<=Enemy.createEnemyTime)
Enemy.createEnemyTime-=5;
}
3-7、模拟手柄
这个模拟手柄是单独被封装好的类Control.java,其中主要包括:构造、重置、绘制、触摸监听。其原理是根据小圆圆心和大圆圆心的夹角判断所在区域并改变主角的移动方向。这里要特别注意的是:在触摸监听里当手指按住屏幕时,就计算小圆该出现的位置并重绘小圆,当手指离开时小圆归位。

//根据夹角设置主角移动方向 [0不动,1up,2,left,3,down,4,right]
angle=angle/Math.PI*180;//将弧度转换为角度[控制]
if(angle>=-150 && angle<=-30)player.setDirect(1);
else if(angle>=30 && angle<=150)player.setDirect(3);
if(Math.abs(angle)>=120)player.setDirect(2);
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
[安卓] 12、开源一个基于SurfaceView的飞行射击类小游戏的更多相关文章
- 一个基于Myeclipse开发的Java打地鼠小游戏(Appletcation)
package javaes.zixue.wangshang.daima; 2 3 import java.awt.Cursor; import java.awt.Image; import java ...
- 12岁的少年教你用Python做小游戏
首页 资讯 文章 频道 资源 小组 相亲 登录 注册 首页 最新文章 经典回顾 开发 设计 IT技术 职场 业界 极客 创业 访谈 在国外 - 导航条 - 首页 最新文章 经典回顾 开发 ...
- 一个基于PDO的数据库操作类(新) 一个PDO事务实例
<?php /* * 作者:胡睿 * 日期:2011/03/19 * 电邮:hooray0905@foxmail.com * * 20110319 * 常用数据库操作,如:增删改查,获取单条记录 ...
- 一个简单用原生js实现的小游戏----FlappyBird
这是一个特别简单的用原生js实现的一个小鸟游戏,比较简单,适合新手练习 这是html结构 <!DOCTYPE html><html lang="en">&l ...
- 开源一个基于天天团购的团购app
可能大家都知道天天团购开源系统,一个做团购的开源项目很赞,前些日子做了基于天天团购系统做的团购客户端和移动端服务器!源代码放出,有了解的可以看看,希望收益! 先说服务器:app的服务器,基于天天团购的 ...
- 开源一个基于nio的java网络程序
因为最近要从公司离职,害怕用nio写的网络程序没有人能看懂(或许是因为写的不好吧),就调整成了mina(这样大家接触起来非常方便,即使没有socket基础,用起来也不难),所以之前基于nio写的网络程 ...
- 开源一个基于dotnet standard的轻量级的ORM框架-Light.Data
还在dotnet framework 2.0的时代,当时还没有EF,而NHibernate之类的又太复杂,并且自己也有一些特殊需求,如查询结果直接入表.水平分表和新增数据默认值等,就试着折腾个轻量点O ...
- C# 开源一个基于 yarp 的 API 网关 Demo,支持绑定 Kubernetes Service
关于 Neting 刚开始的时候是打算使用微软官方的 Yarp 库,实现一个 API 网关,后面发现坑比较多,弄起来比较麻烦,就放弃了.目前写完了查看 Kubernetes Service 信息.创建 ...
- 菜渣开源一个基于 EMIT 的 AOP 库(.NET Core)
目录 1,快速入门 1.1 继承 ActionAttribute 特性 1.2 标记代理类型 2,如何创建代理类型 2.1 通过API直接创建 2,创建代理类型 通过API 通过 Microsoft. ...
随机推荐
- [erlang]supervisor(监控树)的重启策略
1. init函数 init() -> {ok, {SupFlags, [ChildSpec,...]}} | ignore. [ChildSpec,...] 是在init之后默认要启动的子进程 ...
- HttpWebRequest类
HttpWebRequest类与HttpRequest类的区别. HttpRequest类的对象用于服务器端,获取客户端传来的请求的信息,包括HTTP报文传送过来的所有信息.而HttpWebReque ...
- IIS7.5 由于 Web 服务器上的“ISAPI 和 CGI 限制”列表设置,无法提供您请求的页面
IIS7.5中将一网站应用程序池托管管道模式改为经典后,网站页面打不开,错误信息: 引用内容 HTTP 错误 404.2 - Not Found由于 Web 服务器上的“ISAPI 和 CGI 限制” ...
- java 读取文件内容 三种形式及效率对比
IOUtils.getStringFromReader() 读取方式为最快的 InputStream in = null; String line = ""; long start ...
- How to copy remote computer files quickly to local computer
if we want copy file from VM(Remote VM) to local computer. Always can not easy copy file so easy. no ...
- JavaScript (jquery) 数组去重的算法探讨
方法很巧妙 但是要事先知道对应的name或其他属性名称 主键值只适用于已知数据对象进行调用: var arr1 = [{ name: ' ...
- Python处理json格式的数据文件(一些坑、一些疑惑)
这里主要说最近遇到的一个问题,不过目前只是换了一种思路先解决了,脑子里仍然有疑惑,只能怪自己太菜. 最近要把以前爬的数据用一下了,先简单的过滤一下,以前用scrapy存数据的时候为了省事也为了用一下它 ...
- (转)java中的进程与线程
(转自地址http://www.ibm.com/developerworks/cn/java/j-lo-processthread/) Java 进程的建立方法 在 JDK 中,与进程有直接关系的类为 ...
- 【九度OJ】题目1061:成绩排序
题目描述: 有N个学生的数据,将学生数据按成绩高低排序,如果成绩相同则按姓名字符的字母序排序,如果姓名的字母序也相同则按照学生的年龄排序,并输出N个学生排序后的信息. 输入: 测试数据有多组,每组输入 ...
- Python项目:扇贝网小组查卡助手
扇贝网是一个非常棒的英语学习网站,大家还可以加入一些小组,一起交流学习.共同进步.但是,小组管理起来非常辛苦,尤其是在0点前踢出不打卡的成员,因此考虑利用程序来实现小组查卡自动化. 登录 操作 扇贝网 ...