zoukankan      html  css  js  c++  java
  • Java课程设计-泡泡堂(个人)

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

    https://www.cnblogs.com/choco1ate/p/12172223.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动图,实现简易的动画效果。

  • 相关阅读:
    Numpy:数组维度转换:ravel()、shape=()、reshape()、np.tile()
    Numpy:使用numpy.sort()、numpy.argsort()获取排序后的值
    Numpy:数组的sum、max、argmax 函数
    Numpy:通过算数运算取值、替换值
    Numpy:数据类型简述
    Django路径问题
    Linux常用基本命令
    Django--DRF操作
    初始化django项目结构
    Django基础
  • 原文地址:https://www.cnblogs.com/seerking/p/12173351.html
Copyright © 2011-2022 走看看