一. 程序基本结构
基本结构
二. 实现的功能
1、单人模式
2、双人模式
3、通过广度优先算法实现坦克寻路功能
三. 运行效果
主界面
运行
四. 实现思路
数据存储表示: 在JPanel绘制图像,统一规定各个方块的大小为同一大小(如墙壁,坦克之类,子弹除外),从而方便使用二维数组存储地图的各个元素。
关于检测物体碰撞,这里使用了一个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());
}
}
图像打印则借助遍历两个ConcurrentHashMap分别储存坦克和其他类型的方块。将这些方块使用Map而不是使用数组是因为管理起来比较方便,而二维数组则是为了寻路算法而准备的,防止了频繁使用上面的两个Map而导致线程锁的问题。
五. 遇到的问题
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);
}
}
}
}
}
项目内的图片资源如何在项目导出后也能使用
解决方法:假设在项目的scr文件夹中建立img文件夹,在项目的.classpath中加一句<classpathentry kind="src" path="src/img"/>(Eclipse),也可以通过设置项目的Modules,将图像文件夹设置为resources(IDEA),并使用当前类的名.class.getResource("/(文件名)")).getImage()获得图像对象。
寻路的实现
实现思路:通过使用一个存储了地图内各个元素的二维数组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;
}
关注小编,回复坦克大战即可获得源码;
本文来源:CSDN-madongyu-的博客
留言与评论(共有 0 条评论) |