相机控制, 相机跟随
# 实现的功能
(1) 滚轮拉近, 推远相机(带惯性)
(2) 鼠标左键左右,上下转动相机(带惯性)
(3) 相机跟随角色
# 待实现功能
(1) 转动相机时,如果相机和跟随角色间出现了障碍物,相机自动拉近
(2) 跟随的角色向左或向右行走时,相机自动缓慢转动
# ground为可行走地面(绿色),Sphere为不可行走区域(灰色),player为相机跟随的角色(黄色)
# EventSystem这边会用到它的拖动触发阈值
# CameraControl挂在Main Camera上, PlayerMove挂在player上
# 相机控制代码
public class CameraControl : MonoBehaviour { public Transform target; //相机跟随目标 private Vector3 _lastTargetPos; public Vector3 offset = new Vector3(0, 1.35f, 0); //与target位置的偏移, 一般只设置y方向(高度) public float zDistanceMin = 2; public float zDistanceMax = 12; private float _zDistance = 9; //相机和跟随目标当前的距离 private int _zDistanceInertia = 0; //惯性帧数 private float _zDistanceInertiaStep; //惯性步进 private int _rotateInertia = 0; //旋转惯性帧数 private float _rotateDecelerateExp = 0.75f; //减速指数 private Vector3 _lastMousePosition; private Vector3 _deltaMousePosition; private bool _drag; private float _xCurRotate = 5; public bool xRotateLock = false; public float xRotateMin = 5; public float xRotateMax = 40; private float _yCurRotate; public bool yRotateLock = false; private Quaternion _camRotation; private Vector3 _zDistanceVec3; void OnEnable() { UpdateCameraRotateAndPos(); } void Update() { CheckZoomInertia(); CheckRotateInertia(); var mscroll = Input.GetAxis("Mouse ScrollWheel"); if (mscroll < 0) //zoom out, 推远相机 { if (_zDistance < zDistanceMax) { _zDistance -= mscroll; _zDistance = Mathf.Min(_zDistance, zDistanceMax); UpdateCameraPos(); _zDistanceInertia = 15; _zDistanceInertiaStep = mscroll; } } else if (mscroll > 0) //Zoom in, 拉进相机 { if (_zDistance > zDistanceMin) { _zDistance -= mscroll; _zDistance = Mathf.Max(_zDistance, zDistanceMin); UpdateCameraPos(); _zDistanceInertia = 15; _zDistanceInertiaStep = mscroll; } } if (Input.GetMouseButtonDown(0)) { _drag = false; _lastMousePosition = Input.mousePosition; //按下的时候, 停止惯性 _zDistanceInertia = 0; _rotateInertia = 0; } else if (Input.GetMouseButton(0)) { _deltaMousePosition = Input.mousePosition - _lastMousePosition; _lastMousePosition = Input.mousePosition; if (_drag) { RotateCameraByDeltaPos(ref _deltaMousePosition); } else { if (_deltaMousePosition.sqrMagnitude >= Mathf.Sqrt(EventSystem.current.pixelDragThreshold)) //超过拖动阈值才进入drag模式 { _drag = true; } } } else if (Input.GetMouseButtonUp(0)) { if (_drag) { _rotateInertia = 15; } _drag = false; _lastMousePosition = Input.mousePosition; } } ///相机zoom惯性 private void CheckZoomInertia() { if (_zDistanceInertia > 0) { _zDistanceInertia--; if (_zDistanceInertiaStep < 0) //惯性推远 { if (_zDistance < zDistanceMax) { _zDistance -= _zDistanceInertiaStep; _zDistance = Mathf.Min(_zDistance, zDistanceMax); UpdateCameraPos(); } else { _zDistanceInertia = 0; _zDistanceInertiaStep = 0; } } else if (_zDistanceInertiaStep > 0) //惯性拉进 { if (_zDistance > zDistanceMin) { _zDistance -= _zDistanceInertiaStep; _zDistance = Mathf.Max(_zDistance, zDistanceMin); UpdateCameraPos(); } else { _zDistanceInertia = 0; _zDistanceInertiaStep = 0; } } } } ///转动相机惯性 private void CheckRotateInertia() { if (_rotateInertia > 0) { _rotateInertia--; if (_deltaMousePosition.sqrMagnitude >= 0.0001f) { RotateCameraByDeltaPos(ref _deltaMousePosition); _deltaMousePosition *= _rotateDecelerateExp; } else { _rotateInertia = 0; } } } private void RotateCameraByDeltaPos(ref Vector3 deltaPos) { const float Scale_Factor = 0.2f; //缩放系数, 调到看着舒服就行 if (!xRotateLock) { _xCurRotate += deltaPos.y * Scale_Factor; _xCurRotate = Mathf.Min(Mathf.Max(xRotateMin, _xCurRotate), xRotateMax); } if (!yRotateLock) { _yCurRotate += deltaPos.x * Scale_Factor; } UpdateCameraRotateAndPos(); } private void UpdateCameraRotateAndPos() { _camRotation = Quaternion.Euler(_xCurRotate, _yCurRotate, 0); transform.rotation = _camRotation; UpdateCameraPos(); //角度变化同时会引起位置变化 } private void UpdateCameraPos() { _zDistanceVec3.z = -_zDistance; var camPos = _camRotation * _zDistanceVec3 + target.position + offset; transform.position = camPos; } void LateUpdate() { //相机跟随 var diff = target.position - _lastTargetPos; if (diff.sqrMagnitude >= 0.0001f) { _lastTargetPos = target.position; var camPos = _camRotation * _zDistanceVec3 + target.position + offset; transform.position = camPos; } } }
# 角色行走控制代码,wasd为手动行走,鼠标右键点击地面为走到点击的点
public class PlayerMove : MonoBehaviour { private NavMeshAgent _agent; ///跟随角色的相机 public Camera followCamera; private Vector3[] _pathCorners = new Vector3[10]; void Start() { _agent = GetComponent<NavMeshAgent>(); _agent.isStopped = true; if (null == followCamera) followCamera = Camera.main; } void Update() { if (CheckNavToClickPoint() || CheckWASDMove()) return; if (_agent.enabled) { if (_agent.remainingDistance <= 0 || _agent.isStopped) { _agent.isStopped = true; _agent.enabled = false; } else { DrawNavMeshPath(); } } } ///寻路过程中, 绘制出路径 private void DrawNavMeshPath() { if (_agent.hasPath) { var len = _agent.path.GetCornersNonAlloc(_pathCorners); for (var i = 1; i < len; ++i) { var p1 = _pathCorners[i - 1]; var p2 = _pathCorners[i]; Debug.DrawLine(p1, p2, Color.red); } } } ///寻路至点击位置 private bool CheckNavToClickPoint() { if (Input.GetMouseButtonUp(1)) { const int maxDistance = 300; var ray = Camera.main.ScreenPointToRay(Input.mousePosition); //摄像机方向发射1条射线 Debug.DrawRay(ray.origin, ray.direction * maxDistance, Color.yellow); //画出这条射线 if (Physics.Raycast(ray, out var hit, maxDistance)) { Debug.DrawLine(ray.origin, hit.point, Color.red); _agent.enabled = true; //_agent.Warp(transform.position); _agent.SetDestination(hit.point); return true; } } return false; } private bool CheckWASDMove() { var x = 0; if (Input.GetKey(KeyCode.W)) x = 1; else if (Input.GetKey(KeyCode.S)) x = -1; var y = 0; if (Input.GetKey(KeyCode.A)) y = -1; else if (Input.GetKey(KeyCode.D)) y = 1; if (0 != y || 0 != x) { if (_agent.enabled) //还在寻路中, 操作了wasd, 就停止寻路 { _agent.isStopped = true; _agent.ResetPath(); _agent.enabled = false; } var relativeAngle = Mathf.Atan2(y, x) * Mathf.Rad2Deg; //摇杆向上表示与相机forward为0度, 摇杆向右表示与相机forward为90度, 以此类推 var playerTransform = transform; var cameraForwardAngle = followCamera.transform.eulerAngles.y; playerTransform.rotation = Quaternion.Euler(0, cameraForwardAngle + relativeAngle, 0); //todo: 这里可以考虑改成Lerp做平滑处理 playerTransform.Translate(Vector3.forward * 3 * Time.deltaTime, Space.Self); //上面已经调整了转向, 这边只要往前走就好 return true; } return false; } }
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 25岁的心里话
· 闲置电脑爆改个人服务器(超详细) #公网映射 #Vmware虚拟网络编辑器
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· 零经验选手,Compose 一天开发一款小游戏!
· 一起来玩mcp_server_sqlite,让AI帮你做增删改查!!