Quantcast
Channel: CSDN博客移动开发推荐文章
Viewing all articles
Browse latest Browse all 5930

Unity基础包 刚体FPS RigidbodyFirstPersonController 脚本研究

$
0
0

版本: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技术,拿个还算不错的工资,能够自己生活就行了,过得平凡一些。

 

有时候确实会疑惑,为什么没有超人的能力,还成天想着那些不切实际的梦想。如果没有会不会幸福很多。


作者:u012632851 发表于2016/11/8 20:08:36 原文链接
阅读:43 评论:1 查看评论

Viewing all articles
Browse latest Browse all 5930

Trending Articles



<script src="https://jsc.adskeeper.com/r/s/rssing.com.1596347.js" async> </script>