用Java实现坦克大战项目及源代码下载,同时此项目贯穿了23种设计模式

一、简单介绍

这是一个功能相对全的JAVA版坦克大战,界面绘制是通过JAVA的图形化用户界面完成的,包括了菜单界面和游戏界面。其中菜单界面可以供玩家选择重新开始游戏、暂停、继续、是否播放背景音乐、帮助等操作;游戏界面绘制了坦克、河流、草地、鹰碉堡等经典坦克场景,玩家在游戏界面操作坦克开始对战。

本游戏使用的主要技术有Swing编程、面向对象编程、多线程编程。本想用I/O编程实现保存游戏数据,感觉单机版的没必要就没去研究。

游戏实现的主要功能有:

  1. 坦克可以上下左右、以及左上左下右上右下八个方向移动,移动时添加音效
  2. 坦克可以发子弹(可以连发),发射时添加音效
  3. 击中对方坦克时,坦克消失,显示爆炸效果;子弹击中鹰碉堡时,游戏结束;子弹击中壁时,子弹消失
  4. 我方坦克吃到血块时,生命值加满
  5. 移动过程中检测碰撞,包括坦克与坦克,坦克与草地、河流、墙壁等
  6. 声音处理(开始音乐、背景音乐等)
  7. 菜单处理(开始游戏、暂停游戏、继续游戏、帮助等)
  8. 计分(分关、记录成绩)

二、工程目录

用Java实现坦克大战项目及源代码下载,同时此项目贯穿了23种设计模式_第1张图片

 

三、具体代码

每个文件都有详细的注释,不理解的可以联系作者,这里只贴出Tank.java和TankClient.java的代码,源代码将会在文章最后面贴出来,话不多说,先上代码

Tank.java代码

package com.xiaoli.tank;
 
 
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Rectangle;
import java.awt.event.KeyEvent;
import java.util.List;
import java.util.Random;
/**
 * 
 *Title:Tank
 *Description:坦克类,包括坦克移动、坦克发射炮弹等 
 *@author xiaoli
 *@date2017年1月4日
 */
