简单java小游戏的实现



         
简单java小游戏的实现

               ——蓝杰实训1016项目组·吴少聪

1628行代码能做什么……或许实现个功能完善的画图板,或许,做个人工算法比较全面和智能化的五子棋,但是这次,用1628行代码实现一个简单的java小游戏。

 



 

 

一个简单的弹球小游戏,开发的思想如下:

1、基础的游戏开发思路的确定,确定游戏开发的方向;

2、简要估计游戏所需的技术点;

3、对于游戏的总体,包括:界面、规则、功能等方面有个比较清晰的构思;

4、尝试性编写,不断发现技术点难题,并逐步解决;

5、统筹类与变量数据之间的关系,权衡性能与游戏性,做好传值;

6、基本完成之后,进行后期美工修饰;

    

就这么多吧,大致的思路就在这,其实写的代码量远远不止1628,尝试的时候,不断的更改,删删减减是常事,往往在论坛上看到前辈们对于某种功能实现的便捷方法之后,便忍痛ctrl+D把自己写的大删特删……代码的灵感往往也在不经意之间,可能就是一瞬间想到的一个方法就能突破之前一直纠结的瓶颈问题,哪怕一个数据类型的改变也往往可以化腐朽为神奇……程序就是不断修改出来的。

 

 

一个简单的弹球游戏,其实能说的太多了,挑关键的说说吧,也是一开始一直纠结的。

第一个问题:如何实现砖块?

嘿嘿,这个问题看似简单,实则需要好好的权衡,写游戏前,我看了很多前辈写的代码,对于砖块的写法一般有两个方向:线程方向和单一二维数组图像对象方向。

首先先说说线程,由于线程的实现比较熟悉,所以一开始的游戏的初始版本我也是才用线程的方式编写砖块,后来发现有个弊端,屏幕中每一个存在的砖块都是一个线程,同一个界面之内存在着不少于六十块砖块,这就意味着,光是砖块部分,就有不少于六十个线程同时在运行,java由于是在虚拟机上运行,六十个线程同时带动……哪怕定义10ms刷新一次界面,整个效果还是异常的卡,尽管线程对象拥有着诸多好处,但是对于java虚拟机,同时带动这么多的线程,还是个不轻的负担,弹球游戏关键在于碰撞的判断,尤其由于无意间发现了一些实现碰撞的好方法,用Rectangle2D对象可实现(后面会提及)。

接着就是使用二维数组确定图像对象的方法,这个方法简单易行,类似于五子棋棋盘的绘制,砖块其实就是二维数组之中随机的位置。

砖块的相关属性定义如下:

 

