Java课程设计-泡泡堂(个人)

1. 团队课程设计博客链接

http://www.cnblogs.com/wkfg/p/7063081.html

2.需求分析

(1)人物属性:

生命值,携带炸弹数,移动速度,炸弹威力

(2)通过读取人物能够丢炸弹,并且在人物向不同方向移动的时候,人物方向也会随之改动

(3)道具:

   加速道具,增加炸弹携带数量,增加炸弹威力
   无敌南瓜-吃到后获得5秒的无敌效果
   生命泡泡-吃到该道具后生命值加1

(4)游戏背景音乐和游戏地图在每次游戏启动的时候能够随机改动

3. 本组课题及本人任务

本组课题:泡泡堂游戏
本人任务:主要编写Player类以及PlayerAttribute类,添加部分道具以及游戏背景音乐以及部分地图

本人在项目中的Git提交记录截图


4.代码分析

private static final long serialVersionUID = 1L;
    /**玩家键盘控制参数,初始均为false*/
    private boolean bL = false, bU = false, bD = false, bR = false;
    /**玩家当前生命值,初始4*/
    public int live=4;
    /**玩家是否存活*/
    public boolean isalive=true;
    /**玩家当前速度,初始10*/
    public int speed=10;
    /**玩家速度限制最大值*/
    public int maxspeed=20;
    /**玩家同时可投炸弹数,初始1*/
    public int bombnum=1;
    /**玩家可投炸弹数限制最大值*/
    public int maxbombnum=6;
    /**现存的炸弹数*/
    int bombexist=0;
    /**玩家当前炸弹威力,初始1*/
    public int power=1;
    /**玩家炸弹威力限制最大值*/
    public int maxpower=5;

    /**人物方向转动图片*/
    String pathU;
    String pathD;
    String pathL;
    String pathR;

此部分代码为玩家的各种初始属性

Player类的构造函数

public Player(String pathU,String pathD,String pathL,String pathR,int x,int y,int index)//初始化玩家信息
    {
        this.pathU=pathU;
        this.pathD=pathD;
        this.pathL=pathL;
        this.pathR=pathR;//四个存储人物各个方向的图片
        imgPath=pathD;
        this.index=index;//标记是1P还是2P玩家
        x*=80;
        y*=80;
        this.x=x;
        this.y=y;//人物的坐标
        this.lastX=x;
        this.lastY=y;//人物的上一坐标
        this.setBounds(this.x,this.y,80,80);//设置人物大小
        setIcon(pathD,this);//设置人物图片
    }

枚举类 代表上、下、左、右和停止

public enum Direction {
        //分别代表上下左右和停止
        L, D, U, R, STOP
    }

人物移动的实现:

通过一个有参方法进行。该函数通过对人物的坐标更新赋值,从而进行人物的移动,部分代码如下

