java学习之坦克大战游戏

  总结:由于这几天快过年比较忙然后没怎么写,写代码途中一些经验总结现在给忘记了。这次的小项目感觉比上次写的思路清楚了点。没有之前第一次写那么逻辑混乱,结构也搞的比之前的要好,添加功能比较容易。学习了之前的经验,操作对象的方法由对象本身提供。不过这次小项目还有不足和不完善之处,有些可以做的功能没有实现,比如游戏存档,这里应该可以用下对象的序列化,还有游戏难度的设置也可以写个文件弄出来。要过年了,到处走亲戚没什么心思写了,这里只能留个尾巴了。

  前言:之前的及时通信项目完成后感觉线程方面和对java的运用还不是很熟悉,在另外一个学习视频中看到一个做坦克游戏的项目视频,便想自己试着做做看,先看视频把游戏规则什么的都搞清楚。然后开始一步一步实现。

主要功能步骤如下

* 1、画出坦克
* 2、我的坦克可以上下移动
* 3、可以发射子弹,子弹连发(或者最多5发)
* 4、当我的坦克击中敌人坦克时,敌人坦克消失(或者爆炸效果)
* 5、我被击中也显示爆炸效果。
* 6、游戏开始和游戏介绍选项

这次游戏界面没有进行设计,直接在一个frame上放个panel。

游戏设计仿mvc 这里不做详细介绍了直接上代码

model包

package com.gh.model;
/**
* 爆炸类
* 考虑到同时爆炸定义个类
* @author ganhang
*
*/
public class Bomb {
private int x;
private int y;//坐标
public boolean islive=true;
private int time=9;//炸弹生命
public Bomb() {
super();
}
public Bomb(int x, int y) {
super();
this.x = x;
this.y = y;
}
public int getX() {
return x;
}
public void setX(int x) {
this.x = x;
}
public int getY() {
return y;
}
public void setY(int y) {
this.y = y;
}
public int getTime() {
return time;
}
//生命递减
public void livedown(){
if(time>0){
time--;
}else{
islive=false;
}
}
}

Bomb

 package com.gh.model;
/**
* 子弹类,
* 因为多个子弹同时运动所以需要个内部类做线程
* @author ganhang
*
*/
public class Bullet {
private int x;
private int y;
private int speed;
private int drect;
public boolean islive=true;
public Bullet(int x, int y, int speed, int drect) {
super();
this.x = x;
this.y = y;
this.speed = speed;
this.drect = drect;
new Thread(new BulletThread()).start();
}
public Bullet() {
super();
} public int getX() {
return x;
} public void setX(int x) {
this.x = x;
} public int getY() {
return y;
} public void setY(int y) {
this.y = y;
} class BulletThread implements Runnable {
@Override
public void run() {
while (true) {
try {
Thread.sleep(50);
} catch (Exception e) {
e.printStackTrace();
}
switch (drect) {//判断方向坐标移动
case 0:
y-=speed;
break;
case 1:
x+=speed;
break;
case 2:
y+=speed;
break;
case 3:
x-=speed;
break;
}
if (x < 0 || y < 0 || x > 500 || y > 500||!islive) {
islive=false;
break;
}
}
}
}
}

Bullet

package com.gh.model;

/**
* 地图坐标标记
* 防止敌方坦克重叠
* @author ganhang
*
*/
public class Map {
public int[][] location=new int[500][500];
public Map() {
for (int i = 0; i < 500; i++) {
for (int j = 0; j <500; j++) {
location[i][j]=0;
}
}
}
}

Map

坦克类,刚才上传发现点问题暂时没改

 package com.gh.model;

 import java.util.Vector;
