using UnityEngine; using UnityEngine.AI; using UnityEngine.EventSystems; // 添加AI系统的引用 public class CharacterController : MonoBehaviour { // 移动状态枚举 public enum MoveState { Idle, // 空闲状态 Moving, // 移动中 Arrived, // 已到达 Stuck // 卡住状态 } public float moveSpeed = 5f; // 角色移动速度 public Camera mainCamera; // 主相机 public float cameraFollowSpeed = 5f; // 相机跟随速度 public float rotationSpeed = 3f; // 角色转向速度 private Vector3 targetPosition; // 目标位置 private bool isMoving = false; // 是否正在移动 private Vector3 startPosition; // 移动开始时的位置 private int moveStartFrame; // 移动开始的帧数 private float moveStartTime; // 移动开始的时间 private Quaternion startRotation; // 移动开始时的朝向 private bool shouldSmoothRotation = true; // 是否启用平滑转向 private float originalYRotation; // 保存原始的Y轴朝向 // 添加NavMeshAgent组件引用 private NavMeshAgent agent; // 导航网格代理组件 // 添加Animation组件引用 public Animation characterAnimation; // 角色动画组件 // 小球预制体引用 public GameObject ballPrefab; // 小球预制体 // 点击拦截的图层:命中这些Layer将不触发点击移动 public LayerMask blockedClickLayers; // 移动状态管理 private MoveState currentMoveState = MoveState.Idle; public MoveState CurrentMoveState => currentMoveState; // 公共属性,供外部访问 // 相机跟随相关 private Vector3 initialCameraOffset; // 保存相机相对于角色的初始位置偏移 void Start() { // 获取NavMeshAgent组件 agent = GetComponent(); if (agent == null) { // 如果没有NavMeshAgent组件,自动添加一个 agent = gameObject.AddComponent(); } // 检查场景是否有NavMesh if (!NavMesh.SamplePosition(transform.position, out NavMeshHit hit, 1.0f, NavMesh.AllAreas)) { // Debug.LogWarning("警告:角色当前位置不在NavMesh上,请确保场景已烘焙导航网格"); } // 配置NavMeshAgent参数 // agent.speed = moveSpeed; // 设置移动速度 // agent.angularSpeed = 30f; // 设置旋转速度,让角色转向更加平滑自然 // agent.acceleration = 5f; // 设置加速度,让移动更平滑 agent.stoppingDistance = 0.1f; // 设置停止距离,减少精确调整导致的绕圈 // // // 优化路径计算质量,减少绕行 // agent.obstacleAvoidanceType = ObstacleAvoidanceType.HighQualityObstacleAvoidance; // agent.radius = 0.5f; // 设置角色半径,影响路径计算 // agent.height = 2.0f; // 设置角色高度 // // // 优化朝向行为,让角色先移动再调整朝向 agent.updateRotation = false; // 暂时禁用NavMeshAgent的自动旋转 // agent.updateUpAxis = false; // 禁用上轴更新 // 保存原始的Y轴朝向,用于朝向保护 originalYRotation = transform.rotation.eulerAngles.y; // 确保朝向值在合理范围内 if (originalYRotation < 0) originalYRotation += 360f; // 获取Animation组件 // characterAnimation = GetComponent(); // if (characterAnimation == null) // { // // 如果没有Animation组件,自动添加一个 // characterAnimation = gameObject.AddComponent(); // Debug.Log("已自动添加Animation组件到角色上"); // } // 保存相机相对于角色的初始位置偏移,用于后续跟随 if (mainCamera != null) { initialCameraOffset = mainCamera.transform.position - transform.position; } else { } // 检查必要的动画片段是否存在 CheckRequiredAnimations(); } /// /// 检查必要的动画片段是否存在 /// private void CheckRequiredAnimations() { if (characterAnimation != null) { bool hasRunning = characterAnimation.GetClip("Running") != null; bool hasStand = characterAnimation.GetClip("Stand") != null; if (!hasRunning) { } if (!hasStand) { } if (hasRunning && hasStand) { } } } // 公共方法:手动设置移动目标 public void SetMoveTarget(Vector3 target) { if (agent != null && agent.isOnNavMesh) { // 检查目标位置是否过近,只有极近距离才不触发移动 float distanceToTarget = Vector3.Distance(transform.position, target); if (distanceToTarget <= 0.3f) // 增加最小移动距离到0.3米 { Debug.Log($"手动设置目标位置过近 ({distanceToTarget:F2}m),不触发移动"); return; } else if (distanceToTarget < 0.5f) { Debug.Log($"手动设置目标位置较近 ({distanceToTarget:F2}m),但距离足够,允许移动"); } targetPosition = target; bool pathSet = agent.SetDestination(target); if (pathSet) { // 手动设置目标时也立即转向,不使用平滑转向 shouldSmoothRotation = false; SetImmediateRotation(target); // 检查路径是否合理,避免绕行 if (agent.path.corners.Length > 2) { Debug.Log($"检测到复杂路径,路径点数: {agent.path.corners.Length},尝试优化"); // 如果路径过于复杂,尝试直接移动 // agent.ResetPath(); Vector3 directTarget = Vector3.Lerp(transform.position, target, 0.8f); agent.SetDestination(directTarget); } isMoving = true; currentMoveState = MoveState.Moving; startPosition = transform.position; // 记录移动开始位置 moveStartFrame = Time.frameCount; // 记录移动开始帧数 moveStartTime = Time.time; // 记录移动开始时间 startRotation = transform.rotation; // 记录移动开始时的朝向 // shouldSmoothRotation 已在上面设置为 false,手动设置目标时立即转向 // 播放跑步动画 if (characterAnimation != null) { characterAnimation.Play("Running"); Debug.Log("开始播放跑步动画 Running"); } Debug.Log($"手动设置移动目标: {target},直接距离: {distanceToTarget:F2}m,开始位置: {startPosition},已立即转向目标方向"); } else { Debug.LogWarning($"无法设置移动目标: {target}"); } } } // 公共方法:停止移动 public void StopMoving() { if (agent != null) { agent.ResetPath(); isMoving = false; currentMoveState = MoveState.Idle; shouldSmoothRotation = false; // 停止平滑转向 // 停止跑步动画 if (characterAnimation != null) { characterAnimation.Stop(); // 停止所有动画 Debug.Log("手动停止移动,停止所有动画"); // 播放Stand动画 if (characterAnimation.GetClip("Stand") != null) { characterAnimation.Play("Stand"); Debug.Log("开始播放Stand动画"); } } // 强制恢复正确的朝向,避免角色面朝地下 RestoreCorrectRotation(); Debug.Log("角色停止移动,朝向已恢复,动画已停止"); } } // 公共方法:获取当前移动状态 public MoveState GetMoveState() { return currentMoveState; } // 公共方法:检查是否正在移动 public bool IsMoving() { return isMoving; } // 恢复正确的朝向,避免角色面朝地下 private void RestoreCorrectRotation() { Vector3 currentEuler = transform.rotation.eulerAngles; // 只恢复X轴朝向为0(水平),保持当前的Y轴朝向(允许转向) Vector3 correctedEuler = new Vector3(0, currentEuler.y, 0); Quaternion correctedRotation = Quaternion.Euler(correctedEuler); transform.rotation = correctedRotation; Debug.Log($"已恢复水平朝向,X轴: 0°, Y轴: {currentEuler.y:F1}°, 允许角色转向"); } void Update() { if (EventSystem.current.IsPointerOverGameObject()) return; if (Input.touchCount > 0) { // 获取第一个触摸点 Touch touch = Input.GetTouch(0); // 检查触摸阶段是否为触摸开始 if (touch.phase == TouchPhase.Began) { if (EventSystem.current.IsPointerOverGameObject()) { Debug.Log($"检测到触摸空白处!触摸位置: {touch.position}, 触摸ID: {touch.fingerId}"); return; } // 输出触摸调试信息 } } HandleInput(); MoveCharacter(); FollowCamera(); } // 处理输入,检测点击位置 void HandleInput() { // 如果角色正在移动,不处理新的输入 if (isMoving) { return; } // 如果角色状态不是Idle,重置为Idle状态 if (currentMoveState != MoveState.Idle) { currentMoveState = MoveState.Idle; Debug.Log("重置角色状态为Idle,准备接受新的移动指令"); } if (Input.GetMouseButtonDown(0)) // 如果鼠标点击(或触摸屏上的点击) { Ray ray = mainCamera.ScreenPointToRay(Input.mousePosition); // 将鼠标位置转换为射线 RaycastHit hit; if (Physics.Raycast(ray, out hit)) // 检测射线是否与地面碰撞 { // 如果点击到的是需要拦截的Layer,则不触发移动 if (hit.collider != null) { int hitLayer = hit.collider.gameObject.layer; if ((blockedClickLayers.value & (1 << hitLayer)) != 0) { Debug.Log($"点击命中在拦截图层内的物体:{hit.collider.name} (Layer: {hitLayer}),本次点击不触发移动"); return; } } targetPosition = hit.point; // 获取点击的世界坐标 // 在点击位置创建小球 CreateBallAtPosition(targetPosition); // 检查目标位置是否过近,只有极近距离才不触发移动 float distanceToTarget = Vector3.Distance(transform.position, targetPosition); if (distanceToTarget <= 0.3f) // 增加最小移动距离到0.3米 { Debug.Log($"目标位置过近 ({distanceToTarget:F2}m),不触发移动"); return; } else if (distanceToTarget < 0.5f) { Debug.Log($"目标位置较近 ({distanceToTarget:F2}m),但距离足够,允许移动"); } // 使用NavMeshAgent设置目标位置 if (agent != null && agent.isOnNavMesh) { // 设置导航目标 bool pathSet = agent.SetDestination(targetPosition); if (pathSet) { // 点击时立即转向目标方向,不使用平滑转向 shouldSmoothRotation = false; SetImmediateRotation(targetPosition); // 检查路径是否合理,避免绕行 if (agent.path.corners.Length > 2) { Debug.Log($"检测到复杂路径,路径点数: {agent.path.corners.Length},尝试优化"); // 如果路径过于复杂,尝试直接移动 //agent.ResetPath(); Vector3 directTarget = Vector3.Lerp(transform.position, targetPosition, 0.8f); agent.SetDestination(directTarget); } isMoving = true; // 设置为正在移动 currentMoveState = MoveState.Moving; // 更新移动状态 startPosition = transform.position; // 记录移动开始位置 moveStartFrame = Time.frameCount; // 记录移动开始帧数 moveStartTime = Time.time; // 记录移动开始时间 startRotation = transform.rotation; // 记录移动开始时的朝向 // shouldSmoothRotation 已在上面设置为 false,点击时立即转向 // 播放跑步动画 if (characterAnimation != null) { characterAnimation.Play("Running"); Debug.Log("开始播放跑步动画 Running"); } // Debug.Log($"角色开始移动到目标位置: {targetPosition},直接距离: {distanceToTarget:F2}m,开始位置: {startPosition},已立即转向目标方向,NavMeshAgent状态: {agent.isOnNavMesh}"); } else { currentMoveState = MoveState.Stuck; // 更新为卡住状态 Debug.LogWarning("无法设置导航路径,目标位置可能不在导航网格上"); } } else { currentMoveState = MoveState.Stuck; // 更新为卡住状态 Debug.LogWarning("NavMeshAgent不可用或不在导航网格上"); } } } } // 移动角色 void MoveCharacter() { if (isMoving && agent != null) { // 计算直接距离到目标 float directDistanceToTarget = Vector3.Distance(transform.position, targetPosition); // 简化的到达检测逻辑 - 只基于直接距离判断 float actualMovedDistance = Vector3.Distance(transform.position, startPosition); // 检查是否真正到达目标位置 - 基于直接距离 bool hasReachedTarget = false; // 直接距离检查 - 当直接距离很小时认为已到达 if (directDistanceToTarget <= 0.1f) { hasReachedTarget = true; Debug.Log($"已到达目标位置,直接距离: {directDistanceToTarget:F2}m,实际移动: {actualMovedDistance:F2}m"); } // 备用到达检测:当NavMeshAgent报告已到达时 else if (agent.hasPath && !float.IsInfinity(agent.remainingDistance) && agent.remainingDistance <= agent.stoppingDistance) { hasReachedTarget = true; Debug.Log($"NavMeshAgent报告已到达,剩余距离: {agent.remainingDistance:F2}m,直接距离: {directDistanceToTarget:F2}m"); } // 特殊处理:当直接距离很小且速度为0时,认为已到达 else if (directDistanceToTarget <= 0.3f && agent.velocity.magnitude < 0.01f) { hasReachedTarget = true; Debug.Log($"特殊到达检测:直接距离: {directDistanceToTarget:F2}m,速度: {agent.velocity.magnitude:F2}m/s,认为已到达"); } if (hasReachedTarget) { // 角色已到达目标位置,停止移动 isMoving = false; agent.ResetPath(); // 重置导航路径 currentMoveState = MoveState.Arrived; // 更新为已到达状态 shouldSmoothRotation = false; // 停止平滑转向 // 强制停止所有动画,避免动画状态混乱 if (characterAnimation != null) { characterAnimation.Stop(); // 停止所有动画 // Debug.Log("已到达目的地,停止所有动画"); // 播放Stand动画 if (characterAnimation.GetClip("Stand") != null) { characterAnimation.Play("Stand"); // Debug.Log("开始播放Stand动画"); } else { //Debug.LogWarning("未找到Stand动画片段,请确保动画组件中有名为'Stand'的动画"); } } // 强制恢复正确的朝向,避免角色面朝地下 RestoreCorrectRotation(); //Debug.Log($"角色已到达目标位置,停止移动,直接距离: {directDistanceToTarget:F2}m,实际移动: {actualMovedDistance:F2}m"); // 重置移动相关变量,确保下次移动正常 targetPosition = Vector3.zero; startPosition = Vector3.zero; moveStartFrame = 0; moveStartTime = 0f; startRotation = Quaternion.identity; } // 改进的卡住检测:当速度很低且移动距离很小时,认为角色卡住 // 添加启动缓冲:给角色至少3秒时间开始移动 else if (agent.velocity.magnitude < 0.01f && actualMovedDistance < 0.1f && directDistanceToTarget > 2.0f && Time.time - moveStartTime > 3.0f) { // 角色可能卡住了,强制停止移动 isMoving = false; agent.ResetPath(); currentMoveState = MoveState.Stuck; // 更新为卡住状态 shouldSmoothRotation = false; // 停止平滑转向 // 强制停止所有动画 if (characterAnimation != null) { characterAnimation.Stop(); // 停止所有动画 Debug.Log("角色卡住,强制停止移动和动画"); // 播放Stand动画 if (characterAnimation.GetClip("Stand") != null) { characterAnimation.Play("Stand"); Debug.Log("开始播放Stand动画"); } } // 强制恢复正确的朝向 RestoreCorrectRotation(); Debug.LogWarning($"角色卡住,强制停止移动,直接距离: {directDistanceToTarget:F2}m,实际移动: {actualMovedDistance:F2}m,速度: {agent.velocity.magnitude:F2}m/s,路径状态: {agent.pathStatus}"); } else if (agent.pathStatus == NavMeshPathStatus.PathInvalid) { // 路径无效,停止移动 isMoving = false; agent.ResetPath(); currentMoveState = MoveState.Stuck; // 更新为卡住状态 shouldSmoothRotation = false; // 停止平滑转向 // 强制停止所有动画,避免动画状态混乱 if (characterAnimation != null) { characterAnimation.Stop(); // 停止所有动画 Debug.Log("路径无效,停止所有动画"); // 播放Stand动画 if (characterAnimation.GetClip("Stand") != null) { characterAnimation.Play("Stand"); Debug.Log("开始播放Stand动画"); } } // 强制恢复正确的朝向,避免角色面朝地下 RestoreCorrectRotation(); Debug.LogWarning("导航路径无效,停止移动"); } // 处理PathPartial状态:当路径不完整时,如果距离很近就认为已到达 else if (agent.pathStatus == NavMeshPathStatus.PathPartial && directDistanceToTarget <= 0.2f) { // 检查角色是否仍在移动,如果速度很低则停止 if (agent.velocity.magnitude < 0.1f) { // 路径不完整但距离很近且角色已停止移动,认为已到达 isMoving = false; agent.ResetPath(); currentMoveState = MoveState.Arrived; // 更新为已到达状态 shouldSmoothRotation = false; // 停止平滑转向 // 强制停止所有动画,避免动画状态混乱 if (characterAnimation != null) { characterAnimation.Stop(); // 停止所有动画 Debug.Log("路径不完整但已接近目标,停止所有动画"); // 播放Stand动画 if (characterAnimation.GetClip("Stand") != null) { characterAnimation.Play("Stand"); Debug.Log("开始播放Stand动画"); } } // 强制恢复正确的朝向,避免角色面朝地下 RestoreCorrectRotation(); Debug.Log($"路径不完整但已接近目标,停止移动,直接距离: {directDistanceToTarget:F2}m"); // 重置移动相关变量,确保下次移动正常 targetPosition = Vector3.zero; startPosition = Vector3.zero; moveStartFrame = 0; moveStartTime = 0f; startRotation = Quaternion.identity; } else { // 角色仍在移动,继续移动直到速度降低 Debug.Log($"路径不完整但角色仍在移动,继续移动,直接距离: {directDistanceToTarget:F2}m,速度: {agent.velocity.magnitude:F2}m/s"); // 如果距离很近但路径不完整,尝试直接移动到目标(最终冲刺) if (directDistanceToTarget <= 0.3f && agent.velocity.magnitude < 0.5f) { // 使用直接移动来弥补最后的距离差 Vector3 directMove = Vector3.MoveTowards(transform.position, targetPosition, 0.1f * Time.deltaTime); transform.position = directMove; Debug.Log($"执行最终冲刺,直接移动到目标,剩余距离: {Vector3.Distance(transform.position, targetPosition):F2}m"); } } } else { // 正常移动中,确保角色真正在移动 currentMoveState = MoveState.Moving; } // 输出移动状态信息(调试用)- 只在状态变化时输出 if (isMoving) { float distanceFromStart = Vector3.Distance(transform.position, startPosition); // 移动中的朝向处理 - 始终面向前进方向(转弯时也要朝向转弯方向) // 优先使用 NavMeshAgent 的 steeringTarget 来确定下一转向目标 Vector3 lookDirection = Vector3.zero; if (agent.hasPath) { lookDirection = agent.steeringTarget - transform.position; // 指向下一个转向点 } // 如果steeringTarget方向过小,则回退到期望速度方向 if (lookDirection.sqrMagnitude < 0.0001f) { lookDirection = agent.desiredVelocity; // 使用期望速度方向 } // 再次回退:若仍旧过小,使用实际速度方向 if (lookDirection.sqrMagnitude < 0.0001f) { lookDirection = agent.velocity; // 使用实际速度方向 } // 仅在有效方向下进行旋转,并忽略Y轴(保持水平朝向) lookDirection.y = 0f; if (lookDirection.sqrMagnitude > 0.0001f) { lookDirection.Normalize(); Quaternion targetRot = Quaternion.LookRotation(lookDirection); // 只旋转Y轴,保持X/Z为0,避免低头或仰头 Vector3 targetEuler = targetRot.eulerAngles; Quaternion flatTargetRot = Quaternion.Euler(0f, targetEuler.y, 0f); // 使用平滑旋转让转向自然 transform.rotation = Quaternion.Slerp( transform.rotation, flatTargetRot, Mathf.Clamp01(Time.deltaTime * rotationSpeed) ); } // 只在每30帧输出一次状态信息,减少日志量 if (Time.frameCount % 30 == 0) { // 获取当前动画状态信息 string currentAnimation = "无"; if (characterAnimation != null && characterAnimation.isPlaying) { currentAnimation = characterAnimation.clip != null ? characterAnimation.clip.name : "未知"; } // 计算直接距离到目标 float currentDirectDistance = Vector3.Distance(transform.position, targetPosition); // Debug.Log($"角色移动中,直接距离: {currentDirectDistance:F2}m, 速度: {agent.velocity.magnitude:F2}m/s, 从起点移动: {distanceFromStart:F2}m, 状态: {currentMoveState}, 当前朝向: {transform.rotation.eulerAngles.y:F1}°, 当前动画: {currentAnimation}, 路径状态: {agent.pathStatus}"); } } } else if (!isMoving && currentMoveState != MoveState.Idle) { // 如果没有在移动,重置状态为空闲 currentMoveState = MoveState.Idle; // 确保动画状态与移动状态一致 EnsureAnimationStateConsistency(); } // 添加对Arrived状态的处理,确保能转换为Idle状态 else if (currentMoveState == MoveState.Arrived) { // 到达状态后,延迟一帧转换为空闲状态,确保动画播放完成 currentMoveState = MoveState.Idle; Debug.Log("状态转换:Arrived -> Idle,角色准备接受新的移动指令"); } } /// /// 在指定位置创建小球 /// /// 小球创建位置 private void CreateBallAtPosition(Vector3 position) { try { if (ballPrefab != null) { // 在点击位置创建小球 GameObject ball = Instantiate(ballPrefab, position, Quaternion.identity); Debug.Log($"在位置 {position} 创建了小球"); } else { // 如果没有预制体,尝试创建一个简单的球体 // WebGL环境中可能不支持CreatePrimitive,需要使用try-catch try { GameObject ball = GameObject.CreatePrimitive(PrimitiveType.Sphere); ball.transform.position = position; ball.transform.localScale = Vector3.one * 0.1f; // 设置小球大小为0.1 ball.name = "ClickBall"; // 设置名称 // 添加一个简单的材质颜色 Renderer renderer = ball.GetComponent(); if (renderer != null) { renderer.material.color = Color.red; // 设置为红色 } Debug.Log($"在位置 {position} 创建了默认小球"); } catch (System.Exception ex) { // WebGL环境中CreatePrimitive可能失败(如SphereCollider不存在) Debug.LogWarning($"无法使用CreatePrimitive创建球体: {ex.Message},请使用预制体或跳过创建小球"); // 可选:创建一个简单的GameObject作为替代 // GameObject ball = new GameObject("ClickBall"); // ball.transform.position = position; // Debug.Log($"在位置 {position} 创建了占位小球(仅用于调试)"); } } } catch (System.Exception ex) { // 捕获所有其他可能的异常 Debug.LogWarning($"创建小球时发生异常: {ex.Message},位置: {position}"); } } /// /// 立即设置角色朝向到目标位置 /// /// 目标位置 private void SetImmediateRotation(Vector3 targetPos) { // 计算水平方向的目标方向,忽略Y轴变化 Vector3 horizontalDirection = (targetPos - transform.position); horizontalDirection.y = 0; // 忽略Y轴变化,只计算水平方向 if (horizontalDirection.magnitude > 0.01f) { horizontalDirection.Normalize(); // 直接设置朝向,不使用插值 Quaternion targetRotation = Quaternion.LookRotation(horizontalDirection); // 保持X轴和Z轴为0,只改变Y轴朝向 Vector3 targetEuler = targetRotation.eulerAngles; Vector3 finalEuler = new Vector3(0, targetEuler.y, 0); Quaternion finalRotation = Quaternion.Euler(finalEuler); transform.rotation = finalRotation; Debug.Log($"角色立即转向目标方向,Y轴朝向: {finalEuler.y:F1}°"); } } /// /// 确保动画状态与移动状态一致 /// private void EnsureAnimationStateConsistency() { if (characterAnimation != null) { // 检查当前播放的动画 string currentClipName = characterAnimation.clip != null ? characterAnimation.clip.name : ""; // 如果当前是空闲状态,但动画不是Stand,则强制播放Stand动画 if (currentMoveState == MoveState.Idle && currentClipName != "Stand") { // 只有在动画确实在播放且不是Stand时才强制切换 if (characterAnimation.isPlaying && characterAnimation.GetClip("Stand") != null) { characterAnimation.Stop(); // 停止当前动画 characterAnimation.Play("Stand"); Debug.Log($"动画状态不一致,强制播放Stand动画,当前动画: {currentClipName}"); } } // 如果当前是移动状态,但动画不是Running,则强制播放Running动画 else if (currentMoveState == MoveState.Moving && currentClipName != "Running") { // 只有在动画确实在播放且不是Running时才强制切换 if (characterAnimation.isPlaying && characterAnimation.GetClip("Running") != null) { characterAnimation.Stop(); // 停止当前动画 characterAnimation.Play("Running"); Debug.Log($"动画状态不一致,强制播放Running动画,当前动画: {currentClipName}"); } } } } // 相机跟随角色 void FollowCamera() { // 相机跟随角色,保持相对位置关系,只做简单平移 if (mainCamera != null) { // 计算目标位置:角色当前位置 + 初始相对偏移 Vector3 targetCameraPos = transform.position + initialCameraOffset; // 计算当前相机到目标位置的距离 float distanceToTarget = Vector3.Distance(mainCamera.transform.position, targetCameraPos); // 如果距离很小,直接设置位置,避免卡顿 if (distanceToTarget < 0.1f) { mainCamera.transform.position = targetCameraPos; } else { // 使用可配置的跟随速度,确保平滑跟随 mainCamera.transform.position = Vector3.MoveTowards(mainCamera.transform.position, targetCameraPos, cameraFollowSpeed * Time.deltaTime); } // 完全不碰相机的旋转,保持你调整好的角度 } } }