手把手教你做3D扫雷:开发篇

发表于2017-01-06
评论0 6.6k浏览
  我们继续做我们的扫雷,这一节我们要实现:鼠标划过格子会发光;添加放旗子的功能。
在上一节中,我们创建了扫雷的格子区域。这节我们会让他们能够玩起来。没有看过之前的教程的请点击 * 返回上一教程。

鼠标悬停(格子)高亮
  当鼠标在格子上划过时,我们想要让格子亮起来。配合着简单的事件响应(比如鼠标悬停),让格子亮起来,这肯定能成为游戏的一大亮点。
我们调用OnMouseOver函数(原生函数)来实现这一功能。当鼠标悬停在本代码绑定的物体上时便会自动调用这个函数。在Tile脚本中添加以下变量:
1
2
public var materialIdle: Material;
public var materialLightup: Material;
  然后用格子的材质赋给materialidle变量。我们还需要一个高亮的材质,它跟materialidle颜色一样,但是用不同的shader。基础材质我们可以选择Diffuse着色器。


  而高光材质可以选择Specular着色器。很多游戏使用一个附加边缘着色器来实现这样的效果。这些并非Unity原生的,但是如果你想知道如何搞到一个,你可以用这个代替。


  别忘了把材质赋给格子材质,这样他们才能被使用。
  在Tile脚本中添加OnMouseOver函数。
1
2
3
4
function OnMouseOver()
{
    renderer.material = materialLightup;
}
  试一下:当你用鼠标滑过格子,他们便会改变模样。而且你应该注意到格子改变后,变不回来了。对于这个问题,我们还得添加OnMouseExit函数:
1
2
3
4
function OnMouseExit()
{
    renderer.material = materialIdle;
}
  我们已经实现了高亮效果,游戏也变得生动了。

给格子绑定ID
  为了让格子彼此间能够通信(主要是知道临近的格子有多少炸弹),每个格子需要临近的格子信息。实现这个方法就是使用ID,每个格子都得赋一个ID.
1
2
public var ID: int;
public var tilesPerRow: int;
  然后更新Grid代码中的实例命令,就像下面代码块一样(创建之时每个格子即被赋予新的ID):
1
2
3
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脚本中添加以下变量:
1
2
3
4
5
6
7
8
9
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类中保存着所有格子信息的静态数组,我们能够在他们被创建后计算出临近的格子的位置信息。
  代码部分如下:
1
2
3
4
5
6
7
8
9
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脚本中:
1
2
3
4
5
6
7
private function inBounds(inputArray: Array, targetID: int): boolean
{
    if(targetID < 0 || targetID >= inputArray.length)
        return false;
    else
        return true;
}
  现在我们必须在获取到格子信息前检查每个相邻的格子,以保证他们全都在保存格子信息的数组中。
1
2
3
4
5
6
7
8
9
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];
  这个代码段将会检测所有额的可能性。它也会监测格子是不是在区域的边界,因为网格最右边的格子的右边就没有格子了。试一下,随便检查一些格子看看临近的各自是不是都检索正确,就像这样:


  截屏中的格子是处于区域最右边的格子,它的右边相邻,右上,右下的位置是没有格子的。没有被赋值的格子就为空值,图中显示的即为正确的。  
  最后,当我们确保完成了这项工作后,我们就得把所有临近的格子存到一个数组中,这样我们就能在之后的代码中一次性访问他们。不过我们得在代码的开头声明下这个数组:
1
public var adjacentTiles: Array = new Array();
  然后,您可以调整我们创建的算法,添加该数组中的每个相邻的格子信息,添加以下代码:
1
2
3
4
5
6
7
8
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脚本开始添加这个代码,这样我们就能存储炸弹数了:
1
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。格子的响应取决于格子所处状态。先添加变量,一会我们还要用它:
1
public var state: String = "idle";

添加旗子
  我们想实现添加一个旗子给格子做标记。一个被标记过的格子会有一个小旗子在它上面。如果我们鼠标右击格子又会消失。如果所有雷都被标记了旗子,并且没有错误标记的格子,游戏就胜利了。
先搞个旗子,然后把它添加到格子上,成为一个预制物体。


  我们还需要一个变量来访问这个物体旗子,代码如下:
1
public var displayFlag: GameObject;
  在Tile脚本中Start方法添加这些:
1
2
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方法。它会激活或者不激活当前格子上的旗子。


总结
  我们已经将我们的扫雷扩展了挺多功能,让他们更加有意思,还赋予了玩家能够操作游戏的功能。下一节,我们将添加揭开格子的功能,创建简单的外观。

如社区发表内容存在侵权行为,您可以点击这里查看侵权投诉指引