《Java小遊戲實作》:坦克大戰(續2)
相關博文:
《Java小遊戲實作》:坦克大戰http://blog.csdn.net/u010412719/article/details/51712663
《Java小遊戲實作》:坦克大戰(續一):http://blog.csdn.net/u010412719/article/details/51723570
博文《Java小遊戲實作》:坦克大戰(續1)中已經實作到了坦克可以發射一顆子彈了。這篇博文在此基礎上繼續實作更多的功能。
完成功能:坦克發射多顆子彈
有了坦克發射一顆子彈的基礎,發射多顆子彈就相當簡單的,隻需要在TankClien類中加入一個容器來存放多枚子彈即可。由于容器的容量也是有限的,我們不能一直往裡面裝。是以,在子彈類中,我們為子彈對象引入了一個live屬性,用來判斷這個子彈是否還存活。判斷子彈是否還存活是根據子彈的位置是否出界。
下面隻貼出完成這個功能相關代碼
TankClient.java
private List<Missile> missiles = new ArrayList<Missile> ();
public List<Missile> getMissiles() {
return missiles;
}
@Override
public void paint(Graphics g) {
//直接調用坦克類的draw方法
tk.draw(g);
//畫子彈
for(int i=;i<missiles.size();i++){
Missile ms = missiles.get(i);
//判斷子彈是否還存活在,如果不是存活的,則移除
if(!ms.isLive()){
missiles.remove(ms);
}
else{
ms.draw(g);
}
}
}
Missile.java類
在move方法中根據子彈所在的位置判斷子彈是否還存活。
public class Missile {
private void move() {
if(dir==Direction.L){//L,LU,U,RU,R,RD,D,LD,STOP
x -= XSPEED;
}
else if(dir==Direction.LU){
x -= XSPEED;
y -= YSPEED;
}
else if(dir==Direction.U){
y -= YSPEED;
}
else if(dir==Direction.RU){
x += XSPEED;
y -= YSPEED;
}
else if(dir==Direction.R){
x += XSPEED;
}
else if(dir==Direction.RD){
x += XSPEED;
y += YSPEED;
}
else if(dir==Direction.D){
y += YSPEED;
}
else if(dir==Direction.LD){
x -= XSPEED;
y += YSPEED;
}
//根據子彈所在的位置x,y來判斷子彈是否還存活在
if(x<||x>TankClient.GAME_WIDTH||y<||y>TankClient.GAME_HEIGHT){
live = false;
}
}
public boolean isLive() {
return live;
}
}
Tank.java檔案内容如下:
在坦克類中,在keyPressed方法中,每按一次Ctrl鍵,就發射一顆子彈。
public class Tank {
//記錄鍵盤的按鍵情況
public void keyPressed(KeyEvent e){
int key=e.getKeyCode();
//System.out.println(key);
switch(key){
case :
tc.getMissiles().add(fire());
break;
case KeyEvent.VK_LEFT:
b_L=true;
break;
case KeyEvent.VK_UP:
b_U=true;
break;
case KeyEvent.VK_RIGHT:
b_R=true;
break;
case KeyEvent.VK_DOWN:
b_D=true;
break;
}
//根據上面的按鍵情況,确定坦克即将要運作的方向
moveDirection();
}
}
完整代碼下載下傳連結:http://download.csdn.net/detail/u010412719/9555292
完成功能:解決坦克越界問題
坦克越界問題比較簡單,隻需要判斷坦克所在的位置是否越界,如果越界,則将其位置設定為邊界位置即可。
實作代碼如下:
/*
* 函數功能:處理坦克越界問題
* */
private void dealTankBorder() {
if(x<){
x = ;
}
else if(x > TankClient.GAME_WIDTH-this.WIDTH){
x = TankClient.GAME_WIDTH-this.WIDTH ;
}
if(y<){
y = ;
}
else if(y>TankClient.GAME_WIDTH - this.HEIGHT){
y = TankClient.GAME_WIDTH - this.HEIGHT;
}
}
上面函數在move()方法最後進行調用即可。
完成功能:加入敵人的坦克
前面實作的所用功能是自己一個坦克在界面上面運動呀、發子彈呀等等。
下面我們完成在界面上加入一個敵人坦克。
實作敵人坦克有兩種方式
1、第一種是再建立一個EnemyTank類,
2、第二種是在原來的Tank類中加入一個屬性來表示此坦克的類型。
由于我們有一個坦克類了,為友善起見,這裡采用第二種方式:在原來的Tank類中加入一個屬性來表示此坦克的類型。
Tank.java中新增加的内容主要有
//添加一個屬性,表示此坦克是好還是壞
private boolean good;
//更改下構造方法
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, TankClient tc) {
this(x,y,good);
this.tc = tc;
}
//根據坦克的類型給出不同的顔色
public void draw(Graphics g){
Color c = g.getColor();
if(good){
g.setColor(Color.RED);
}
else{
g.setColor(Color.BLUE);
}
g.fillOval(x, y, WIDTH, HEIGHT);
g.setColor(c);
//畫一個炮筒
drawGunBarrel(g);
move();//根據鍵盤按鍵的結果改變坦克所在的位置
}
Missile類沒有任何變動。
總管家TankClient.java中需要添加的内容有:
1、new 出兩個坦克對象
private Tank tk=new Tank(,,true,this);
private Tank enemy = new Tank(,,false,this);
2、在paint方法中畫出兩個坦克
@Override
public void paint(Graphics g) {
//直接調用坦克類的draw方法
tk.draw(g);
enemy.draw(g);
//畫子彈
for(int i=;i<missiles.size();i++){
Missile ms = missiles.get(i);
//判斷子彈是否還存活在,如果不是存活的,則移除
if(!ms.isLive()){
missiles.remove(ms);
}
else{
ms.draw(g);
}
}
}
以上就實作了添加敵對坦克這一功能,但是還沒有對其添加随機運動。
完成的功能:子彈打死敵人坦克
經過上面的實作,我們有了一個不會動且不會發子彈的傻傻的敵對坦克,但是我們也不能打死它,下面我們就來實作打死坦克。哈哈,是不是稍微變得有趣一點了。
分析:
1、由于是子彈打死敵人坦克,根據面向對象的思想,在子彈類中,加入一個hitTank方法。
public boolean hitTank(Tank t){
}
2、那麼hitTank這個方法應該如何來判斷是否子彈打中了該坦克呢??這就涉及到一個碰撞的問題。由于我們的子彈和坦克都是矩形,是以碰撞問題就得到了很好的簡化,隻是判斷兩個矩形是否有重疊的部分。如果有,則就判斷發生了碰撞,子彈就打中了坦克。
是以,在Missile類和Tank類中,,均添加一個getRect()方法,傳回子彈和坦克所在的矩形區域對象。
public Rectangle getRect(){
return new Rectangle(x, y, WIDTH, HEIGHT);
}
3、當子彈打中了坦克,則子彈和坦克就應該都消失。是以,在坦克中需要引入一個布爾變量來判斷坦克是否存活
public boolean hitTank(Tank t){
//首先判斷此坦克是否是存活的,如果是死的,就不打了
if(!t.isLive()){
return false;
}
if(this.getRect().intersects(t.getRect())){//判斷是否有碰撞
//碰撞之後,子彈和該坦克就應該都死了
this.live = false;//子彈死了
t.setLive(false);//坦克死了
return true;
}
else{
return false;
}
}
無論是子彈消失,在前面的版本中有處理,但是本版本上的坦克消失,目前的處理方法就是不繪畫出來,即在draw方法中,首先判斷一下,看此對象是否存活,如果存活,則繪畫出來。
代碼如下:
public void draw(Graphics g){
if(!live){ //判斷坦克是否存活,如果死了,則不繪畫出來,直接傳回
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);
//畫一個炮筒
drawGunBarrel(g);
move();//根據鍵盤按鍵的結果改變坦克所在的位置
}
最後,貼下Tank類、Missile類、TankClient類的完整代碼:
Tank類
public class Tank {
//坦克所在的位置坐标
private int x;
private int y;
//坦克的高度和寬度
private static final int WIDTH = ;
private static final int HEIGHT = ;
//定義兩個常量,表示運動的速度
private static final int XSPEED = ;
private static final int YSPEED = ;
//定義四個布爾類型變量來記錄按鍵的情況,預設狀态下為false,表示沒有鍵按下
private boolean b_L,b_U,b_R,b_D;
//添加一個屬性,表示此坦克是好還是壞
private boolean good;
public boolean isGood() {
return good;
}
//用來辨別此坦克對象是否存活
private boolean live =true;
public boolean isLive() {
return live;
}
public void setLive(boolean live) {
this.live = live;
}
//定義一個枚舉類型來表示運作的方向
public enum Direction{
L,LU,U,RU,R,RD,D,LD,STOP
}
//定義一個變量來表示坦克要運作的方向,初始狀态為STOP
private Direction dir = Direction.STOP;
//炮筒方向
private Direction ptDir = Direction.D;
private TankClient tc;
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, TankClient tc) {
this(x,y,good);
this.tc = tc;
}
public void draw(Graphics g){
if(!live){ //判斷坦克是否存活,如果死了,則不繪畫出來,直接傳回
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);
//畫一個炮筒
drawGunBarrel(g);
move();//根據鍵盤按鍵的結果改變坦克所在的位置
}
private void drawGunBarrel(Graphics g) {
int centerX = this.x + this.WIDTH/;
int centerY = this.y + this.HEIGHT/;
if(ptDir==Direction.L){//L,LU,U,RU,R,RD,D,LD,STOP
g.drawLine(centerX, centerY, x, y + HEIGHT/);
}
else if(ptDir==Direction.LU){
g.drawLine(centerX, centerY, x, y );
}
else if(ptDir==Direction.U){
g.drawLine(centerX, centerY, x+ WIDTH/, y );
}
else if(ptDir==Direction.RU){
g.drawLine(centerX, centerY, x + WIDTH, y );
}
else if(ptDir==Direction.R){
g.drawLine(centerX, centerY, x+ WIDTH, y + HEIGHT/);
}
else if(ptDir==Direction.RD){
g.drawLine(centerX, centerY, x+ WIDTH, y + HEIGHT);
}
else if(ptDir==Direction.D){
g.drawLine(centerX, centerY, x+ WIDTH/, y + HEIGHT);
}
else if(ptDir==Direction.LD){
g.drawLine(centerX, centerY, x, y + HEIGHT);
}
}
//記錄鍵盤的按鍵情況
public void keyPressed(KeyEvent e){
int key=e.getKeyCode();
//System.out.println(key);
switch(key){
// case 17://避免因Ctrl一直按下,一直發射子彈,是以将這一功能放入keyReleased方法中了
// tc.getMissiles().add(fire());
// break;
case KeyEvent.VK_LEFT:
b_L=true;
break;
case KeyEvent.VK_UP:
b_U=true;
break;
case KeyEvent.VK_RIGHT:
b_R=true;
break;
case KeyEvent.VK_DOWN:
b_D=true;
break;
}
//根據上面的按鍵情況,确定坦克即将要運作的方向
moveDirection();
}
//鍵盤按鍵松下時,也要進行記錄
public void keyReleased(KeyEvent e) {
int key=e.getKeyCode();
switch(key){
case :
tc.getMissiles().add(fire());
break;
case KeyEvent.VK_LEFT:
b_L=false;
break;
case KeyEvent.VK_UP:
b_U=false;
break;
case KeyEvent.VK_RIGHT:
b_R=false;
break;
case KeyEvent.VK_DOWN:
b_D=false;
break;
}
}
//根據鍵盤的按鍵情況來确定坦克的運作方向
private void moveDirection() {//L,LU,U,RU,R,RD,D,LD,STOP
if(b_L&&!b_U&&!b_R&&!b_D){
dir = Direction.L;
}
else if(b_L&&b_U&&!b_R&&!b_D){
dir = Direction.LU;
}
else if(!b_L&&b_U&&!b_R&&!b_D){
dir = Direction.U;
}
else if(!b_L&&b_U&&b_R&&!b_D){
dir = Direction.RU;
}
else if(!b_L&&!b_U&&b_R&&!b_D){
dir = Direction.R;
}
else if(!b_L&&!b_U&&b_R&&b_D){
dir = Direction.RD;
}
else if(!b_L&&!b_U&&!b_R&&b_D){
dir = Direction.D;
}
else if(b_L&&!b_U&&!b_R&&b_D){
dir = Direction.LD;
}
else{//其它所有情況,都是不動
dir = Direction.STOP;
}
//将坦克方向指派給炮筒方向
if(dir!=Direction.STOP){
ptDir = dir;
}
}
//上面有運作方向,但是還缺少具體的運作細節,例如:假設是按下了右鍵,則應該橫坐标x+=XSPEED;
private void move(){
if(dir==Direction.L){//L,LU,U,RU,R,RD,D,LD,STOP
x -= XSPEED;
}
else if(dir==Direction.LU){
x -= XSPEED;
y -= YSPEED;
}
else if(dir==Direction.U){
y -= YSPEED;
}
else if(dir==Direction.RU){
x += XSPEED;
y -= YSPEED;
}
else if(dir==Direction.R){
x += XSPEED;
}
else if(dir==Direction.RD){
x += XSPEED;
y += YSPEED;
}
else if(dir==Direction.D){
y += YSPEED;
}
else if(dir==Direction.LD){
x -= XSPEED;
y += YSPEED;
}
else if(dir==Direction.STOP){
//... nothing
}
//處理坦克越界問題
dealTankBorder();
}
/*
* 函數功能:處理坦克越界問題
* */
private void dealTankBorder() {
if(x<){
x = ;
}
else if(x > TankClient.GAME_WIDTH-this.WIDTH){
x = TankClient.GAME_WIDTH-this.WIDTH ;
}
if(y<){
y = ;
}
else if(y>TankClient.GAME_WIDTH - this.HEIGHT){
y = TankClient.GAME_WIDTH - this.HEIGHT;
}
}
public Missile fire(){
//計算子彈的位置,并利用炮筒的方向來new一個子彈對象
int x = this.x +(this.WIDTH)/ - (Missile.WIDTH)/;
int y = this.y + (this.HEIGHT)/ -(Missile.HEIGHT)/;
Missile ms = new Missile(x,y,this.ptDir);
return ms;
}
/*
* 函數功能:得到坦克所在位置的矩形框
* */
public Rectangle getRect(){
return new Rectangle(x, y, WIDTH, HEIGHT);
}
}
Missile類
代碼如下
public class Missile {
//定義兩個常量,表示運動的速度
private static final int XSPEED = ;
private static final int YSPEED = ;
//子彈所在的位置
private int x;
private int y;
//坦克的高度和寬度
public static final int WIDTH = ;
public static final int HEIGHT = ;
//子彈的運作方向
private Direction dir;
private boolean live = true;
public Missile(int x, int y, Direction dir) {
this.x = x;
this.y = y;
this.dir = dir;
}
public void draw(Graphics g){
//如果該子彈不是存活的,則不進行繪圖
if(!live){
return ;
}
Color c = g.getColor();
g.setColor(Color.YELLOW);
g.fillOval(x, y, WIDTH, HEIGHT);
g.setColor(c);
move();
}
private void move() {
if(dir==Direction.L){//L,LU,U,RU,R,RD,D,LD,STOP
x -= XSPEED;
}
else if(dir==Direction.LU){
x -= XSPEED;
y -= YSPEED;
}
else if(dir==Direction.U){
y -= YSPEED;
}
else if(dir==Direction.RU){
x += XSPEED;
y -= YSPEED;
}
else if(dir==Direction.R){
x += XSPEED;
}
else if(dir==Direction.RD){
x += XSPEED;
y += YSPEED;
}
else if(dir==Direction.D){
y += YSPEED;
}
else if(dir==Direction.LD){
x -= XSPEED;
y += YSPEED;
}
//根據子彈所在的位置x,y來判斷子彈是否還存活在
if(x<||x>TankClient.GAME_WIDTH||y<||y>TankClient.GAME_HEIGHT){
live = false;
}
}
public boolean isLive() {
return live;
}
public Rectangle getRect(){
return new Rectangle(x, y, WIDTH, HEIGHT);
}
public boolean hitTank(Tank t){
//首先判斷此坦克是否是存活的,如果是死的,就不打了
if(!t.isLive()){
return false;
}
if(this.getRect().intersects(t.getRect())){//判斷是否有碰撞
//碰撞之後,子彈和該坦克就應該都死了
this.live = false;//子彈死了
t.setLive(false);//坦克死了
return true;
}
else{
return false;
}
}
TankClient類代碼如下:
/*
* 此版本添加了子彈打死敵對坦克
* */
public class TankClient extends Frame{
public final static int GAME_WIDTH=;
public final static int GAME_HEIGHT=;
private Tank tk=new Tank(,,true,this);
private Tank enemy = new Tank(,,false,this);
private List<Missile> missiles = new ArrayList<Missile> ();
public List<Missile> getMissiles() {
return missiles;
}
private Image offScreenImage = null;
public static void main(String[] args) {
new TankClient().launchFrame();
}
@Override
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.darkGray);
goffScreen.fillRect(, , GAME_WIDTH, GAME_HEIGHT);
goffScreen.setColor(c);
paint(goffScreen);
g.drawImage(offScreenImage, , , null);
}
@Override
public void paint(Graphics g) {
//直接調用坦克類的draw方法
tk.draw(g);
enemy.draw(g);
//畫子彈
for(int i=;i<missiles.size();i++){
Missile ms = missiles.get(i);
//判斷子彈是否還存活在,如果不是存活的,則移除
if(!ms.isLive()){
missiles.remove(ms);
}
else{
ms.hitTank(enemy);
ms.draw(g);
}
}
}
public void launchFrame(){
this.setTitle("坦克大戰");
this.setLocation(, );
this.setSize(GAME_WIDTH, GAME_HEIGHT);
this.setBackground(Color.GRAY);
//為關閉視窗添加響應
this.addWindowListener(new WindowAdapter(){
@Override
public void windowClosing(WindowEvent e) {
System.exit();
}
});
//設定是否允許使用者改變視窗的大小,這裡限制下,不允許
this.setResizable(false);
this.setVisible(true);
new Thread(new MyRepaint()).start();
this.addKeyListener(new KeyMonitor());
}
private class MyRepaint implements Runnable{
@Override
public void run() {
while(true){
//每50ms重畫一次
repaint();
try {
Thread.sleep();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
private class KeyMonitor extends KeyAdapter{
@Override
public void keyPressed(KeyEvent e) {
tk.keyPressed(e);
}
@Override
public void keyReleased(KeyEvent e) {
tk.keyReleased(e);
}
}
}
以上就完成了坦克發射子彈可以擊打敵對坦克的功能。
未完,剩餘功能見下篇博文