Java之详解坦克大战游戏(三)

这篇博客详细介绍了如何在Java中实现坦克大战游戏的敌我坦克和子弹动态模拟。使用集合中的Vector实现敌方坦克的动态管理,通过线程实现子弹的移动。在Tank类中定义坦克颜色,子弹类实现了Runnable接口以实现移动。通过键盘监听处理攻击事件,不断刷新页面使子弹可见并移动。文章强调了代码的健壮性、可读性和封装性的重要性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

上篇我们已经能画出能移动的坦克了,这次来画出敌人的坦克,敌人的坦克数量肯定是多的,那么考虑用数组还是用集合,毫无疑问我们选的是集合,因为敌人坦克击毁后会再增加坦克,也就是说数目会增加的,那样用数组是不太合理了..集合中我们选择线程安全的Vector…继续续写上篇的代码
首先到Members.java添加一个敌方坦克的类

//敌人的坦克
class EnemyTank extends Tank{

    public EnemyTank(int x, int y) {
        super(x, y);    
    }

}

在Tank类定义坦克颜色(区分自己与敌人的坦克)

    int color;//坦克的颜色

    public int getColor() {
        return color;
    }
    public void setColor(int color) {
        this.color = color;
    }
class MyPanel extends Panel implements KeyListener
{
//.....
    //敌人的坦克组
    Vector<EnemyTank> ets = new Vector<EnemyTank>();

