java实现的坦克大战


源码获取途径:

  • 1.百度云链接: https://pan.baidu.com/s/1UK9FIgEztKINHog7Y6r0uw 提取码: eaus
  • 2.可以到我的GitHub上面拉取项目
  • 3.直接下载(下载链接:https://github.com/ddddyyyy/TankBattle/archive/master.zip)

一. 程序基本结构

基本结构


二. 实现的功能

1、单人模式
2、双人模式
3、通过广度优先算法实现坦克寻路功能


三. 运行效果

主界面
运行


四. 实现思路

  1. 数据存储表示: 在JPanel绘制图像,统一规定各个方块的大小为同一大小(如墙壁,坦克之类,子弹除外),从而方便使用二维数组存储地图的各个元素。
  2. 关于检测物体碰撞,这里使用了一个MyImage的父类,将坦克,墙壁
    定义为继承这个父类的一个类。
class MyImage {
    int width = Game.width;
    int height = Game.height;
    //二维地图的坐标
    Coord coord;
    //屏幕上的像素坐标
    int x;
    int y;
    MyImage(Coord coord) {
        x = coord.x * width;
        y = coord.y * height;
        this.coord = coord;
    }
    private Rectangle getRect() {
        return new Rectangle(x, y, width, height);
    }
    //碰撞检测
    boolean isIntersects(MyImage other) {
        return other.getRect().intersects(getRect());
    }
}
  1. 图像打印则借助遍历两个ConcurrentHashMap分别储存坦克和其他类型的方块。将这些方块使用Map而不是使用数组是因为管理起来比较方便,而二维数组则是为了寻路算法而准备的,防止了频繁使用上面的两个Map而导致线程锁的问题。

五. 遇到的问题

  1. java的按键监听在响应按键长按时会有1-2秒的延迟,导致操作手感极差
    解决方法:在坦克类中设置一个布尔属性move以及整形变量key储存键入的按键值,创建一个线程来响应按键。

实现代码:

//按键响应的线程类
class MyTankMove implements Runnable{
	public void run(){
		while(flag){
				GetKey(key);
			while(move){//决定是否移动
				try {
					e.printStackTrace();
					Thread.sleep(100);
				} catch (InterruptedException e) {
				e.printStackTrace();
				}
			try {
				Thread.sleep(10);//防止无按键时陷入死循环导致线程堵塞
			} catch (InterruptedException e) {
			}
			}
	}

按键监听事件,即给key和move赋值

//按键监听接口
private class KeyBoradListener extends KeyAdapter{
public void keyPressed(KeyEvent e){
	super.keyPressed(e);
	int key = e.getKeyCode();
	if(key<65){//为了实现双人对战而设置的。。。
		if(key!=KeyEvent.VK_SHIFT&&!MyTank.isEmpty()){
			MyTank.getFirst().key=key;
			MyTank.getFirst().move=true;
		}
	}
	else{
		if(key!=KeyEvent.VK_G&&!MyTank.isEmpty()){
			switch (key){
			case KeyEvent.VK_W:key = KeyEvent.VK_UP;break;
			case KeyEvent.VK_A:key = KeyEvent.VK_LEFT;break;
			case KeyEvent.VK_S:key = KeyEvent.VK_DOWN;break;
			case KeyEvent.VK_D:key = KeyEvent.VK_RIGHT;break;
			}
			MyTank.getLast().key=key;
			MyTank.getLast().move=true;
		}
	}
}
public void keyReleased(KeyEvent e){
	super.keyReleased(e);
	int key = e.getKeyCode();
	if(key<65){
		if(!MyTank.isEmpty()){
			if(key!=KeyEvent.VK_SHIFT&&key==MyTank.getFirst().key){//避免了同时按两个以上按键后会卡住
				MyTank.getFirst().move=false;
			}
			else{
				MyTank.getFirst().GetKey(key);
			}
		}
	}
	else{
		switch (key){
		case KeyEvent.VK_W:key = KeyEvent.VK_UP;break;
		case KeyEvent.VK_A:key = KeyEvent.VK_LEFT;break;
		case KeyEvent.VK_S:key = KeyEvent.VK_DOWN;break;
		case KeyEvent.VK_D:key = KeyEvent.VK_RIGHT;break;
		case KeyEvent.VK_G:key = KeyEvent.VK_SHIFT;break;
		}
		if(!MyTank.isEmpty()){
			if(key!=KeyEvent.VK_SHIFT&&key==MyTank.getLast().key){
				MyTank.getLast().move=false;
			}
			else{
				MyTank.getLast().GetKey(key);
			}
		}
	}
}
}

  1. 项目内的图片资源如何在项目导出后也能使用
    解决方法:假设在项目的scr文件夹中建立img文件夹,在项目的.classpath中加一句<classpathentry kind="src" path="src/img"/>(Eclipse),也可以通过设置项目的Modules,将图像文件夹设置为resources(IDEA),并使用当前类的名.class.getResource("/(文件名)")).getImage()获得图像对象。

  1. 寻路的实现
    实现思路:通过使用一个存储了地图内各个元素的二维数组Game.map,使用广度优先算法遍历出一条路线,将结果存放于栈之中。
    实现代码:
 /**
  * 使用广度遍历算法,使用队列存储遍历的节点
  *
  * @return 移动的路径
  */
 private Stack<Coord> GetPath() {
     Coord target = Game.tanks.get(Game.P1_TAG).coord;
     Queue<Coord> d_q = new LinkedBlockingQueue<>();
     ArrayList<Coord> IsMove = new ArrayList<>();
     d_q.offer(coord);
     IsMove.add(coord);
     Coord last = null;
     boolean flag;
     while (!d_q.isEmpty()) {
         Coord t = d_q.poll();
         int tx = t.x;
         int ty = t.y;
         int i;
         //遍历所有的方向
         for (i = 0; i < 4; ++i) {
             switch (i) {
                 case Game.UP:
                     ty -= 1;
                     break;
                 case Game.LEFT:
                     tx -= 1;
                     break;
                 case Game.RIGHT:
                     tx += 1;
                     break;
                 case Game.DOWN:
                     ty += 1;
                     break;
             }
             //判断该点是否可行
             flag = true;
             Coord z = new Coord(tx, ty);
             //检查是否为目标终点
             if (z.equals(target)) {
                 z.per = t;
                 last = z;
                 break;
             }
             //检查该坐标是否已经遍历了
             for (Coord c : IsMove) {
                 if (c.equals(z)) {
                     flag = false;
                     break;
                 }
             }
             if (flag) {
                 //检查下一格是否可以抵达
                 flag =(Game.map[ty][tx] == Game.BLANK || Game.map[ty][tx] == Game.WALLS);
             }
             //该点可以用
             if (flag) {
                 //将坐标纳入已经遍历的队列中
                 d_q.offer(z);
                 IsMove.add(z);
                 z.per = t;
                 last = z;
             }
             IsMove.add(z);
             //重新选择方向遍历
             tx = t.x;
             ty = t.y;
         }
         //如果没有四个方向都遍历完就跳出,说明已经找到了终点
         if (i != 4) {
             break;
         }
     }
     Stack<Coord> coords = new Stack<>();
     while (null != last && last.per != null) {
         coords.push(last);
         last = last.per;
     }
     return coords;
 }

  • 55
    点赞
  • 250
    收藏
    觉得还不错? 一键收藏
  • 51
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 51
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值