783 lines
33 KiB
C#
783 lines
33 KiB
C#
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<NavMeshAgent>();
|
||
if (agent == null)
|
||
{
|
||
// 如果没有NavMeshAgent组件,自动添加一个
|
||
agent = gameObject.AddComponent<NavMeshAgent>();
|
||
}
|
||
|
||
// 检查场景是否有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<Animation>();
|
||
// if (characterAnimation == null)
|
||
// {
|
||
// // 如果没有Animation组件,自动添加一个
|
||
// characterAnimation = gameObject.AddComponent<Animation>();
|
||
// Debug.Log("已自动添加Animation组件到角色上");
|
||
// }
|
||
|
||
|
||
// 保存相机相对于角色的初始位置偏移,用于后续跟随
|
||
if (mainCamera != null)
|
||
{
|
||
initialCameraOffset = mainCamera.transform.position - transform.position;
|
||
}
|
||
else
|
||
{
|
||
}
|
||
|
||
// 检查必要的动画片段是否存在
|
||
CheckRequiredAnimations();
|
||
}
|
||
|
||
/// <summary>
|
||
/// 检查必要的动画片段是否存在
|
||
/// </summary>
|
||
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,角色准备接受新的移动指令");
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 在指定位置创建小球
|
||
/// </summary>
|
||
/// <param name="position">小球创建位置</param>
|
||
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<Renderer>();
|
||
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}");
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 立即设置角色朝向到目标位置
|
||
/// </summary>
|
||
/// <param name="targetPos">目标位置</param>
|
||
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}°");
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 确保动画状态与移动状态一致
|
||
/// </summary>
|
||
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);
|
||
}
|
||
|
||
// 完全不碰相机的旋转,保持你调整好的角度
|
||
}
|
||
}
|
||
} |