以AWT特性简单写出坦克大战(1)
在学习java图形界面的时候我发现JLabel的特性可以完美避开图片闪烁问题,由此我以JLabel(javax.swing.JLabel)为核心完成了坦克大战的实现,并且拥有良好的扩展性,但由于自身能力有限,经过调试后任然有少量Bug和一些不完美的地方。
首先我回答使用JLabel的优越性,它支持添加图片,不会闪烁。下面看演示代码:
import javax.swing.*;
import java.awt.*;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
public class Example extends JFrame {
static int a=350;
static int b=350;
public static void main(String[] args) {
// TODO Auto-generated method stub
Example car=new Example(); //初始化顶层容器:窗体
JPanel jp=new JPanel(); //初始化面板
car.add(jp);
jp.setLayout(null);
JLabel testjl=new JLabel(new ImageIcon("enemy2D.gif"));//添加一张放在项目根目录的图片
testjl.setBounds(350, 350, 100, 100);
jp.add(testjl);
car.setResizable(false);//打开窗体
car.setBounds(400, 20, 800, 800);
car.setTitle("Battle City Left:a Right:d Up:w Down:s");
car.setVisible(true);
car.setDefaultCloseOperation(car.DISPOSE_ON_CLOSE);
//控制方向
car.addKeyListener(new KeyListener() {
@Override
public void keyPressed(KeyEvent arg0) {
// TODO Auto-generated method stub
if(arg0.getKeyChar()=='w'||arg0.getKeyChar()=='W')
{
b-=5;
testjl.setBounds(a, b, 60, 60);
}
if(arg0.getKeyChar()=='d'||arg0.getKeyChar()=='D')
{
a+=5;
testjl.setBounds(a, b, 60, 60);
}
if(arg0.getKeyChar()=='s'||arg0.getKeyChar()=='S')
{
b+=5;
testjl.setBounds(a, b, 60, 60);
}
if(arg0.getKeyChar()=='a'||arg0.getKeyChar()=='A')
{
a-=5;
testjl.setBounds(a, b, 60, 60);
}
}
@Override
public void keyReleased(KeyEvent arg0) {
// TODO Auto-generated method stub
}
@Override
public void b
}
});
}
}
如代码显示,图片没有闪烁。
下是坦克大战的逻辑结构:
总体思路
程序启动后,四个线程和主线程一起运行。主类和敌方坦克控制类用于调用坦克类中的行走方法或开炮方法;这两个方法仅仅修改坦克对象的位置数据或新建一个炮弹线程(每颗炮弹我都用一个线程来处理)。画图类的工作只是遍历所有坦克对象并根据对象的数据(比如位置坐标)在面板上绘图。结束类用于判断游戏结束条件(所有敌方坦克的生命为0,或则P1生命为0)
详细设计
1.我们先构建Tank类:Tank.java
Tank类主要作用:
- 定义坦克属性:坦克图片,生命,位置,速度,朝向 炮弹图片,图片直径
- 实现坦克行走功能:walk()
该函数用于坦克的移动,每次调用都会改变坦克对象的位置数据。 - 实现坦克开炮功能:fire()
该函数用于坦克开炮,每次调用都会新建一个炮弹线程,这个线程就在有限空间内负责炮弹所有的能力。
4.下面是详细代码
import javax.swing.*;
import java.lang.Math;
public class Tank extends Main {
//坦克类
//属性:坦克图片,生命,位置,速度,朝向 炮弹图片,图片直径
JLabel image;//坦克图片
ImageIcon imiclib[]=new ImageIcon[4];//四个方向的坦克图标
JLabel shellPicture=new JLabel(new ImageIcon("tankmissile.gif"));//炮弹图片
boolean life=true;//生命
int locationX;//x坐标
int locationY;//y坐标
int speed;//速度
int direction;//方向
double circled=2*25*Math.sqrt(2);//图片直径
boolean fire=true;//能否开炮
//构造方法
public Tank(ImageIcon imiclib[],int x,int y,int speed,int direction)
{
this.image=new JLabel(imiclib[0]);
this.imiclib=imiclib;
this.locationX=x;
this.locationY=y;
this.speed=speed;
this.direction=direction;
}
public Tank() {};
//行走
void walk(int direction,Tank aimtank[])
{
boolean walk2=true;
boolean walk=true;
//1.图片情况(根据不同的方向装载图片)
if(this.direction!=direction)
{
this.direction=direction;
this.image.setIcon(this.imiclib[this.direction]);
}
//2.边界碰撞用walk判断
switch(this.direction)
{
case 0:if(this.locationY<=0) walk=false; break;
case 1:if(this.locationX>=736) walk=false; break;
case 2:if(this.locationY>=704) walk=false; break;
case 3:if(this.locationX<=0) walk=false; break;
}
//3.行进方向坦克碰撞 用walk2判断
for(int i=0;i<aimtank.length;i++)
{
if(this.equals(aimtank[i]))
continue;
//方法二,把坦克看成一个圆,判断相切;
//*********************
//1.未相撞到相撞的判断
//2.相撞到离开的判断
//*********************
int tX=this.locationX+30;//获取圆心
int tY=this.locationY+30;
int atX=aimtank[i].locationX+30;
int atY=aimtank[i].locationY+30;
double distance=Math.sqrt((Math.pow(tX-atX, 2)+Math.pow(tY-atY, 2)));//两坦克中心的距离
if(this.circled>=distance)//图片直径大于两坦克中心距离时,判断为相切
{
walk2=false;
/*
*下面要考虑怎么离开相切状态。相切后如果向着使distance减小的方向,那么任然会处在相切的状态
*所以只能向着使distance增大方向移动,直到它大于图片直径。
*所以我用一个switch语句表达这个过程,有点像提前预测坦克行走方向,如果向着某个方向能使distance
*增大,那么这个方向就走出相撞状态的方向。
*/
switch(this.direction)//进行预测,往哪个方向能够离开
{
case 0:
{
//System.out.println("方向0");
tY-=10;
distance=Math.sqrt((Math.pow(tX-atX, 2)+Math.pow(tY-atY, 2)));//预测后的距离
if(this.circled<=distance)
{
walk2=true;
}
break;
}
case 1:
{
tX+=10;
distance=Math.sqrt((Math.pow(tX-atX, 2)+Math.pow(tY-atY, 2)));
if(this.circled<=distance)
walk2=true;
break;
}
case 2:
{
tY+=10;
distance=Math.sqrt((Math.pow(tX-atX, 2)+Math.pow(tY-atY, 2)));
if(this.circled<=distance)
walk2=true;
break;
}
case 3:
{
tX-=10;
distance=Math.sqrt((Math.pow(tX-atX, 2)+Math.pow(tY-atY, 2)));
if(this.circled<=distance)
walk2=true;
break;
}
}
}
}
//3.确定方向,改变数据
if(walk&&walk2)
{
switch(this.direction)
{
case 0:this.locationY-=8; break;
case 1:this.locationX+=8; break;
case 2:this.locationY+=8; break;
case 3:this.locationX-=8; break;
}
}
}
//开炮
public void fire(JPanel Cenjp,Tank tank[],Tank tankmian)
{
switch(this.direction)
{
//根据不同方向实例化一个炮弹对象并创建线程
//把炮弹加入到窗体中
case 0:
{
//根据不同方向实例化一个炮弹对象
Shell shell=new Shell(shellPicture, locationX+22, locationY-20,direction);
//把炮弹加入到窗体中
Cenjp.add(shell.shell);
Thread th=new Thread(new ThreadShellMe(tank,shell,tankmian));
th.start();
break;
}
case 1:
{
Shell shell=new Shell(shellPicture, locationX+70, locationY+23,direction);
Cenjp.add(shell.shell);
Thread th=new Thread(new ThreadShellMe(tank,shell,tankmian));
th.start();
break;
}
case 2:
{
Shell shell=new Shell(shellPicture, locationX+22, locationY+70,direction);
Cenjp.add(shell.shell);
Thread th=new Thread(new ThreadShellMe(tank,shell,tankmian));
th.start();
break;
}
case 3:
{
Shell shell=new Shell(shellPicture, locationX-20,locationY+22,direction);
Cenjp.add(shell.shell);
Thread th=new Thread(new ThreadShellMe(tank,shell,tankmian));
th.start();
break;
}
}
}
}
到此Tank.java完成。我们成功构建了坦克对象以及实现了坦克的两大能力:行走 开炮。
·····························未完待续,下一篇讲游戏开始动画类,主类,结束类等几个简单类的实现。