public void moveStep(int n)
    {
             int l;
             int loop=80;
        for(l=0;l<=loop;l+=speed)
            {
             switch(n)
             {
             case 1:
                 this.setLocation(x-l,y);
                 break;
             case 2:
                 this.setLocation(x,y-l);
                 break;
             case 3:
                 this.setLocation(x+l,y);
                 break;
             case 4:
                 this.setLocation(x,y+l);
                 break;
             default:
                break;
                 
             }
                
                //如果遇到碰撞物,则不移动
                if(meetbox())
                    
                { 
                    break;
                }
                if(l<80)
                {
                    try {
                        Thread.sleep(40);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
            //如果循环后最后移动值不为80,则修复。强制用户每次只能移动一格的距离
            if(l!=loop)

            {
                l=80;
                switch(n)
                 {
                 case 1:
                     this.setLocation(x-l,y);
                     break;
                 case 2:
                     this.setLocation(x,y-l);
                     break;
                 case 3:
                     this.setLocation(x+l,y);
                     break;
                 case 4:
                     this.setLocation(x,y+l);
                     break;
                 default:
                        break;
                     
                 }
                
            }
    }

通过读取方向变量dir,调用moveStep函数移动人物

public void move() {
        //定义上一位置
        this.lastX = x;
        //定义上一位置
        this.lastY = y;
        //在不遇到(碰撞物、边界、炸弹)的情况下,每40μs移动speed个像素,一共移动80像素(地图上一个格子的宽度)
        switch (dir) {
            //向左移动
            case L:
                
                 if(!invincible)
                 {
                     setIcon(pathL);
                 }
                moveStep(1);
                break;
            //向下移动  
            case U:
                 if(!invincible)
                 {
                     setIcon(pathU);
                 }
                moveStep(2);
                break;
            //向右移动
            case R: 
                 if(!invincible)
                 {
                     setIcon(pathR);
                 }
                moveStep(3);
                break;
            //向下移动
            case D:
                 if(!invincible)
                 {
                     setIcon(pathD);
                 }
                moveStep(4);
                break;
            case STOP:
                //停止
                break;
            default:
                break;
        }
        //更新x,y坐标值
        this.x=this.getX();
        this.y=this.getY();
    }

meetbox方法来检测是否遇到了各种障碍物,如果检测到了障碍物则返回false结果

boolean meetbox()
    
    {
        int x = 1120;
        int y = 880;
        if(this.getX()<0||this.getX()>x||this.getY()<0||this.getY()>y)
        {
            return true;
        }
        int w = 15;
        int h = 12;
        for(int i=0;i<w;i++)
        {   
            for(int j=0;j<h;j++)
            {
                Box temp=GameFrame.thismap.getBoxbyLocation(i,j);
                if(temp.getRect().intersects(this.getRect())&&temp.isExist)
                {
                    if(!temp.isdestroyshowT)
                        //遇到箱子
                    {
                        return true;
                    }
                }
                if(temp.isExistBomb&&!temp.isExistPlayer&&temp.getRect().intersects(this.getRect()))
                    //遇到炸弹
                {
                    return true;
                }
            }
        }
        return false;
    }

无敌道具效果的实现

class InvincibleThread2 extends Thread
    {

        @Override
        public void run(){
            //无敌时间开始
            invincible=true;

            int loop = 50;
            //获得5秒的闪烁无敌时间
            for(int i=0;i<loop;i++)
            {
                //每0.1秒闪烁一个循环,一共闪烁50次
                setIcon("images/default.png");

                try {
                    Thread.sleep(50);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                setIcon("images/invinciblePlayer.png");
                try {
                    Thread.sleep(50);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            setIcon(imgPath);
            invincible=false;
        }
    }

当玩家受到炸弹伤害时,调用这个方法

/**玩家被炸到(受伤)*/
    public void beInjured()
    {
        //生命值-1
        this.live--;
        //设置玩家属性面板的生命值
        this.pla.setLabel("生命值:"+this.live,this.pla.live);

        //如果死了(生命=0)
        if(live==0)

        {
            //玩家死亡
            this.isalive=false;
            //玩家属性面板头像更换
            setIcon("images/player"+this.index+"DIED.png",this.pla.photo);
            //玩家图片更换
            setIcon("images/player"+this.index+"DIED.png",this);
            //提示消息
            JOptionPane.showMessageDialog(this,"玩家"+this.index+"阵亡!","GameOver",2);
            //总人数减1
            GameFrame.NumofAlive--;
            //如果总存活数为1,则游戏结束
            if(GameFrame.NumofAlive==1)

            {
                JOptionPane.showMessageDialog(this,"游戏结束!单机确认退出~","GameOver",2);
                //显示主界面
                System.exit(0);

            }
        }
        else
        {
            //定义玩家无敌的线程
            Thread th=new InvincibleThread1();
            th.start();
        }
    }

以下代码为加速道具,加威力道具,加生命道具以及加携带炸弹数量道具,通过对相应的属性增加进行功能的实现

public void plusspeed()
    {
        //如果当前速度小于最高速度
        if(myP.speed<myP.maxspeed)
        {
            myP.speed+=2;
            //更新速度数据
            setLabel("速度:"+myP.speed,speed);

        }
    }
    
    public void plusbombnum()
    {
        //如果当前炸弹数小于最高炸弹数
        if(myP.bombnum<myP.maxbombnum)

        {
            //炸弹数加一
            myP.bombnum++;
            //更新炸弹数据
            setLabel("泡泡数:"+myP.bombnum,bombnum);
        }
    }
    
    public void pluspower()

    {
        if(myP.power<myP.maxpower)
            //如果当前威力小于最高威力
        {
            //威力加一
            myP.power++;
            //更新威力数据
            setLabel("威力:"+myP.power,power);

        }
    }
    public void pluslive()

    {
        //生命加一
        myP.live++;
        //刷新生命值数据
        setLabel("生命值:"+myP.live,live);

        
    }

背景音乐的线程

public class Music extends Thread

{
    
     public volatile boolean flag = true; 
    
    
    
    
    private String fileName;
    private final int EXTERNAL_BUFFER_SIZE = 524288;

    public Music(int  n) {
        
        switch (n)
        {
        case 1:
            this.fileName="gameBgm.wav";
            break;
        case 2:
            this.fileName="Music.wav";
            break;
        case 3:
            this.fileName="MUsic2.wav";
            break;
            default :
            break;
        }
        }
    @Override
    public void run() {
        //1 获取你要播放的音乐文件
        File soundFile = new File(fileName);
        if (!soundFile.exists()) {
            System.err.println("Wave file not found:" + fileName);
            return;
        }
        while (flag){
            //2、定义一个AudioInputStream用于接收输入的音频数据
            AudioInputStream audioInputStream = null;
            try {
                //3、使用AudioSystem来获取音频的音频输入流(处理(抛出)异常)
                audioInputStream = AudioSystem.getAudioInputStream(soundFile);
            } catch (UnsupportedAudioFileException e1) {
                e1.printStackTrace();
                return;
            } catch (IOException e1) {
                e1.printStackTrace();
                return;
            }
            //4、使用AudioFormat来获取AudioInputStream的格式
            AudioFormat format = audioInputStream.getFormat();
            //5、一个源数据行
            SourceDataLine auline = null;
            //6、获取受数据行支持的音频格式DataLine.info
            DataLine.Info info = new DataLine.Info(SourceDataLine.class, format);
            try {
                //7、获取与上面类型相匹配的行 写到源数据行里 
                auline = (SourceDataLine) AudioSystem.getLine(info);
                auline.open(format);
            } catch (LineUnavailableException e) {
                e.printStackTrace();
                return;
            } catch (Exception e) {
                e.printStackTrace();
                return;
            }
            //9 允许某个数据行执行数据i/o
            auline.start();
            //10、写数据
            int nBytesRead = 0;
            //设置字节数组大小
            byte[] abData = new byte[EXTERNAL_BUFFER_SIZE];
            try {
                //11、从音频流读取指定的最大数量的数据字节,并将其放入给定的字节数组中
                while (nBytesRead != -1) {
                    nBytesRead = audioInputStream.read(abData, 0, abData.length);
                    if (nBytesRead >= 0) {
                        //12、读取了之后将数据写入混频器,开始播放
                        auline.write(abData, 0, nBytesRead);
                    }
                }
            } catch (IOException e) {
                e.printStackTrace();
                return;
            } finally {
                //关闭
                auline.drain();
                auline.close();
            }
        }

总结音乐播放的步骤:

总结步骤:
1 获取你要播放的音乐文件
2、定义一个AudioInputStream用于接收输入的音频数据
3、使用AudioSystem来获取音频的音频输入流(处理(抛出)异常)
4、使用AudioFormat来获取AudioInputStream的格式
5、创建一个源数据行
6、获取受数据行支持的音频格式 DataLine.info 如果采用.getSourceDataLine()方法可以省略)
7、获取与上面类型相匹配的行 写到源数据行里 二选一
8、打开具有指定格式的行,这样可以使行获得资源并进行操作
9、允许某个数据行执行数据i/o
10、写数据
11、从音频流读取指定的最大数量的数据字节,并将其放入给定的字节数组中。
12、读取哪个数组
13、读取了之后将数据写入混频器,开始播放

5. 测试、改进与感想。

测试与改进

(1) 对于音乐,程序刚开始使用的是java.applet.AudioClip来进行音乐文件的播放,实际测试后,容易和后面的爆炸线程等冲突,导致音乐停止播放,所以后来改成了使用javax.sound.sampled.*进行音乐的播放,通过读取音乐文件的Io流进行播放音乐

(2) 人物的移动方案最开始使用的是直接对坐标进行有间隔的赋值,但是这样会造成人物移动的“瞬移”,不仅不美观,而且给人感觉游戏不是很流畅

改进:改进之后使用一个for循环对人物进行移动,但是改进后的移动方法是对人物进行“像素”级别的移动,这样会让人物移动有一个简易的动画,提高游玩体验。

感想:

对于这次java的课程设计,我明白了处理好多线程的关系对于程序的重要性,当一个线程运行时,如果想使另外一个线程运行正常,需要注意线程之间的共享资源问题,可适当地使用synchronized关键字对多个线程进行优化。对于图片和音乐的调用,要注意格式以及分辨率的问题。例如图片,如果在程序的某一个块需要放一个图片,要注意图片分辨率的大小最好和程序块吻合,否则,无论原图分辨率过高或者过低,在实际程序运行的时候,都会造成图片的模糊,影响感官体验。另外,通过这次的java实验,我还学习到了部分图片处理的知识,例如去除图片的背景色,进行透明化处理,以及将多张图片合成为一张gif动图,实现简易的动画效果。

猜你喜欢

转载自www.cnblogs.com/seerking/p/12173351.html