贪吃蛇系列之五——动起来

      今天这篇文章本来应该早些时候就应该发布出来的了,但是今天孤狼去参加颁奖典礼去了,耽搁了些时间,呵呵,所以就晚了一些。今天领奖,很高兴,还是说出来跟大家分享一下吧,孤狼参加了移动在学校做的比赛,有幸获了奖:


当然,姓名和奖金我给P掉了,呵呵,还是值得高兴一下的。这款软件呢是基于Android平台的,软件会在之后公布源代码,并且将这个项目的开发过程也挨着挨着分解开给大家探讨和学习。那么说到这里呢,孤狼还是简单的说一下我的一个简单的计划吧,因为今天也看到了大家给我的留言,很谢谢大家对我的支持与鼓励。贪吃蛇呢只是作为一个引入,或者说是对于J2SE的一个简单的整合,这个游戏本身不是重点。在做完这个游戏的第一版之后,孤狼会尝试着和各位一起进行更深层次的挖掘,方向呢是关于设计模式和软件架构的,我写项目这么长时间以来,还是觉得这一块很有学问也很有意思,也希望和大家一起探讨一下。然后呢,孤狼博客的重点还是在JavaEE的技术和框架应用上,当然,也会同步更新Android平台上的相关项目的开发过程。这些我们需要涉及的话题呢,他们都有一个共同点,就是都使用Java作为编程语言,这也就是为什么我会选择一个比较的蹩脚的游戏开始的原因。当然,在进行项目的过程中,孤狼尽可能的会从一个软件代码开发的各个阶段去进行撰文,这里面也包含了UI设计。我个人虽然比不了那些专业的设计师,但是还是做过很多项目的UI设计,PhotoShop用的也还比较熟练,因此在这方面我的博文也会有所涉及,希望可以帮助大家对软件开发有一个比较全面的认识。
     
好了,下面我们进入我们这次的项目。首先我们要做的当然是在Snake类中添加蛇运动的方法move,下面是该方法的代码:

        /**
	 * 蛇移动的方法
	 */
	public void move(){
		//1.去尾。这个很简单,意思就是说去掉我们snakeList的最后一个元素
		snakeBody.removeLast();
		 //2.加头。这个就相对复杂一点,需要我们根据蛇当前运动的方向来判断我们的蛇头应该加在什么位置
		Body head = snakeBody.getFirst();//获得当前蛇头的那个对象
		switch(direction){
		case DIR_UP:
			snakeBody.addFirst(new Body(head.x, head.y - BODY_SIZE));
			break;
		case DIR_DOWN:
			snakeBody.addFirst(new Body(head.x, head.y + BODY_SIZE));
			break;
		case DIR_LEFT:
			snakeBody.addFirst(new Body(head.x - BODY_SIZE, head.y));
			break;
		case DIR_RIGHT:
			snakeBody.addFirst(new Body(head.x + BODY_SIZE, head.y));
			break;
		}
	}
在这个方法中,按照我们分析的蛇的运动方式的算法,我们先去掉蛇尾,再在合适的位置加上我们新的蛇头。
      接下来呢,我们就要在线程中来不断的调用我们蛇的move方法并且不断刷新我们的游戏窗口,这个原理我想大家都很清楚,和动画片的实现是一个道理。还是先看线程部分的代码:
        /**
	 * 游戏的主线程
	 * @author [email protected]
	 *
	 */
	class GameThread extends Thread{
		
		/** 控制游戏开始于暂停的标志 */
		private boolean isRunning = true;
		
		/**
		 * 设置游戏的运行状态,运行或是暂停
		 * @param isRunning	让游戏运行则传入true,让游戏停止则传入false
		 */
		public void setIsRunning(boolean isRunning){
			this.isRunning = isRunning;
		}
		
		/**
		 * 构造一个游戏的运行线程对象
		 * @param isRunning	让游戏运行则传入true,让游戏停止则传入false
		 */
		public GameThread(boolean isRunning){
			setIsRunning(isRunning);
		}
		
		/**
		 * 线程执行的具体逻辑
		 */
		public void run(){
			//我们让线程在循环中每隔1000毫秒执行一次
			while(isRunning){
				//更新游戏的数据
				updateGame();
				//刷新游戏界面
				repaint();
				//让线程休眠100毫秒后继续执行
				try {
					Thread.sleep(1000);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}
		}
		
	}
这个类呢我们同样采用内部类的形式来实现,因为我们的这个线程只需要获取GameView中的数据,并且其他的类也不需要调用它。线程的基本概念呢孤狼就不讲了,希望不理解线程的同学自己去查阅相关的资料。在线程中呢,最重要的就是要去重写run方法,在run方法中去实现我们自己的逻辑。大家可以看到,我们在run方法中做了这么几件事情,首先是更新游戏的数据,然后是让界面进行重绘。在这里,孤狼解释一下repaint()方法,当我们调用这个方法的时候,我们的java虚拟机会去调用我们的paint(Graphics g)这个方法,也就是我们写在GameView中的绘制游戏界面的方法。接着,我们让线程休眠1000毫秒,近似于1秒。这个值主要是我们来模拟游戏的运行速度,或者说是一个简单的控制游戏的帧数。当然,真实的游戏线程不会这么简单,我只是进行了简单的模拟,在游戏的线程中这部分要复杂的多,光帧数的控制就有多种方式,感兴趣的同学可以去查阅和游戏线程相关的资料,这不是我们研究的重点。接着呢,大家可以看到我们在这个线程中还设置了一个标志来控制游戏的暂停和运行。

      接着,我们在GameView中写一个新的方法updateGame(),意思就是,我们所有游戏的数据更新都在这个方法中去完成,有利于我们的代码维护和管理:

        /**
	 * 刷新游戏的数据
	 */
	public void updateGame(){
		snake.move();
	}

      有了我们游戏的主线程,那么我们就只需要在GameView中的initGameData()方法中加上关于线程的部分就可以了,下面是代码:

        /**
	 * 初始化和游戏相关的数据
	 */
	private void initGameData() {
		//构造蛇的对象
		snake = new Snake();
		//初始化游戏的主线程
		gameThread = new GameThread(true);
		//让游戏的主线程开始运行
		gameThread.start();
	}
     到这里呢,基本上我们的项目已经完成了,最后有一个细节:我们需要修改paint()方法:

        /**
	 * 绘制界面的方法
	 */
	@Override
	public void paint(Graphics g) {
		//将窗口清空
		g.clearRect(0, 0, WINDOW_WIDTH, WINDOW_HEIGHT);
		//绘制蛇的对象
		snake.drawMe(g);
	}
我们每次绘制之前都需要将窗口清空,要不然你会发现你的蛇会不断边长,原因是什么呢,就是以前绘制的那些数据没有被清除。
      至此,你就可以点击运行试试看了,是不是我们的蛇就开始运动了,不过貌似我们的蛇现在一路向东,撞了东墙都不回头,这个呢,我们在下一个版本中进行修复,好了,就到这吧。
      我把代码都放在迅雷快传上了,大家奔走相告吧,这里给出链接,一篇博文对应一个项目文件,大家直接导入工程就可以运行:
http://kuai.xunlei.com/d/nNdkCUf1HBXTUAQA44e


猜你喜欢

转载自my.oschina.net/u/554627/blog/97343