入门项目–坦克大战
参考资料:韩顺平老师讲Java
需求分析
首先分析下坦克大战的需求,主要功能包括:
- 敌我方坦克
- 敌方坦克随机移动,我方坦克键盘控制移动
- 敌方坦克随机发射炮弹,我方坦克键盘控制发射炮弹
- 坦克击中爆炸并消失
- 记录玩家成绩
- 恢复上局游戏或开始新游戏
- 坦克大战背景音乐
设计阶段
根据需求分析的整理的主要功能,设计项目UML类图设计、开发流程设计、模块设计、数据库设计、项目架构设计等。坦克大战项目比较简单,不涉及模块设计、数据库设计、项目架构设计。
- UML类图设计
从主要功能分析出发,项目包括的类主要有:坦克类、炮弹类、玩家类、坦克信息类
实现阶段
- 有了坦克类,炮弹类怎么显示坦克和炮弹?
Java绘图技术
- 绘图原理
- Component类提供了两个和绘图相关的重要方法
paint(Graphics g) – 绘制组件的外观
repaint() – 刷新组件的外观 - 当组件第一次在屏幕显示的时候,程序会自动的调用paint()方法来绘制组件,以下情况也会自动调用paint()方法
窗口最小化,再最大化
窗口的大小发生变化
repaint被调用
- Component类提供了两个和绘图相关的重要方法
- Graphics类
常用方法- 画直线 drawLine(int x1, int y1, int x2, int y2)
- 画矩形边框 drawRect(int x, int y, int width, int height)
- 画椭圆边框 drawOval(int x, int y, int width, int height)
- 填充矩形 fillRect(int x, int y, int width, int height)
- 填充椭圆 fillOval(int x, int y, int width, int height)
- 画图片 drawImage(Image img, int x, int y, …)
- 画字符串 drawString(String str, int x, int y)
- 设置画笔的字体 setFont(Font font)
- 设置画笔的颜色 setColor(Color c)
问题1: 敌方坦克随机移动?
- 敌方坦克实现多线程,解决每个敌方坦克随机移动的问题
public class EnemyTank extends Tank implements Runnable {
/**
* 每颗子弹都是独立线程,用Vector保存
*/
Vector<Bullet> bullets = new Vector<Bullet>();
/**
* 敌方坦克构造器
*/
public EnemyTank(int x, int y) {
super(x, y);
}
/**
* 每一个敌方坦克都是随机移动的,都是一个独立的线程,线程方法主体中写出坦克随机移动的方法
* 坦克移动同时发射子弹,写出子弹发射方法
*/
public void run() {
while (true) {
//发射多个子弹
if (bullets.size() < 3 && isLive) {
switch (getDirection()) {
case 0: //向上
bullet = new Bullet(getX() + 20, getY(), 0);
break;
case 1: //向右
bullet = new Bullet(getX() + 60, getY() + 20, 1);
break;
case 2: //向下
bullet = new Bullet(getX() + 20, getY() + 60, 2);
break;
case 3: //向左
bullet = new Bullet(getX(), getY() + 20, 3);
break;
}
bullets.add(bullet);
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(bullet).start();
}
/**判断坦克的方向,让坦克保持随机方向移动,在某一方向移动一会,后随机转换方向*/
switch (getDirection()) {
case 0:
for (int i = 0; i < 30; i++) {
if (getY() > 10) {
//让坦克朝一个方向移动一会
moveUp();
}
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
break;
case 1:
for (int i = 0; i < 30; i++) {
if (getX() < 940) {
moveRight();
}
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
break;
case 2:
for (int i = 0; i < 30; i++) {
if (getY() < 690) {
moveDown();
}
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
break;
case 3:
for (int i = 0; i < 30; i++) {
if (getX() > 10) {
moveLeft();
}
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
break;
}
//设置坦克方向为0,1,2,3随机数
setDirection((int) (Math.random() * 4));
if (!isLive) {
break;
}
}
}
}
问题2: 所有的炮弹之间的关系?
- 炮弹实现多线程,解决每个炮弹相互独立,互不影响
public class Bullet implements Runnable{
/**子弹x坐标*/
int x;
/**子弹y坐标*/
int y;
/**子弹速度*/
int speed = 2;
/**子弹方向*/
int direction;
/**子弹是否存活*/
boolean isLive = true;
/**子弹构造器*/
public Bullet(int x, int y, int direction) {
this.x = x;
this.y = y;
this.direction = direction;
}
/**
* 根据子弹方向让子弹移动起来
*/
public void run() {
while (true){
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
/**判断子弹方向*/
switch (direction){
case 0:
y -= speed;
break;
case 1:
x += speed;
break;
case 2:
y += speed;
break;
case 3:
x -= speed;
break;
}
//当子弹碰到敌人坦克时,也应该销毁
if(!(x >= 0 && x <= 1000 && y >=0 && y <= 750 && isLive)){
isLive = false;
break;
}
}
}
}
新增画板类MyPanel,绘制敌我双方坦克和敌我双方炮弹
public class MyPanel extends JPanel{
/**
* 我方坦克初始化
*/
MyTank myTank = null;
/**
* 每一个敌方坦克都是一个独立线程,考虑线程同步问题
*/
Vector<EnemyTank> vector = new Vector<EnemyTank>();
/**
* 构造器完成初始化
*/
/**
* 敌方坦克数量
*/
int enemyTankSize = 8;
/**
* 构造器完成初始化
*/
public MyPanel() {
myTank = new MyTank(900, 600);
for (int j = 0; j < enemyTankSize; j++) {
//创建一个敌人的坦克
EnemyTank enemyTank = new EnemyTank((j + 1) * 100, 10);
//启动敌人坦克线程
Thread thread = new Thread(enemyTank);
thread.start();
// 设置方向
enemyTank.setDirection(2);
vector.add(enemyTank);
}
}
/**
* 画笔操作
*
* @param g
*/
@Override
public void paint(Graphics g) {
super.paint(g);
/**定义坦克移动范围*/
g.fillRect(0, 0, 1000, 750);
//画我方坦克
if (myTank.isLive) {
drawTank(myTank.getX(), myTank.getY(), g, myTank.getDirection(), 1);
}
/**画出我方坦克的子弹*/
if (myTank.bullets.size() > 0) {
for (int i = 0; i < myTank.bullets.size(); i++) {
if (myTank.bullets.get(i) != null && myTank.bullets.get(i).isLive) {
g.draw3DRect(myTank.bullets.get(i).x, myTank.bullets.get(i).y, 2, 2, false);
}
if (!myTank.bullets.get(i).isLive) {
myTank.bullets.remove(i);
}
}
}
//画敌方坦克
for (int i = 0; i < vector.size(); i++) {
// 取出敌方坦克
EnemyTank enemyTank = vector.get(i);
//判断敌人坦克是否还存活
if (enemyTank.isLive) {
for (int j = 0; j < enemyTank.bullets.size(); j++) {
// 取出子弹
Bullet bullet = enemyTank.bullets.get(j);
// 判断子弹是否存活
if (bullet.isLive) {
g.draw3DRect(enemyTank.bullets.get(j).x, enemyTank.bullets.get(j).y, 2, 2, false);
} else {//移除子弹
enemyTank.bullets.remove(bullet);
}
}
//画敌人坦克
drawTank(vector.get(i).getX(), vector.get(i).getY(), g, vector.get(i).getDirection(), 0);
}
}
}
/**
* @param x 坦克横坐标
* @param y 坦克纵坐标
* @param direction 坦克方向
* @param g 画笔
* @param type 坦克类型
*/
public void drawTank(int x, int y, Graphics g, int direction, int type) {
//设置坦克类型颜色
switch (type) {
case 0:
g.setColor(Color.cyan);
break;
case 1:
g.setColor(Color.yellow);
break;
}
/**根据坦克朝向,画出不同方向的坦克*/
switch (direction) {
case 0: //表示向上
g.fill3DRect(x, y, 10, 60, false);//画出坦克左边轮子
g.fill3DRect(x + 30, y, 10, 60, false);//画出坦克右边轮子
g.fill3DRect(x + 10, y + 10, 20, 40, false);//画出坦克盖子
g.fillOval(x + 10, y + 20, 20, 20);//画出圆形盖子
g.drawLine(x + 20, y + 30, x + 20, y);//画出炮筒
break;
case 1: //表示向右
g.fill3DRect(x, y, 60, 10, false);//画出坦克上边轮子
g.fill3DRect(x, y + 30, 60, 10, false);//画出坦克下边轮子
g.fill3DRect(x + 10, y + 10, 40, 20, false);//画出坦克盖子
g.fillOval(x + 20, y + 10, 20, 20);//画出圆形盖子
g.drawLine(x + 30, y + 20, x + 60, y + 20);//画出炮筒
break;
case 2: //表示向下
g.fill3DRect(x, y, 10, 60, false);//画出坦克左边轮子
g.fill3DRect(x + 30, y, 10, 60, false);//画出坦克右边轮子
g.fill3DRect(x + 10, y + 10, 20, 40, false);//画出坦克盖子
g.fillOval(x + 10, y + 20, 20, 20);//画出圆形盖子
g.drawLine(x + 20, y + 30, x + 20, y + 60);//画出炮筒
break;
case 3: //表示向左
g.fill3DRect(x, y, 60, 10, false);//画出坦克上边轮子
g.fill3DRect(x, y + 30, 60, 10, false);//画出坦克下边轮子
g.fill3DRect(x + 10, y + 10, 40, 20, false);//画出坦克盖子
g.fillOval(x + 20, y + 10, 20, 20);//画出圆形盖子
g.drawLine(x + 30, y + 20, x, y + 20);//画出炮筒
break;
}
}
}
- 我方坦克响应键盘,移动和发射炮弹?
Java事件处理机制
- 基本说明:Java事件处理是采取“委派事件模型”。当事件发生时,产生事件的对象,会把此“信息”传递给“事件的监听者”处理,这里所说的“信息”实际上就是java.awt.event事件类库里某个类所创建的对象,把它称为“事件的对象”。
- 事件源:事件源是一个产生事件的对象,比如按钮、窗口等
- 事件:事件就是承载事件源状态改变时的对象,比如键盘事件、鼠标事件、窗口事件等,会生成一个事件对象,该对象保存着当前事件很多信息,比如KeyEvent对象有含义被按下键的Code值。java.awt.event包和javax.swing.event包中定义了各种事件类型
- 事件监听接口
- 当事件源产生一个事件,可以传送给事件监听者处理
- 事件监听者实际上就是一个类,该类实现了某个事件监听器接口
- 事件监听接口有多种,不同的事件监听器接口可以监听不同的事件,一个类可以实现多个监听接口
- 这些接口在java.awt.event包和javax.swing.event包中定义
思路:MyPanel实现KeyListener接口,监听键盘事件,根据键盘输入选择坦克移动方向,根据键盘输入调用MyTank的shotEnemy()方法,实现发射炮弹
/**
* 键盘事件监听KeyListener接口中方法,根据按下键的不同完成对应的操作
*
* @param e
*/
public void keyPressed(KeyEvent e) {
if (e.getKeyCode() == KeyEvent.VK_W) {//按下W键
//改变坦克的方向
myTank.setDirection(0);//
//控制坦克边界
if (myTank.getY() > 0) {
myTank.moveUp();
}
} else if (e.getKeyCode() == KeyEvent.VK_D) {//D键, 向右
myTank.setDirection(1);
if (myTank.getX() < 940) {
myTank.moveRight();
}
} else if (e.getKeyCode() == KeyEvent.VK_S) {//S键
myTank.setDirection(2);
if (myTank.getY() < 690) {
myTank.moveDown();
}
} else if (e.getKeyCode() == KeyEvent.VK_A) {//A键
myTank.setDirection(3);
if (myTank.getX() > 0) {
myTank.moveLeft();
}
}
// 按下j发射炮弹
if (e.getKeyCode() == KeyEvent.VK_J) {
myTank.shotEnemy();
}
// 调用repaint()函数重新绘画坦克和子弹
this.repaint();
}
public class MyTank extends Tank{
/**初始化子弹类*/
Bullet bullet = null;
/**多线程利用Vector存储子弹类对象,每一颗子弹都是一个线程*/
Vector<Bullet> bullets = new Vector<Bullet>();
/**初始化我方坦克*/
public MyTank(int x, int y) {
super(x, y);
setSpeed(5);
}
/**
* 射击敌方坦克方法,根据坦克方向,创建子弹对象
*/
public void shotEnemy(){
/**我方坦克发射子弹数量限制*/
if(bullets.size() > 2){
return;
}
switch (getDirection()){
case 0: //向上
bullet = new Bullet(getX() + 20, getY(), 0);
break;
case 1: //向右
bullet = new Bullet(getX() + 60, getY() + 20, 1);
break;
case 2: //向下
bullet = new Bullet(getX() + 20, getY() + 60, 2);
break;
case 3: //向左
bullet = new Bullet(getX(), getY() + 20, 3);
break;
}
/**加入子弹到多线程*/
bullets.add(bullet);
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
/**启动子弹线程*/
new Thread(bullet).start();
}
}
- 敌方坦克随机移动互相碰撞?
思路:创建保存敌方坦克Vector,遍历Vector判断是否有相互碰撞的坦克
/**
* 判断两辆坦克是否碰撞
*
* @return
*/
public boolean isCrashed() {
switch (this.getDirection()) {
case 0: //目标坦克向上
for (int i = 0; i < enemyTanks.size(); i++) {
EnemyTank enemyTank = enemyTanks.get(i);
if (enemyTank != this) {
switch (enemyTank.getDirection()) {
//敌人坦克上下方向
case 0:
case 2:
//当前坦克左上角是否碰撞
if (this.getY() <= enemyTank.getY() + 60
&& this.getY() >= enemyTank.getY()
&& (this.getX() <= enemyTank.getX() + 40
&& this.getX() >= enemyTank.getX())) {
return true;
}
//当前坦克右上角是否碰撞
if (this.getY() <= enemyTank.getY() + 60
&& this.getY() >= enemyTank.getY()
&& (this.getX() <= enemyTank.getX()
&& this.getX() >= enemyTank.getX() - 40)) {
return true;
}
break;
//敌人坦克左右方向
case 1:
case 3:
//当前坦克左上角是否碰撞
if (this.getY() <= enemyTank.getY() + 40
&& this.getY() >= enemyTank.getY()
&& (this.getX() <= enemyTank.getX() + 60
&& this.getX() >= enemyTank.getX())) {
return true;
}
//当前坦克右上角是否碰撞
if (this.getY() <= enemyTank.getY() + 40
&& this.getY() >= enemyTank.getY()
&& (this.getX() <= enemyTank.getX() + 20
&& this.getX() >= enemyTank.getX() - 40)) {
return true;
}
break;
}
}
}
break;
case 2: // 目标坦克朝下
for (int i = 0; i < enemyTanks.size(); i++) {
EnemyTank enemyTank = enemyTanks.get(i);
if (enemyTank != this) {
switch (enemyTank.getDirection()) {
//敌人坦克上下方向
case 0:
case 2:
//当前坦克左下角是否碰撞
if (this.getY() <= enemyTank.getY()
&& this.getY() >= enemyTank.getY() - 60
&& this.getX() <= enemyTank.getX() + 40
&& this.getX() >= enemyTank.getX()) {
return true;
}
//当前坦克右下角是否碰撞
if (this.getY() <= enemyTank.getY()
&& this.getY() >= enemyTank.getY() - 60
&& this.getX() <= enemyTank.getX()
&& this.getX() >= enemyTank.getX() - 40) {
return true;
}
break;
//敌人坦克左右方向
case 1:
case 3:
//当前坦克左下角是否碰撞
if (this.getY() <= enemyTank.getY() - 20
&& this.getY() >= enemyTank.getY() - 60
&& this.getX() <= enemyTank.getX() + 60
&& this.getX() >= enemyTank.getX()) {
return true;
}
//当前坦克右下角是否碰撞
if (this.getY() <= enemyTank.getY() - 20
&& this.getY() >= enemyTank.getY() - 60
&& this.getX() <= enemyTank.getX() + 20
&& this.getX() >= enemyTank.getX() - 40) {
return true;
}
break;
}
}
}
break;
case 1: // 目标坦克朝右
for (int i = 0; i < enemyTanks.size(); i++) {
EnemyTank enemyTank = enemyTanks.get(i);
if (enemyTank != this) {
switch (enemyTank.getDirection()) {
//敌人坦克上下方向
case 0:
case 2:
//当前坦克右上角是否碰撞
if (this.getY() <= enemyTank.getY() + 60
&& this.getY() >= enemyTank.getY()
&& this.getX() <= enemyTank.getX() - 20
&& this.getX() >= enemyTank.getX() - 60) {
return true;
}
//当前坦克右下角是否碰撞
if (this.getY() <= enemyTank.getY() + 20
&& this.getY() >= enemyTank.getY() - 40
&& this.getX() <= enemyTank.getX() - 20
&& this.getX() >= enemyTank.getX() - 60) {
return true;
}
break;
//敌人坦克左右方向
case 1:
case 3:
//当前坦克右上角是否碰撞
if (this.getY() <= enemyTank.getY() + 40
&& this.getY() >= enemyTank.getY()
&& this.getX() <= enemyTank.getX()
&& this.getX() >= enemyTank.getX() - 60) {
return true;
}
//当前坦克右下角是否碰撞
if (this.getY() <= enemyTank.getY()
&& this.getY() >= enemyTank.getY() - 40
&& this.getX() <= enemyTank.getX()
&& this.getX() >= enemyTank.getX() - 60) {
return true;
}
break;
}
}
}
break;
case 3: //目标坦克朝左
for (int i = 0; i < enemyTanks.size(); i++) {
EnemyTank enemyTank = enemyTanks.get(i);
if (enemyTank != this) {
switch (enemyTank.getDirection()) {
//敌人坦克上下方向
case 0:
case 2:
//当前坦克左上角是否碰撞
if (this.getY() <= enemyTank.getY() + 60
&& this.getY() >= enemyTank.getY()
&& this.getX() <= enemyTank.getX() + 40
&& this.getX() >= enemyTank.getX()) {
return true;
}
//当前坦克左下角是否碰撞
if (this.getY() <= enemyTank.getY() + 20
&& this.getY() >= enemyTank.getY() - 40
&& this.getX() <= enemyTank.getX() + 40
&& this.getX() >= enemyTank.getX()) {
return true;
}
break;
//敌人坦克左右方向
case 1:
case 3:
//当前坦克左上角是否碰撞
if (this.getY() <= enemyTank.getY() + 40
&& this.getY() >= enemyTank.getY()
&& this.getX() <= enemyTank.getX() + 60
&& this.getX() >= enemyTank.getX()) {
return true;
}
//当前坦克左下角是否碰撞
if (this.getY() <= enemyTank.getY()
&& this.getY() >= enemyTank.getY() - 40
&& this.getX() <= enemyTank.getX() + 60
&& this.getX() >= enemyTank.getX()) {
return true;
}
break;
}
}
}
break;
}
return false;
}
- 坦克击中爆炸效果?炮弹和坦克消失
思路:
- 爆炸效果实现思路:图片快速切换
- 新增bomb炸弹类,如果子弹击中坦克,新建bomb类
- Toolkit.getDefaultToolkit().getImage()加载图片
- 炮弹和坦克的isLive设置为false,状态消失
public class Bomb {
/**爆炸x坐标*/
int x;
/**爆炸x坐标*/
int y;
/**爆炸持续时间*/
int life = 9;
/**爆炸是否存活*/
boolean isLive = true;
/**爆炸类构造器*/
public Bomb(int x, int y) {
this.x = x;
this.y = y;
}
/**爆炸声明衰减*/
public void lifeDown(){
if(life > 0){
life--;
}else{
isLive = false;
}
}
}
项目地址:坦克大战