/**
* 坦克类
* 每个坦克就是一个线程,
* 这里自己坦克并没有启动线程
* @author ganhang
*
*/
public class Tank implements Runnable {
private int x = 0;
private int y = 0;// 坐标
private int drect = 0;// 方向 0向上,1向右,2向下,3向左;
private int type = 0;// 坦克类型 0表示自己
private int speed = 3;// 速度
public Vector<Bullet> mybs = new Vector<Bullet>();// 子弹集
private Bullet myBullet;// 子弹
public boolean islive = true;
private Map map;
public boolean start = true;
public Map getMap() {
return map;
} public void setMap(Map map) {
this.map = map;
} public Tank(int x, int y, int drect, int type) {
super();
this.x = x;
this.y = y;
this.drect = drect;
this.type = type;
} public Tank() {
super();
} public Bullet getMyBullet() {
return myBullet;
} public int getSpeed() {
return speed;
} public void setSpeed(int speed) {
this.speed = speed;
} public int getX() {
return x;
} public int getDrect() {
return drect;
} public void setDrect(int drect) {
this.drect = drect;
} public int getType() {
return type;
} public void setType(int type) {
this.type = type;
} public void setX(int x) {
this.x = x;
} public int getY() {
return y;
} public void setY(int y) {
this.y = y;
} public void moveUp() {
if (y - speed < 0)
y = 0;
else {
y -= speed;
map.location[x][y]=1;//标记此坦克坐标在地图上防止其他坦克过来占用导致重叠
// 这里只标记了坦克坐标那一个点,会有bug,部分坦克还是有重叠现象,
// 这里可以遍历整个坦克坐标(x到x+20,y到y+30)设置标记。
// for(int i=x;i<x+20;i++){
// for (int j = y; j < y+30; j++) {
// map.location[x][y]=1;
// }
// }
}
} public void moveDown() {
if (y + speed > 470)
y = 470;
else {
y += speed;
map.location[x][y]=1;
}
} public void moveRight() {
if (x + speed > 470)
x = 470;
else {
x += speed;
map.location[x][y]=1;
}
} public void moveLeft() {
if (x - speed < 0)
x = 0;
else {
x -= speed;
map.location[x][y]=1;
}
} public void shot() {
switch (drect) {
case 0:
myBullet = new Bullet(x + 10, y, 5, 0);
mybs.add(myBullet);
break;
case 1:
myBullet = new Bullet(x + 30, y + 10, 5, 1);
mybs.add(myBullet);
break;
case 2:
myBullet = new Bullet(x + 10, y + 30, 5, 2);
mybs.add(myBullet);
break;
case 3:
myBullet = new Bullet(x, y + 10, 5, 3);
mybs.add(myBullet);
break;
}
} @Override
public void run() {
while (islive) {
if (start) {
int step;
int s;
try {
switch (drect) {
case 0:
step = (int) (Math.random() * 30);
for (int i = 0; i < step; i++) {
moveUp();
if (y <= 0)
break;// 撞墙跳出循环
if (y >= 30)// 仿数组越界
if (map.location[x][y - 30] == 1 || map.location[x][y - 20] == 1) {
map.location[x][y - 30] = 0;//这里没分开判断
map.location[x][y - 20] = 0;
break;
}
Thread.sleep(80);
}
break;
case 1:
step = (int) (Math.random() * 30);
for (int i = 0; i < step; i++) {
moveRight();
if (x >= 500)
break;
if (x < 470)
if (map.location[x + 20][y] == 1 || map.location[x + 30][y] == 1) {
map.location[x + 20][y] = 0;
map.location[x + 30][y] = 0;
break;
}
Thread.sleep(80);
}
break;
case 2:
step = (int) (Math.random() * 30);
for (int i = 0; i < step; i++) {
moveDown();
if (y >= 500)
break;
if (y < 470)
if (map.location[x][y + 30] == 1 || map.location[x][y + 20] == 1) {
map.location[x][y + 30] = 0;
map.location[x][y + 20] = 0;
break;
}
Thread.sleep(80);
}
break;
case 3:
step = (int) (Math.random() * 30);
for (int i = 0; i < step; i++) {
moveLeft();
if (x <= 0)
break;
if (x >= 30)
if (map.location[x - 20][y] == 1 || map.location[x - 30][y] == 1) {
map.location[x - 20][y] = 0;
map.location[x - 30][y] = 0;
break;
}
Thread.sleep(80);
}
break;
}
} catch (InterruptedException e) {
e.printStackTrace();
}
drect = (int) (Math.random() * 4);// 随机方向
s = (int) (Math.random() * 10);
if (s > 8) {
shot();
}
}
}
}
}

