版本: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技术,拿个还算不错的工资,能够自己生活就行了,过得平凡一些。
有时候确实会疑惑,为什么没有超人的能力,还成天想着那些不切实际的梦想。如果没有会不会幸福很多。