Unity使用协程要求用户按顺序“一步一步”进行操作
许多游戏会在新手教程要求玩家按顺序操作,或者解谜游戏会要求玩家按顺序进行操作。
使用if语句未免不是一种方法,但是当步骤数特别多的时候,嵌套会使整段代码看上去非常臃肿,难以阅读。
使用协程去管理这些步骤,则不需要if语句去进行嵌套了。
此处抛砖引玉,演示一个极简的项目,要求用户按顺序依次点击ABC三个按钮,顺序正确则Debug打印按钮名字,错误则报错,并且从上一次按下的正确的按钮继续执行。
新建一个C#脚本命名为GameManager,并且新建空物体挂载这个脚本
代码
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class GameManager : MonoBehaviour
{
int step;//当前进行到的步骤
int pressed;//当前按下的按键,用来跟step进行比较判断是否点击正确
bool check;
private void Start()
{
step = 1;
StartCoroutine("Begin");
}
public void Button_Press(int i)//每个按键并不执行输出功能,而只改变pressed值,然后在另一个方法判断是否点击正确并打印
{
pressed = i;
check = true;//按下按键时使check为真,开始按键检测,检测完要将check重新设为false
}
void Error()//错误函数,每次按键错误“只会调用一次”该函数
{
Debug.Log("Wrong");
//此处可以再调用一些动画机函数之类的方法,添加错误特效
}
bool Check()//检查按键是否正确,错误就报错
{
if (check)
{
if (pressed != step)
{
Error();
check = false;
return false;
}
check = false;
return true;
}
return false;
}
IEnumerator Begin()
{
Debug.Log("开启协程");
while (step <= 3)//step从1开始,“3”是总共的步骤数
{
yield return StartCoroutine("Step" + step);
step++;
}
yield return null;
}
IEnumerator Step1()
{
Debug.Log("开始协程一");
yield return new WaitUntil(() => Check() == true);//会一直调用Check()这个函数,直到这个函数返回true,
//但是因为加了check值,故每次错误只会调用一次报错函数
Debug.Log("A");
//此处可以加入想要执行的函数
yield return null;
}
IEnumerator Step2()
{
Debug.Log("开始协程二");
yield return new WaitUntil(() => Check() == true);
Debug.Log("B");
//此处可以加入想要执行的函数
yield return null;
}
IEnumerator Step3()
{
Debug.Log("开始协程三");
yield return new WaitUntil(() => Check() == true);
Debug.Log("C");
//此处可以加入想要执行的函数
yield return null;
}
}
给每一个按钮增添点击事件,“1”表示希望这个按钮第一个被点击,即顺序。
因为只有三个按钮,所以只加了三个协程,根据需要可以继续往下加Step4、Step5…
并不限于按钮,要点是改变pressed的值,故只要用户其他动作加入改变pressed值的代码即可。
补充
上述设计的思想是,每一个按钮会赋予全局变量pressed不同的值,并且启动检测机制判断preesed是否跟当前的步骤数相等,相等就执行相应的代码,否则就报错。
该设计一个弊端就是,因为step每次按钮点击正确就会自增1,而按钮的值是固定的,因此当同一个按钮需要被多次点击时就不适用了。
解决方案是
①修改pressed,每一个按钮增加一个int计数器计算点击次数,根据计数器值的不同改变不同的pressed值;
这样的话需要用if语句进行判断甚至嵌套,与我们设计的初衷违背。
②修改step,让它不再每次自增1。
StartCoroutine(“Step” + step)会根据函数名启用不同的协程,修改step意味着要修改函数名,而且后续新增步骤会带来麻烦。
故③修改检测机制。
原来的检测机制是step与pressed进行判断,那么只要不与step进行判断,而另起炉灶重载Check()函数,指定pressed跟我们需要的值进行判断即可。
bool Check(int i)
{
//函数体与源代码一致,只需把step改为i
}
....
....
IEnumerator Step1()
{
Debug.Log("开始协程一");
yield return new WaitUntil(() => Check(1) == true);
Debug.Log("A");
//此处可以加入想要执行的函数
yield return null;
}
IEnumerator Step2()
{
Debug.Log("开始协程二");
yield return new WaitUntil(() => Check(1) == true);
Debug.Log("A");
//此处可以加入想要执行的函数
yield return null;
}
这样便可以使pressed指定与常量1进行比较,上述代码表示需要连续点击按钮A两次。
另外根据需要可以改变Check()的参数,例如
bool Check(int i,int j)
{
if (check)
{
if (pressed != i && pressed != j)
{
Error();
check = false;
return false;
}
check = false;
return true;
}
return false;
}
IEnumerator Step3()
{
Debug.Log("开始协程三");
yield return new WaitUntil(() => Check(1, 2) == true);
if (pressed == 1)
{
Debug.Log("A");
//此处可以加入想要执行的函数,表示点击按钮A时执行的动作
}
else if( pressed == 2)
{
Debug.Log("B");
//此处可以加入想要执行的函数,表示点击按钮B时执行的动作
}
//此处可以加入想要执行的函数,表示点击A、B都会执行的动作
yield return null;
}
就表示允许用户在特定的步骤里,既可以点击A,也可以点击B完成该步骤。