Tank

view包

 package com.gh.view;

 import java.awt.Color;
import java.awt.Graphics;
import java.awt.Image;
import java.awt.Toolkit;
import java.util.Vector; import javax.swing.JOptionPane;
import javax.swing.JPanel; import com.gh.model.Bomb;
import com.gh.model.Bullet;
import com.gh.model.Map;
import com.gh.model.Tank; /**
* 游戏显示面板
*
* @author ganhang
*
*/ public class Mypanel extends JPanel implements Runnable {
public Tank mytank = null;// 我的坦克
Tank ek = null;
Image img;
Vector<Tank> eks = new Vector<Tank>();//地方坦克集
Vector<Bomb> bs = new Vector<Bomb>();//爆炸集合
Map map = new Map(); public Mypanel() {
mytank = new Tank(200, 200, 0, 0);
mytank.setMap(map);
// 创建敌人坦克
for (int i = 0; i < 17; i++) {
ek = new Tank(i * 30, 10, 2, 1);
eks.add(ek);
ek.setMap(map);
new Thread(ek).start();
}
img = Toolkit.getDefaultToolkit().getImage(this.getClass().getResource("/1.png"));
} @Override
public void paint(Graphics g) {
super.paint(g);
// 画背景
g.fillRect(0, 0, 500, 500);
// 画自己的坦克
if (mytank.islive)
drawTank(mytank.getX(), mytank.getY(), g, mytank.getDrect(), mytank.getType());
// 画自己的子弹
for (int i = 0; i < mytank.mybs.size(); i++) {// 循环时删除集合时,不要用foreach,用for
Bullet b = new Bullet();
b = mytank.mybs.get(i);
if (b.islive) {
g.setColor(Color.white);
g.fill3DRect(b.getX(), b.getY(), 2, 2, false);
} else
mytank.mybs.remove(b);
}
// 画敌人坦克
for (int i = 0; i < eks.size(); i++) {
Tank ek = new Tank();
ek = eks.get(i);
if (ek.islive)
drawEnemyTank(ek.getX(), ek.getY(), ek.getDrect(), g);
// 画敌人子弹
for (int j = 0; j < ek.mybs.size(); j++) {
Bullet eb = new Bullet();
eb = ek.mybs.get(j);
if (eb.islive) {
g.setColor(Color.green);
g.fill3DRect(eb.getX(), eb.getY(), 2, 2, false);
} else
ek.mybs.remove(eb);
}
}
// 画爆炸,这里有个bug第一次爆炸没有爆炸效果图出来,检查原因是只一闪而过
// 添加休眠好了点,不过影响后面爆炸效果,不明白为什么第一次画得快些
for (int i = 0; i < bs.size(); i++) {
// System.out.println(bs.size());
Bomb bb = bs.get(i);
if (bb.islive) {
if (bb.getTime() > 6) {
try {
Thread.sleep(50);
} catch (Exception e) {
e.printStackTrace();
}
g.drawImage(img, bb.getX(), bb.getY(), 30, 30, this);
} else if (bb.getTime() > 3) {
g.drawImage(img, bb.getX(), bb.getY(), 15, 15, this);
} else if (bb.getTime() > 0) {
g.drawImage(img, bb.getX(), bb.getY(), 1, 1, this);
}
}
bb.livedown();
if (bb.getTime() == 0)
bs.remove(bb);
}
} public boolean isHitEnemy(Bullet b, Tank ek) {
if (ek.getDrect() == 0 || ek.getDrect() == 2) {
// 坦克竖着时宽20,高30
if (b.getX() >= ek.getX() && b.getX() <= ek.getX() + 20 && b.getY() >= ek.getY()
&& b.getY() <= ek.getY() + 30) {
b.islive = false;
ek.islive = false;
Bomb bb = new Bomb(ek.getX(), ek.getY());
bs.add(bb);
return true;
}
return false;
} else {// 横着宽30,高20;
if (b.getX() >= ek.getX() && b.getX() <= ek.getX() + 30 && b.getY() >= ek.getY()
&& b.getY() <= ek.getY() + 20) {
ek.islive = false;
b.islive = false;
Bomb bb = new Bomb(ek.getX(), ek.getY());
bs.add(bb);
return true;
}
return false;
}
} public void drawEnemyTank(int x, int y, int drect, Graphics g) {
drawTank(x, y, g, drect, 1);
} public void drawTank(int x, int y, Graphics g, int drect, int type) {
switch (type) {
case 0:
g.setColor(Color.cyan);
break;
case 1:
g.setColor(Color.GREEN);
default:
break;
}
switch (drect) {
case 0:
// 坦克向上时宽20,高30
g.fill3DRect(x, y, 5, 30, false);
g.fill3DRect(x + 15, y, 5, 30, false);
g.fill3DRect(x + 5, y + 9, 10, 15, false);
g.drawLine(x + 10, y + 14, x + 10, y);
break;
case 1:
// 坦克向右时宽30,高20
g.fill3DRect(x, y, 30, 5, false);
g.fill3DRect(x, y + 15, 30, 5, false);
g.fill3DRect(x + 7, y + 5, 15, 10, false);
g.drawLine(x + 13, y + 10, x + 30, y + 10);
break;
case 2:
g.fill3DRect(x, y, 5, 30, false);
g.fill3DRect(x + 15, y, 5, 30, false);
g.fill3DRect(x + 5, y + 7, 10, 15, false);
g.drawLine(x + 10, y + 12, x + 10, y + 30);
break;
case 3:
g.fill3DRect(x, y, 30, 5, false);
g.fill3DRect(x, y + 15, 30, 5, false);
g.fill3DRect(x + 8, y + 5, 15, 10, false);
g.drawLine(x, y + 10, x + 13, y + 10);
break;
}
} @Override
public void run() {
while (true) {
try {
Thread.sleep(50);// 画板刷新频率
} catch (InterruptedException e) {
e.printStackTrace();
}
// 判断自己坦克的子弹是否击中敌人坦克
for (int i = 0; i < mytank.mybs.size(); i++) {
Bullet mb = new Bullet();
mb = mytank.mybs.get(i);
if (mb.islive) {
for (int j = 0; j < eks.size(); j++) {
Tank ek = new Tank();
ek = eks.get(j);
if (ek.islive) {
isHitEnemy(mb, ek);
}
}
}
}
// 判断敌方坦克 的子弹是否击中我方坦克
for (int i = 0; i < eks.size(); i++) {
Tank et = new Tank();
et = eks.get(i);
for (int j = 0; j < et.mybs.size(); j++) {// 这里写错ek查到死。。。
Bullet etb = new Bullet();
etb = et.mybs.get(j);
if (etb.islive) {
isHitEnemy(etb, mytank);
}
}
}
this.repaint();// 刷新
if (!mytank.islive) {
JOptionPane.showMessageDialog(this, "你被GG");
mytank.islive = true;
}
}
}
}

