项目github地址:基于Netty的联机版坦克大战
该项目实现了联机版坦克大战,项目包括客户端与服务端
项目使用技术:
实现功能:
在人机对战模式中,有多幅地图可供选择:
![]() |
![]() |
![]() |
![]() |
本项目使用maven构建,原则上不需要用户自行下载各种jar包,但是在pom.xml中添加jboss-marshalling-serial-1.3.0.CR9.jar依赖包,运行程序时会抛出异常,无法正常运行(原因暂时不明),因此需要还自行将lib文件夹下的jboss-marshalling-serial-1.3.0.CR9.jar包添加到客户端和服务端的Build Path中
使用步骤:
碰撞检测机制比较简单,检测的原理如下图。先假设坦克往前移动一步,然后计算两个物体中心之间的距离d,若d不超过两个物体宽度之和,则坦克向前走一步不会发生碰撞,否则会碰撞
以服务端为例,服务端程序需要根据不同的消息类型,采取不同的处理方式。最简单的实现方式,就是使用if-else先识别消息类型,再对消息进行处理,但每增加一种消息类型,就需要增加一个if分支。
使用Spring容器与java反射机制,对消息类型判定的逻辑进行简化,逻辑如下:
下面是使用if-else对消息类型进行判别时的部分代码,代码比较冗长:
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
if (msg instanceof RegisterRequest) {
} else if (msg instanceof StartGameRequest) {
} else if (msg instanceof CloseClientRequest) {
} else if (msg instanceof SavePogressRequest) {
} else if (msg instanceof JudgeStorageRequest) {
} else if (msg instanceof PingRequest) {
} else if (msg instanceof ReConnectRequest) {
}
}
使用@HandlerAnno注解机制之后,仅需为每种消息的处理方法加上@HandlerAnno注解,而channelRead()的代码只需几行即可,不再需要冗长的if-else语句对消息类型进行判定
@HandlerAnno
public void login(Channel channel, LoginRequest request) {// 登陆请求
String name = request.getId();
String password = request.getPassword();
LoginResponse response = gameService.login(name, password, channel);
channel.writeAndFlush(response);
}
@HandlerAnno
public void startGame(Channel channel, StartGameRequest request) { //开始游戏
gameService.startGame(request, channel);
}
@HandlerAnno
public void register(Channel channel, RegisterRequest request) {// 注册请求
RegisterResponse response = gameService.register(request);
channel.write(response);
}
...
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
log.debug("RECEIVE MSG->"+msg);
if(msg instanceof PingRequest){ //如果收到的是心跳消息,不处理
}else{ //处理非心跳消息
dispatcher.doHandle(ctx.channel(), msg);
}
}
在介绍A*算法之前,先简单说一下广度优先遍历的寻路算法。在广度优先遍历寻路算法中,从起点开始,通过不断增大搜索半径,对周围的节点进行扩展,以找到终点。很显然,广度优先遍历寻路算法找到路径一定是最短的,但是缺点也很明显,该算法的时间复杂度非常大,需要额外扩展很多无用的节点。这主要是因为广度优先遍历寻路算法只关注当前节点和起始节点之间的距离,而忽略了当前节点到目标节点的距离
A*算法是一种比较常见的启发式路径搜索算法,不同于广度优先遍历寻路算法,A*算法在关注当前节点和起始节点之间的距离的同时,还关注当前节点到目标节点的距离。
定义几个表达式:
在A*算法中会维护一个open表和closed表
算法伪代码:
使用PriorityQueue定义openTable(F值最小的节点排在队首)
使用HashSet定义closedTable
将起始节点放入openTable
repeat
从openTable中取出队首节点P
if P是终点
算法结束
for neighbor in P的所有邻居:
if neighbor不是障碍物 and neighbor不在closedTable中:
将neighbor放入openTable
将节点P放入closedTable
until openTable为空 or 找到终点
心跳机制就是客户端定时发送一个心跳包,让服务端知道自己还活着,以确保连接的有效性。
实现方式:
由于坦克发射子弹之后,子弹会沿着发射的方向飞行,需要定时更新子弹的位置。为了解决该问题,在本项目中,当一个坦克发射一枚子弹后,会开启一个新线程,用于更新子弹的状态。当子弹爆炸之后,该线程结束。
子弹的飞行距离是有上限的,每隔一定的时间间隔,线程更新子弹的位置信息,并且判断子弹是否击中砖块、坦克、钢块、是否达到飞行距离上限、是否达到边界。如果条件成立,则子弹会发生爆炸,产生范围性伤害,对爆炸范围内的物体造成伤害。
在人机对战模式中,机器坦克有个巡逻范围的概念。只有当玩家坦克进入机器坦克的巡逻范围,机器坦克才会对玩家坦克的位置进行路径规划。
在人机对战中,每局游戏在服务端中都有一个专门进行路径规划的线程,为该局游戏中的所有机器坦克进行路径规划,并且分时间片执行每个机器坦克的路径。游戏结束时,线程结束。
当机器坦克在移动过程中,若玩家坦克与机器坦克处于同一直线上,且机器坦克还有子弹,则机器坦克会向玩家坦克发射一枚子弹