软件工程第二次二人协作项目 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=12,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: 设置为空间俄罗斯方块,有三个面板,左侧,右侧,和底面,分别从三个面的前方掉落方块,同时对三个面方块进行游戏,会很大程度的增加游戏难度,但会给人带来既那么熟悉,又不乏乐趣的游戏和视觉体验。
- 软件工程第二次二人协作项目 3D俄罗斯方块
- 软件工程第二次作业-3D游戏开发(3D版俄罗斯方块)
- 软件工程作业二——结对编程(用HTML5搭建3D俄罗斯方块)
- 软件工程之二 俄罗斯方块
- 软件工程第二次作业:基于Unity的3D魔方实现
- 第二次作业 项目二
- 软件工程第二次作业(3)
- Unity 3D俄罗斯方块
- 结合directx3D函数库3D视角游戏(软件工程第二次作业)
- C++第二次实验项目二
- c++第二次实验:项目二
- 软件工程结对项目--3D魔方小游戏
- 软件工程第二次作业
- 软件工程第二次作业
- 软件工程第二次作业
- 软件工程第二次作业
- 软件工程第二次作业修改
- 软件工程第二次实验
- Linux 查看内核版本
- 栈的应用--中缀表达式转换为后缀表达式&逆波兰计算器的实现
- Eclipse快捷键大全(转载)
- 报文处理中的主动和被动轮询
- java生成指定长度的随机字符串
- 软件工程第二次二人协作项目 3D俄罗斯方块
- 一些问题
- Asterisk 电话系统的音频文件
- Java程序访问Oracle数据库集群与非集群的不同连接字符串
- Linux下用来获取各种系统信息的C++类
- Eclipse中出现Select at least one project解决办法
- ehcache memcache redis 三大缓存框架
- AndroidStudio INSTALL_PARSE_FAILED_UNEXPECTED_EXCEPTION
- 开放世界游戏中的大地图背后有哪些实现技术?