Mypanel

开始游戏类

 package com.gh.view;

 import java.awt.EventQueue;
/**
* 1、画出坦克
* 2、我的坦克可以上下移动
* 3、可以发射子弹,子弹连发(或者最多5发)
* 4、当我的坦克击中敌人坦克时,敌人坦克消失(或者爆炸效果)
* 5、我被击中也显示爆炸效果。
* 6、游戏开始选项
* @author ganhang
*
*/
public class TankGame {
private JFrame frame;
/**
* Launch the application.
*/
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
public void run() {
try {
TankGame window = new TankGame();
window.frame.setVisible(true);
} catch (Exception e) {
e.printStackTrace();
}
}
});
} /**
* Create the application.
*/
public TankGame() {
initialize();
} /**
* Initialize the contents of the frame.
*/
private void initialize() {
frame = new JFrame();
frame.setTitle("\u5766\u514B\u5927\u6218");
frame.setBounds(450, 70, 600, 600);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); JMenuBar menuBar = new JMenuBar();
frame.setJMenuBar(menuBar); JMenu mnNewMenu = new JMenu("\u9009\u9879");
mnNewMenu.setFont(new Font("微软雅黑", Font.PLAIN, 13));
menuBar.add(mnNewMenu); JMenuItem mntmNewMenuItem = new JMenuItem("开始游戏");
mntmNewMenuItem.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
Mypanel mp = new Mypanel();
Thread t=new Thread(mp);
t.start();
frame.getContentPane().add(mp, BorderLayout.CENTER);
frame.addKeyListener(new KeyListen(mp));
frame.setVisible(true);
}
}); mntmNewMenuItem.setFont(new Font("微软雅黑", Font.PLAIN, 12));
mnNewMenu.add(mntmNewMenuItem); JMenu mnNewMenu_1 = new JMenu("\u6E38\u620F\u8BF4\u660E");
mnNewMenu_1.setFont(new Font("微软雅黑", Font.PLAIN, 13));
menuBar.add(mnNewMenu_1); JMenuItem mntmNewMenuItem_1 = new JMenuItem("\u5173\u4E8E\u6E38\u620F");
mntmNewMenuItem_1.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
JOptionPane.showMessageDialog(frame, "上:W, 下:A ,左:S, 右:D ,射击:空格\n Made by Ganhang");;
}
});
mntmNewMenuItem_1.setFont(new Font("微软雅黑", Font.PLAIN, 12));
mnNewMenu_1.add(mntmNewMenuItem_1); } }

