软件工程第二次二人协作项目 3D俄罗斯方块

来源:互联网 发布:淘宝客如何做淘口令 编辑:程序博客网 时间:2024/05/22 12:33

开发背景:

完成软件工程任务,提高自己解决问题的实际能力,适应二人协作开发的模式,互助进取。 本次作业将完成一个3D版本的俄罗斯方块的小游戏开发。

开发组员: 

张羿  2012211841  宋浩达 2012211896 


Unity3D,3D开发软件    IDE工具:选用Unity3D软件自带的 MonoDevelop工具 

     


采用的脚本语言是C#,操作系统 win8.1

任务分析:

俄罗斯方块是从小就接触的一个游戏,曾经我也学着做过一个java版本的平面俄罗斯方块游戏:

所以游戏算法逻辑还是很了解的,我还记得当时在JAVA平面中开发俄罗斯方块的时候,需要用图形绘制的类绘制出一些方块图形,用一个二维的逻辑矩阵来表示游戏的逻辑,所谓的游戏逻辑就是用二维的数组矩阵(0或1)来表示此位置是否有方块(在后面我会用图形解释),将方块和游戏逻辑分开,就可以对我们的游戏方块的位置进行抽象,方块的移动靠的是java中对图形的重新绘制,间隔一定的时间对图形进行重新绘制,对于边界判断,障碍物的碰撞,就是用逻辑的矩阵来实现。


在3D中,并没有Java那样需要事件处理机制,线程,等相关技术,所以也没有需要那么多个类,代码相对简单。主要的技术难点也是在于图像的边界判断,和java不同的是,3D中图形的变化不是重新绘制图形,而是通过Unity中的一个重要的Transform 类 通过对 Position, Rantation Scale 

三个属性的 操作来实现游戏组件的动作。在3D中,任何一个游戏组件都有这个组件,不管是用户视角的摄像机,还是 一块地板,还是一个方块。

项目中, 我们设定了三个类:

Block用于表示一个方块的各种属性和操作,Manager 用于游戏逻辑控制和运行,GUIController 用于2D界面的按钮控制。

具体实现:

要实现一个3D俄罗斯方块需要做到以下几点:

在出,3D中,我的游戏思路是: 在空间当中构造出一个,类似二维的立体的平面,摄像机正对平面,构造出一个类似槽的东西,在空间中约束方块的活动返回。


                                                                                                                


它由三个面板构成:

底面:Ground   leftWall  rightWall

                                                  

这是只是在空间当中的建模,然而在程序当中需要用数据结构来表示面板,面板具有表示方块是否在位的能力,方块的变换是需要基于面板的,所以在程序中用了一个二维数组来表示面板:

<span style="font-family:Microsoft YaHei;font-size:12px;">for (int i = 0;i < fieldHeight;i++){for (int j =0 ;j < maxBlockSize;j++){fields[j, i] = true;fields[fieldWidth -1 - j, i] = true;}}for (int i=0;i<fieldWidth;i++){fields[i, 0] = true;}</span>


通过此算法 得出的面板数据结构及值为下图表示:


我开始自己的思路是边上只用宽度为1来判断,参考了网上此算法后,才觉得用size为4比较合理。 其中 10为宽度 with 14为高度 方块所能运行的空间就是中间值为0的白色区域。底面是用于逻辑判断的。



2,方块的表示:

俄罗斯方块一共有7类图

            

由于 在游戏中 图形需要变换,第一个图 和第二图 以及 第三个图和第四个图是不一样的。所以 在程序设计中,俄罗斯方块其实一共有7种图形。不是我们认为的5种。

      每种图形有四个小正方形构成,曾经我在java平面开发当中,用的是java中的图形绘制的一个类来绘制方块,是用一个4*4的二维数组来表示一个图形的样子。

在Unity3d中采用了另外一种解决方案:

00

00

表示第7个图形。

011

010

010

 为一个3*3的矩阵   就可以表示第一个图形。

0000

1111

0000

0000

同样为一个4*4的正方形矩阵,表示的是第6个图形。

      

因此用一个正方的矩阵来表示一个图形是很恰当的。然而在3D中,表示一个图形,不可能是简单的填充矩形。在Unity 我们操作的就是一个实际的立体的方块(Cube):这是我随意建立的一个方块模型,由这样四个小方块将组成一个图形:

                                          

                                                             

