阴影实现 - 准备工作:场景中行走的角色

相机控制

实现的功能:

1) 相机跟随, 2) 围绕Player旋转(左右和上下调整相机),3) 镜头zoom

using UnityEngine;

[RequireComponent(typeof(Camera))]
public class ThirdPersonCamera : MonoBehaviour
{

    /// 摄像机和碰撞体的交点向摄像机的观察点移动的距离
    private const float Collision_Return_Dis = 0.5f;

    private Transform m_CachedCamera;

    public Transform m_Player;

    public Vector3 m_Offset = new Vector3(0.0f, 0.5f, 0.0f); //到原点的距离为旋转半径

    public float m_HorizontalAimingSpeed = 10.0f; //水平方向旋转速度
    public float m_VerticalAimingSpeed = 10.0f; //垂直方向旋转速度

    private float m_AngleH = 0.0f; //水平方向旋转角度

    public float m_MaxVerticalAngle = -5.0f;
    public float m_MinVerticalAngle = -50.0f;
    private float m_AngleV = -30.0f;

    public float m_MinDistance = 1.0f; //最小zoom距离
    public float m_MaxDistance = 6.0f; //最大zoom距离
    private float m_Distance = 0.0f; //当前zoom距离
    public float m_ZoomSpeed = 2.0f;

    void Awake()
    {
        m_CachedCamera = GetComponent<Camera>().transform;
        m_Distance = (m_MinDistance + m_MaxDistance) * 0.5f;
        Debug.Log($"Awake: dist: {m_Distance}");
    }

    void Update()
    {
        float mousex = 0;
        float mousey = 0;
        if (Input.GetMouseButton(0))
        {
            mousex = Input.GetAxis("Mouse X");
            mousey = Input.GetAxis("Mouse Y");
        }

        m_AngleH += (Mathf.Clamp(mousex, -1.0f, 1.0f) * m_HorizontalAimingSpeed);

        m_AngleV += (Mathf.Clamp(mousey, -1.0f, 1.0f) * m_VerticalAimingSpeed);
        m_AngleV = Mathf.Clamp(m_AngleV, m_MinVerticalAngle, m_MaxVerticalAngle);

        float zoom = Input.GetAxis("Mouse ScrollWheel");
        m_Distance -= zoom * m_ZoomSpeed;
        m_Distance = Mathf.Clamp(m_Distance, m_MinDistance, m_MaxDistance);

        Quaternion animRotation = Quaternion.Euler(-m_AngleV, m_AngleH, 0.0f); //鼠标上移x角度变小, 下移变大
        m_CachedCamera.rotation = animRotation;

        Vector3 cameraBack = animRotation * Vector3.back; //用四元数计算Camera的back, 等同于mCamera.forward(z取反)
        cameraBack.Normalize();

        Quaternion camYRotation = Quaternion.Euler(0.0f, m_AngleH, 0.0f);
        Vector3 lookatpos = m_Player.position + camYRotation * m_Offset;
        m_CachedCamera.position = lookatpos + cameraBack * m_Distance;

        // 计算碰撞后的摄像机点
        RaycastHit rayhit;
        bool hit = Physics.Raycast(lookatpos, cameraBack, out rayhit, m_Distance);
        if (hit)
        {
            // 屏蔽角色碰撞
            bool charcol = rayhit.collider as CharacterController;
            if (!charcol)
            {
                m_CachedCamera.position = rayhit.point - cameraBack * Collision_Return_Dis;

                // 距离修正在范围内(1, 避免摄像机穿插进入角色)
                float distance = Vector3.Distance(m_CachedCamera.position, lookatpos);
                distance = Mathf.Clamp(distance, m_MinDistance, m_MaxDistance);
                m_CachedCamera.position = lookatpos + cameraBack * distance;
            }
        }
    }

}

 

角色控制

基于CharacterController的行走和跳跃

using UnityEngine;

public class PlayerControl : MonoBehaviour
{
    private const float Dir_Speed = 10;
    private const float Gravity = 10;

    public CharacterController m_characterCtrl;

    public Animator m_Animator;
    public float m_MoveSpeed = 2;
    public float m_JumpSpeed = 4;
    public bool m_CtrlEnable = true;

    private bool m_WasGrounded = true; //上一帧在地面上
    private bool m_IsGrounded = true; //当前在地面上
    private Vector3 m_CurMoveDir = Vector3.zero;

    private float m_JumpTimeStamp = 0;
    private float m_MinJumpInterval = 0.25f; //控制跳跃间隔
    private bool m_JumpInput = false; //控制一帧触发1次

    private Vector3 m_VerticalSpeed = Vector3.zero;
    private Vector3 m_DownChecker = new Vector3(0, -0.03f, 0); //在地面上时, 不断检测是否还在地面上用

    private float m_GroundY = 0;
    private bool m_GroundYDirty = true;