public class Tank {
//坦克X方向速度,设置游戏难度时可拓展
    public static int XSPEED = 5;
    //坦克Y方向速度,设置游戏难度时可拓展
public static int YSPEED = 5;
//坦克宽度
public static final int WIDTH = 30;
//坦克高度
public static final int HEIGHT = 30;
//坦克是否活着
private boolean live = true;
//坦克的生命值
private int life = 100;
//持有对TankClient大管家的引用
TankClient tc;
//判断是否是我方坦克,默认true
private boolean good=true;
//用于记录坦克原来的坐标,碰到墙、坦克时方便退一步
private int oldX,oldY;
//绘制坦克的左上角坐标
private int x, y;
//用于产生随机数
private static Random r = new Random();
//用于控制敌人随机发出子弹
private int step = r.nextInt(30)+10;
//判断是否按下方向键
private boolean bL=false, bU=false, bR=false, bD = false;
//枚举类型定义了坦克的八个方向,和静止时的方向
enum Direction {L, LU, U, RU, R, RD, D, LD, STOP};
//坦克的方向
private Direction dir = Direction.STOP;
//炮筒的方向
private Direction ptDir = Direction.D;
    //血条
private BloodBar bar = new BloodBar();
//构造方法
public Tank(int x, int y, boolean good) {
this.x = x;
this.y = y;
this.good = good;
}
//构造方法
public Tank(int x, int y, boolean good, Direction dir,TankClient tc) {
this(x, y, good);
this.tc = tc;
this.oldX=x;
this.oldY=y;
this.dir=dir;
}
/**
* 
*@Description:画出坦克
* @param g
*/
public void draw(Graphics g) {
if(!live) {
if(!good) {
tc.tanks.remove(this);
}
return;
}
Color c = g.getColor();
if(good) g.setColor(Color.RED);
else g.setColor(Color.BLUE);
g.fillOval(x, y, WIDTH, HEIGHT);
g.setColor(c);
//血条
   if(good) bar.draw(g); 
switch(ptDir) {
case L:
g.drawLine(x + Tank.WIDTH/2, y + Tank.HEIGHT/2, x, y + Tank.HEIGHT/2);
break;
case LU:
g.drawLine(x + Tank.WIDTH/2, y + Tank.HEIGHT/2, x, y);
break;
case U:
g.drawLine(x + Tank.WIDTH/2, y + Tank.HEIGHT/2, x + Tank.WIDTH/2, y);
break;
case RU:
g.drawLine(x + Tank.WIDTH/2, y + Tank.HEIGHT/2, x + Tank.WIDTH, y);
break;
case R:
g.drawLine(x + Tank.WIDTH/2, y + Tank.HEIGHT/2, x + Tank.WIDTH, y + Tank.HEIGHT/2);
break;
case RD:
g.drawLine(x + Tank.WIDTH/2, y + Tank.HEIGHT/2, x + Tank.WIDTH, y + Tank.HEIGHT);
break;
case D:
g.drawLine(x + Tank.WIDTH/2, y + Tank.HEIGHT/2, x + Tank.WIDTH/2, y + Tank.HEIGHT);
break;
case LD:
g.drawLine(x + Tank.WIDTH/2, y + Tank.HEIGHT/2, x, y + Tank.HEIGHT);
break;
}
 
move();
}
/*
* 坦克移动
*/
void move() {
this.oldX=x;
this.oldY=y;
switch(dir) {
case L:
x -= XSPEED;
break;
case LU:
x -= XSPEED;
y -= YSPEED;
break;
case U:
y -= YSPEED;
break;
case RU:
x += XSPEED;
y -= YSPEED;
break;
case R:
x += XSPEED;
break;
case RD:
x += XSPEED;
y += YSPEED;
break;
case D:
y += YSPEED;
break;
case LD:
x -= XSPEED;
y += YSPEED;
break;
case STOP:
break;
}
if(this.dir != Direction.STOP) {
this.ptDir = this.dir;
}
if(!good){
Direction[] dirs = Direction.values();
if(step==0){
step=r.nextInt(30)+10;
int rn = r.nextInt(9);
this.dir=dirs[rn];
}
step--;
//敌人发子弹
if(r.nextInt(40)>36){
  this.fire();
}
}
if(x < 0) x = 0;
if(y < 55) y = 55;
if(x + Tank.WIDTH > TankClient.GAME_WIDTH) x = TankClient.GAME_WIDTH - Tank.WIDTH;
if(y + Tank.HEIGHT > TankClient.GAME_HEIGHT) y = TankClient.GAME_HEIGHT - Tank.HEIGHT;
}
/**
* 
*@Description:用于撞到墙、坦克时返回上一步
*/
private void stay(){
x=oldX;
y=oldY;
}
/**
* 
*@Description:按下键时监听
* @param e
*/
public void keyPressed(KeyEvent e) {
int key = e.getKeyCode();
switch(key) {
case KeyEvent.VK_R:
tc.bloods.clear();
    tc.grasses.clear();
    tc.rivers.clear();
    tc.walls.clear();
    tc.missiles.clear();
    tc.tanks.clear();
    tc.explodes.clear();
    
    //关卡、分数重置
    tc.score=0;
    tc.level=1;
    //草地
    tc.newGrass();
    //河流
    tc.newRiver();
    //墙
    tc.newWall();
    //当在区域中没有坦克时,就出来坦克      
    if (tc.tanks.size() == 0) {   
    tc.newTank();
}
tc.myTank = new Tank(220, 560, true, Direction.STOP, tc);//设置自己出现的位置
if(!tc.home.isLive()){
tc.home.setLive(true);
}
tc.dispose();
new TankClient().lauchFrame();
break;
case KeyEvent.VK_LEFT :
bL = true;
break;
case KeyEvent.VK_UP :
bU = true;
break;
case KeyEvent.VK_RIGHT :
bR = true;
break;
case KeyEvent.VK_DOWN :
bD = true;
break;
}
locateDirection();
}
/**
* 
*@Description:定位坦克的方向
*/
void locateDirection() {
if(bL && !bU && !bR && !bD) dir = Direction.L;
else if(bL && bU && !bR && !bD) dir = Direction.LU;
else if(!bL && bU && !bR && !bD) dir = Direction.U;
else if(!bL && bU && bR && !bD) dir = Direction.RU;
else if(!bL && !bU && bR && !bD) dir = Direction.R;
else if(!bL && !bU && bR && bD) dir = Direction.RD;
else if(!bL && !bU && !bR && bD) dir = Direction.D;
else if(bL && !bU && !bR && bD) dir = Direction.LD;
else if(!bL && !bU && !bR && !bD) dir = Direction.STOP;
}
    /**
     * 
     *@Description:松开键时监听
     * @param e
     */
public void keyReleased(KeyEvent e) {
int key = e.getKeyCode();
switch(key) {
case KeyEvent.VK_K:
if(this.good){
if(!this.live){
this.live=true;
this.life=100;
//复活次数加1
tc.count++;
}
}
break;
case KeyEvent.VK_J:
superFire();
break;
case KeyEvent.VK_CONTROL:
fire();
//发射炮弹音效
new Audio(2);
break;
case KeyEvent.VK_LEFT :
bL = false;
new Audio(1);
break;
case KeyEvent.VK_UP :
bU = false;
new Audio(1);
break;
case KeyEvent.VK_RIGHT :
bR = false;
new Audio(1);
break;
case KeyEvent.VK_DOWN :
bD = false;
new Audio(1);
break;
}
locateDirection();
}
/**
* 
*@Description:坦克开火
* @return 炮弹对象
*/
public Missile fire() {
if(!live)return null;
int x = this.x + Tank.WIDTH/2 - Missile.WIDTH/2;
int y = this.y + Tank.HEIGHT/2 - Missile.HEIGHT/2;
Missile m = new Missile(x, y, ptDir,this.good, this.tc);
tc.missiles.add(m);
return m;
}
/**
* 
*@Description:坦克根据方向开火
* @return 炮弹对象
*/
public Missile fire(Direction dir) {
if(!live)return null;
int x = this.x + Tank.WIDTH/2 - Missile.WIDTH/2;
int y = this.y + Tank.HEIGHT/2 - Missile.HEIGHT/2;
Missile m = new Missile(x, y, dir,this.good, this.tc);
tc.missiles.add(m);
return m;
}
/**
* 
*@Description:超级炮弹,可以向八个方向开火
*/
public void superFire(){
Direction[] dirs = Direction.values();
for(int i=0;i<8;i++){
fire(dirs[i]);
}
}
/**
* 
*@Description:判断坦克是否撞墙
* @param 墙对象
* @return 是否撞墙了
*/
public boolean CollidesWithWall(Wall w){
if(this.live&&this.getRect().intersects(w.getRect())){
this.stay();
return true;
}
return false;
}
public boolean CollidesWithWalls(List walls){
        for(int i=0;i tanks){
for(int i=0;i rivers){
for(int i=0;i

TankClient.java代码

package com.xiaoli.tank;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.Frame;
import java.awt.Graphics;
import java.awt.Image;
import java.awt.Menu;
import java.awt.MenuBar;
import java.awt.MenuItem;
import java.awt.Toolkit;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
 
 
import javax.swing.JOptionPane;
/**
 * 
 *Title:TankClient
 *Description:这个类是大管家,控制着其他类,也是程序入口 
 *@author xiaoli
 *@date2017年1月4日
 */
public class TankClient extends Frame implements ActionListener {
//游戏宽度
public static final int GAME_WIDTH = 800;
//游戏高度
public static final int GAME_HEIGHT = 600;
    //是否开启重绘线程
public static boolean printable=true;
//用于产生随机数
Random r = new Random();
//记录复活次数
public static int count=0;
//关卡
public static int level=1;
//分数
public static int score=0;
//菜单条
MenuBar jmb = null;
//菜单
Menu jm1 = null, jm2 = null, jm3 = null, jm4 = null;
//菜单项
MenuItem jmi1 = null, jmi2 = null, jmi3 = null, jmi4 = null, jmi5 = null,
jmi6 = null, jmi7 = null, jmi8 = null, jmi9 = null,jmi10=null,jmi11=null;
//从上到下从左到右画墙
Wall w1,w2,w3,w4,w5,w6,w7,w8,w9,w10,w11,w12;
//创建我的坦克
Tank myTank = new Tank(220, 560, true,Tank.Direction.STOP, this);
//创建鹰碉堡
Home home = new Home(379,556,this);
    //墙集合,用于存放墙
List walls = new ArrayList();
//河流集合,用于存放河流
List rivers = new ArrayList();
//定义一个血块
Blood b=null;
//血块集合,用于存放血块。用集合的原因是方便删除
List bloods = new ArrayList();
//草集合,用于存放草
List grasses = new ArrayList();
//炸弹集合,用于存放炸弹
List explodes = new ArrayList();
//炮弹集合,用于存放炮弹
List missiles = new ArrayList();
//坦克集合,用于存放坦克
List tanks = new ArrayList();
//用于解决双缓冲闪烁现象
Image offScreenImage = null;
/**
* 绘制场景
*/
public void paint(Graphics g) {
//分关
if (score >= 1000 * level ) {
setLevel(level + 1); // 进入下一关
new Audio(5);
grasses.clear();
    rivers.clear();
    missiles.clear();
    tanks.clear();
       explodes.clear();
    walls.clear();
    this.dispose();
new TankClient().lauchFrame();
}
//家
home.draw(g);
//只能复活一次
if(myTank.getLife()<=0&&count==1){
myTank.setLive(false);
home.gameOver(g);
}
if(r.nextInt(50)>48){
newBlood();
}
//敌人死光
if(tanks.size()<=0){
for(int i=0; i<6; i++) {
if(i<3){
   tanks.add(new Tank(50 + 40*(i+1), 50, false,Tank.Direction.D, this));
}else{
tanks.add(new Tank(50 + 40*(i+1), 50, false,Tank.Direction.R, this));
}
}
}
//g.drawString("血块数量:" + bloods.size(), 10, 50);
//g.drawString("子弹数量:" + missiles.size(), 10, 70);
//g.drawString("炸弹数量:" + explodes.size(), 10, 90);
//g.drawString("坦克数量:" + tanks.size(), 10, 110);
g.drawString("关卡:" + level, 10, 90);
g.drawString("分数:" + score, 10, 110);
g.drawString("生命值:" + myTank.getLife(), 10, 130);
g.drawString("复活次数:" + count, 10, 150);
//画墙
for(int i=0;i=3){
w1 = new Wall(210, 200, 100, 30, this);
w2 = new Wall(210, 200, 30, 190, this);
w3 = new Wall(280, 200, 30, 190, this);
w4 = new Wall(210, 370, 100, 30, this);
 
w5 = new Wall(430, 200, 90, 30, this);
w6 = new Wall(430, 285, 90, 30, this);
w7 = new Wall(430, 370, 90, 30, this);
w8 = new Wall(500, 200, 30, 200, this);
 
w10 = new Wall(379, 530, 43, 20, this);
w11 = new Wall(280, 530, 30, 70, this);
w12 = new Wall(510, 530, 30, 70, this);
walls.add(w1);
walls.add(w2);
walls.add(w3);
walls.add(w4);
walls.add(w5);
walls.add(w6);
walls.add(w7);
walls.add(w8);
 
walls.add(w10);
walls.add(w11);
walls.add(w12);
}
}
/**
* 
*@Description:生成血块
*/
public void newBlood(){
//血块
b = new Blood();
//if(b.CollidesWithWalls(walls)){//血块不能画在墙里面
//return;
//}
if(bloods.size()==0){
 bloods.add(b);
}
}
/**
* 
*@Description:生成河流
*/
public void newRiver(){
for(int i=0;i<6;i++){
River river = new River(18+i*30,340);
rivers.add(river);
river = new River(18+i*30,340+30);
rivers.add(river);
}
for(int i=0;i<6;i++){
River river = new River(568+i*30,340);
rivers.add(river);
river = new River(568+i*30,340+30);
rivers.add(river);
}
}
/**
* 
*@Description:生成草地
*/
public void newGrass() {
// 草地
for (int i = 0; i < 10; i++) {
Grass grass = new Grass((i + 1) * 18, 200);
grasses.add(grass);
grass = new Grass((i + 1) * 18, 200 + 1 * 18);
grasses.add(grass);
grass = new Grass((i + 1) * 18, 200 + 2 * 18);
grasses.add(grass);
grass = new Grass((i + 1) * 18, 200 + 3 * 18);
grasses.add(grass);
grass = new Grass((i + 1) * 18, 200 + 4 * 18);
grasses.add(grass);
grass = new Grass((i + 1) * 18, 200 + 5 * 18);
grasses.add(grass);
grass = new Grass((i + 1) * 18, 200 + 6 * 18);
grasses.add(grass);
grass = new Grass((i + 1) * 18, 200 + 7 * 18);
grasses.add(grass);
}
for (int i = 1; i <= 11; i++) {
Grass grass = new Grass(250 + 18 * 7, 182 + i * 18);
grasses.add(grass);
grass = new Grass(250 + 18 * 8, 182 + i * 18);
grasses.add(grass);
grass = new Grass(250 + 18 * 5, 182 + i * 18);
grasses.add(grass);
grass = new Grass(250 + 18 * 6, 182 + i * 18);
grasses.add(grass);
}
for(int i=0;i<10;i++){
Grass grass = new Grass(550+(i + 1) * 18, 200);
grasses.add(grass);
grass = new Grass(550+(i + 1) * 18, 200 + 1 * 18);
grasses.add(grass);
grass = new Grass(550+(i + 1) * 18, 200 + 2 * 18);
grasses.add(grass);
grass = new Grass(550+(i + 1) * 18, 200 + 3 * 18);
grasses.add(grass);
grass = new Grass(550+(i + 1) * 18, 200 + 4 * 18);
grasses.add(grass);
grass = new Grass(550+(i + 1) * 18, 200 + 5 * 18);
grasses.add(grass);
grass = new Grass(550+(i + 1) * 18, 200 + 6 * 18);
grasses.add(grass);
grass = new Grass(550+(i + 1) * 18, 200 + 7 * 18);
grasses.add(grass);
}
}
/**
* 
*@Description:生成坦克
*/
public void newTank(){
for (int i = 0; i < 10; i++) {
if (i < 5) {
tanks.add(new Tank(50 + 40 * (i + 1), 55, false,
Tank.Direction.D, this));
} else {
tanks.add(new Tank(50 + 40 * (i + 1), 55, false,
Tank.Direction.R, this));
}
}
}
/**
* 
*@Description:主函数、程序入口
* @param args
*/
public static void main(String[] args) {
TankClient tc = new TankClient();
tc.lauchFrame();
 
}
/**
* 
*Title:PaintThread
*Description:重绘线程,模拟动态效果 
*@author xiaoli
*@date2017年1月4日
*/
private class PaintThread implements Runnable {
 
 
public void run() {
while(printable) {
repaint();
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
/**
* 
*@Description:按键监听,对坦克、炮弹等进行人机交互
* @param e
*/
private class KeyMonitor extends KeyAdapter {
public void keyReleased(KeyEvent e) {
myTank.keyReleased(e);
}
 
 
public void keyPressed(KeyEvent e) {
myTank.keyPressed(e);
}
 
}
    /**
     * 对菜单项操作
     */
@Override
public void actionPerformed(ActionEvent e) {
// TODO Auto-generated method stub
if (e.getActionCommand().equals("NewGame")) {
printable = false;
Object[] options = { "确定", "取消" };
int response = JOptionPane.showOptionDialog(this, "您确认要重新开始!", "",
JOptionPane.YES_OPTION, JOptionPane.QUESTION_MESSAGE, null,
options, options[0]);
if (response == 0) {
                this.score=0;
                this.setLevel(1);
printable = true;
this.dispose();
new TankClient().lauchFrame();
} else {
printable = true;
new Thread(new PaintThread()).start(); // 线程启动
}
 
 
} else if (e.getActionCommand().endsWith("Stop")) {
printable = false;
// try {
// Thread.sleep(10000);
//
// } catch (InterruptedException e1) {
// // TODO Auto-generated catch block
// e1.printStackTrace();
// }
} else if (e.getActionCommand().equals("Continue")) {
 
 
if (!printable) {
printable = true;
new Thread(new PaintThread()).start(); // 线程启动
}
// System.out.println("继续");
} else if (e.getActionCommand().equals("Exit")) {
printable = false;
Object[] options = { "确定", "取消" };
int response = JOptionPane.showOptionDialog(this, "您确认要退出吗", "",
JOptionPane.YES_OPTION, JOptionPane.QUESTION_MESSAGE, null,
options, options[0]);
if (response == 0) {
System.out.println("退出");
System.exit(0);
} else {
printable = true;
new Thread(new PaintThread()).start(); // 线程启动
 
 
}
 
 
} else if (e.getActionCommand().equals("help1")) {
printable = false;
JOptionPane.showMessageDialog(null, "随着人们精神文化生活的日益丰富,游戏成为了人们生活中必不可少的一部分\n90《坦克大战》更是90后一代人童年的回忆,也是一款经典游戏。\n"
+ "开发java版坦克大战有利用更深入理解java面向对象编程、swing界面编程、多线程编程"+"\n"+"参考:马士兵坦克大战视频教程、以及互联网资源"+"\n"+"作者邮箱:[email protected]"+"\n"
+"若有关内容侵犯了您的权益,请及时联系作者删除,谢谢!",
"提示!", JOptionPane.INFORMATION_MESSAGE);
this.setVisible(true);
printable = true;
new Thread(new PaintThread()).start(); // 线程启动
} else if(e.getActionCommand().equals("help2")){
printable = false;
JOptionPane.showMessageDialog(null, "用→ ← ↑ ↓控制方向,CTRL键盘发射,J超级炮弹,K复活(只能复活一次),R重新开始!\n"+"作者邮箱:[email protected]"+"\n"
+"若有关内容侵犯了您的权益,请及时联系作者删除,谢谢!",
"提示!", JOptionPane.INFORMATION_MESSAGE);
this.setVisible(true);
printable = true;
new Thread(new PaintThread()).start(); // 线程启动
}
 
else if (e.getActionCommand().equals("level1")) {
//Tank.count = 12;
Tank.XSPEED = 5;
Tank.YSPEED = 5;
Missile.XSPEED = 10;
Missile.YSPEED = 10;
this.dispose();
new TankClient().lauchFrame();;
} else if (e.getActionCommand().equals("level2")) {
//Tank.count = 12;
Tank.XSPEED = 8;
Tank.YSPEED = 8;
Missile.XSPEED = 14;
Missile.YSPEED = 14;
this.dispose();
new TankClient().lauchFrame();;
 
 
} else if (e.getActionCommand().equals("level3")) {
//Tank.count = 20;
Tank.XSPEED = 12;
Tank.YSPEED = 12;
Missile.XSPEED = 18;
Missile.YSPEED = 18;
this.dispose();
new TankClient().lauchFrame();;
} else if (e.getActionCommand().equals("level4")) {
//Tank.count = 20;
Tank.XSPEED = 16;
Tank.YSPEED = 16;
Missile.XSPEED = 24;
Missile.YSPEED = 14;
this.dispose();
new TankClient().lauchFrame();;
}else if(e.getActionCommand().equals("startMain")){
printable = false;
JOptionPane.showMessageDialog(null, "开启后不能关闭!请不要重复点击!",
"提示!", JOptionPane.INFORMATION_MESSAGE);
this.setVisible(true);
printable = true;
// 线程启动
new Thread(new PaintThread()).start(); 
new Audio(0);
}
 }
/*
 * 设置关卡
 */
public void setLevel(int level) { //设置三关
if (level > 10) {
level = 10;
}
this.level = level;
}
}

 四、运行截图

用Java实现坦克大战项目及源代码下载,同时此项目贯穿了23种设计模式_第2张图片

 

用Java实现坦克大战项目及源代码下载,同时此项目贯穿了23种设计模式_第3张图片

 

 

用Java实现坦克大战项目及源代码下载,同时此项目贯穿了23种设计模式_第4张图片

 

用Java实现坦克大战项目及源代码下载,同时此项目贯穿了23种设计模式_第5张图片

 

用Java实现坦克大战项目及源代码下载,同时此项目贯穿了23种设计模式_第6张图片

 

五、总结

源代码参考了马老师的java坦克大战视频教程以及众多互联网资源,这次练手有利用深入理解java面向对象编程、swing界面编程、多线程编程;

最后

下面是马士兵老师的23种设计模式与坦克大战完整版视频,现在免费分享给小伙伴们,需要的关注我后私信“666”免费获取

23种设计模式:

用Java实现坦克大战项目及源代码下载,同时此项目贯穿了23种设计模式_第7张图片

 

坦克大战视频:

用Java实现坦克大战项目及源代码下载,同时此项目贯穿了23种设计模式_第8张图片

你可能感兴趣的:(游戏,java,多线程,webgl,游戏开发)