赞
踩
一般我们在控制玩家移动时,会选择使用getAxis来获取玩家在指定轴向上的输入。
对于一个需要读取玩家输入,并改变角色的控制类代码,我们会选择将其放入到update中使其逐帧执行。
但有时候,我们会需要使用到unity的物理系统来实现角色间的实体碰撞交互,此时物理系统相关的代码需要放入到fixdupdate中。
比如如下代码,一个简单的控制玩家移动的脚本。
public class PlayerBehavior : MonoBehaviour { public float moveSpeed = 1.0f; public float rotateSpeed = 60f; public float jumpVelocity = 5.0f; private float vInput; private float hInput; private Rigidbody _rb; // Start is called before the first frame update void Start() { _rb = GetComponent<Rigidbody>(); } // Update is called once per frame void Update() { vInput = Input.GetAxis("Vertical") * moveSpeed; hInput = Input.GetAxis("Horizontal" ) * rotateSpeed; } void FixedUpdate() { Vector3 rotation = Vector3.up * hInput; Quaternion angleRot = Quaternion.Euler(rotation * Time.fixedDeltaTime); _rb.MovePosition(this.transform.position + this.transform.forward * vInput * Time.fixedDeltaTime); _rb.MoveRotation(_rb.rotation * angleRot); } }
此时,若我们想加入一个按space跳跃的功能,我们第一反应就会使用getkeydown(KeyCode.Space)去检查space是否有被按下。
由于我们的物理系统相关代码,只能写入到FixedUpdate中,如果我们仍通过对物体施加力的方式来实现跳跃的话,就需要要把执行逻辑写入到FixedUpdate中:给物体施加一个向上的瞬时力。
void FixedUpdate()
{
if (Input.GetKeyDown(KeyCode.Space))
{
_rb.AddForce(Vector3.up * jumpVelocity, ForceMode.Impulse);
}
Vector3 rotation = Vector3.up * hInput;
Quaternion angleRot = Quaternion.Euler(rotation * Time.fixedDeltaTime);
_rb.MovePosition(this.transform.position + this.transform.forward * vInput * Time.fixedDeltaTime);
_rb.MoveRotation(_rb.rotation * angleRot);
}
我们很快就会发现,由于FixedUpdate的执行和帧率的计算是相对独立的,所以会出现获取键盘输入不及时的情况,表现上,则为经常读取不到space输入导致角色跳不起来的情况。
下图这里,即便是我使用了洪荒之力疯狂欧拉连击空格,这个小胶囊顽固的跟杀手皇后的小车一样纹丝不动。最后这个胶囊也只是傲娇地一跳,对我进行狠狠地嘲讽。
好小子,那既然放到FixedUpdate里面行不通,毕竟物理系统的帧计算独立于游戏帧。我们直接把Input.GetKeyDown(KeyCode.Space)扔到Update里面,这下总能读取到玩家输入了吧。
我们的逻辑非常简单:
新增一个private变量JInput ,用来控制AddForce里的起跳变量,在Update读取space的输入,有按下时矢量标签JInput 为起跳速度,没按下时JInput 为0。避免只按了一下,角色就原地升天的情况。
代码如下:
为了测试下能否读到空格输入,我们这里加一行打印输出。
void Update() { vInput = Input.GetAxis("Vertical") * moveSpeed; hInput = Input.GetAxis("Horizontal" ) * rotateSpeed; if (Input.GetKeyDown(KeyCode.Space)) { JInput = jumpVelocity; Debug.Log("jump jump jump!"); } else { JInput = 0f; } } void FixedUpdate() { _rb.AddForce(Vector3.up * JInput, ForceMode.Impulse); Vector3 rotation = Vector3.up * hInput; Quaternion angleRot = Quaternion.Euler(rotation * Time.fixedDeltaTime); _rb.MovePosition(this.transform.position + this.transform.forward * vInput * Time.fixedDeltaTime); _rb.MoveRotation(_rb.rotation * angleRot); }
空格是读到了,但是你小子,倒是动一下啊!你对得起旁边奋力旋转的胶囊吗!
这里其实是因为Update和FixedUpdate之间的计算是相互独立的,就是说,游戏帧和物理系统的计算不具备真正时间上的先后关系。
当我们在Update中把JInput 置为1之后,由于FixedUpdate往往未能及时跟上完成JInput 的处理,下一次Update又把JInput 置为0了,导致物理系统迟迟读不到空格按下的信息。
那我们在两个相对独立的线程中,要如何进行信息的正确共享呢?这就是加锁的基本思路。
即我们在Update中更新信息后,要确保FixedUpdate对其处理完后,才能进行重置,再让Update进行下一次的更新。
实现的代码如下:
void Update() { vInput = Input.GetAxis("Vertical") * moveSpeed; hInput = Input.GetAxis("Horizontal" ) * rotateSpeed; if (Input.GetKeyDown(KeyCode.Space)) { JInput = jumpVelocity; Debug.Log("jump jump jump!"); } } void FixedUpdate() { _rb.AddForce(Vector3.up * JInput, ForceMode.Impulse); JInput = 0f; Vector3 rotation = Vector3.up * hInput; Quaternion angleRot = Quaternion.Euler(rotation * Time.fixedDeltaTime); _rb.MovePosition(this.transform.position + this.transform.forward * vInput * Time.fixedDeltaTime); _rb.MoveRotation(_rb.rotation * angleRot); }
可算是把你这妖猴给降服了。
可是我转念一想,凭什么这个GetAxis就能够正确地读到键盘输入给FixedUpdate呢?
我们查找unity doc的描述,发现其中有那么一句话:
GetAxis获取的输入是独立于游戏帧计算的,即其可以无需等到下一个游戏帧的到来,就可以获取输入反馈给物理系统。
而GetKeyDown则显然是帧相关的输入获取方式,可以看到连unity doc都推荐使用GetAxis来处理输入,麻烦GetKeyDown您尽快退群罢。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。