阴影实现 - 准备工作:场景中行走的角色
相机控制
实现的功能:
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平面阴影
最终效果
可以看到目前还没有阴影,后面会在这基础上展示在平面上以及平台上各种阴影的表现情况
相关链接
CharacterController 角色控制器实现移动和跳跃 - 盘子脸 - 博客园 (cnblogs.com)