花了一整天看了马士兵老师讲的坦克大战单机版的视频,作为一名java初学者来说,收获颇多,
尤其是感受到面向对象的方法,特此总结一下,以备后用。
(最后会附整体代码,及详细注释)说明一下,此小游戏只是熟悉java基础只是而已,谈不上项目...大牛忽视之即可~
截止此时已经实现的功能是:画出了一辆坦克,可以八个方向灵活移动,按CTRL建可以发射炮弹 。
《第一阶段》做出界面:练习GUI基础知识
该阶段代码
1import java.awt.*;
2import java.awt.event.*;
3
4public class TankClient extends Frame {
5
6 public void lauchFrame() {
7 this.setLocation(400, 300);//设定初始位置
8 this.setSize(800, 600);//设定大小
9 this.setTitle("TankWar");//设定标题
10 this.addWindowListener(new WindowAdapter() {
11 public void windowClosing(WindowEvent e) {
12 System.exit(0);
13 }
14 });//匿名类实现窗口关闭
15 this.setResizable(false);//使窗口固定
16 setVisible(true);//将窗口显示出来
17 }
18
19 public static void main(String[] args) {
20 TankClient tc = new TankClient();
21 tc.lauchFrame();//启动窗口
22 }
23
24}
《第二阶段》画出一辆坦克,并实现四个方向的移动:此阶段写完后实现了,坦克根据键盘控制进行四个方向的移动,但是运动不灵活。
(1)重写paint方法,画出代表坦克的实心圆。paint()方法在每次窗口被激活会自动调用,repaint()方法也会自动调用paint()方法。
此部代码
1public void paint(Graphics g) {//g相当于一个画笔
2 Color c = g.getColor();//拿到当前颜色,以便恢复
3 g.setColor(Color.RED);
4 g.fillOval(x, y, 30, 30);//画一个圆
5 g.setColor(c);//恢复为初始颜色
6 }
(2)让坦克运动起来。其实初想感觉很简单,按一下方向键,改变一下x,y的值,如x+=5,y+=5,然后再重画一下,不就可以实现坦克的移动了吗?但是如果按一次键,才重新画一下的话,动画会很不均匀。解决方法是加一个线程,这个线程只做一件事情,就是每隔35ms调用repaint()方法重新刷新一下屏幕。x,y的值如果没改,则坦克没动,如果按下键盘,那么x,y值立刻变为x',y'。在改变的<35ms的时间内,坦克必定会运动。而且用线程控制屏幕刷新,也可方便之后子弹运动的实现。
新写的线程类PaintThread,事件处理类KeyMonitor,都设定为内部类,原因:可以方便的访问包装类的方法不方便公开的,只为包装类服务的类应当定义为内部类。
其中的双缓冲处理机制,将所有东西画在虚拟图片上,一次性显示出来,其它不多做解释了,毕竟此案例目的是熟悉java基础知识 !
按键事件处理代码:
//TankClient类中的内部类
private class KeyMonitor extends KeyAdapter {
public void keyPressed(KeyEvent e) {
int key = e.getKeyCode();
switch(key) {
case KeyEvent.VK_LEFT :
x -= 5;
break;
case KeyEvent.VK_UP :
y -= 5;
break;
case KeyEvent.VK_RIGHT :
x += 5;
break;
case KeyEvent.VK_DOWN :
y += 5;
break;
}
}
}
/*********************************************/
//勿忘加上监听器!!!
public void lauchFrame() {
。。。。。。 。。。。。。
this.addKeyListener(new KeyMonitor());
setVisible(true);
new Thread(new PaintThread()).start();
}
线程类代码
每隔50ms刷新一次,勿忘在launchFrame()函数中启动线程,见上段代码。
1private class PaintThread implements Runnable {
2
3 public void run() {
4 while(true) {
5 repaint();
6 try {
7 Thread.sleep(50);
8 } catch (InterruptedException e) {
9 e.printStackTrace();
10 }
11 }
12 }
13 }
此阶段整体代码
/****************************** TankClient.java ***********************************/
import java.awt.*;
import java.awt.event.*;
public class TankClient extends Frame {
//常量定义
public static final int GAME_WIDTH = 800;
public static final int GAME_HEIGHT = 600;
//成员变量
int x = 50, y = 50;
public void paint(Graphics g) {
Color c = g.getColor();
g.setColor(Color.RED);
g.fillOval(x, y, 30, 30);
g.setColor(c);
}
//通过双缓冲取消屏闪问题,从写update()方法
Image offScreenImage = null;
public void update(Graphics g) {
if(offScreenImage == null) {
offScreenImage = this.createImage(GAME_WIDTH, GAME_HEIGHT);
}
Graphics gOffScreen = offScreenImage.getGraphics();
Color c = gOffScreen.getColor();
gOffScreen.setColor(Color.GREEN);
gOffScreen.fillRect(0, 0, GAME_WIDTH, GAME_HEIGHT);
gOffScreen.setColor(c);
paint(gOffScreen);
g.drawImage(offScreenImage, 0, 0, null);
}
public void lauchFrame() {
this.setLocation(400, 300);
this.setSize(GAME_WIDTH, GAME_HEIGHT);
this.setTitle("TankWar");
this.addWindowListener(new WindowAdapter() {
public void windowClosing(WindowEvent e) {
System.exit(0);
}
});
this.setResizable(false);
this.setBackground(Color.GREEN);
//加监听器
this.addKeyListener(new KeyMonitor());
setVisible(true);
//启动线程
new Thread(new PaintThread()).start();
}
public static void main(String[] args) {
TankClient tc = new TankClient();
tc.lauchFrame();
}
//线程控制刷新,每隔50ms刷新一下屏幕
private class PaintThread implements Runnable {
public void run() {
while(true) {
repaint();
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
//键盘事件处理类,继承KeyAdapter类比实现KeyListener接口要方便些
private class KeyMonitor extends KeyAdapter {
public void keyPressed(KeyEvent e) {
int key = e.getKeyCode();
switch(key) {
case KeyEvent.VK_LEFT :
x -= 5;
break;
case KeyEvent.VK_UP :
y -= 5;
break;
case KeyEvent.VK_RIGHT :
x += 5;
break;
case KeyEvent.VK_DOWN :
y += 5;
break;
}
}
}
}
《第三阶段》
到关键时刻了,体会面向对象的思想,创建tank类,关于tank的事情,让tank自己去做,TankClient类只作为一个大管家,实现各个类,对象的信息沟通和管理。
Tank类代码:
其中该类中,实现了坦克运动的流畅走向,并不像过去,按一下键盘,改变一下x,y的坐标,而是,按一下键盘,仅改变方向,move()方法通过方向再改变
x,y的坐标。此机制也实现了,同时按下右和上可以向东北方向运动。
具体看代码备注中标注的四步<1>.键盘事件处理____void keyPressed(KeyEvent e) void keyReleased(KeyEvent e)
<2>.根据键盘事件中拿到的方向记录信息,判定dir的值____void locateDirection()
<3>根据判定出的dir值来改变x,y坐标____void move()
<4>实现动画public void draw(Graphics g)
/***********************************Tank.java*******************************/
import java.awt.*;
import java.awt.event.*;
public class Tank {
//常量声明
public static final int WIDTH = 30,HEIGHT = 30;
public static final int XSPEED = 10,YSPEED = 10;
//成员变量
TankClient tc;
private int x, y;
private boolean bL=false, bU=false, bR=false, bD = false;
private Direction dir = Direction.STOP;//坦克方向,决定坦克走向
//定义枚举类型,成员变量 dir 的类型,而且在switch语句中表达更加形象
enum Direction {L, LU, U, RU, R, RD, D, LD, STOP};
//构造方法一(基本)
public Tank(int x, int y) {
this.x = x;
this.y = y;
}
//保证坦克流畅运动的四步,实际上就没让小车停
//键盘的控制只是改变方向,把stop也理解为一种方向
//<1>.键盘事件处理
//键盘按下,则bool类型的方向记录信息设为true
public void keyPressed(KeyEvent e) {
int key = e.getKeyCode();
switch(key) {
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();
}
//键盘松开时,bool类型的方向记录信息立刻设为false
public void keyReleased(KeyEvent e) {
int key = e.getKeyCode();
switch(key) {
case KeyEvent.VK_LEFT :
bL = false;
break;
case KeyEvent.VK_UP :
bU = false;
break;
case KeyEvent.VK_RIGHT :
bR = false;
break;
case KeyEvent.VK_DOWN :
bD = false;
break;
}
locateDirection();
}
//<2>.根据键盘事件中拿到的方向记录信息,判定dir的值
void locateDirection() {
if(bL && !bU && !bR && !bD) dir = ptdir = Direction.L;
else if(bL && bU && !bR && !bD) dir = ptdir = Direction.LU;
else if(!bL && bU && !bR && !bD) dir = ptdir = Direction.U;
else if(!bL && bU && bR && !bD) dir = ptdir = Direction.RU;
else if(!bL && !bU && bR && bD) dir = ptdir = Direction.RD;
else if(!bL && !bU && !bR && bD) dir = ptdir = Direction.D;
else if(bL && !bU && !bR && bD) dir = ptdir = Direction.LD;
else if(!bL && !bU && bR && !bD) dir = ptdir = Direction.R;
else if(!bL && !bU && !bR && !bD) dir = Direction.STOP;
}
//<3>根据判定出的dir值来改变x,y坐标
void move() {
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(x<7)x=7;
if(y<30)y=30;
if(x>TankClient.GAME_WIDTH-Tank.WIDTH-7)x=TankClient.GAME_WIDTH-Tank.WIDTH-7;
if(y>TankClient.GAME_HEIGHT-Tank.HEIGHT-10)y=TankClient.GAME_HEIGHT-Tank.HEIGHT-10;
}
//<4>实现动画
public void draw(Graphics g) {
Color c = g.getColor();
g.setColor(Color.RED);
g.fillOval(x, y, WIDTH, HEIGHT);
g.setColor(c);
//每刷一次屏(35ms)move一次,所以方向中要有stop方向
move();
}
}
TankClient 类得重构,大管家的身份渐渐明显 !
import java.awt.*;
import java.util.List;
import java.util.ArrayList;
import java.util.*;
import java.awt.event.*;
public class TankClient extends Frame {
//常量声明
public static final int WIDTH = 10,HEIGHT = 10;
public static final int GAME_WIDTH=800,GAME_HEIGHT =600;
/****************************************************************************/
//对象建立
Tank myTank = new Tank(50,50);
//启动框架frame
public void launchFrame(){
this.setSize(GAME_WIDTH,GAME_HEIGHT);
this.setLocation(100,100);
this.setBackground(Color.BLACK);
this.setVisible(true);
new Thread(new PaintThread()).start();//启动线程
this.addKeyListener(new KeyMonitor());//添加事件监听器
this.addWindowListener(new WindowAdapter(){//窗口关闭方法
public void windowClosing(WindowEvent e){
System.exit(0);
}
});
}
/****************************************************************************/
//把画笔g传递给对象自己去画!
public void paint(Graphics g){
Color c = g.getColor();
g.setColor(Color.WHITE);
g.setColor(c);
myTank.draw(g);//把主画笔交个坦克自己去画
}
/****************************************************************************/
//双缓冲机制,取消屏闪
Image offScreenImage = null;
public void update(Graphics g) {
if(offScreenImage == null) {
offScreenImage = this.createImage(GAME_WIDTH, GAME_HEIGHT);
}
Graphics gOffScreen = offScreenImage.getGraphics();
Color c = gOffScreen.getColor();
gOffScreen.setColor(Color.BLACK);
gOffScreen.fillRect(0, 0, GAME_WIDTH, GAME_HEIGHT);
gOffScreen.setColor(c);
paint(gOffScreen);
g.drawImage(offScreenImage, 0, 0, null);
}
//主函数,构造整体框架frame,并启动launchFrame函数
public static void main(String[] args) {
TankClient client = new TankClient();
client.launchFrame();
}
//线程控制,每35ms刷新一下屏幕,即调用repaint()方法
//repaint自动调用paint()方法,即画出此时的x,y,即可实现动画效果
private class PaintThread implements Runnable{
public void run() {
while(true){
repaint();
try {
Thread.sleep(30);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
/******************************************************************/
//键盘事件的处理,将事件e传递给tank类,让其自己处理自己的事情
private class KeyMonitor extends KeyAdapter{
@Override
public void keyReleased(KeyEvent e) {
myTank.keyReleased(e);//把事件对象e传递给tank自己去处理!!!
}
public void keyPressed(KeyEvent e){
myTank.keyPressed(e);
}
}
/*******************************************************************/
}