TankGame

control包 这里放的事件监听主要业务逻辑在画板类和模型类里写了

 package com.gh.control;

 import java.awt.event.KeyEvent;
import java.awt.event.KeyListener; import com.gh.view.Mypanel; /**
* 事件监听
* 这里有个控制最大发射子弹数
* @author ganhang
*/
public class KeyListen implements KeyListener{
private Mypanel mp=null; public KeyListen(Mypanel mp) {
super();
this.mp = mp;
}
@Override
public void keyTyped(KeyEvent e) { } @Override
public void keyPressed(KeyEvent e) {
//方向键监听
if(e.getKeyCode()==KeyEvent.VK_W){
mp.mytank.setDrect(0);
mp.mytank.moveUp();
}else if(e.getKeyCode()==KeyEvent.VK_S){
mp.mytank.setDrect(2);
mp.mytank.moveDown();
}else if(e.getKeyCode()==KeyEvent.VK_D){
mp.mytank.setDrect(1);
mp.mytank.moveRight();
}else if(e.getKeyCode()==KeyEvent.VK_A){
mp.mytank.setDrect(3);
mp.mytank.moveLeft();
}
//发射子弹监听
if(e.getKeyCode()==KeyEvent.VK_SPACE){
if(mp.mytank.mybs.size()<5)
mp.mytank.shot();
}
mp.repaint();
}
@Override
public void keyReleased(KeyEvent e) {
// TODO Auto-generated method stub }
}

KeyListen

github:https://github.com/ganhang/My_TankGame

想到什么再更吧。。

上一篇:学习Linux系列--布署常用服务


下一篇:Windows10系统的Python3+Anaconda3+PyCharm2021安装