我们继续做我们的扫雷,这一节我们要实现:鼠标划过格子会发光;添加放旗子的功能。
在上一节中,我们创建了扫雷的格子区域。这节我们会让他们能够玩起来。没有看过之前的教程的请点击 * 返回上一教程。
鼠标悬停(格子)高亮
当鼠标在格子上划过时,我们想要让格子亮起来。配合着简单的事件响应(比如鼠标悬停),让格子亮起来,这肯定能成为游戏的一大亮点。
我们调用OnMouseOver函数(原生函数)来实现这一功能。当鼠标悬停在本代码绑定的物体上时便会自动调用这个函数。在Tile脚本中添加以下变量:
| public var materialIdle: Material; public var materialLightup: Material; |
然后用格子的材质赋给materialidle变量。我们还需要一个高亮的材质,它跟materialidle颜色一样,但是用不同的shader。基础材质我们可以选择Diffuse着色器。
而高光材质可以选择Specular着色器。很多游戏使用一个附加边缘着色器来实现这样的效果。这些并非Unity原生的,但是如果你想知道如何搞到一个,你可以用这个代替。
别忘了把材质赋给格子材质,这样他们才能被使用。
在Tile脚本中添加OnMouseOver函数。
| function OnMouseOver() { renderer.material = materialLightup; } |
试一下:当你用鼠标滑过格子,他们便会改变模样。而且你应该注意到格子改变后,变不回来了。对于这个问题,我们还得添加OnMouseExit函数:
| function OnMouseExit() { renderer.material = materialIdle; } |
我们已经实现了高亮效果,游戏也变得生动了。
给格子绑定ID
为了让格子彼此间能够通信(主要是知道临近的格子有多少炸弹),每个格子需要临近的格子信息。实现这个方法就是使用ID,每个格子都得赋一个ID.
| public var ID: int; public var tilesPerRow: int; |
然后更新Grid代码中的实例命令,就像下面代码块一样(创建之时每个格子即被赋予新的ID):
| var newTile = Instantiate(tilePrefab, Vector3(transform.position.x + xOffset, transform.position.y, transform.position.z + zOffset), transform.rotation); newTile.ID = tilesCreated; newTile.tilesPerRow = tilesPerRow; |
第一个格子ID被赋值为0,第二个被赋值为1,以此类推。当然你也可以在运行时候点击来检查ID,看看被赋值多少。
获取临近的格子
现在我们想实现当执行一个动作(比如去翻格子),每个格子就能知道临近格子的信息,因此我们得把临近的格子都得记录一下。
在我们的实例中,这就得“数数”翻中的格子周围的炸弹数,对于其他的格子也是这么设定,这个之后再实现。
在Tile脚本中添加以下变量:
| public var tileUpper: Tile; public var tileLower: Tile; public var tileLeft: Tile; public var tileRight: Tile;
public var tileUpperRight: Tile; public var tileUpperLeft: Tile; public var tileLowerRight: Tile; public var tileLowerLeft: Tile; |
这样就能够掌握所有的临近的格子的信息。因为这些变量被设定为公有,所以在程序运行时我们可以直观而准确的看到这些变量的值。
现在,所有的格子都有了ID,格子的ID排在一列显示,通过访问Grid类中保存着所有格子信息的静态数组,我们能够在他们被创建后计算出临近的格子的位置信息。
代码部分如下:
| tileUpper = Grid.tilesAll[ID + tilesPerRow]; tileLower = Grid.tilesAll[ID - tilesPerRow]; tileLeft = Grid.tilesAll[ID - 1]; tileRight = Grid.tilesAll[ID + 1]; tileUpperRight = Grid.tilesAll[ID + tilesPerRow + 1]; tileUpperLeft = Grid.tilesAll[ID + tilesPerRow - 1]; tileLowerRight = Grid.tilesAll[ID - tilesPerRow + 1]; tileLowerLeft = Grid.tilesAll[ID - tilesPerRow - 1]; |
有了格子的ID个位于每行的次序数,我们就能计算出临近的格子的信息。假如一个格子的ID为3,而每行有5个格子,上面格子的ID就是8,最右边的格子ID就是6,依次类推。
不过,这还是不行。代码是会计算出数量,但是当它访问allTile数组时,所请求的索引数将会越界,造成错误。
为了防止这种情况发生,我们需要检测请求的数组是否有效。最好的方法就是重新写一个inBounds方法来检测,将其添加到Tile脚本中:
| private function inBounds(inputArray: Array, targetID: int): boolean { if(targetID < 0 || targetID >= inputArray.length) return false; else return true; } |
现在我们必须在获取到格子信息前检查每个相邻的格子,以保证他们全都在保存格子信息的数组中。
| if(inBounds(Grid.tilesAll, ID + tilesPerRow)) tileUpper = Grid.tilesAll[ID + tilesPerRow]; if(inBounds(Grid.tilesAll, ID - tilesPerRow)) tileLower = Grid.tilesAll[ID - tilesPerRow]; if(inBounds(Grid.tilesAll, ID - 1) && ID % tilesPerRow != 0) tileLeft = Grid.tilesAll[ID - 1]; if(inBounds(Grid.tilesAll, ID + 1) && (ID+1) % tilesPerRow != 0) tileRight = Grid.tilesAll[ID + 1]; if(inBounds(Grid.tilesAll, ID + tilesPerRow + 1) && (ID+1) % tilesPerRow != 0) tileUpperRight = Grid.tilesAll[ID + tilesPerRow + 1]; if(inBounds(Grid.tilesAll, ID + tilesPerRow - 1) && ID % tilesPerRow != 0) tileUpperLeft = Grid.tilesAll[ID + tilesPerRow - 1]; if(inBounds(Grid.tilesAll, ID - tilesPerRow + 1) && (ID+1) % tilesPerRow != 0) tileLowerRight = Grid.tilesAll[ID - tilesPerRow + 1]; if(inBounds(Grid.tilesAll, ID - tilesPerRow - 1) && ID % tilesPerRow != 0) tileLowerLeft = Grid.tilesAll[ID - tilesPerRow - 1]; |
这个代码段将会检测所有额的可能性。它也会监测格子是不是在区域的边界,因为网格最右边的格子的右边就没有格子了。试一下,随便检查一些格子看看临近的各自是不是都检索正确,就像这样:
截屏中的格子是处于区域最右边的格子,它的右边相邻,右上,右下的位置是没有格子的。没有被赋值的格子就为空值,图中显示的即为正确的。
最后,当我们确保完成了这项工作后,我们就得把所有临近的格子存到一个数组中,这样我们就能在之后的代码中一次性访问他们。不过我们得在代码的开头声明下这个数组:
| public var adjacentTiles: Array = new Array(); |
然后,您可以调整我们创建的算法,添加该数组中的每个相邻的格子信息,添加以下代码:
| if(tileUpper) adjacentTiles.Push(tileUpper); if(tileLower) adjacentTiles.Push(tileLower); if(tileLeft) adjacentTiles.Push(tileLeft); if(tileRight) adjacentTiles.Push(tileRight); if(tileUpperRight) adjacentTiles.Push(tileUpperRight); if(tileUpperLeft) adjacentTiles.Push(tileUpperLeft); if(tileLowerRight) adjacentTiles.Push(tileLowerRight); if(tileLowerLeft) adjacentTiles.Push(tileLowerLeft); |
计算周围的炸弹
既然格子已经被创建好了,所有的炸弹也被赋值了,而且每个格子也都检索了临近的格子,接下来我们就需要看看周围的格子有没有炸弹了。
Grid代码已经随机给格子赋值特定的炸弹数了。现在我们只需检测它临近的格子。在Tile脚本开始添加这个代码,这样我们就能存储炸弹数了:
| public var adjacentMines: int = 0; |
为了计算炸弹数,我们遍历之前存储临近格子的数组,查看返回的接口是不是有炸弹,如果有,把adjacentMines加1.
01 02 03 04 05 06 07 08 09 10 11 12 13 | function CountMines() { adjacentMines = 0; for each(var currentTile: Tile in adjacentTiles) if(currentTile.isMined)//游戏蛮牛2017 adjacentMines += 1; displayText.text = adjacentMines.ToString();// if(adjacentMines <= 0) displayText.text = ""; } |
这个函数还要设置显示周围炸弹数的文本,如果没有炸弹,就显示0.
追踪炸弹状态
让我们再给每个格子添加一个状态。这样,我们就能记录当前格子的状态---idle,uncovered,或者flagged。格子的响应取决于格子所处状态。先添加变量,一会我们还要用它:
| public var state: String = "idle"; |
添加旗子
我们想实现添加一个旗子给格子做标记。一个被标记过的格子会有一个小旗子在它上面。如果我们鼠标右击格子又会消失。如果所有雷都被标记了旗子,并且没有错误标记的格子,游戏就胜利了。
先搞个旗子,然后把它添加到格子上,成为一个预制物体。
我们还需要一个变量来访问这个物体旗子,代码如下:
| public var displayFlag: GameObject; |
在Tile脚本中Start方法添加这些:
| displayFlag.renderer.enabled = false; displayText.renderer.enabled = false; |
这样开始旗子和文本都不会显示。之后,我们会激活旗子,让它可见,然后放在那儿。如果我们揭开一个格子,我们就让数字可见。
放置,移除旗子
我们写一个方法来实现放置、移除旗子:
01 02 03 04 05 06 07 08 09 10 11 12 13 | function SetFlag() { if(state == "idle") { state = "flagged"; displayFlag.renderer.enabled = true; } else if(state == "flagged") { state = "idle"; displayFlag.renderer.enabled = false; } } |
当我们添加这个后,我们还得添加一个鼠标点击事件。改一下OnMouseOver函数,测试一下:
01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 | function OnMouseOver() { if(state == "idle") { renderer.material = materialLightup; if (Input.GetMouseButtonDown(1)) SetFlag(); } else if(state == "flagged") { renderer.material = materialLightup; if (Input.GetMouseButtonDown(1)) SetFlag(); } } |
这个是会识别鼠标右击,然后调用SetFlag方法。它会激活或者不激活当前格子上的旗子。
总结
我们已经将我们的扫雷扩展了挺多功能,让他们更加有意思,还赋予了玩家能够操作游戏的功能。下一节,我们将添加揭开格子的功能,创建简单的外观。