unity小项目——扫雷
作为一个扫雷玩家,想重现这么经典的游戏了~于是我就试着用unity做了一个尝试,具体并不难,算是一个练习吧。我会在这里写出大致的思路,想练习的同学可以照着试试。
1.总体思路:
//手写,字不好,请见谅。
2.主要脚本
3.格子控制器
以每一个格子为单位进行操作,一个格子挂一个PressManager,里面存储了格子的信息,包括坐标,是非是雷,状态等,方便判定。格子下有图片与用来显示数字的文本。
1.改变状态方法:
用枚举提高可读性,状态值有
unopen,signed,opened,mine,worrySign,doom六种。
设立一个方法,改变格子的显示,同时改变它的状态。
2.点击的获取
这个工程中涉及较复杂的鼠标事件,我用了EventSystem来处理。具体是:创建pointerEnter方法与pointerExit方法,分别关联两个方法,然后创建一个bool变量,enter将他变为true,exit将他变为false,这样它就表示鼠标在格子里,然后Input.GetMouseButtonUp(0),即可获得点击格子的判定。
3.点击方法:
左键点击,判断,格子是未打开时,调用主控制器方法,传递坐标,剩下的交给主控完成。
右键点击可以让格子在标记与未打开间循环。
中键点击时,判断,当格子是打开状态时,通知主控。
4.两个接口方法改变显示
一个改变图片,一个改变文本。
4.主控制器
1.存储信息
,包括一个用来存储格子的二维数组,横宽有几个格子,总雷数信息,剩余的空格(用来判定胜利)剩余雷数。
2.初始化方法
删除旧网格,创建新网格,把格子存到数组里,初始化每个格子的数据(name,位置,坐标,初始状态等)初始化所有全局变量,反正所有东西都要new一遍。
public void Restart()
{
for (int i = 0; i < allPress.GetLength(0); i++)
{
for (int j = 0; j < allPress.GetLength(1); j++)
{
if (allPress[i, j] != null)
{
Destroy(allPress[i, j].gameObject);
allPress[i, j] = null;
}
}
}
for (int i = 0; i < MaxX; i++)
{
for (int j = 0; j < MaxY; j++)
{
PressController newGameObject =
Instantiate(pressProabs, transform).GetComponent<PressController>();
allPress[i, j] = newGameObject;
newGameObject.gameObject.GetComponent<RectTransform>().anchoredPosition =
new Vector2(30 * i + 30 , -30 * j -30);
newGameObject.gameObject.name = "Press("+i+","+j+")";
newGameObject.x = i;
newGameObject.y = j;
newGameObject.OnVuleChanged(PressType.unopen);
}
}
GameOver = false;
UIManager.instance.optionGO.SetActive(false);
UIManager.instance.time = 0f;
space = MaxX * MaxY;
mine = MaxMineNumber;
UIManager.instance.UIRestart();
firstClick = true;
}//初始化,重置格子
3.通用方法:OperateNearby与CalculateNearby
OperateNearby用来对附近所有格子进行操作,这里类似于泛洪算法。
可以打开附近所有格子,标记附近所有格子。
CalculateNearby用来计算附近格子处于某种状态的数量,
比如雷的数量,已标记的数量,未打开的数量。
注意这两个方法不能越界。
这两个操作用的多,先封装起来,使用时输入参数即可。标识位使方法进行分化,最好用枚举。
void OperateNearby(int x,int y,int n)//操作附近格子,n为标志位,0打开1标记
{
//Debug.Log("something");
for (int i = -1; i <= 1; i++)
{
for (int j = -1; j <= 1; j++)
{
int TrueX = x + i, TrueY = y + j;
if (i == 0 && j == 0) continue;
if (TrueX < 0 || TrueX >= MaxX || TrueY < 0 || TrueY >= MaxY) continue;
if (allPress[TrueX, TrueY].pressType == PressType.unopen)
//下面为标志位区分的部分,0表示打开格子,1表示标记格子
switch (n){
case 0:
instance.Onclick(TrueX, TrueY);
break;
case 1:
if (allPress[TrueX, TrueY].pressType == PressType.unopen)
{
allPress[TrueX, TrueY].OnVuleChanged(PressType.signed);
instance.mine--;
}
break;
}
}
}
}
int CalculateNearby(int x,int y,int n)//计算附近格子状态数,n为标志位,0雷1已标记2未打开
{
int number = 0;
for (int i = -1; i <= 1; i++)
{
for (int j = -1; j <= 1; j++)
{
int TrueX = x + i, TrueY = y + j;
if (i == 0 && j == 0) continue;
if (TrueX < 0 || TrueX >= MaxX || TrueY < 0 || TrueY >= MaxY) continue;
switch (n) {
case 0:
if (allPress[TrueX, TrueY].ismine)
number++;break;
case 1:
if (allPress[TrueX, TrueY].pressType == PressType.signed)
number++; break;
case 2:
if (allPress[TrueX, TrueY].pressType == PressType.unopen)
number++; break;
}
}
}
return number;
}
4.处理左键点击格子事件
分情况,第一次点击时,布下雷区,不是第一次时,判断是否触雷,触雷调用died方法,没触雷打开格子,如果格子附近雷数为0,打开附近的格子。
public void Onclick(int x, int y)
{
if (firstClick)
{
for (int k = 0; k < MaxMineNumber;)
{
int MineX = Random.Range(0, MaxX), MineY = Random.Range(0, MaxY);
if (Mathf.Abs(MineX - x) <= 1 && Mathf.Abs(MineY - y) <= 1) continue;
//不能不在第一次点击格子附近
if (allPress[MineX, MineY].ismine) continue;
//已经布雷,换个地方
allPress[MineX, MineY].ismine = true;
k++;
firstClick = false;//这是个boll变量,用来判断是否是第一次点击
UIManager.instance.beginTime = true;
}
Onclick(x, y);
}//首次点击,放置雷
else{
if (allPress[x, y].ismine)
{
allPress[x, y].OnVuleChanged(PressType.doom);
Died();
}
else
{
if (allPress[x, y].pressType == PressType.unopen)
{
int mineNumber = CalculateNearby(x, y, 0);
allPress[x, y].OnVuleChanged(PressType.opened);
allPress[x, y].ShowMineNumber(mineNumber);
space--;
if (space - MaxMineNumber <= 0) Win();//胜利判断
if (mineNumber == 0) OperateNearby(x, y, 0);//若数字为0,打开附近
}
//如果是空格,自动打开附近的格子
}
}
}//打开格子,第一次点击时放置雷
5.处理中键点击格子事件
由于实现定义了两个方法,这里就比较简单了。
public void OnMouse3Click(int x, int y,int mineNumber)//按下中键的事件,1打开2标记
{
if (mineNumber== CalculateNearby(x, y, 1))
OperateNearby(x, y, 0);
//计算附近的标记数量,如果标出所有雷,打开附近
if (mineNumber == CalculateNearby(x, y, 2)+ CalculateNearby(x, y, 1))
OperateNearby(x, y, 1);
//计算附近未打开的格子,如果等于雷数数字,全部标记
}
6.触雷的处理与胜利判断
控制UI显示失败,比较简单不说了
每次打开空格时,将剩余空格数-1,当剩余空格+总雷数>=横*宽时胜利,显示UI。
5.UI控制器
我使用一个UI控制器控制UI,以简化脚本。
1.更新小人图标,雷数与时间
在Update里,注意计时器在第一次点击开始,胜利或触雷时停止
2.根据格子数控制窗口大小与布局
public void UIRestart()
{
crossRTF.sizeDelta = new Vector2(
30 * (MainController.MaxX + 1),
30 * (MainController.MaxY + 1));
//控制格子背景大小,正好可以放下所有格子
crossRTF.anchoredPosition = new Vector2(0, crossRTF.sizeDelta.y/2f+25);
//重置格子的位置
Screen.SetResolution
(
80 + 30 * MainController.MaxX,
155 + 30 * MainController.MaxY,
false
);//控制游戏窗口大小的语句
}//根据格子数控制窗口大小与布局
这里注意各个UI元素锚点的设置。
6.设置界面
用来设置宽高与雷数7.其他:
还有一些更新雷数,记时之类的,太简单就不写了。
全文完,感谢阅读!