引言
个人学习积累中,如有任何问题与错误,欢迎指出与讨论。
这系列将会记录我在搭建自己的2D平台游戏时遇到的一些问题与解决方案,核心目的均为更好的游戏体验与更棒的代码逻辑结构。所有代码基于C#与Unity。
正文
跳跃的手感能衡量一个2D平台游戏的好坏。——鲁迅
不知道你是处理玩家跳跃的判断条件的?反正就我而言,射线或者子物体检测地面图层:如果角色在地面上,则允许跳跃;反之则不允许。
但是这样在游玩的时候会导致一个问题:当你想要连跳时,单按跳跃键,你以为自己已经落到了地面,而实际上,你还在空中,从而造成了“按键失灵”的问题。这对于玩家的游玩体验有着相当大的影响。
而解决这个问题的方法,就是允许指令的预输入,在预输入后的一段时间内,若检测到条件满足,再执行操作——即“输入缓冲”。
不过,在介绍输入缓冲的方法前,我们先来了解一下计时器。
计时器
计时器,顾名思义,是为了计算一段时间,当计时器到达设定条件后,会执行相应的操作。
Unity提供了一个类似的方法,
Invoke("方法名(无参), 延迟时间")
或者
InvokeRepeating("方法名(无参), 延迟时间, 间隔时间")
用于重复调用。但是限制较多,且不适用于我们的输入缓冲:它只能做到延迟调用,而不能在延迟的这段时间内一满足条件就调用。
另外还可以在协程中使用
yield return new WaitForSeconds(具体秒数);
等方法实现。同样的问题是,它也只能实现延迟调用。
那么,我们到底该怎么定义一个可用于输入缓冲的计时器呢?以下是个人常用的一种写法。
// 所用变量 private float timer; // 计时器 private float timer_max = 2f; // 限定时间 // 初始化,一般在按下按键时执行,实现预输入 timer = timer_max; // 计时过程,一般放在Update里,每帧调用 if (timer != 0) { timer -= Time.deltaTime; if (timer <= 0) { timer = 0; /* 计时器到点结束执行的内容,超出限定时间,类似于延迟执行的部分 */ } else { /* 计时器还在计算时的内容,在限定时间内,输入缓冲就可以放在这 */ } }
主要思路就是利用Time.deltaTime来计算并减去时间,关于增量时间,这里有一篇不错的文章(https://blog.csdn.net/ChinarCSDN/article/details/82914420),就不再赘述。
那么,接下来,利用这个计时器,实现“输入缓冲”效果吧。
输入缓冲
让我们再明确下,我们想要随时能够输入跳跃指令,并让这个指令在内存中保存一定时间,在该段时间内只要满足条件(接触地面)就执行跳跃指令。以下是两种执行写法(第一种为我游戏中使用 / 第二种为在上方计时器模板上进行修改):
/* 所用变量 */ private float buffer_jump_counter = 0; // 跳跃输入缓冲计数器 private float buffer_jump_max = 0.1f; // 跳跃输入缓冲最大值 private bool hasJumpForce; // 此时是否拥有跳跃力了,避免重复给跳跃力,该力会在接触地面后自动重置为false /* 输入指令,Update()中 */ if (Input.GetButtonDown("Jump")) { buffer_jump_counter = 0; } /* 计时器与执行指令,Update()中 */ if (buffer_jump_counter < buffer_jump_max) { buffer_jump_counter += (1 * Time.deltaTime); if (IsOnGround() && !hasJumpForce) { hasJumpForce = true; rigidbody2D_Role.AddForce(new Vector2(0f, jumpForce), ForceMode2D.Impulse);//具体施加跳跃力操作 Debug.Log("输入缓冲,启动一次!"); } }
下面这种我未在游戏中测试过,不保证正确性。
/* 所用变量一致,不再赘述 */ /* 输入指令,Update()中 */ buffer_jump_counter = buffer_jump_max; /* 计时器与执行指令,Update()中 */ if (buffer_jump_counter != 0) { buffer_jump_counter -= Time.deltaTime; if (buffer_jump_counter <= 0) { buffer_jump_counter = 0; /* 计时器到点结束执行的内容,超出限定时间,类似于延迟执行的部分 */ } else { /* 计时器还在计算时的内容,在限定时间内,输入缓冲就可以放在这 */ if (IsOnGround() && !hasJumpForce) { hasJumpForce = true; rigidbody2D_Role.AddForce(new Vector2(0f, jumpForce), ForceMode2D.Impulse);//具体施加跳跃力操作 Debug.Log("输入缓冲,启动一次!"); } } }
这样,我们就实现了输入缓冲的效果。输入缓冲还可以用在很多的地方,如游戏中在空中连续多次按下↓方向键实现砸击地面的效果......更多的用法,就留待各位自行尝试了。
除此之外,跳跃的输入缓冲还有一个好兄弟,“土狼时间”。
土狼时间
土狼时间,就是让玩家所操控的人物,能够在离开平台的一段时间内,仍能执行起跳操作。它的目的,也是优化操作,减少“操作失灵”的现象。那么,我们是不是也可以用个计时器,来实现呢?可以自己先想一想。
怎么样,有思路了吗?
我们只要把计时器启动的时间改为离开地面即可,当我们离开地面,又没有执行过跳跃,就可以在一定的时间内,执行跳跃指令。以下是两种执行方法(同样,第一种为我游戏中使用 / 第二种修改自计时器模板):
/* 所用变量 */ private float buffer_coyote_counter = 0; // 跳跃土狼时间计数器 private float buffer_coyote_max = 0.1f; // 跳跃土狼时间最大值 private bool hasJumpForce; // 此时是否拥有跳跃力了,避免重复给跳跃力 /* 初始化,在Start()中 */ buffer_coyote_counter = buffer_coyote_max; /* 更新指令,该函数在Update()中调用 */ void CheckForJump() { if (IsOnGround() && rigidbody2D_Role.velocity.y < 0.05f && rigidbody2D_Role.velocity.y > -0.05f) { hasJumpForce = false; buffer_coyote_counter = 0; } } /* 计时器与执行指令,Update()中 */ if (buffer_coyote_counter < buffer_coyote_max) { if (!hasJumpForce && Input.GetButtonDown("Jump")) { hasJumpForce = true; buffer_coyote_counter = buffer_coyote_max; rigidbody2D_Role.AddForce(new Vector2(0f, jumpForce), ForceMode2D.Impulse); Debug.Log("土狼时间,启动一次!"); } } if (buffer_coyote_counter < buffer_coyote_max) buffer_coyote_counter += Time.deltaTime;
下面这种我未在游戏中测试过,不保证正确性 * 2。
/* 所用变量一致,不再赘述 */ /* 更新指令,该函数在Update()中调用 */ void CheckForJump() { if (IsOnGround() && rigidbody2D_Role.velocity.y < 0.05f && rigidbody2D_Role.velocity.y > -0.05f) { hasJumpForce = false; buffer_coyote_counter = buffer_coyote_max; } } /* 计时器与执行指令,Update()中 */ if (buffer_coyote_counter != 0) { buffer_coyote_counter -= Time.deltaTime; if (buffer_coyote_counter <= 0) { buffer_coyote_counter = 0; /* 计时器到点结束执行的内容,超出限定时间,类似于延迟执行的部分 */ } else { /* 计时器还在计算时的内容,在限定时间内,输入缓冲就可以放在这 */ if (!hasJumpForce && Input.GetButtonDown("Jump")) { hasJumpForce = true; buffer_coyote_counter = buffer_coyote_max; rigidbody2D_Role.AddForce(new Vector2(0f, jumpForce), ForceMode2D.Impulse); Debug.Log("土狼时间,启动一次!"); } } }
怎么样?这样就完美了吧。
其实关于游戏中的跳跃,还有很多的学问,例如如何合理高效的处理跳跃各个状态的动画(起跳、上升、最高点、下落、落地),跳跃中额外力的施加(如马里奥中的跳跃上升慢,下降快,并不只受到重力影响)......
其他的内容,就下次再说吧!
后记
我在学习本文相关内容时,借鉴了不少帖子、视频,包括但不限于:
译文|Gamemaker Studio系列:2D 平台游戏的输入缓冲 ——highway★(https://indienova.com/indie-game-development/2d-platformer-input-buffering-design/)
使用Unity实现动作游戏的打击感 —— 奥飒姆_Awesome(https://www.bilibili.com/video/BV1fX4y1G7tv)
暂无关于此日志的评论。