public class CreateGameBrick implements BallConfig {
	// 设置砖块矩阵左上角点的坐标位置
	static int BRICK_OX = 8;
    static int BRICK_OY = 1;
	// 设置每一块砖块真正的坐标位置
	int BRICK_TRUE_X = 20;
	int BRICK_TRUE_Y = 1;
	// 设置砖块矩阵的横竖行数量
	static int BRICK_ROWS = 25;
	static int BRICK_COLUMNS = 16;
	// 设置矩形砖块的规格
	static int BRICK_WIDTH = 72;
	static int BRICK_HENGHT = 33;
	// 设置矩形砖块的身份辨识ID号
	double BRICK_ID = 0;
	// 砖块间的空隙其实也是二维数组中的一部分,所以也要设置空隙的大小
	static int INTERSPACEf_WIDTH = BRICK_WIDTH;
	static int INTERSPACEf_HENGHT = 2;// 第一种空隙的规格,此空隙处于上下砖块之间
	static int INTERSPACEs_HENGHT = BRICK_HENGHT;
	static int INTERSPACEs_WIDTH = 2;// 第二种空隙的规格,此空隙处于左右砖块之间
	// 创建砖块矩阵的二维数组
	static int Brick[][] = new int[BRICK_ROWS][BRICK_COLUMNS];
	// 创建一个随机数,用来随机在砖块矩阵中创建砖块对象
		static private java.util.Random judge = new java.util.Random();

 

值得关注一下的就是……当我们需要在砖块矩阵中看到砖块与砖块之间不是紧紧贴着的,而是存在空隙的,那么我们就必须把空隙也考虑到最初构造的二维数组之中。

二维数组的下标,与相应的属性做简单的加减乘除便是砖块的坐标,而二维数组中存储的数据则是砖块的ID编号,标明这是什么类型的砖块,有什么特性。

 

/**
	 * 定义随机在砖块矩阵二维数组中创建砖块的方法
	 * 
	 * @return Brick二维数组
	 */
	public static int[][] createBrick() {
		if (!isStop) {
			// 遍历矩形砖块数组
			for (int i = 0; i < BRICK_ROWS; i++) {
				for (int j = 0; j < BRICK_COLUMNS; j++) {
					// 给所有的砖块位置赋值-1,代表不存在砖块
					Brick[i][j] = -brickChange;
				}
			}
		}
		while (brickNum < 80 && comBrickNum < 35) {
			// 随机普通砖块在矩阵中出现的横竖坐标
			int JUDGE_X = judge.nextInt(BRICK_ROWS);
			int JUDGE_Y = judge.nextInt(BRICK_COLUMNS);
			// 只有矩阵数组中符合特殊条件才能放上砖块,
			// 由于使用了计数器brickNum,所以其一直会循环直至生成所有满足位置条件相应数量的砖块
			if (JUDGE_X % 2 == 0 && JUDGE_Y % 2 == 0
					&& Brick[JUDGE_X][JUDGE_Y] == -brickChange
					&& Brick[JUDGE_X][JUDGE_Y] != brickChange) {
				// 在相应的随机坐标处改变赋值,使得普通砖块存在
				Brick[JUDGE_X][JUDGE_Y] = brickChange;
				// 每完成一次循环就将计数器comBrickNum加1
				comBrickNum++;
				brickNum++;
				isStop = true;

			}

		}
		while (specialBrickNum1 < 10) {
			// 随机特殊砖块1在矩阵中出现的横竖坐标
			int SPECIAL_1_X = judge.nextInt(BRICK_ROWS);
			int SPECIAL_1_Y = judge.nextInt(BRICK_COLUMNS);
			// 当符合放置条件且该位置未放置任何砖块时才能放置特殊砖块1
			if (SPECIAL_1_X % 2 == 0 && SPECIAL_1_Y % 2 == 0
					&& Brick[SPECIAL_1_X][SPECIAL_1_Y] == -brickChange
					&& Brick[SPECIAL_1_X][SPECIAL_1_Y] != brickChange
					&& Brick[SPECIAL_1_X][SPECIAL_1_Y] != brickChange * 2
					&& comBrickNum == 35) {
				Brick[SPECIAL_1_X][SPECIAL_1_Y] = brickChange * 2;
				specialBrickNum1++;
				brickNum++;
			}
		}
		while (specialBrickNum2 < 10) {
			// 随机特殊砖块2在矩阵中出现的横竖坐标
			int SPECIAL_2_X = judge.nextInt(BRICK_ROWS);
			int SPECIAL_2_Y = judge.nextInt(BRICK_COLUMNS);
			// 当符合放置条件且该位置未放置任何砖块时才能放置特殊砖块2
			if (SPECIAL_2_X % 2 == 0 && SPECIAL_2_Y % 2 == 0
					&& Brick[SPECIAL_2_X][SPECIAL_2_Y] == -brickChange
					&& Brick[SPECIAL_2_X][SPECIAL_2_Y] != brickChange
					&& Brick[SPECIAL_2_X][SPECIAL_2_Y] != brickChange * 2
					&& Brick[SPECIAL_2_X][SPECIAL_2_Y] != brickChange * 3
					&& Brick[SPECIAL_2_X][SPECIAL_2_Y] != brickChange * 4
					&& comBrickNum == 35 && specialBrickNum1 == 10) {
				Brick[SPECIAL_2_X][SPECIAL_2_Y] = brickChange * 3;
				specialBrickNum2++;
				brickNum++;
			}
		}
		while (specialBrickNum3 < 5) {
			// 随机特殊砖块3在矩阵中出现的横竖坐标
			int SPECIAL_3_X = judge.nextInt(BRICK_ROWS);
			int SPECIAL_3_Y = judge.nextInt(BRICK_COLUMNS);
			// 当符合放置条件且该位置未放置任何砖块时才能放置特殊砖块3
			if (SPECIAL_3_X % 2 == 0 && SPECIAL_3_Y % 2 == 0
					&& Brick[SPECIAL_3_X][SPECIAL_3_Y] == -brickChange
					&& Brick[SPECIAL_3_X][SPECIAL_3_Y] != brickChange
					&& Brick[SPECIAL_3_X][SPECIAL_3_Y] != brickChange * 2
					&& Brick[SPECIAL_3_X][SPECIAL_3_Y] != brickChange * 3
					&& Brick[SPECIAL_3_X][SPECIAL_3_Y] != -brickChange * 4
					&& comBrickNum == 35 && specialBrickNum1 == 10
					&& specialBrickNum2 == 10) {
				Brick[SPECIAL_3_X][SPECIAL_3_Y] = brickChange * 4;
				specialBrickNum3++;
				brickNum++;
			}
		}
		while (specialBrickNum4 < 8) {
			// 随机特殊砖块4在矩阵中出现的横竖坐标
			int SPECIAL_4_X = judge.nextInt(BRICK_ROWS);
			int SPECIAL_4_Y = judge.nextInt(BRICK_COLUMNS);
			// 当符合放置条件且该位置未放置任何砖块时才能放置特殊砖块4
			if (SPECIAL_4_X % 2 == 0 && SPECIAL_4_Y % 2 == 0
					&& Brick[SPECIAL_4_X][SPECIAL_4_Y] == -brickChange
					&& Brick[SPECIAL_4_X][SPECIAL_4_Y] != brickChange
					&& Brick[SPECIAL_4_X][SPECIAL_4_Y] != brickChange * 2
					&& Brick[SPECIAL_4_X][SPECIAL_4_Y] != brickChange * 3
					&& Brick[SPECIAL_4_X][SPECIAL_4_Y] != -brickChange * 4
					&& comBrickNum == 35 && specialBrickNum1 == 10
					&& specialBrickNum2 == 10 && specialBrickNum3 == 5) {
				Brick[SPECIAL_4_X][SPECIAL_4_Y] = brickChange * 5;
				specialBrickNum4++;
				brickNum++;
			}
		}
		// 返回经过更改的二维数组对象
		return Brick;
		}
//随机性生成各种砖块,但是要对各种类砖块的数量做相应的限定。
//接下来,就是在主面板类中,交由paint方法画出相应的砖块:
// 取出砖块队列中ID为1的砖块,绘制普通砖块
	 if (tureBrick[i][j] == 1) {
								Rectangle2D.Double drawBrick = new Rectangle2D.Double(
										CreateGameBrick.BRICK_OX
												+ CreateGameBrick.BRICK_WIDTH
												* (i / 2)
											+ CreateGameBrick.INTERSPACEs_WIDTH
												* (i / 2),
										CreateGameBrick.BRICK_OY
												+ CreateGameBrick.BRICK_HENGHT
												* (j / 2)
												+ CreateGameBrick.INTERSPACEf_HENGHT
												* (j / 2),
										CreateGameBrick.BRICK_WIDTH,
										CreateGameBrick.BRICK_HENGHT);
								ImageIcon ImageIcon1 = new ImageIcon(
										"特殊砖块一.png");
								gs.drawImage(
										ImageIcon1.getImage(),
										CreateGameBrick.BRICK_OX
												+ CreateGameBrick.BRICK_WIDTH
												* (i / 2)
												+ CreateGameBrick.INTERSPACEs_WIDTH
												* (i / 2),
										CreateGameBrick.BRICK_OY
												+ CreateGameBrick.BRICK_HENGHT
												* (j / 2)
												+ CreateGameBrick.INTERSPACEf_HENGHT
												* (j / 2),
										CreateGameBrick.BRICK_WIDTH,
										CreateGameBrick.BRICK_HENGHT, null);

 

(仅仅以普通砖块的画法为例,其余的特殊砖块只是贴的图不同而已)

    

    那么接下来,第二个问题:如何进行碰撞的判断?

碰撞的方法在我看来算是这个游戏的代码精华,弹球把砖块消掉,是碰撞;挡板接住弹球,是碰撞;弹球碰到边缘墙壁返回,是碰撞;挡板接到掉落的道具,也是碰撞……这就是个碰撞的游戏……

那么,问题一个个解决,首先就是我修改得最多,也是写得最纠结的球与砖块的碰撞。

不难发现,上述在paint方法中提及到构建Rectangle2D却没有最后用graphics2D对象fill一下,因为我只需要其进行碰撞判断,在JDK说明文档里面,对于Rectangle2D有这么一些方法:

<!--EndFragment--><!--EndFragment-->

 

<!--EndFragment-->



 

 

 
<!--EndFragment-->

不难看出,其实intersects方法是个判断碰撞的神器,最初的版本就是用其进行碰撞判断,但是忽略了JDK中对于其解释的“谨慎”“精确”等字眼……根据方法,碰撞的判断就是一个boolean值,简单的true or false,碰了还是没碰而已……但是测试结果往往不如人意,普通的砖块还好办,在我定义的特殊砖块中有一类是需要连续碰撞方能消掉的砖块……测试中,弹球直接以一次碰撞就将该类砖块碰撞消失……细细分析下来,在于intersects方法精确的效果并不理想,或者说,过于理想……在肉眼看来或许只是一次碰撞,但是在计算机内部以毫秒计算的变化频率下,其实进行了多次碰撞判断,直接一次碰撞却将其消除便很正常不过了……如此一来,对于需要多次碰撞判断方能达到一定效果的目的就达不到了,于是,忍痛舍弃之。(如果关于涉及子弹类或是其余以一次碰撞就达成效果为目的的游戏大可放心使用这个intersects方法)

在舍弃intersects方法之后,我转而关注另一个十分有用的方法:contains但是细细想来,这个方法的使用存在局限性,只是适用于一些特定的碰撞:球与挡板、道具与挡板……这两类碰撞的共同点在于,球是从上方任意方向碰撞挡板,道具也是一样,所以无论球还是道具,率先接触挡板的只有一个点!它们的最下点!而刚好contains方法便是判断点与图形对象的……

但是我最终并未使用,因为想要尝试全部的碰撞都使用同一种判断算法,于是在翻阅JDK说明文档一夜之后,无意间发现了outcode方法。

起初,outcode方法在我看来,返回一个数值用来表示一个点与图形边缘的相对距离,碰撞,只不过就是相对距离为0的情况而已……

遂恍然大悟,痛改代码三百行……

闲话少说,上代码:

<!--EndFragment-->

 

// 进行弹球与挡板碰撞的判定
							double isCrash = drawStick.outcode(ball.BALL_X
									+ ball.BALL_WIDTH / 2, ball.BALL_Y
									+ ball.BALL_HENGHT);
							if (isCrash <= 0) {
								ball.BALL_Y = ball.BALL_Y - 2;
								ball.CHANGE_Y = -ball.CHANGE_Y;
							}
							// 进行弹球与超级挡板碰撞的判定
							double isCrash1 = superStick5.outcode(ball.BALL_X
									+ ball.BALL_WIDTH / 2, ball.BALL_Y
									+ ball.BALL_HENGHT);
							if (isCrash1 <= 0) {
								ball.BALL_Y = ball.BALL_Y - 2;
								ball.CHANGE_Y = -ball.CHANGE_Y;
							}
							double isCrash2 = superStick6.outcode(ball.BALL_X
									+ ball.BALL_WIDTH / 2, ball.BALL_Y
									+ ball.BALL_HENGHT);
							if (isCrash2 <= 0) {
								ball.BALL_Y = ball.BALL_Y - 2;
								ball.CHANGE_Y = -ball.CHANGE_Y;
								}
	

  

谨慎起见,在碰撞判定的瞬间,将弹球按相应方向小小地平移几个像素,防止了由于后台线程运算频率过高导致多次判断的发生……

 

由于道具和弹球相对于挡板的碰撞都是由上方向到下方向的……所以只需要判断一个方向的,而弹球和砖块的碰撞则可能是各个方向的,故必须根据坐标再来,所谓弹球,就是一个平面圆,所谓平面圆,就是一个看不到的正方形的内接圆,于是这个圆与正方形存在四个内接点,对应上下左右,由于对角的判断至今没有想到什么太好的方法实现,所以当球相对于挡板的大小处于一定范围内时,碰到砖块四个边角的几率不会很大,日后再做改进,现在只考虑四个基本方向的碰撞:

以普通砖块的碰撞为例:

 

<!--EndFragment-->

 // 上端点的碰撞判断
								double isCrashUp = drawBrick.outcode(
										ball.BALL_X + ball.BALL_WIDTH / 2,
										ball.BALL_Y);
								// 下端点的碰撞判断
								double isCrashDown = drawBrick.outcode(
										ball.BALL_X + ball.BALL_WIDTH / 2,
										ball.BALL_Y + ball.BALL_HENGHT);
								// 左端点的碰撞判断
								double isCrashLeft = drawBrick.outcode(
										ball.BALL_X, ball.BALL_Y
												+ ball.BALL_HENGHT / 2);
								// 右端点的碰撞判断
								double isCrashRight = drawBrick.outcode(
										ball.BALL_X + ball.BALL_WIDTH,
										ball.BALL_Y + ball.BALL_HENGHT / 2);
								if (isCrashUp <= 0) {
									ball.BALL_Y = ball.BALL_Y + 1;
									ball.CHANGE_Y = -ball.CHANGE_Y;
									score = score + tureBrick[i][j] * 10;
									tureBrick[i][j] = -1;
									CreateGameBrick.brickNum--;

								} else if (isCrashDown <= 0) {
									ball.BALL_Y = ball.BALL_Y - 1;
									ball.CHANGE_Y = -ball.CHANGE_Y;
									score = score + tureBrick[i][j] * 10;
									tureBrick[i][j] = -1;
									CreateGameBrick.brickNum--;
								} else if (isCrashLeft <= 0) {
									ball.BALL_X = ball.BALL_X + 1;
									ball.CHANGE_X = -ball.CHANGE_X;
									score = score + tureBrick[i][j] * 10;
									tureBrick[i][j] = -1;
									CreateGameBrick.brickNum--;
								} else if (isCrashRight <= 0) {
									ball.BALL_X = ball.BALL_X - 1;
									ball.CHANGE_X = -ball.CHANGE_X;
									score = score + tureBrick[i][j] * 10;
									tureBrick[i][j] = -1;
									CreateGameBrick.brickNum--;
									}

  

(在我的定义中,二维数组中为-1的地方不画出任何图形,即为空隙和空白砖块,所谓碰撞后讲砖块击碎,就是将其对应的二维数组中存储的数值变为-1,计算机便不会再将其画出来……

 



 
<!--EndFragment-->

顺便一提,想要做切水果游戏的同志们记得用用intersectsLine方法哦……具体就不解释了……大家伙自己看哈。

 

最后总结总结,写个游戏真不容易,各种需要考虑,各种要纠结,但是写出来也是很值得高兴的,尤其对于我这种初学java的,真的感觉到,洋洋洒洒写几百上千行不报错真的是种境界……好程序都是改出来的,没有程序是可以一气呵成的,问题都在过程中,创意和发现也在过程中,迈出一步,或许会发现走过的路没有踏实,于是还得回去修修补补,有需要就去写,动手写之前,永远算不出到时候具体还需要些什么。一条路,只有走了才会知道它是平坦还是曲折……

 

  • 3
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值