版本:unity 5.3.4 语言:C#
今天又研究了一个脚本。
刚体的第一人称,不过这个脚本没有像之前的FPS脚本一样,加那么多另外的脚本,唯一一个就是MouseLook,这个脚本我们之前分析过了,就不再赘述了。
所以整个看下来都是一个比较完整的FPS模型,个人喜欢用这个刚体实现,因为以后用其他什么力都比较方便。
下面上代码:
// 刚体FPS移动主脚本,用刚体和胶囊组件代替了Character Controller组件 // 提醒一下,这个脚本有两百多行,稍微有点混乱的,建议先看看都有些什么属性留个印象,然后按照Start、Update、FixedUpdate一步步按照逻辑执行顺寻看过去会轻松很多 [RequireComponent(typeof (Rigidbody))] [RequireComponent(typeof (CapsuleCollider))] public class RigidbodyFirstPersonController : MonoBehaviour { // 内部类,移动设置 [Serializable] public class MovementSettings { public float ForwardSpeed = 8.0f; // 向前走的最大速度 public float BackwardSpeed = 4.0f; // 后退的最大速度 public float StrafeSpeed = 4.0f; // 倾斜走的最大速度 public float RunMultiplier = 2.0f; // 奔跑速度跟行走速度的比例 public KeyCode RunKey = KeyCode.LeftShift; //跑步的按键 public float JumpForce = 30f; //起跳的力 public AnimationCurve SlopeCurveModifier = new AnimationCurve(new Keyframe(-90.0f, 1.0f), new Keyframe(0.0f, 1.0f), new Keyframe(90.0f, 0.0f)); //斜面移动的调节曲线,默认是0到90,从1减到0 [HideInInspector] public float CurrentTargetSpeed = 8f; //当前的目标速度 #if !MOBILE_INPUT private bool m_Running; //当前是否在跑步状态,只有在非手机平台上有效 #endif // 更新目标的速度。 public void UpdateDesiredTargetSpeed(Vector2 input) { if (input == Vector2.zero) return; //没有输入不处理 if (input.x > 0 || input.x < 0) //x轴(即水平方向)有输入,速度为倾斜速度 { CurrentTargetSpeed = StrafeSpeed; } if (input.y < 0) //y小于0,速度为后退速度 { CurrentTargetSpeed = BackwardSpeed; } if (input.y > 0) //y大于0,前进速度,这个写在最后,使其优先级最高,即如果即倾斜又前进,则为前进速度 { CurrentTargetSpeed = ForwardSpeed; } #if !MOBILE_INPUT if (Input.GetKey(RunKey)) //奔跑状态下,速度默认乘以2 { CurrentTargetSpeed *= RunMultiplier; m_Running = true; } else { m_Running = false; } #endif } #if !MOBILE_INPUT public bool Running { get { return m_Running; } } #endif } // 内部类,高级设置 [Serializable] public class AdvancedSettings { public float groundCheckDistance = 0.01f; //判断当前是否着陆离地面的距离(设置为0.01f应该是比较好的) public float stickToGroundHelperDistance = 0.5f; // stops the character //停止角色运动的距离 public float slowDownRate = 20f; //当没有输入时,控制器缓慢停下时的比例 public bool airControl; //当角色在空中时,用户是否能控制角色 [Tooltip("set it to 0.1 or more if you get stuck in wall")] //鼠标悬停在下面属性上会显示该提示,当然中文是不行的 public float shellOffset; //减小半径用于减少墙面对卡住角色的影响(值为0.1f是比较好的) } // 这边开始是主类的代码 public Camera cam; //当前的相机,组件组织结构跟非刚体的PFS脚本是一样的,分为角色层和镜头层 public MovementSettings movementSettings = new MovementSettings(); //内部类,移动设置 public MouseLook mouseLook = new MouseLook(); //我们的老朋友,鼠标控制角色和镜头旋转 public AdvancedSettings advancedSettings = new AdvancedSettings(); //内部类,高级设置 private Rigidbody m_RigidBody; //角色的刚体 private CapsuleCollider m_Capsule; //胶囊碰撞体 private float m_YRotation; //y轴的旋转,这个变量好像并没有什么用 private Vector3 m_GroundContactNormal; //与地面接触的法线向量 private bool m_Jump, m_PreviouslyGrounded, m_Jumping, m_IsGrounded; //当前跳跃、着陆等的一些状态bool变量 // 获取当前的速度 public Vector3 Velocity { get { return m_RigidBody.velocity; } } // 当前的状态是否着陆了 public bool Grounded { get { return m_IsGrounded; } } // 是否在跳跃中 public bool Jumping { get { return m_Jumping; } } // 是否在奔跑状态 public bool Running { get { #if !MOBILE_INPUT //只有在非手机平台上才能奔跑 return movementSettings.Running; #else return false; #endif } } // 初始化 private void Start() { m_RigidBody = GetComponent<Rigidbody>(); m_Capsule = GetComponent<CapsuleCollider>(); mouseLook.Init (transform, cam.transform); //给入角色层transform和相机transform } // 每帧更新 private void Update() { RotateView(); //旋转角色和镜头 if (CrossPlatformInputManager.GetButtonDown("Jump") && !m_Jump) { m_Jump = true; } } // 固定更新 private void FixedUpdate() { GroundCheck(); //判断当前是否在陆地上 Vector2 input = GetInput(); //获取输入 // input有输入,并且可以控制的情况,处理速度 if ((Mathf.Abs(input.x) > float.Epsilon || Mathf.Abs(input.y) > float.Epsilon) && (advancedSettings.airControl || m_IsGrounded)) { // 总是以Camera的正方向作为前进方向 Vector3 desiredMove = cam.transform.forward*input.y + cam.transform.right*input.x; desiredMove = Vector3.ProjectOnPlane(desiredMove, m_GroundContactNormal).normalized; //将速度投射到法线的斜面,并将其单位化 desiredMove.x = desiredMove.x*movementSettings.CurrentTargetSpeed; //计算各轴的速度 desiredMove.z = desiredMove.z*movementSettings.CurrentTargetSpeed; desiredMove.y = desiredMove.y*movementSettings.CurrentTargetSpeed; if (m_RigidBody.velocity.sqrMagnitude < (movementSettings.CurrentTargetSpeed*movementSettings.CurrentTargetSpeed)) //当前速度小于目标速度,则加个冲力,我最初的实现是直接该刚体的速度,导致损失了其他的力,比如重力、炸弹爆炸对角色的冲力 { m_RigidBody.AddForce(desiredMove*SlopeMultiplier(), ForceMode.Impulse); //斜率跟冲力做计算 } } if (m_IsGrounded) { m_RigidBody.drag = 5f; //在地上把空气阻力设置为5f // 要跳跃了,空气阻力设置为0,着陆状态到跳跃状态的一个中间状态 if (m_Jump) { m_RigidBody.drag = 0f; m_RigidBody.velocity = new Vector3(m_RigidBody.velocity.x, 0f, m_RigidBody.velocity.z); //保存x、z轴速度 m_RigidBody.AddForce(new Vector3(0f, movementSettings.JumpForce, 0f), ForceMode.Impulse); //在y轴上使用一个冲力 m_Jumping = true; //正式进入jump状态 } // 没有速度,不在跳跃,并且刚体的速度小于1,则使刚体休眠,节约cpu运算 if (!m_Jumping && Mathf.Abs(input.x) < float.Epsilon && Mathf.Abs(input.y) < float.Epsilon && m_RigidBody.velocity.magnitude < 1f) { m_RigidBody.Sleep(); } } else { m_RigidBody.drag = 0f; //空中,空气阻力设置为0,否则跳得很矮 if (m_PreviouslyGrounded && !m_Jumping) { StickToGroundHelper(); //跳跃完成后把刚体粘到地上 } } m_Jump = false; } // 斜率跟冲力做计算 private float SlopeMultiplier() { float angle = Vector3.Angle(m_GroundContactNormal, Vector3.up); //计算地面斜面法线和z轴正方向的角度 return movementSettings.SlopeCurveModifier.Evaluate(angle); //根据斜率曲线,计算出当前应该给与物体冲力的比例,斜面斜率到90的时候,即使按了方向键也没有冲力 } // 粘到地上助手 private void StickToGroundHelper() { RaycastHit hitInfo; if (Physics.SphereCast(transform.position, m_Capsule.radius * (1.0f - advancedSettings.shellOffset), Vector3.down, out hitInfo, ((m_Capsule.height/2f) - m_Capsule.radius) + advancedSettings.stickToGroundHelperDistance, ~0, QueryTriggerInteraction.Ignore)) //丢球到地上 { if (Mathf.Abs(Vector3.Angle(hitInfo.normal, Vector3.up)) < 85f) { m_RigidBody.velocity = Vector3.ProjectOnPlane(m_RigidBody.velocity, hitInfo.normal); //刚体的速度映射到斜面上 } } } // 获取输入 private Vector2 GetInput() { Vector2 input = new Vector2 { x = CrossPlatformInputManager.GetAxis("Horizontal"), y = CrossPlatformInputManager.GetAxis("Vertical") }; movementSettings.UpdateDesiredTargetSpeed(input); return input; } // 旋转镜头和角色 private void RotateView() { // 当游戏暂停时忽略鼠标移动的影响 if (Mathf.Abs(Time.timeScale) < float.Epsilon) return; /* 这边引用宏哥1995的总结: * 1.timeScale不影响Update和LateUpdate,会影响FixedUpdate * 2.timeScale不影响Time.realtimeSinceStartup,会影响Time.timeSinceLevelLoad和Time.time * 3.timeScale不影响Time.fixedDeltaTime和Time.unscaleDeltaTime,会影响Time.deltaTime */ // 在旋转之前获取一下旋转角度 float oldYRotation = transform.eulerAngles.y; mouseLook.LookRotation (transform, cam.transform); //MouseLook处理角色镜头旋转 // 只有在方向键可以控制角色时,才进入判断,其他情况反正xz平面速度为0嘛 if (m_IsGrounded || advancedSettings.airControl) { // 旋转刚体的速度,以符合新的角色旋转角度 Quaternion velRotation = Quaternion.AngleAxis(transform.eulerAngles.y - oldYRotation, Vector3.up); //获取旋转角度差 m_RigidBody.velocity = velRotation*m_RigidBody.velocity; //相乘即旋转增加该角度,这个之前计算出欧拉角相乘是一样的(严格来说是用一个欧拉角来计算出一个Quaternion对象) } } // 用一个球从胶囊中间丢下,看看是否碰撞陆地,来判断是否在陆地上,这跟非刚体的FPS脚本是一样的 private void GroundCheck() { m_PreviouslyGrounded = m_IsGrounded; RaycastHit hitInfo; if (Physics.SphereCast(transform.position, m_Capsule.radius * (1.0f - advancedSettings.shellOffset), Vector3.down, out hitInfo, ((m_Capsule.height/2f) - m_Capsule.radius) + advancedSettings.groundCheckDistance, ~0, QueryTriggerInteraction.Ignore)) //有一些不同的是这边用到了几个高级属性,用于减少球的半径,增加投球的距离,使其倾向于判断胶囊底部的碰撞,而非侧边的碰撞 { m_IsGrounded = true; m_GroundContactNormal = hitInfo.normal; //获取法线 } else { m_IsGrounded = false; m_GroundContactNormal = Vector3.up; } // 前个固定一帧的状态是不在地上,现在在地上的情况,跳跃状态结束 if (!m_PreviouslyGrounded && m_IsGrounded && m_Jumping) { m_Jumping = false; } } }
嗯嗯,今天分析了4个脚本,感觉前途还很遥远。
下边是我自己的一些心情,没兴趣的读者玩家们可以跳过了。
很多时候描绘自己的梦想很轻松,自己想要成为什么,但实际上做起来又是另一会事,总会抱怨自己的环境有那些欠缺,但就是不肯自己动手做些什么,拖沓的病、以及大量的计划也让我应接不暇,然而这些东西也只能自己在博文中发一发,说自己未来一定有一个团队的,但是……
刚刚看了一圈独立游戏的开发者,发现那些游戏我大部分都玩过,说来惭愧,有些我自己都奉为神作的作品自己一分钱也没有花。
多数作品是一些团队完成的,分工明确、目标一致,资金上可能缺一点,不过挺过来的相对都是成功的。有几句话挺触动我的,说是团队的核心成员基本上是最要好的朋友,有不同的技能,但有相同的梦想。
想起一个用虚幻引擎做鹿的一个游戏的小青年,初中?反正10几岁吧,就跟着自己的同学组建了一个三人的团队,令人惊叹。
感慨自己没有这样一起怀有相同梦想人的同时,认真思考,可能最终只能一个人来做。
不过美术、音乐、策划都是相当需要时间的一件事情,自己除了程序以外又什么也没接触过。
我自己的创作作品的计划一拖再拖,美其名曰没有时间,现在还有很多前置的计划,不过谁知道是不是内心的恐惧抓住了我,让我不敢开始。我经历过太多太多的失败了,从来也不是那些成功者的一员,或许将来会好一些吧。
每次借口说自己没认真做,但是认真做了就能成功吗?我的眼光、能力和性格始终摆在那里,不进不退。我觉得最现实的就是磨练好自己unity技术,拿个还算不错的工资,能够自己生活就行了,过得平凡一些。
有时候确实会疑惑,为什么没有超人的能力,还成天想着那些不切实际的梦想。如果没有会不会幸福很多。