`
摇梦江畔
  • 浏览: 8970 次
  • 性别: Icon_minigender_2
  • 来自: 朝阳
文章分类
社区版块
存档分类
最新评论

山寨版植物大战僵尸总结

 
阅读更多

      最近做了一个简化版的山寨植物大战僵尸,虽然还有很多功能没有实现,但初步的样子还是有了,下面就谈一下我的收获和遇到的问题吧。

      一、总体构架

            我的游戏主要实现了在主界面上以图片做背景,通过鼠标的点击控制,种植向日葵,豌豆,通过收获阳光获得武器,并通过豌豆的射击消灭僵尸。下面是我在各个功能实现上的想法。

            [1]在有背景的界面上插入有图片的按钮。

                这里主要涉及的是将流式布局设为空,然后使用setBounds组件。

	// 设置模块
		panel = new MyPanel();

		Dimension paneldim = new Dimension(1026, 700);
		panel.setPreferredSize(paneldim);
		panel.setLayout(null);// 以绝对方式添加组件,须将容器panel布局置空
		this.add(panel);
		mainshow = new JLabel(Config.mainshow);// 背景图片大小:1026*700
		mainshow.setBounds(0, 0, 1026, 760);// 坐标从x=0,y=0开始,它将按背景图片大小来占据北部空间
		panel.add(mainshow);
        
		//设置豌豆射手按钮
		peabutton = new JButton(Config.peaButton);// 图标按钮大小:60*90
		peabutton.setBounds(122, 6, 60, 90);
		peabutton.setActionCommand("peashooter");// 设置按钮动作监听命令
		peabutton.addActionListener(al);
		panel.add(peabutton);// peabutton在mainshow的范围内

   这样可以摆脱布局的干扰,防止背景图片把按钮挤到窗口之外。但是还会遇到按钮上的图片会被底层背景覆盖,只有当鼠标在上面点击时,图片才会闪现。

            [2]在背景图片上插入记分牌。

            这个记分牌实际是一个文本输入框,我在输入框上插入图片,并将传入的数字设为变量,并改变了字体和颜色,也就和原版不相上下了。

      

	// 创建并设置文本域
		final Image CHAT_TEXT_IMAGE_BG = Config.num.getImage();
	    JTextField jl = new JTextField("   100"){
		        {
		            setOpaque(false);
		        } 

		        public void paint(Graphics g) {
		            g.drawImage(CHAT_TEXT_IMAGE_BG, 0, 0, this);
		            super.paint(g);
		        }
		    };
		    jl.setBorder(new EtchedBorder());
	        jl.setForeground(Color.getHSBColor(0,0,0));//设置字体的颜色
	        jl.setFont(new Font(" ", Font.ROMAN_BASELINE, 20));//设置字体的形状和大小
	        jl.setBounds(29, 74, 67, 30);//设置文本框的位置和大小
	        panel.add(jl);

            [3]在草地上画太阳花和豌豆射手

                采用和五子棋一样的方法,将草地看成数组,给太阳花和射手定义不同的数字,给窗口添加鼠标监听器,当点击时为数组附上相应的数值,并根据数值进行重绘。

// 1.豌豆
		// -1.向日葵
		// 获取草地上中点的坐标
		for (int i = 0; i < Config.ROWS; i++) {
			for (int j = 0; j < Config.COLUMNS; j++) {
				 x2 = Config.X + j * Config.plantwidth;
				 y2 = Config.Y + i * Config.plantheight;
				// 得到离点击位置最近的草坪中点坐标
				if ((x1 > x2) && (x1 < (x2 + Config.plantwidth)) && (y1 > y2)
						&& (y1 < (y2 + Config.plantheight))) {
					try{
                    	if (Config.plant[i][j] == 0) {
    						if (PKUI.item.equalsIgnoreCase("peashooter")&&number>=100) {
    							check = 1;
    						} else if (PKUI.item.equalsIgnoreCase("sunflower")&&number>=50) {
    							check = -1;
    						}
    						putplant(i, j, check);
    					}
					}catch(Exception ef){
						ef.getStackTrace();
					}
	
                }
					
			}

		}
	}

	/**
	 * 画植物的方法
	 * 
	 * @param i
	 *            :
	 * @param j
	 * @param check
	 *            ,植物,1;向日葵,2;
	 */
	public void putplant(int i, int j, int check) {
		x = Config.X + j * Config.plantwidth;
		y = Config.Y + i * Config.plantheight;

		if (check == 1) {
           
			g.drawImage(Config.peashooter.getImage(), x, y, Config.width,
					Config.height, null);
			// 启动豌豆线程
			peashooterThread thread = new peashooterThread(panel,x,y);
			thread.start();
			Config.pslist.add(thread);
		    number-=100;
		} else if (check == -1) {
			g.drawImage(Config.sunflower.getImage(), x, y, Config.width,
					Config.height, null);
			// 向日葵线程
			sunflawerThread thread = new sunflawerThread(panel,x,y);
			thread.start();
			number-=50;
		}
		// 标记二维数组
		Config.plant[i][j] = check;
		// 判断输赢

 

             [4]多线程的控制

             这是这个游戏采用最多的方法,每一种植物是一个线程,控制是否喷出豌豆或阳光,再用另一个线程控制豌豆和阳光尖端喷出的频率,僵尸的延时出场以及每隔一段时间出现一批,也是通过可控时间的线程TimerTask类实现的。在线程中要通过对死循环的控制来控制每种植物的出现,在其中我遇到的问题是绘制的僵尸及豌豆和阳光不停地闪烁,原因是在双缓冲中进行了重绘,在线程中又绘制了图片,绘制的多次重叠导致图片不断闪烁。只要将线程中的绘制图片取消就可以解决。

             [5]双缓冲解决屏幕重绘时闪烁问题

           在主类中再定义一个内部类,实现双缓冲。

class MyPanel extends JPanel {

		private Image offScreenImage;
		Graphics gImage;

		// 重写update方法,先将窗体上的图形画在图片对象上,再一次性显示
		public void update(Graphics g) {
			if (offScreenImage == null) {
				// 截取窗体所在位置的图片
				offScreenImage = this.createImage(1026, 760);
			}
			// 获得截取图片的画布
			gImage = offScreenImage.getGraphics();
			// 获取画布的底色并且使用这种颜色填充画布(默认的颜色为黑色)
			Color c = Color.BLACK;
			gImage.setColor(c);
			gImage.fillRect(0, 0, 1026, 760); // 有清除上一步图像的功能,相当于gImage.clearRect(0,
												// 0, WIDTH, HEIGHT)
			// 将截下的图片上的画布传给重绘函数,重绘函数只需要在截图的画布上绘制即可,不必在从底层绘制
			paint(gImage);
			// 将接下来的图片加载到窗体画布上去,才能得到每次画的效果
			g.drawImage(offScreenImage, 0, 0, null);
		}

		public void paint(Graphics g) {
			// 在重绘函数中实现双缓冲机制
			offScreenImage = this.createImage(1026, 760);
			// 获得截取图片的画布
			gImage = offScreenImage.getGraphics();
			// 获取画布的底色并且使用这种颜色填充画布,如果没有填充效果的画,则会出现拖动的效果
			gImage.setColor(gImage.getColor());
			gImage.fillRect(0, 0, 1026, 760); // 有清楚上一步图像的功能,相当于gImage.clearRect(0,
												// 0, WIDTH, HEIGHT)

			// 调用父类的重绘方法,传入的是截取图片上的画布,防止再从最底层来重绘
			super.paint(gImage);
			gImage.drawImage(Config.mainshow.getImage(), 0, 0, 1026, 760, null);
			for (int i = 0; i < Config.ROWS; i++) {
				for (int j = 0; j < Config.COLUMNS; j++) {
					int x = Config.X + j * Config.plantwidth;
					int y = Config.Y + i * Config.plantheight;
					if (Config.plant[i][j] == 1) {
						gImage.drawImage(Config.peashooter.getImage(), x, y,
								null);
					} else if (Config.plant[i][j] == -1) {
						gImage.drawImage(Config.sunflower.getImage(), x, y,
								null);
					}
				}
			}

			// 绘制子弹
			for (int i = 0; i < Config.plist.size(); i++) {
				peaThread pt = Config.plist.get(i);
				gImage.drawImage(Config.pea.getImage(), pt.x, pt.y,
						Config.size, Config.size, null);
				//panel.repaint();
			}

			// 绘制太阳
			for (int i = 0; i < Config.sunlist.size(); i++) {
				sunfThread sf = Config.sunlist.get(i);
				gImage.drawImage(Config.sun.getImage(), sf.x, sf.y,
						100, Config.sun_width, null);
			}
			
			for (int i = 0; i < Config.suntlist.size(); i++) {
				suntThread sf = Config.suntlist.get(i);
				gImage.drawImage(Config.sun.getImage(), sf.x, sf.y,
						100, Config.sun_width, null);
			}
           //绘制僵尸
		   for(int i = 0;i<Config.dlist.size();i++){
				   death2Thread df = Config.dlist.get(i);
				   gImage.drawImage(Config.death1.getImage(), df.x,df.y, null); 
//				   if((j+1)==(3*i-2)){
//					   gImage.drawImage(Config.death1.getImage(), df.x,df.y, null);   
//				   }else if((j+1)==(3*i-1)){
//					   gImage.drawImage(Config.death2.getImage(), df.x,df.y, null); 
//				   }else if((j+1)==3*i){
//					   gImage.drawImage(Config.death2.getImage(), df.x,df.y, null); 
//				   }
//			   }
//			    death2Thread df = Config.dlist.get(i);
//			    gImage.drawImage(Config.death1.getImage(), df.x,df.y, null);
//			    
		   }
//			for (int i = 0; i < Config.dlist.size(); i++) {
//
//				final death2Thread df = Config.dlist.get(i);
//				Runnable rb = new Runnable(){
//					int n=1;
//					public void run(){
//						while(true){
//							gImage.drawImage(new ImageIcon("death"+n+".png").getImage(), df.x--, df.y, null);
//							n++;
//							if(n>3){
//								n=1;
//							}
//							try {
//								Thread.sleep(1000);
//							} catch (InterruptedException e) {
//								e.printStackTrace();
//							}
//						}
//						
//					}
//				};
//				new Thread(rb).start();
//				
//				
//			}

			// 将接下来的图片加载到窗体画布上去,才能考到每次画的效果
			g.drawImage(offScreenImage, 0, 0, null);
		}
	}

 

     [6]控制小球与僵尸碰撞时消去小球和僵尸

         主要采用了线程监听类,当碰撞时,在存储的队列中移除对象即可。

 二、自己遇到的困难和需要解决的问题

       1、图片交换来实现僵尸的动态行走,图片和移动的位置,以及线程所取得的图片未能很好的搭配到一起,所以这个功能没有实现。 2、点击相应的按钮后每种植物只能种植一次,这也是我接下来要解决的问题。

       同时由于每一个线程都是一个类,所以类的繁多造成了传参的混乱,也给我的彼岸陈国成造成了不小的麻烦

       最后也是最要提的一点就是,一定要把自己的项目及时备份,我的项目在第三天的时候,被自己误删了,不得不从头开始写,这耽误了我很多时间,也是给我一个教训,一定要备份。

 

 

 

 

 

 

 

 

 

分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics