Java培训新手实战必备!单机版坦克大战分步实现项目源码
很多java初学者,在学习一段时间java后,都想写一个项目练练手来检测一下学习成果,而坦克大战就是一个非常好的试手项目。坦克大战本身就是一个非常经典的单机项目,其次使用java来实现,技术点几乎能够涉及到java所有的基础知识。
那么千锋老谢就从最开始手把手的教你实现坦克大战,简单明了,步骤详细,绝对让你写出自己的坦克大战,同时加深对java知识的理解。那我们就开始吧。
1. 窗体绘制
JFrame是指一个计算机语言-java的GUI程序的基本思路是以JFrame为基础,它是屏幕上window的对象,能够最大化、最小化、关闭。
1.1 创建窗口
创建类继承JFrame
创建启动方法launch()
setTitle() 设置标题
setSize(width, height); 设置宽高
setLocationRelativeTo(null); 设置位置,null为居中
setResizable(); 是否可拉伸
setVisible(); 是否可见
setDefaultCloseOperation(3); 1,2,3 (点差号,关闭程序)
package com.qf;
import javax.swing.JFrame;
public class GamePanel extends JFrame {
private static final long serialVersionUID = 7471238471259660459L;
//窗口宽高
int width = 800;
int height = 610;
public void launch() {
//设置窗口标题
setTitle("千锋坦克大战");
//设置宽高
setSize(width, height);
//居中
setLocationRelativeTo(null);
//差掉窗口,结束
setDefaultCloseOperation(3);
//窗口不可拉伸
setResizable(false);
//可见
setVisible(true);
}
public static void main(String[] args) {
GamePanel gp = new GamePanel();
gp.launch();
}
}
1.2 为窗口上色
重写paint()方法,参数g为图形
设置图形颜色
设置图形大小
package com.qf;
import java.awt.Color;
import java.awt.Graphics;
import javax.swing.JFrame;
public class GamePanel extends JFrame {
private static final long serialVersionUID = 7471238471259660459L;
//窗口宽高
int width = 800;
int height = 610;
//获取画笔(为窗口上色)
@Override
public void paint(Graphics g) {
//设置颜色
g.setColor(Color.GRAY);
//填充窗口
g.fillRect(0, 0, width, height);
}
public void launch() {
setTitle("千锋坦克大战");
//设置宽高
setSize(width, height);
//居中
setLocationRelativeTo(null);
//差掉窗口,结束
setDefaultCloseOperation(3);
//窗口不可拉伸
setResizable(false);
//可见
setVisible(true);
}
public static void main(String[] args) {
GamePanel gp = new GamePanel();
gp.launch();
}
}
1.3 添加选项文字
设置字体颜色,不要和背景色一样
设置字体,字体大小
添加文字,及文字位置
package com.qf;
import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics;
import javax.swing.JFrame;
public class GamePanel extends JFrame {
private static final long serialVersionUID = 7471238471259660459L;
//窗口宽高
int width = 800;
int height = 610;
//获取画笔
@Override
public void paint(Graphics g) {
g.setColor(Color.GRAY);
g.fillRect(0, 0, width, height);
//添加游戏选项
g.setColor(Color.BLUE);
g.setFont(new Font("仿宋", Font.BOLD, 50));
g.drawString("选择游戏模式", 220, 100);
g.drawString("单人模式", 220, 200);
g.drawString("双人模式", 220, 300);
}
public void launch() {
setTitle("千锋坦克大战");
//设置宽高
setSize(width, height);
//居中
setLocationRelativeTo(null);
//差掉窗口,结束
setDefaultCloseOperation(3);
//窗口不可拉伸
setResizable(false);
//可见
setVisible(true);
}
public static void main(String[] args) {
GamePanel gp = new GamePanel();
gp.launch();
}
}
1.4 为窗口添加键盘事件
添加内部类,实现KeyAdapter类,重写keyPressed方法,监听按钮
class KeyMonitor extends KeyAdapter {
@Override
public void keyPressed(KeyEvent e) {
System.out.println(e.getKeyChar());
}
}
窗口添加键盘监视器
this.addKeyListener(new GamePanel.KeyMonitor());
package com.qf;
import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import javax.swing.JFrame;
public class GamePanel extends JFrame {
private static final long serialVersionUID = 7471238471259660459L;
//窗口宽高
int width = 800;
int height = 610;
//获取画笔
@Override
public void paint(Graphics g) {
g.setColor(Color.GRAY);
g.fillRect(0, 0, width, height);
//添加游戏选项
g.setColor(Color.BLUE);
g.setFont(new Font("仿宋", Font.BOLD, 50));
g.drawString("选择游戏模式", 220, 100);
g.drawString("单人模式", 220, 200);
g.drawString("双人模式", 220, 300);
}
//添加内部类,实现KeyAdapter类,重写keyPressed方法
class KeyMonitor extends KeyAdapter {
@Override
public void keyPressed(KeyEvent e) {
System.out.println(e.getKeyChar());
}
}
public void launch() {
setTitle("千锋坦克大战");
//设置宽高
setSize(width, height);
//居中
setLocationRelativeTo(null);
//差掉窗口,结束
setDefaultCloseOperation(3);
//窗口不可拉伸
setResizable(false);
//可见
setVisible(true);
this.addKeyListener(new GamePanel.KeyMonitor());
}
public static void main(String[] args) {
GamePanel gp = new GamePanel();
gp.launch();
}
}
1.5 键盘控制选择游戏模式
添加指针图片
在项目文件夹下创建文件夹images,关于项目的图片,插件都放在这个文件夹内
创建图片对象
Image select = Toolkit.getDefaultToolkit().getImage("images/mytank_right.gif");
设置初始纵坐标为150
int y = 150;
在paint()方法,添加此图片
g.drawImage(select, 160, y, null);
在launch()方法中重绘图形(每隔30毫秒)
//重绘
while(true) {
repaint();
try {
Thread.sleep(25);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
重写键盘事件,1选择单人模式,2选择双人模式
//添加内部类,实现KeyAdapter类,重写keyPressed方法
class KeyMonitor extends KeyAdapter {
@Override
public void keyPressed(KeyEvent e) {
int key = e.getKeyCode();
switch(key) {
case KeyEvent.VK_1 :
y = 150;
break;
case KeyEvent.VK_2 :
y = 250;
break;
case KeyEvent.VK_ENTER :
break;
default:
break;
}
}
}
定义选择相应模式后,点击回车键,显示响应窗口
定义模式state 0:未选择,1:单人 2:双人
//模式state 0:未选择,1:单人 2:双人
int state = 0;
int a = 0;
重写点击事件
//添加内部类,实现KeyAdapter类,重写keyPressed方法
class KeyMonitor extends KeyAdapter {
@Override
public void keyPressed(KeyEvent e) {
int key = e.getKeyCode();
switch(key) {
case KeyEvent.VK_1 :
a = 1; // 单人
y = 150;
break;
case KeyEvent.VK_2 :
a = 2; //双人
y = 250;
break;
case KeyEvent.VK_ENTER :
state = a;
break;
default:
break;
}
}
}
写入图形判断
if (state == 0 ) {
g.drawString("选择游戏模式", 220, 100);
g.drawString("单人模式", 220, 200);
g.drawString("双人模式", 220, 300);
//绘制指针
g.drawImage(select, 160, y, null);
} else if (state ==1 || state ==2) {
g.drawString("游戏开始", 220, 100);
if (state ==1) {
g.drawString("单人模式", 220, 200);
} else if(state ==2) {
g.drawString("双人模式", 220, 200);
}
}
整体代码
package com.qf;
import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.Image;
import java.awt.Toolkit;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import javax.swing.JFrame;
public class GamePanel extends JFrame {
private static final long serialVersionUID = 7471238471259660459L;
//指针图片
Image select = Toolkit.getDefaultToolkit().getImage("images/mytank_right.gif");
int y =150; //纵坐标
//模式state 0:未选择,1:单人 2:双人
int state = 0;
int a = 0;
//窗口宽高
int width = 800;
int height = 610;
//获取画笔
@Override
public void paint(Graphics g) {
g.setColor(Color.GRAY);
g.fillRect(0, 0, width, height);
//添加游戏选项
g.setColor(Color.BLUE);
g.setFont(new Font("仿宋", Font.BOLD, 50));
if (state == 0 ) {
g.drawString("选择游戏模式", 220, 100);
g.drawString("单人模式", 220, 200);
g.drawString("双人模式", 220, 300);
//绘制指针
g.drawImage(select, 160, y, null);
} else if (state ==1 || state ==2) {
g.drawString("游戏开始", 220, 100);
if (state ==1) {
g.drawString("单人模式", 220, 200);
} else if(state ==2) {
g.drawString("双人模式", 220, 200);
}
}
}
//添加内部类,实现KeyAdapter类,重写keyPressed方法
class KeyMonitor extends KeyAdapter {
@Override
public void keyPressed(KeyEvent e) {
int key = e.getKeyCode();
switch(key) {
case KeyEvent.VK_1 :
a = 1; // 单人
y = 150;
break;
case KeyEvent.VK_2 :
a = 2; //双人
y = 250;
break;
case KeyEvent.VK_ENTER :
state = a;
break;
default:
break;
}
}
}
public void launch() {
setTitle("千锋坦克大战");
//设置宽高
setSize(width, height);
//居中
setLocationRelativeTo(null);
//差掉窗口,结束
setDefaultCloseOperation(3);
//窗口不可拉伸
setResizable(false);
//可见
setVisible(true);
this.addKeyListener(new GamePanel.KeyMonitor());
//重绘
while(true) {
repaint();
try {
Thread.sleep(25);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) {
GamePanel gp = new GamePanel();
gp.launch();
}
}
1.6 解决闪频
创建一个图片
//解决闪动问题
Image offScreemImage = null;
重写paint()方法
创建一个和弹出窗口宽高相同的图片,
获取该图片的图形对象,把所有内容添加到该图片中
//创建和容器一样大小的Image图片
if(offScreemImage == null) {
offScreemImage = this.createImage(width,height);
}
//获该图片的图形
Graphics gImage = offScreemImage.getGraphics();
gImage.setColor(Color.GRAY);
gImage.fillRect(0, 0, width, height);
//添加游戏选项
gImage.setColor(Color.BLUE);
gImage.setFont(new Font("仿宋", Font.BOLD, 50));
if (state == 0 ) {
gImage.drawString("选择游戏模式", 220, 100);
gImage.drawString("单人模式", 220, 200);
gImage.drawString("双人模式", 220, 300);
//绘制指针
gImage.drawImage(select, 160, y, null);
} else if (state ==1 || state ==2) {
gImage.drawString("游戏开始", 220, 100);
if (state ==1) {
gImage.drawString("单人模式", 220, 200);
} else if(state ==2) {
gImage.drawString("双人模式", 220, 200);
}
}
把该图片写入到窗口中
g.drawImage(offScreemImage, 0, 0,null);
package com.qf;
import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.Image;
import java.awt.Toolkit;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import javax.swing.JFrame;
public class GamePanel extends JFrame {
private static final long serialVersionUID = 7471238471259660459L;
//解决闪动问题
Image offScreemImage = null;
//指针图片
Image select = Toolkit.getDefaultToolkit().getImage("images/mytank_right.gif");
int y =150; //纵坐标
//模式state 0:未选择,1:单人 2:双人
int state = 0;
int a = 0;
//窗口宽高
int width = 800;
int height = 610;
//获取画笔
@Override
public void paint(Graphics g) {
//创建和容器一样大小的Image图片
if(offScreemImage == null) {
offScreemImage = this.createImage(width,height);
}
//获该图片的图形
Graphics gImage = offScreemImage.getGraphics();
gImage.setColor(Color.GRAY);
gImage.fillRect(0, 0, width, height);
//添加游戏选项
gImage.setColor(Color.BLUE);
gImage.setFont(new Font("仿宋", Font.BOLD, 50));
if (state == 0 ) {
gImage.drawString("选择游戏模式", 220, 100);
gImage.drawString("单人模式", 220, 200);
gImage.drawString("双人模式", 220, 300);
//绘制指针
gImage.drawImage(select, 160, y, null);
} else if (state ==1 || state ==2) {
gImage.drawString("游戏开始", 220, 100);
if (state ==1) {
gImage.drawString("单人模式", 220, 200);
} else if(state ==2) {
gImage.drawString("双人模式", 220, 200);
}
}
g.drawImage(offScreemImage, 0, 0,null);
}
//添加内部类,实现KeyAdapter类,重写keyPressed方法
class KeyMonitor extends KeyAdapter {
@Override
public void keyPressed(KeyEvent e) {
int key = e.getKeyCode();
switch(key) {
case KeyEvent.VK_1 :
a = 1; // 单人
y = 150;
break;
case KeyEvent.VK_2 :
a = 2; //双人
y = 250;
break;
case KeyEvent.VK_ENTER :
state = a;
break;
default:
break;
}
}
}
public void launch() {
setTitle("千锋坦克大战");
//设置宽高
setSize(width, height);
//居中
setLocationRelativeTo(null);
//差掉窗口,结束
setDefaultCloseOperation(3);
//窗口不可拉伸
setResizable(false);
//可见
setVisible(true);
this.addKeyListener(new GamePanel.KeyMonitor());
//重绘
while(true) {
repaint();
try {
Thread.sleep(25);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) {
GamePanel gp = new GamePanel();
gp.launch();
}
}
2. 键盘控制坦克移动
2.1 添加游戏类父类
属性
图片
位置(坐标)
面板
方法
构造方法
在图形中构建自己
获取自身矩形(用于比较中弹)
package com.qf;
import java.awt.Graphics;
import java.awt.Image;
import java.awt.Rectangle;
import java.awt.Toolkit;
//游戏父类
public abstract class GameObject {
//图片
public Image img;
//位置(坐标) 纵坐标 竖坐标
public int x;
public int y;
//在哪个面板
public GamePanel gamePanel;
//有参构造器 img参数为字符串,图片路径
public GameObject(String img, int x, int y, GamePanel gamePanel) {
super();
this.img = Toolkit.getDefaultToolkit().getImage(img);
this.x = x;
this.y = y;
this.gamePanel = gamePanel;
}
//画自己
public abstract void paintSelf(Graphics g);
//获取自身矩形
public abstract Rectangle getRec();
}
2.2 添加坦克类
尺寸(宽高)
速度
方向
不同方向图片
package com.qf;
import java.awt.Point;
import java.awt.Toolkit;
//坦克父类
public abstract class Tank extends GameObject {
//尺寸
public int width = 40;
public int height = 50;
//速度
public int speed = 3;
//方向
public Direction direction = Direction.UP;
//四个方向图片
public String upImg;
public String leftImg;
public String rightImg;
public String downImg;
public Tank(String img, int x, int y, GamePanel gamePanel,
String upImg, String leftImg, String rightImg, String downImg) {
super(img, x, y, gamePanel);
this.upImg = upImg;
this.leftImg = leftImg;
this.rightImg = rightImg;
this.downImg = downImg;
}
}
2.3 添加玩家类
package com.qf;
import java.awt.Graphics;
import java.awt.Rectangle;
import java.awt.event.KeyEvent;
//玩家
public class PlayerOne extends Tank {
public PlayerOne(String img, int x, int y, GamePanel gamePanel, String upImg, String leftImg, String rightImg,
String downImg) {
super(img, x, y, gamePanel, upImg, leftImg, rightImg, downImg);
}
@Override
public void paintSelf(Graphics g) {
g.drawImage(img, x, y, null);
}
@Override
public Rectangle getRec() {
return new Rectangle(x, y, width, height);
}
}
2.4 在窗口中添加玩家
创建玩家对象
//添加玩家一到面板
PlayerOne playerOne = new PlayerOne("images/mytank_up.gif", 125, 510,
this,
"images/mytank_up.gif",
"images/mytank_left.gif",
"images/mytank_right.gif",
"images/mytank_down.gif");
把玩家对象添加到图形中
playerOne.paintSelf(g);
package com.qf;
import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.Image;
import java.awt.Toolkit;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import javax.swing.JFrame;
/*
* 已解决闪动
*/
public class GamePanel extends JFrame {
private static final long serialVersionUID = -9194828502749643640L;
//解决闪动问题
Image offScreemImage = null;
//窗口长宽
int width = 800;
int height = 610;
//指针图片
Image select = Toolkit.getDefaultToolkit().getImage("images/mytank.gif");
int y =150; //纵坐标
//定义游戏模式 0:游戏未开始 1:单人模式 2:双人模式
int state = 0;
int a = 0; //根据输入的键值,修改a值,再赋值给state
//添加玩家一到面板
PlayerOne playerOne = new PlayerOne("images/mytank_up.gif", 125, 510,
this,
"images/mytank_up.gif",
"images/mytank_left.gif",
"images/mytank_right.gif",
"images/mytank_down.gif");
//窗口的启动方法
public void launch() {
setTitle("千锋坦克大战");
//设置宽高
setSize(width, height);
//居中
setLocationRelativeTo(null);
//差掉窗口,结束
setDefaultCloseOperation(3);
//窗口不可拉伸
setResizable(false);
//可见
setVisible(true);
//添加键盘监视器
this.addKeyListener(new GamePanel.KeyMonitor());
//重绘
while(true) {
repaint();
try {
Thread.sleep(25); //间隔时间
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
//画 图形
@Override
public void paint(Graphics g) {
//创建和容器一样大小的Image图片
if(offScreemImage == null) {
offScreemImage = this.createImage(width,height);
}
//获的该图片的画笔
Graphics gImage = offScreemImage.getGraphics();
//把窗口背景色设置为灰色
gImage.setColor(Color.GRAY);
//把这个背景色填充整个窗口
gImage.fillRect(0, 0, width, height);
//设置颜色
gImage.setColor(Color.BLUE);
//设置字体
gImage.setFont(new Font("仿宋", Font.BOLD, 50));
if(state == 0 ) {
//添加文字
gImage.drawString("选择游戏模式", 220, 100);
gImage.drawString("单人游戏", 220, 200);
gImage.drawString("双人游戏", 220, 300);
//绘制指针
gImage.drawImage(select, 160, y, null);
} else if (state == 1 || state == 2) {
gImage.drawString("游戏开始", 220, 100);
if(state == 1) {
gImage.drawString("单人游戏", 220, 200);
} else {
gImage.drawString("双人游戏", 220, 200);
}
//添加玩家
playerOne.paintSelf(gImage);
}
g.drawImage(offScreemImage, 0, 0,null);
}
//键盘监视器,需要添加到窗口中
class KeyMonitor extends KeyAdapter {
//按下键盘的回调方法
@Override
public void keyPressed(KeyEvent e) {
//返回按钮的值
int key = e.getKeyChar();
switch(key) {
case KeyEvent.VK_1 :
a = 1;
y = 150;
break;
case KeyEvent.VK_2 :
a = 2;
y = 250;
break;
case KeyEvent.VK_ENTER :
state = a;
break;
default:
}
}
}
public static void main(String[] args) {
GamePanel gp = new GamePanel();
gp.launch();
}
}
2.5使用键盘控制坦克移动
在坦克类(Tank)中添加上下左右四个方法
不同方向设置不同图片
不同的方向
//移动方法
public void leftward() {
x -=speed;
direction = Direction.LEFT;
setImg(leftImg);
}
public void rightward() {
x +=speed;
direction = Direction.RIGHT;
setImg(rightImg);
}
public void upward() {
y -=speed;
direction = Direction.UP;
setImg(upImg);
}
public void downward() {
y +=speed;
direction = Direction.DOWN;
setImg(downImg);
}
public void setImg(String img) {
this.image = Toolkit.getDefaultToolkit().getImage(img);
}
在PlayerOne类中按钮点击,松开方法,及移动方法
W上,A左,S下,D右
boolean left, right, up, down;
//键盘按下去方法
public void keyPressed(KeyEvent e) {
int key = e.getKeyCode();
switch(key) {
case KeyEvent.VK_A : left=true; break;
case KeyEvent.VK_S : down=true; break;
case KeyEvent.VK_D : right=true; break;
case KeyEvent.VK_W : up=true; break;
}
}
//键盘松开方法
public void keyReleased(KeyEvent e) {
int key = e.getKeyCode();
switch(key) {
case KeyEvent.VK_A : left=false; break;
case KeyEvent.VK_S : down=false; break;
case KeyEvent.VK_D : right=false; break;
case KeyEvent.VK_W : up=false; break;
}
}
//移动的方法,每次在绘制的时候移动
public void move() {
if(left) {
leftward();
} else if (right) {
rightward();
} else if (up) {
upward();
} else if(down) {
downward();
}
}
@Override
public void paintSelf(Graphics g) {
g.drawImage(image, x, y, null);
//每次绘图的时候,根据最新的属性,绘图
move();
}
在主窗口类中,添加对A,S,D,W按键的监控
//键盘监控
class KeyMonitor extends KeyAdapter {
//点击键盘的回调方法
@Override
public void keyPressed(KeyEvent e) {
int key = e.getKeyChar();
switch(key) {
case KeyEvent.VK_1:
y = 150;
a = 1;
break;
case KeyEvent.VK_2:
y = 250;
a = 2;
break;
case KeyEvent.VK_ENTER : //点击回车,选择相应的模式
state = a;
default:
playerOne.keyPressed(e); //这里
}
}
@Override
public void keyReleased(KeyEvent e) { //监听按钮的松开事件
playerOne.keyReleased(e); //这里
}
}
3. 坦克发射子弹
3.1 创建子弹类
属性
尺寸
速度
方向
//尺寸
int width = 10;
int height =10;
//速度
int speed = 7;
//方向,方向和坦克方向一致
Direction direction;
方法
左移,右移,下移,上移
根据坦克方向不同,移动方向不同
//移动方法
public void leftward() {
x -=speed;
}
public void rightward() {
x +=speed;
}
public void upward() {
y -=speed;
}
public void downward() {
y +=speed;
}
//根据方向移动,绘制自己时,调用
public void go() {
switch(direction) {
case LEFT: leftward(); break;
case DOWN: downward(); break;
case RIGHT: rightward();break;
case UP: upward(); break;
}
}
全部代码:
package com.qf;
import java.awt.Graphics;
import java.awt.Rectangle;
public class Bullet extends GameObject {
//尺寸
int width = 10;
int height =10;
//速度
int speed = 7;
//方向,方向和坦克方向一致
Direction direction;
//移动方法
public void leftward() {
x -=speed;
}
public void rightward() {
x +=speed;
}
public void upward() {
y -=speed;
}
public void downward() {
y +=speed;
}
//绘制自己时,调用
public void go() {
switch(direction) {
case LEFT: leftward(); break;
case DOWN: downward(); break;
case RIGHT: rightward();break;
case UP: upward(); break;
}
}
public Bullet(String imageUrl, int x, int y, GamePanal gamePanal,Direction direction) {
super(imageUrl, x, y, gamePanal);
this.direction = direction;
}
@Override
public void paintSelf(Graphics g) {
g.drawImage(image, x, y, null);
//绘制子弹时,可以移动
go();
}
@Override
public Rectangle getRec() {
return new Rectangle(x, y, width, height);
}
}
3.2 实现坦克发射子弹
在坦克类中
获取坦克的头部坐标
public Point getHeadPoint() {
switch(direction) {
case LEFT: return new Point(x, y+height/2);
case DOWN: return new Point(x+width/2, y+height);
case RIGHT: return new Point(x+width, y+height/2);
case UP: return new Point(x+width/2, y);
default: return null;
}
}
添加发射方法
根据坦克头坐标,及坦克方向创建子弹
把创建的子弹添加到面板中创建子弹集合中
//发射子弹
public void attack() {
//获取头坐标
Point point = getHeadPoint();
//创建子弹
Bullet bullet = new Bullet("images/bullet.gif", point.x, point.y,
this.gamePanal, this.direction);
//可以一次按很多次发射,把产生的子弹存储在面板的子弹集合中
this.gamePanal.bulletList.add(bullet);
}
//子弹集合 在面板类中定义
List<Bullet> bulletList = new ArrayList<Bullet>();
//绘制玩家
playerOne.paintSelf(gImage);
//绘制子弹 在面板类中操作
for(Bullet bullet : bulletList) {
bullet.paintSelf(gImage);
}
在PlayerOne类中添加键盘事件
当点击空格按钮,就会调用射击方法
//键盘按下去方法
public void keyPressed(KeyEvent e) {
int key = e.getKeyCode();
switch(key) {
case KeyEvent.VK_A : left=true; break;
case KeyEvent.VK_S : down=true; break;
case KeyEvent.VK_D : right=true; break;
case KeyEvent.VK_W : up=true; break;
case KeyEvent.VK_SPACE: attack(); //添加发送子弹方法
}
}
3.3 添加子弹冷却时间
在坦克类中添加冷却状态,冷却时间
//冷却状态
boolean attackCoolDown = true;
//冷却时间
int attackCoolTime = 1000;
创建冷却线程
//冷却线程
class AttackCool extends Thread {
public void run() {
attackCoolDown = false;
try {
Thread.sleep(attackCoolTime);
} catch (InterruptedException e) {
e.printStackTrace();
}
attackCoolDown = true;
}
}
修改发射方法,每发射一次,启动冷却线程
//发射子弹
public void attack() {
if (attackCoolDown) {
//获取头坐标
Point point = getHeadPoint();
//创建子弹
Bullet bullet = new Bullet("images/bullet.gif", point.x, point.y,
this.gamePanal, this.direction);
//可以一次按很多次发射,把产生的子弹存储在面板的子弹集合中
this.gamePanal.bulletList.add(bullet);
//启动冷却线程
new AttackCool().start();
}
}
4. 敌方坦克
4.1 随机添加敌方坦克
添加敌方坦克类
//敌方坦克
public class Bot extends Tank{
public Bot(String imageUrl, int x, int y, GamePanal gamePanal, String upImg, String leftImg, String rightImg,
String downImg) {
super(imageUrl, x, y, gamePanal, upImg, leftImg, rightImg, downImg);
}
@Override
public void paintSelf(Graphics g) {
g.drawImage(image, x, y, null);
}
@Override
public Rectangle getRec() {
return new Rectangle(x, y, width, height);
}
}
批量把敌方坦克图片添加到面板上
把敌方坦克的图片添加到项目中(4个方向各一个)
在面板中创建敌方坦克集合
面板每重新加载100次,添加一个敌方坦克,敌方坦克最多有10各
//重绘次数
int count =0;
//敌方坦克个数
int enemyCount = 0;
在重新绘制循环中,添加敌方坦克
//重新载入窗口
while(true) {
if(count%100 ==1 && enemyCount<5 ) {
//添加敌方坦克
Random r = new Random();
int rnum = r.nextInt(800);
botList.add(new Bot("images/enemy_up.png", rnum, 110,
this, "images/enemy_up.png", "images/enemy_left.png",
"images/enemy_right.png", "images/enemy_down.gif"));
enemyCount++;
}
this.repaint();
try {
Thread.sleep(25);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
在开始游戏加载面板中显示敌方坦克并对count自增(paint方法)
if (state ==0) {
//220离左边的距离, 100离上面的距离
gImage.drawString("选择游戏模式", 220, 100);
gImage.drawString("单人模式", 220, 200);
gImage.drawString("双人模式", 220, 300);
//画入图像
gImage.drawImage(select, 140, y, null);
} else {
gImage.drawString("开始游戏", 220, 100);
if(state == 1) {
gImage.drawString("单人模式", 220, 200);
} else if (state == 2) {
gImage.drawString("双人模式", 220, 200);
}
//绘制玩家
playerOne.paintSelf(gImage);
//绘制子弹
for(Bullet bullet : bulletList) {
bullet.paintSelf(gImage);
}
//绘制敌方坦克
for(Bot bot : botList) {
bot.paintSelf(gImage);
}
count++;
}
4.2 敌方坦克随机移动
随机生成方向(Bot)
//随机生成个数,表示方向
public Direction getRandomDirection() {
Random random = new Random();
int rnum = random.nextInt(4); //0-3
switch(rnum) {
case 0 : return Direction.LEFT;
case 1 : return Direction.RIGHT;
case 2 : return Direction.UP;
case 3 : return Direction.DOWN;
default: return null;
}
}
添加运动方法,每20次,转化一次方向(Bot)
//每间隔20,转换方向一次
int moveTime = 20;
//行走方法
public void go() {
if (moveTime >=20) {
direction = getRandomDirection();
moveTime=0;
} else {
moveTime++;
}
switch(direction) {
case LEFT: leftward(); break;
case DOWN: downward(); break;
case RIGHT: rightward();break;
case UP: upward(); break;
}
}
//勿忘把重绘方法添加到绘制自己方法中
@Override
public void paintSelf(Graphics g) {
g.drawImage(image, x, y, null);
go();
}
4.3 敌方坦克射击
创建敌方子弹类,直接继承Bullet即可
package com.qf;
import java.awt.Graphics;
import java.awt.Rectangle;
//敌方子弹类
public class EnemyBullet extends Bullet {
public EnemyBullet(String imageUrl, int x, int y, GamePanal gamePanal, Direction direction) {
super(imageUrl, x, y, gamePanal, direction);
}
@Override
public void paintSelf(Graphics g) {
g.drawImage(image, x, y, null);
//在图形绘制自己时移动
go();
}
@Override
public Rectangle getRec() {
return new Rectangle(x, y, width, height);
}
}
在机器人坦克类中添加攻击发射方法
机器人自行发射,把该方法添加到go()方法中
//攻击类,添加到go方法中
public void attack() {
Point p = getHeadPoint();
Random random = new Random();
int num = random.nextInt(100); // [0,99]
if(num <4) { //4%设计概率,可自行调节发射频率 0,1,2,3
this.gamePanal.bulletList.add(new EnemyBullet("images/bullet.gif",
p.x, p.y, this.gamePanal, this.direction));
}
}
//机器人坦克自行行走
public void go() {
attack(); // 添加到这里,边走边发射子弹
if (moveTime>=20) {
direction = getRandomDirection();
moveTime =0;
} else {
moveTime++;
}
switch(direction) {
case LEFT : leftward(); break;
case RIGHT : rightward(); break;
case DOWN : downward(); break;
case UP : upward(); break;
}
}
5. 碰撞检测
5.1 我方子弹(Bullet)和敌方坦克(Bot)碰撞
在面板类中定义要删除/消失的子弹(GamePanal)
在每次重绘时,把删除的子弹从绘制子弹集合中删除
//碰撞后要删除的子弹
List<Bullet> removeList = new ArrayList<Bullet>();
//绘制子弹
for(Bullet bullet : bulletList) {
bullet.paintSelf(gImage);
}
//去除要删除的子弹
bulletList.removeAll(removeList);
在子弹类中添加碰撞方法(Bullet)
在绘制每个子弹时,把当前子弹对象和所有计算机坦克比较是否碰撞
该方法要添加到绘制方法中
//碰撞 添加到paintSelf方法中,每次绘制时,检测
public void hitBot() {
List<Bot> bots = this.gamePanal.botList;
for(Bot bot : bots) {
if (this.getRec().intersects(bot.getRec())) {
this.gamePanal.botList.remove(bot); // 坦克消失
this.gamePanal.removeList.add(this); //子弹消失
break;
}
}
}
@Override
public void paintSelf(Graphics g) {
g.drawImage(image, x, y, null);
//在图形绘制自己时移动
go();
//每次绘制时,检测碰撞
hitBot();
}
5.2 敌方子弹和我方坦克碰撞
添加我方坦克集合(为后续扩展使用)
把玩家都添加到此集合中,在选择游戏模式时添加
玩家的绘制,修改成遍历该集合
不要忘记把原绘制玩家代码删除
//玩家坦克集合
List<Tank> playerList = new ArrayList<Tank>();
//键盘监控
class KeyMonitor extends KeyAdapter {
//点击键盘的回调方法
@Override
public void keyPressed(KeyEvent e) {
int key = e.getKeyChar();
switch(key) {
case KeyEvent.VK_1:
y = 150;
a = 1;
break;
case KeyEvent.VK_2:
y = 250;
a = 2;
break;
case KeyEvent.VK_ENTER : //点击回车,选择相应的模式
state = a;
//添加玩家,双人待做
playerList.add(playerOne); //----这里添加
default:
playerOne.keyPressed(e); //坦克的移动
}
}
//绘制玩家
// playerOne.paintSelf(gImage);
for(Tank player : playerList) {
player.paintSelf(gImage);
}
在敌方子弹类中添加碰撞方法
该方法添加到绘制方法中
//碰撞 添加到paintSelf方法中,每次绘制时,检测
public void hitPlayer() {
List<Tank> tanks = this.gamePanel.playerList;
for(Tank t : tanks) {
if (this.getRec().intersects(t.getRec())) {
this.gamePanel.playerList.remove(t); // 玩家坦克消失
this.gamePanel.removeList.add(this); //子弹消失
break;
}
}
}
@Override
public void paintSelf(Graphics g) {
g.drawImage(image, x, y, null);
//在图形绘制自己时移动
go();
//检测碰撞
hitPlayer();
}
5.3 与围墙的碰撞检测
5.3.1面板中添加围墙
添加围墙类
package com.qf;
import java.awt.Graphics;
import java.awt.Rectangle;
public class Wall extends GameObject {
//围墙尺寸
int length = 60;
public Wall(String imageUrl, int x, int y, GamePanel gamePanal) {
super(imageUrl, x, y, gamePanel);
}
@Override
public void paintSelf(Graphics g) {
g.drawImage(image, x, y, null);
}
@Override
public Rectangle getRec() {
return new Rectangle(x, y, length, length);
}
}
在面板中添加围墙集合,在lunch方法中添加围墙,然后把集合绘制到面板中
//围墙列表
List<Wall> wallList = new ArrayList<Wall>();
//在lunch方法中
//添加围墙
for(int i = 0; i<14; i++) {
wallList.add(new Wall("images/wall.png", i*60, 170, this));
}
wallList.add(new Wall("images/wall.png", 305, 560, this));
wallList.add(new Wall("images/wall.png", 305, 500, this));
wallList.add(new Wall("images/wall.png", 365, 500, this));
wallList.add(new Wall("images/wall.png", 425, 500, this));
wallList.add(new Wall("images/wall.png", 425, 560, this));
//在paint方法中绘制围墙
for(Wall wall : wallList) {
wall.paintSelf(gImage);
}
5.3.2 添加围墙和子弹碰撞检测
子弹类添加和围墙碰撞方法(Bullet)
把该方法添加到go()方法中,让敌方子弹也可以和围墙碰撞
//子弹和围墙碰撞,添加到go方法中,让敌方子弹也可以击破围墙
public void hitWall() {
List<Wall> walls = this.gamePanal.wallList;
for(Wall wall : walls) {
if (this.getRec().intersects(wall.getRec())) {
this.gamePanal.wallList.remove(wall); // 围墙消失
this.gamePanal.removeList.add(this); //子弹消失
break;
}
}
}
public void go() {
switch(direction) {
case LEFT : leftward(); break;
case RIGHT : rightward(); break;
case DOWN : downward(); break;
case UP : upward(); break;
}
hitWall(); //---------------添加到这里
}
5.3.3 坦克和围墙碰撞
当坦克和围墙碰撞时,坦克无法移动
在坦克类中添加围墙碰撞方法
然后在移动类中判断是否会和围墙碰撞,如果碰撞则不移动
//与围墙碰撞检测
public boolean hitWall(int x, int y) {
List<Wall> wallList = this.gamePanal.wallList;
//坦克下一步将要移动后的矩形
Rectangle next = new Rectangle(x, y, width, height);
for(Wall wall : wallList) {
if(next.intersects(wall.getRec())) {
return true;
}
}
return false;
}
修改所有移动的方法
//移动方法 左移
public void leftward() {
if(!hitWall(x-speed, y)) {
x -=speed;
}
direction = Direction.LEFT;
setImg(leftImg);
}
//移动方法 右移
public void rightward() {
if(!hitWall(x+speed, y)) {
x +=speed;
}
direction = Direction.RIGHT;
setImg(rightImg);
}
//移动方法 上移
public void upward() {
if(!hitWall(x, y-speed)) {
y -=speed;
}
direction = Direction.UP;
setImg(upImg);
}
//移动方法 下移
public void downward() {
if(!hitWall(x, y+speed)) {
y +=speed;
}
direction = Direction.DOWN;
setImg(downImg);
}
5.4 判断坦克边缘的碰撞
坦克类中添加边缘碰撞判断(Tank)
该判断添加到移动方法中
//判断和边缘的碰撞
public boolean moveToBorder(int x,int y) {
if(x<0) {
return true;
} else if (x+width > this.gamePanal.width) {
return true;
} else if (y <0) {
return true;
} else if (y+height > this.gamePanal.height) {
return true;
}
return false;
}
//移动方法 左移
public void leftward() {
if(!hitWall(x-speed, y) && !moveToBorder(x-speed, y)) {
x -=speed;
}
direction = Direction.LEFT;
setImg(leftImg);
}
//移动方法 右移
public void rightward() {
if(!hitWall(x+speed, y) && !moveToBorder(x+speed, y)) {
x +=speed;
}
direction = Direction.RIGHT;
setImg(rightImg);
}
//移动方法 上移
public void upward() {
if(!hitWall(x, y-speed)&&!moveToBorder(x, y-speed)) {
y -=speed;
}
direction = Direction.UP;
setImg(upImg);
}
//移动方法 下移
public void downward() {
if(!hitWall(x, y+speed)&&!moveToBorder(x, y+speed)) {
y +=speed;
}
direction = Direction.DOWN;
setImg(downImg);
}
5.5判断子弹是否出界
判断子弹坐标是否出界,如果出界则删除
该方法添加到go方法中
//如果子弹出界,该方法添加在go方法中,敌人子弹也可以删除 删除子弹,节省资源
public void moveToBorder() {
if(x<0 || x+width > this.gamePanel.width) {
this.gamePanel.removeList.add(this);
}
if(y<0 || y+height > this.gamePanel.height) {
this.gamePanel.removeList.add(this);
}
}
//子弹的移动是根据方向来的
public void go() {
switch(direction) {
case LEFT : leftward(); break;
case RIGHT : rightward(); break;
case DOWN : downward(); break;
case UP : upward(); break;
}
hitWall(); //子弹撞墙
moveToBorder(); //子弹出界删除
}
6. 添加基地
6.1 创建并添加基地
创建基地类
package com.qf;
import java.awt.Graphics;
import java.awt.Rectangle;
//基地类
public class Base extends GameObject {
int length = 60;
public Base(String imageUrl, int x, int y, gamePanel gamePanal) {
super(imageUrl, x, y, gamePanal);
}
@Override
public void paintSelf(Graphics g) {
g.drawImage(image, x, y, null);
}
@Override
public Rectangle getRec() {
return new Rectangle(x, y, length, length);
}
}
在面板中添加基地
//基地集合
List<Base> baseList = new ArrayList<Base>();
//在lunch方法添加创建一个基地
Base base = new Base("images/base.png", 365, 560, this);
baseList.add(base);
//绘制基地
for(Base base : baseList) {
base.paintSelf(gImage);
}
6.2 子弹与基地的碰撞测试
在子弹类中添加碰撞方法
该方法添加到go方法中
//与基地碰撞方法
public void hitBase() {
List<Base> bases = this.gamePanel.baseList;
for(Base base : bases) {
if (this.getRec().intersects(base.getRec())) {
this.gamePanel.baseList.remove(base); // 基地消失
this.gamePanel.removeList.add(this); //子弹消失
break;
}
}
}
public void go() {
switch(direction) {
case LEFT : leftward(); break;
case RIGHT : rightward(); break;
case DOWN : downward(); break;
case UP : upward(); break;
}
hitWall(); //子弹撞墙
moveToBorder(); //子弹出界删除
hitBase(); //---销毁基地
}
7. 游戏规则
我方胜利
把所有的敌人干倒
机器赢了
玩家坦克被干掉
基地被倒了
//判断规则,在每次重绘时
//玩家赢了
if(botList.size() == 0 && enemyCount == 3) {
state = 3;
}
//机器赢了
if((playerList.size()==0&&(state ==1 || state ==2))
|| baseList.size() ==0) {
state = 4;
}
//获取画笔
@Override
public void paint(Graphics g) {
if(offScreemImage==null) {
offScreemImage = this.createImage(width, height);
}
Graphics gImage = offScreemImage.getGraphics();
//设置图形的颜色
gImage.setColor(Color.GRAY);
//把设置的颜色,填充整个矩形
gImage.fillRect(0, 0, width, height);
//重新设置字体颜色
gImage.setColor(Color.BLUE);
gImage.setFont(new Font("楷体", Font.BOLD, 50));
if (state ==0) {
//220离左边的距离, 100离上面的距离
gImage.drawString("选择游戏模式", 220, 100);
gImage.drawString("单人模式", 220, 200);
gImage.drawString("双人模式", 220, 300);
//画入图像
gImage.drawImage(select, 140, y, null);
} else if (state == 1 || state ==2){
gImage.drawString("开始游戏", 220, 100);
if(state == 1) {
gImage.drawString("单人模式", 220, 200);
} else if (state == 2) {
gImage.drawString("双人模式", 220, 200);
}
//绘制玩家
// playerOne.paintSelf(gImage);
//绘制玩家列表
for(Tank tank : playerList) {
tank.paintSelf(gImage);
}
//绘制子弹
for(Bullet bullet : bulletList) {
bullet.paintSelf(gImage);
}
//删除发生碰撞的子弹
bulletList.removeAll(removeList);
//绘制机器人坦克
for (Bot bot : botList) {
bot.paintSelf(gImage);
}
//绘制城墙
for (Wall wall : wallList) {
wall.paintSelf(gImage);
}
//绘制基地
for (Base base : baseList) {
base.paintSelf(gImage);
}
count++;
} else if(state ==3) {
gImage.drawString("我赢了", 220, 100);
} else if(state == 4) {
gImage.drawString("机器赢了", 220, 100);
}
//把重新绘制的图片放到窗口中(从左上角开始放)
g.drawImage(offScreemImage, 0, 0, null);
}
猜你喜欢LIKE
最新文章NEW
相关推荐HOT
更多>>热门推荐
零基础必看的前端HTML+CSS教程
沸Java培训新手实战必备!单机版坦克大战分步实现项目源码
热3种Javascript图片预加载的方法详解
热长沙前端培训:一招教你用vue3+canvas实现坦克大战
新互联网凉了?参加长沙Java培训能找到工作吗?
长沙Java培训实战项目,出游咨询订票系统开发流程
不参加长沙Java培训能学会Java吗?2022Java技能学习路线图
千锋长沙Java培训分享之怎么学习Java集合?
千锋长沙前端培训分享之JavaScript面向对象编程思想详解
千锋长沙前端培训分享之web前端的回流和重绘
千锋长沙前端培训分享之3种Javascript图片预加载的方法详解
千锋长沙前端培训分享之利用Jest测试React组件
千锋长沙前端培训分享之JavaScript中Slice的用例
千锋长沙java培训分享之Socket编程