版本: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<</span>Rigidbody>();
m_Capsule =
GetComponent<</span>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 <</span>
(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;
//
在旋转之前获取一下旋转角度
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技术,拿个还算不错的工资,能够自己生活就行了,过得平凡一些。
有时候确实会疑惑,为什么没有超人的能力,还成天想着那些不切实际的梦想。如果没有会不会幸福很多。
加载中,请稍候......