    private void Awake()
    {
        if (!m_characterCtrl)
            m_characterCtrl = GetComponent<CharacterController>();

        if (!m_Animator)
            m_Animator = GetComponent<Animator>();
    }

    private void Update()
    {
        if (m_CtrlEnable && !m_JumpInput && Input.GetKeyDown(KeyCode.Space))
        {
            m_JumpInput = true;
        }
    }

    private void FixedUpdate()
    {
        m_Animator.SetBool("Grounded", m_IsGrounded);
        //if (m_characterCtrl.isGrounded) Debug.Log($"fixed: true");
        DirectUpdate();

        m_WasGrounded = m_IsGrounded;
        m_JumpInput = false;
    }

    private void DirectUpdate()
    {
        float v = 0;
        float h = 0;
        if (m_CtrlEnable)
        {
            v = Input.GetAxis("Vertical");
            h = Input.GetAxis("Horizontal");
        }
        Transform camera = Camera.main.transform;

        Vector3 inputDir = camera.forward * v + camera.right * h; //摇杆上下为相机forward方向, 左右为相机right方向
        float inputForce = inputDir.magnitude; //区分力度的情况
        inputDir.y = 0;

        if (inputDir != Vector3.zero)
        {
            m_CurMoveDir = Vector3.Slerp(m_CurMoveDir, inputDir, Time.deltaTime * Dir_Speed);

            transform.rotation = Quaternion.LookRotation(m_CurMoveDir);
            var flags = m_characterCtrl.Move(m_CurMoveDir * m_MoveSpeed * Time.deltaTime);
            //Debug.Log($"flags: {flags}");
            m_Animator.SetFloat("MoveSpeed", inputForce); //力度
        }
        else
        {
            m_Animator.SetFloat("MoveSpeed", 0);
        }

        JumpingAndLanding();
    }

    private void JumpingAndLanding()
    {
        bool jumpCooldownOver = (Time.time - m_JumpTimeStamp) >= m_MinJumpInterval;

        if (jumpCooldownOver && m_IsGrounded && m_JumpInput) //地面上按下跳, 多次跳要有一定间隔
        {
            m_JumpTimeStamp = Time.time;
            m_IsGrounded = false;
            m_GroundYDirty = true;
            m_VerticalSpeed.y = m_JumpSpeed;
        }

        if (!m_IsGrounded) //不在地面上时, 要不断下落
        {
            m_characterCtrl.Move(m_VerticalSpeed * Time.deltaTime);
            m_IsGrounded = m_characterCtrl.isGrounded;
            if (m_IsGrounded)
            {
                m_VerticalSpeed.y = 0;
            }
            else
            {
                m_VerticalSpeed.y -= Gravity * Time.deltaTime;
            }
            //Debug.Log($"isGround: {m_characterCtrl.isGrounded}");
        }
        else //在地面上时, 也要不断检测是否需要下落(比如: 从一个高的平台走到低的平台)
        {
            var flags = m_characterCtrl.Move(m_DownChecker); //在地面上的时候, 走不下去的, 会返回below, 并回弹
            //Debug.Log($"flags: {flags}");
            if (flags == CollisionFlags.None)
            {
                m_IsGrounded = false;
                m_GroundYDirty = true;
                m_VerticalSpeed.y = 0;
            }
        }

        if (!m_WasGrounded && m_IsGrounded) //之前不在地面上, 现在在地面上了
        {
            m_Animator.SetTrigger("Land");
        }

        if (!m_IsGrounded && m_WasGrounded) //之前在地面上的, 现在不在地面上了
        {
            m_Animator.SetTrigger("Jump");
        }
    }

    public float GetGroundY()
    {
        var playerPos = this.transform.position;
        if (!m_IsGrounded)
        {
            if (m_GroundYDirty)
            {
                bool isHit = Physics.Raycast(playerPos, Vector3.down, out var hitInfo, 5);
                if (isHit)
                    m_GroundY = hitInfo.point.y;
                m_GroundYDirty = false;
            }
            return m_GroundY;
        }

        return playerPos.y;
    }

}

模型资源: Character Pack: Free Sample | 3D 人形角色 | Unity Asset Store

地面资源以及代码参考: GitHub - xieliujian/UnityDemo_PlanarShadow: UnityDemo平面阴影

 

最终效果

可以看到目前还没有阴影,后面会在这基础上展示在平面上以及平台上各种阴影的表现情况

 

相关链接

Unity中角色着地功能_terric的博客-CSDN博客

Unity中CharacterController.IsGrounded的值在True和False中反复出现导致跳跃键失灵的原因及解决方案_unity isgrounded_H2ojunjun的博客-CSDN博客

CharacterController 角色控制器实现移动和跳跃 - 盘子脸 - 博客园 (cnblogs.com)

 

posted @ 2023-05-06 01:58  yanghui01  阅读(25)  评论(0编辑  收藏  举报