    int enSize=3; //敌人坦克初始数量
    public MyPanel()
    {   
        //初始化敌人的坦克
        for(int i=0;i<enSize;i++)
        {   //... 
            //创建一辆敌人的坦克对象
            EnemyTank et =new EnemyTank((i+1)*50,0);//让坦克初始位置不同
            et.setColor(0);//敌人坦克颜色
            et.setDirect(2);//设置敌人坦克方向向下
            ets.add(et);//加入    
        }
    }
    //重新paint
    public void paint(Graphics g)
    {
        super.paint(g);
        g.fillRect(0, 0, 1200, 900);  //填充使背景变为黑色   
        //画出自己的坦克
        this.drawTank(hero.getX(), hero.getY(), g, this.hero.direct, 1);
        //画出敌人的坦克
        for(int i=0;i<ets.size();i++)
        {
            this.drawTank(ets.get(i).getX(), ets.get(i).getY(), g,       ets.get(i).getDirect(), 0);
        }
    }

程序运行效果如图:这里写图片描述
接下来双方都有坦克,就来考虑出子弹吧…
子弹是会移动的,这里我们将用到线程知识,我们需要一个子弹类(为了避免乱了后改不回去,还是新建一个包com.TankGame2复制原来两个java文件)
到Members.java写子弹类,子弹一旦被创建立马就要跑了,我们这里让子弹类实现Runnable接口

//子弹类
class Shot implements Runnable
{
    int x;
    int y;
    int direct;//子弹的方向
    int speed=1; //子弹速度
    boolean isLive=true;//子弹是否活着
    public Shot(int x,int y,int direct)
    {
        this.x=x;
        this.y=y;
        this.direct=direct;
    }
    public void run() 
    {
        while(true)
        {
            try {
                Thread.sleep(50);  //经测试休息50毫秒比较好,要不然内存容易撑不了
            } catch (Exception e) {
                // TODO: handle exception
            }
            switch(direct)
            {
            case 0://向上打出
                y-=speed;
                break;
            case 1://向右打出
                x+=speed;
                break;
            case 2://向下打出
                y+=speed;
                break;
            case 3://向左打出
                x-=speed;
                break;
            }
            //判断子弹是否碰到边缘 我们刚开始是setsize(400,300)
            if( x<0 || x>400 || y<0 || y>300)
            {
                this.isLive=false;
                break;
            }
        }
    }
}

能发射子弹是我们坦克的一个功能,所以我们到Hero类定义..

class Hero extends Tank
{   //...
    //子弹
    Shot s = null;
    //开火
    public void shotEnemy()
    {
        switch (this.direct) //子弹的方向
        {
        case 0:
            s=new Shot(x+10, y,0);
            break;
        case 1:
            s=new Shot(x+30,y+10,1);
            break;
        case 2:
            s=new Shot(x+10,y+30,2);
            break;
        case 3:
            s=new Shot(x,y+10,3);
            break;
        }
        //启动子弹线程
        Thread t = new Thread(s);
        t.start();
    }
    //...

上面代码子弹坐标的确定还是靠第一篇说的矩形左上角参照点来确定的,子弹从炮筒打出(也就是在坦克组成中的圆形引出的线段的终点,即为子弹起点)
接下来来写画子弹的代码了,到TankGame2.java文件,首先按键必须要有反应吧,我们以”J“ 作为攻击键..

//按下某个键时调用此方法: a代表向左,w代表向上,d代表向右,s代表向下
    public void keyPressed(KeyEvent e) {
        //......
        //判断玩家是否按下J键
        if(e.getKeyCode() == KeyEvent.VK_J)
        {
            //开火
            this.hero.shotEnemy();
        }
        this.repaint();//调用repaint()函数,来重绘页面
    }

画子弹的话就到paint方法去

//重新paint
    public void paint(Graphics g)
    {
        super.paint(g);
        g.fillRect(0, 0, 1200, 900);  //填充使背景变为黑色   

        if(hero.s!=null && hero.s.isLive == true)   
        {
            g.setColor(Color.yellow); //子弹为黄色
            g.draw3DRect(hero.s.x, hero.s.y, 1, 1, false);  //即画一个点
        }

这时候,运行程序,会发现看得到子弹(按J键坦克再移动开),它不会动,而且一次只能发一颗这里写图片描述
子弹打出后是不断移动的,所以要不断刷新页面,即使我们启动子弹的线程也不会看到子弹动,如果用输出语句标记一下会发现子弹的坐标还是不断改变的,这时,要想看得到子弹,就需要到MyPanel实现Runnable

//我的面板
class MyPanel extends Panel implements KeyListener,Runnable
{//子弹要看到,要不停的重绘,paint函数每隔段时间久更新,重绘是在paint函数发生的,所以MyPanel要实现Runnable接口
    //...
    public void run() {
        //每隔100毫秒来重绘
        while(true)
        {
            try {
                Thread.sleep(100); //经测试100毫秒比较合理
            } catch (Exception e) {
                // TODO: handle exception
            }
            //重绘
            this.repaint();
        }

    }
}

再去启动线程,TankGame2类的构造函数

public TankGame2()
    {
        mp=new MyPanel();
        Thread t = new Thread(mp);
        t.start();
        //......
    }

接下来子弹可以飞了…
这里写图片描述
我们对代码的编写总是跳来跳去,而事实上即使很有整体思路,也做不到一行一行顺序编写代码编下去,学过数据结构的我们都知道要保证程序的健壮性和可读性,在Java里面还要体现出封装性,所以要不断对代码进行修正和完善….更多精彩内容,请听下节分解….

(附上完整代码)
Members.java

package com.TankGame2;

//坦克类
class Tank
{
    //表示坦克的横坐标
    int x=0;
    //表示坦克的纵坐标
    int y=0;

    //坦克方向 
    //0表示上,1表示右,2表示下,3表示左
    int direct=0;
    int color;//坦克的颜色

    public int getColor() {
        return color;
    }

    public void setColor(int color) {
        this.color = color;
    }

    public int getDirect() {
        return direct;
    }

    public void setDirect(int derect) {
        this.direct = derect;
    }

    //坦克速度
    int speed=1;

    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 Tank(int x,int y)
    {
        this.x=x;
        this.y=y;
    }
}

//敌人的坦克
class EnemyTank extends Tank{

    public EnemyTank(int x, int y) {
        super(x, y);

    }

}

//我的坦克
class Hero extends Tank
{
    //子弹
    Shot s = null;
    public Hero(int x,int y)
    {
        super(x,y);
    }

    //开火
    public void shotEnemy()
    {

        switch (this.direct) //子弹的方向
        {
        case 0:
            s=new Shot(x+10, y,0);
            break;
        case 1:
            s=new Shot(x+30,y+10,1);
            break;
        case 2:
            s=new Shot(x+10,y+30,2);
            break;
        case 3:
            s=new Shot(x,y+10,3);
            break;
        }
        //启动子弹线程
        Thread t = new Thread(s);
        t.start();

    }


    //坦克向上移动
    public void moveUp()
    {
        y-=speed;
    }
    //坦克向右移动
    public void moveRight()
    {
        x+=speed;
    }
    //坦克向下移动
    public void moveDown()
    {
        y+=speed;
    }
    //坦克向左移动
    public void moveLeft()
    {
        x-=speed;
    }
}

//子弹类
class Shot implements Runnable
{
    int x;
    int y;
    int direct;//子弹的方向
    int speed=1; //子弹速度
    boolean isLive=true;//子弹是否活着
    public Shot(int x,int y,int direct)
    {
        this.x=x;
        this.y=y;
        this.direct=direct;
    }
    public void run() {

        while(true)
        {
            try {
                Thread.sleep(50);
            } catch (Exception e) {
                // TODO: handle exception
            }
            switch(direct)
            {
            case 0://向上打出
                y-=speed;
                break;
            case 1:
                x+=speed;
                break;
            case 2:
                y+=speed;
                break;
            case 3:
                x-=speed;
                break;
            }
            //判断子弹是否碰到边缘
            if( x<0 || x>400 || y<0 || y>300)
            {
                this.isLive=false;
                break;
            }
        }

    }
}

TankGame2.java

package com.TankGame2;
import javax.swing.*;
import java.awt.*;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.util.Vector;
/*
 * */
public class TankGame2 extends JFrame
{
    MyPanel mp =null;
    public static void main(String[] args) 
    {
        TankGame2 tankgame2 = new TankGame2();
    }

    public TankGame2()
    {
        mp=new MyPanel();
        Thread t = new Thread(mp);
        t.start();
        this.addKeyListener(mp);//注册监听
        this.setVisible(true);
        this.setSize(400, 300);
        this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        this.add(mp);
    }

}

//我的面板
class MyPanel extends Panel implements KeyListener,Runnable
{//子弹要看到,要不停的重绘,paint函数每隔段时间久更新,重绘是在paint函数发生的,所以MyPanel要实现Runnable接口
    //定义一个我的坦克
    Hero hero =null;

    //敌人的坦克组
    Vector<EnemyTank> ets = new Vector<EnemyTank>();

    int enSize=3; //敌人坦克初始数量
    public MyPanel()
    {
        hero =new Hero(100,100); //坦克的初始位置(10,10)

        //初始化敌人的坦克
        for(int i=0;i<enSize;i++)
        {
            //创建一辆敌人的坦克对象
            EnemyTank et =new EnemyTank((i+1)*50,0);
            et.setColor(0);
            et.setDirect(2);
            //加入
            ets.add(et);

        }
    }

    //重新paint
    public void paint(Graphics g)
    {
        super.paint(g);
        g.fillRect(0, 0, 1200, 900);  //填充使背景变为黑色   

        //画出子弹
        if(hero.s!=null && hero.s.isLive == true)
        {
            g.setColor(Color.yellow);
            g.draw3DRect(hero.s.x, hero.s.y, 1, 1, false);
        }

        //画出自己的坦克
        this.drawTank(hero.getX(), hero.getY(), g, this.hero.direct, 1);
        //画出敌人的坦克
        for(int i=0;i<ets.size();i++)
        {
            this.drawTank(ets.get(i).getX(), ets.get(i).getY(), g, ets.get(i).getDirect(), 0);
        }
    }

    //画出坦克的函数
    public void drawTank(int x,int y,Graphics g,int direct,int type)
    {
        switch(type)
        {
        case 0:
            g.setColor(Color.cyan);
            break;
        case 1:
            g.setColor(Color.yellow);
            break;
        }

        //判断方向
        switch(direct)
        {
        //向上
        case 0:
            //画出我的坦克(到时再封装成一个函数)
            //1.画出左边的矩形
            g.fill3DRect(x, y, 5, 30,false);
            //2.画出右边矩形
            g.fill3DRect(x+15, y, 5, 30,false);
            //3.画出中间矩形
            g.fill3DRect(x+5, y+5, 10, 20,false);
            //4.画出圆形
            g.fillOval(x+5, y+10, 10, 10);
            //5.画直线
            g.drawLine(x+10, y+15, x+10, y);
            break;
        case 1://炮筒向右
            //画出上面矩形
            g.fill3DRect(x, y, 30, 5,false);
            //2.画出右边矩形
            g.fill3DRect(x, y+15, 30, 5,false);
            //3.画出中间矩形
            g.fill3DRect(x+5, y+5, 20, 10,false);
            //4.画出圆形
            g.fillOval(x+10, y+5, 10, 10);
            //5.画直线
            g.drawLine(x+15, y+10, x+30, y+10);
            break;
        case 2://炮筒向下
            //画出上面矩形
            g.fill3DRect(x, y, 5, 30,false);
            //2.画出右边矩形
            g.fill3DRect(x+15, y, 5, 30,false);
            //3.画出中间矩形
            g.fill3DRect(x+5, y+5, 10, 20,false);
            //4.画出圆形
            g.fillOval(x+5, y+10, 10, 10);
            //5.画直线
            g.drawLine(x+10, y+15, x+10, y+30);
            break;

        case 3://炮筒向左
            //画出上面矩形
            g.fill3DRect(x, y, 30, 5,false);
            //2.画出右边矩形
            g.fill3DRect(x, y+15, 30, 5,false);
            //3.画出中间矩形
            g.fill3DRect(x+5, y+5, 20, 10,false);
            //4.画出圆形
            g.fillOval(x+10, y+5, 10, 10);
            //5.画直线
            g.drawLine(x+15, y+10, x, y+10);
            break;
        }

    }
/*实现KeyListener接口的方法*/
    //键入某个键时调用此方法。 
    public void keyTyped(KeyEvent e) {


    }
    //释放某个键时调用此方法
    public void keyReleased(KeyEvent e) {


    }
    //按下某个键时调用此方法: a代表向左,w代表向上,d代表向右,s代表向下
    public void keyPressed(KeyEvent e) {

        if(e.getKeyCode() == KeyEvent.VK_W)//向上
        {
            this.hero.setDirect(0);
            this.hero.moveUp();
        }
        else if(e.getKeyCode() == KeyEvent.VK_D)//向右
        {
            this.hero.setDirect(1);
            this.hero.moveRight();
        }
        else if(e.getKeyCode() == KeyEvent.VK_S)//向下
        {
            this.hero.setDirect(2);
            this.hero.moveDown();
        }
        else if(e.getKeyCode() == KeyEvent.VK_A)//向左
        {
            this.hero.setDirect(3);
            this.hero.moveLeft();
        }

        //判断玩家是否按下j键
        if(e.getKeyCode() == KeyEvent.VK_J)
        {
            //开火
            this.hero.shotEnemy();
        }
        this.repaint();//调用repaint()函数,来重绘页面
    }

    public void run() {
        //每隔100毫秒来重绘
        while(true)
        {
            try {
                Thread.sleep(100);
            } catch (Exception e) {
                // TODO: handle exception
            }
            //重绘
            this.repaint();
        }
    }   
}
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值