这一部分跟大家分享一个游戏开发中一个必须明白的问题,那就是怎么设计按键,有哪几种按键,怎么实现?
一、按键的种类
简单来说,一般有三类,
- press signal :键位输入等,按下时获取并更新输入
- once trigger signal:跳跃、(非蓄力)攻击等,当玩家按下按钮时,只执行一次便结束
- double trigger signal:连击打combo,按键较少需要区分按下一次和短时间按下两次的功能时
先考虑前面两种状态,我们可以设计一个MyButton类,专门用于获取相应的状态。
public class MyButton_TEST
{
//press signal
public bool OnPressing;
//once trigger
public bool OnPressed;
public bool OnReleased;
private bool curstate;
private bool laststate;
//double trigger
public bool isExtending;
public bool isDelaying;
public void Tick(bool input)
{
//按键输入
curstate = input;
OnPressing = curstate;
OnPressed = false;
OnReleased = false;
if(curstate != laststate)
{
if(curstate == true)
{
OnPressed = true;
}
else
{
OnReleased = true;
}
}
laststate = curstate;
}
}
input脚本代码如下:
public class Input_TEST : MonoBehaviour
{
public bool iswalk;
public bool isjump;
private MyButton_TEST iswalk_btn;
private MyButton_TEST isjump_btn;
// Start is called before the first frame update
void Start()
{
iswalk_btn = new MyButton_TEST();
isjump_btn = new MyButton_TEST();
}
// Update is called once per frame
void Update()
{
iswalk_btn.Tick(Input.GetKey(KeyCode.W));
isjump_btn.Tick(Input.GetKey(KeyCode.Space));
iswalk = iswalk_btn.OnPressing;
isjump = isjump_btn.OnPressed;
print(iswalk);
print(isjump);
}
}
这样完整的将按钮的处理逻辑与其自身分离开来,符合里氏替换原则。untiy输出也是正确的。
下面我们来考虑double trigger的实现办法,由于double trigger需要计时操作,所以我们实现一个Mytimer类专门用来计时。
public class MyTimer_TEST
{
public enum STATE
{
IDLE,
RUN,
FINISHED
}
public float duration;
private float elapsedtime;
public STATE state = STATE.IDLE;
public void Tick()
{
switch (state)
{
case STATE.IDLE:
break;
case STATE.RUN:
elapsedtime += Time.deltaTime;
if(elapsedtime >= duration)
{
state = STATE.FINISHED;
}
break;
case STATE.FINISHED:
break;
default:
Debug.Log("Error");
break;
}
}
public void Go()
{
elapsedtime = 0;
state = STATE.RUN;
}
}
每一个button类中,应该有两种延迟信号,一种是按下后,持续多久还,一种是按下后放下,继续保持多久。说起来有点乱,可以画图表示:
对每一个按钮,我们除了设计上面所说的OnPressed、OnPressing、OnReleased三种信号外,还会设计IsExtending和IsDelaying两种信号,这两种信号的实现依赖于上面所说的Timer。
对照上图,在时间点1的位置,OnPressed信号会获得true,其他时候都为false,在2时间段也就是delayTime时间段内,IsDelaying信号为true,其他信号的表示可以参考图左侧的描述。
对此,我们重新修改一下button类的结构,增加两种信号:
public class MyButton_TEST
{
//press signal
public bool OnPressing;
//once trigger
public bool OnPressed;
public bool OnReleased;
private bool curstate;
private bool laststate;
//double trigger
public bool isExtending;
public bool isDelaying;
private MyTimer_TEST extendtimer = new MyTimer_TEST();
private MyTimer_TEST delaytimer = new MyTimer_TEST();
public void Tick(bool input)
{
extendtimer.Tick();
delaytimer.Tick();
//按键输入
curstate = input;
OnPressing = curstate;
OnPressed = false;
OnReleased = false;
isExtending = false;
isDelaying = false;
if (curstate != laststate)
{
if (curstate == true)
{
OnPressed = true;
StartTimer(delaytimer, 0.1f);
}
else
{
OnReleased = true;
StartTimer(extendtimer, 0.1f);
}
}
laststate = curstate;
if (extendtimer.state == MyTimer_TEST.STATE.RUN)
{
isExtending = true;
}
if (delaytimer.state == MyTimer_TEST.STATE.RUN)
{
isDelaying = true;
}
}
private void StartTimer(MyTimer_TEST timer, float duration)
{
timer.Go();
timer.duration = duration;
}
}
回到input脚本中,我们测试一下,这里设计一个doublekick字段,连按两下表示一次攻击,并修改walk字段的实现要求,当玩家按下walk,松开按钮的0.2秒的时间里,我们依然认为walk==true
该设计就可以完成要求,可以在button的实现方法里将duration改为0.2即可。当然这里还可以修改一下button类,给予一个构造函数,该构造函数需要两个参数分别是delayingtime和extendingtime的值
至此就修改完毕了,完成了对button按键的抽象,以此功能可以实现各种延迟按钮、各种combo效果。