它被称作一个游戏物体 GameObject 我们可以对这个游戏物体的各种信息进行修改,位置,移动,纹理等。但在游戏当中,我们需要通过程序控制,实现对游戏物体的操控。控制一个方块的旋转,下移,加速下移,停止,消去等等。

表示图形的代码:

<span style="font-family:Microsoft YaHei;font-size:12px;">//固定halfSsize 和 childSize大小 halfSize = (size + 1) * 0.5f;//halfsize = 2 childSize = (size - 1) * 0.5f;  //childsize=1halfSizeFloat = size * .5f;  //定位屏幕位置.//将字符串数组矩阵抓换成bool型的矩阵 1 表示有方块.blockMatrix = new bool[size, size];for(int y=0;y<size;y++){for(int x=0;x<size;x++){if (block[y][x] == '1'){//如果为1 就生成小方块blockMatrix[y, x] = true;//实例化一个方块出来.  克隆原始物体并返回克隆物体  Vector3 位置  Quatenion.identity 旋转    var cube = (Transform)Instantiate(Manager.manager.cube, new Vector3(x - childSize, childSize - y, 0), Quaternion.identity); //将矩阵信息改变成位置信息     cube.parent = transform; }}}</span>

2方块的变形和移动:

首先是方块的下落:

Unity中物体的移动,是很方便的,只需要改变游戏物体的Transform ,加上程序控制,就可以让我们的小方块动起来了。

在Unity的C#中,游戏的下落是可以通过Update()方法来实现,也可以通过IEnumerator方案来实现,参考3D开发前辈们的实现方式,采用IEumerator方案实现:在while(true)循环里面使用yield,满足特定条件时跳出循环,设置一个跳出的特定条件,而且是程序会最终执行到那一步,不然,程序会陷入死循环

算法代码:

<span style="font-family:Microsoft YaHei;font-size:12px;">IEnumerator Fall(){while(true){yPosition--;if (Manager.manager.CheckBlock(blockMatrix, xPosition, yPosition)){Manager.manager.SetBlock(blockMatrix, xPosition, yPosition + 1);Destroy(gameObject);break;}for (float i = yPosition + 1;i > yPosition;i -= Time.deltaTime * fallSpeed){transform.position = new Vector3(transform.position.x, i - childSize, transform.position.z);yield return null;  //迭代器中取得数据立即返回 提高了遍历效率}}}</span>
图形的旋转:
图形的旋转,也是需要修改逻辑矩阵中01的值,通过检查方块周围是否有障碍物来判断是否可以旋转,也就是通过 01 true false 逻辑判断的矩阵来判断是否可以旋转。

主要代码如下:

<span style="font-family:Microsoft YaHei;font-size:12px;">void RotateBlock(){//修改逻辑矩阵中的值var tempMatrix = new bool[size, size];    for (int y = 0; y < size; y++) {     for (int x = 0; x < size; x++) {          tempMatrix[y, x] = blockMatrix[x, (size-1)-y];         }}//根据CheckBlock 旋转90度 如果周围没有方块则可以旋转if (!Manager.manager.CheckBlock(tempMatrix, xPosition, yPosition)){System.Array.Copy(tempMatrix, blockMatrix, size * size);transform.Rotate(0, 0, 90);//旋转90度}}</span>

图形的移动:
图形的移动主要是针对图形的左右移动,和向下加速:

<span style="font-family:Microsoft YaHei;font-size:12px;">IEnumerator CheckInput(){while(true){var input = Input.GetAxisRaw("Horizontal");if (input < 0){yield return StartCoroutine(MoveHorizontal(-1));}if (input > 0){yield return StartCoroutine(MoveHorizontal(1));}if (Input.GetKeyDown(KeyCode.UpArrow)){RotateBlock();}if (Input.GetKeyDown(KeyCode.DownArrow)){fallSpeed = Manager.manager.blockDropSpeed;drop = true;}if (Input.GetKeyUp("space")){fallSpeed = Manager.manager.blockNormalFallSpeed;drop = false;}yield return null;}}</span>

3边界的判断和消除:

检查方块的算法:

<span style="font-family:Microsoft YaHei;font-size:12px;">public bool CheckBlock(bool [,] blockMatrix, int xPos, int yPos){var size = blockMatrix.GetLength(0);for (int y = 0;y < size;y++){for (int x = 0;x < size;x++){if (blockMatrix[y, x] && fields[xPos + x, yPos - y]){return true;}}}return false;}</span>
满行的判断和消除
<span style="font-family:Microsoft YaHei;font-size:12px;">//判断每行IEnumerator CheckRows(int yStart, int size){yield return null;if (yStart < 1)yStart = 1;int count = 1;for (int y = yStart;y < yStart + size;y++){int x;for (x = maxBlockSize;x < fieldWidth - maxBlockSize;x++){if (!fields[x, y]){break;}}if (x == fieldWidth - maxBlockSize){yield return StartCoroutine(SetRows(y));Score += 10 * count;y--;count++;}}CreateBlock(blockRandom);}IEnumerator SetRows(int yStart){for (int y = yStart;y < fieldHeight - 1;y++){for (int x = maxBlockSize;x < fieldWidth - maxBlockSize;x++){fields[x, y] = fields[x, y + 1];}}for (int x = maxBlockSize;x < fieldWidth - maxBlockSize;x++){fields[x, fieldHeight - 1] = false;}var cubes = GameObject.FindGameObjectsWithTag("Cube");int cubeToMove = 0;for (int i = 0;i < cubes.Length;i++){GameObject cube = cubes[i];if (cube.transform.position.y > yStart){cubeYposition[cubeToMove] = (int)(cube.transform.position.y);cubeTransforms[cubeToMove++] = cube.transform;}else if (cube.transform.position.y == yStart){Destroy(cube);}}float t = 0;while (t <= 1f){t += Time.deltaTime * 5f;for(int i = 0;i < cubeToMove;i++){cubeTransforms[i].position = new Vector3(cubeTransforms[i].position.x, Mathf.Lerp(cubeYposition[i], cubeYposition[i] - 1, t),cubeTransforms[i].position.z);}    yield return null;}if (++clearTimes == TimeToAddSpeed){blockNormalFallSpeed += addSpeed;clearTimes = 0;}}</span>





信息提示:

OnGUI函数,相当于摄像机是一个平面:

<span style="font-family:Microsoft YaHei;font-size:12px;">void OnGUI(){GUI.BeginGroup (new Rect(10,Screen.height/8*0.5f,100,100));//采用相对布局 适应屏幕的变化GUI.Label (new Rect(0,10,100,40),".组员:张羿,宋浩达");GUI.Label(new Rect(0, 30, 80, 40),"分数:");GUI.Label(new Rect(80, 30, 100, 40),Score.ToString());GUI.Label(new Rect(0, 50, 80, 40),"最高分:");GUI.Label(new Rect(80, 50, 80, 40),Highest.ToString());GUI.EndGroup (); //设置颜色大小 for (int y = 0;y < nextSize;y++){for (int x = 0;x < nextSize;x++){if (nextblock[y][x] == '1'){GUI.Button(new Rect(180 + 30 * x, 100 + 30 * y, 30, 30), cubeTexture);}}}}</span>
如图:

分数的记录和最高分的记录:

manager类中:

<span style="font-family:Microsoft YaHei;font-size:14px;">void Start () {if (manager == null){manager = this;}//游戏存档.if (PlayerPrefs.HasKey("最高分")){Highest = PlayerPrefs.GetInt("最高分");}else{PlayerPrefs.SetInt("最高分", 0);}</span>

程序运行时,Start()函数就会加载,此时就从Unity的游戏存档中读取最高分,如果没有最高分则初始化最高分为0。

背景的添加:


图中的三维坐标系的原点就是摄像机的位置,蓝色方向就是摄像机的方向,也就是我们的视野,图中右下角就是我们眼中的游戏界面,添加游戏背景有两种方法:

1,在游戏槽的后面,也就是摄像机的正前方添加一块Plan(一块挡板),将一张2D的图片,当做纹理附着在面板上。让图片充满整个摄像机的视野。

2,第二种方法就是 我采用的方法,在摄像机游戏物体上添加一个天空盒子的组件:




可以按照我们的需要添加不同的天空


如此以来 游戏变得有背景了。

之后为游戏添加背景音乐:



最后在OnGUIController中添加游戏控制:

<span style="font-family:Microsoft YaHei;font-size:12px;">void OnGUI(){<span style="white-space: pre;"></span>//定位屏幕中的位置GUI.BeginGroup (new Rect(Screen.width/2+Screen.width/4,Screen.height/4-Screen.height/8,120,300));if (GUI.Button (new Rect (0, 20, 100, 30), "重新开始游戏"))Application.LoadLevel(0);if (GUI.Button (new Rect (0, 60, 100, 30), "暂停游戏"))PauseGame ();if (GUI.Button (new Rect (0, 100, 100, 30), "继续游戏"))StartGame ();if (GUI.Button (new Rect (0, 140, 100, 30), "播放背景音乐"))  audio.Play ();  if (GUI.Button (new Rect (0, 180, 100, 30), "暂停背景音乐"))  audio.Pause ();  if (GUI.Button (new Rect (0, 220, 100, 30), "停止背景音乐"))  audio.Stop ();  GUI.EndGroup();}void StartGame(){IsGamePaused = false;Time.timeScale = 1;//Debug.Log("Start Game" + Time.fixedTime);}void PauseGame(){IsGamePaused = true;Time.timeScale = 0;//Debug.Log("Pause Game");}</span>


最后添加面板的纹理,使槽看起来不像最开始的那么苍白:

                                

                                               


最后打包生成PC windows平台游戏:


遇到的问题:

    在开发过程中,遇到的问题很多,主要分为两个方面:实现方式,和实现过程。

在实现方式上,方块的边界判断方式,和方块的消行方式。是我们解决的重点,也是靠做过Unity开发的前辈代码的指点。

而在实现过程上,主要是坐标的定位,会容易出错,因为是三维坐标,坐标定位的准确,和坐标的变化是难点,这是学习了别人的算法明白的,比如:

在代码中:

<span style="font-family:Microsoft YaHei;"><span style="font-size:14px;"></span><span style="font-size:12px;">//固定halfSsize 和 childSize大小 halfSize = (size + 1) * 0.5f;//halfsize = 2 childSize = (size - 1) * 0.5f;  //childsize=1halfSizeFloat = size * .5f;  //定位屏幕位置.//将字符串数组矩阵抓换成bool型的矩阵 1 表示有方块.blockMatrix = new bool[size, size];for(int y=0;y<size;y++){for(int x=0;x<size;x++){//这是哪里赋值if (block[y][x] == '1'){blockMatrix[y, x] = true;//实例化一个方块出来.  克隆原始物体并返回克隆物体  Vector3 位置  Quatenion.identity 旋转    var cube = (Transform)Instantiate(Manager.manager.cube, new Vector3(x - childSize, childSize - y, 0), Quaternion.identity); //将矩阵信息改变成位置信息     cube.parent = transform; //设置 cube 的父级为transfrom 相对于transform来变换 }}}</span></span>
在上断代码中:

如何将Bool形的矩阵定位到屏幕中的位置 通过了两个步骤:

1,halfSize,和ChildSize的取值 

<span style="white-space:pre"></span>halfSize = (size + 1) * 0.5f;//halfsize = 2 childSize = (size - 1) * 0.5f;  //childsize=1
2,vector3的定位

<span style="white-space:pre"></span>var cube = (Transform)Instantiate(Manager.manager.cube, new Vector3(x - childSize, childSize - y, 0), Quaternion.identity); //将矩阵信息改变成位置信息 

比如:Size= 3  也就是: 

边界长度为3   我们可以看出  为 1 的点 在矩阵中的坐标为:  (0,1)  (1,1)    (1,2)   ( 2,2 )  而在空间当中 要以中间的方块为原点生成方块,所以 矩阵的坐标和空间的坐标的映射关系就为:   

<span style="font-family:Microsoft YaHei;font-size:18px;"><span style="white-space:pre"></span>x - childSize, childSize - y, 0</span>

映射结果为   0,1,0) (0, 0, 0) (1,0,1) (1,-1 ,0)  其实就是以中心点为三维的原点坐标。分别取得每个小方块左前下角的世界坐标。

游戏扩展思路:

方案1,将游戏改为多层的俄罗斯方块,方块不仅可以左右移动还可以前后移动,图像的变形方式 将改为 树立和颠倒和旋转,也就是图形可以360度旋转。

方案2:  设置为空间俄罗斯方块,有三个面板,左侧,右侧,和底面,分别从三个面的前方掉落方块,同时对三个面方块进行游戏,会很大程度的增加游戏难度,但会给人带来既那么熟悉,又不乏乐趣的游戏和视觉体验。


0 0