Files
ft/Client/Assets/Scripts/EventPartnerGather/Mining/EventPartnerMiningCharacterController.cs
2026-06-29 21:18:33 +08:00

1436 lines
53 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using System;
using System.Threading.Tasks;
using asap.core;
using Game;
using TMPro;
using UI.PartnerGather.Mining;
using UnityEngine;
using UnityEngine.AddressableAssets;
using UnityEngine.UI;
using Quaternion = UnityEngine.Quaternion;
using Vector3 = UnityEngine.Vector3;
namespace EventPartnerGather.Mining
{
/// <summary>
/// 事件合作伙伴挖矿角色控制器
/// 控制角色的移动、动画播放、挖矿行为等
///
/// 建议挂载在:角色模型的根对象上
/// 需要组件CharacterController必须
///
/// 场景结构建议:
/// Character (挂载此脚本 + CharacterController)
/// ├── Model (角色模型)
/// │ └── Animator (动画控制器)
/// └── Body (可选的身体Transform用于旋转控制)
/// </summary>
[RequireComponent(typeof(CharacterController))]
public class EventPartnerMiningCharacterController : MonoBehaviour
{
private const bool EnableTraceLogs = false;
private static readonly int BasicMove = Animator.StringToHash("_basicMove");
private static readonly int BackToIdle = Animator.StringToHash("_backToIdle");
private static readonly int ToBasic = Animator.StringToHash("_toBasic");
private static readonly int BasicGathering = Animator.StringToHash("_basicGathering");
private static readonly int ToAdvance = Animator.StringToHash("_toAdvance");
private static readonly int ToMaster = Animator.StringToHash("_toMaster");
private static readonly int ShowType = Animator.StringToHash("_showType");
private static readonly int Celebrate = Animator.StringToHash("_celebrate");
// private static readonly int Advancedmove = Animator.StringToHash("_advancedmove");
private static readonly int MasterMove = Animator.StringToHash("_masterMove");
private static readonly int AdvancedGathering = Animator.StringToHash("_advancedGathering");
private static readonly int MasterGathering = Animator.StringToHash("_masterGathering");
private static readonly int AdvancedMove = Animator.StringToHash("_advancedMove");
private static readonly int EndIdle = Animator.StringToHash("_endIdle");
private static readonly int IsGathering = Animator.StringToHash("_isGathering");
// 0 Idle 1 Move 2 Gathering 3 Celebrate
private static readonly int CurrentStatus = Animator.StringToHash("_currentStatus");
public Vector3 LastTargetPosition { get;set ;}
[Header("组件引用")] public Animator animator;
public Transform body;
public CharacterController characterController;
// [Tooltip("用户落点偏移")]
// [Range(-10f, 10f)] public List<float> userPointDelta = new();
// // 设置一下,以后修改
// [SerializeField]
// private float columnDistance = 1.0f;
[Header("移动配置")]
public float moveSpeed = 2f;
public float idleThreshold = 0.05f;
public float rotationSpeed = 5f;
[Tooltip("跟随速度")]
public float followSpeed = 3f;
[Header("模型配置")] [Tooltip("是否平滑旋转")]
public bool smoothRotation = true;
[Tooltip("旋转动画持续时间")]
[Range(0.1f, 1f)]
public float rotationDuration = 0.2f;
[Header("调试配置")] public bool showDebugInfo = true;
public bool showMovementPath = true;
public Color targetPointColor = Color.red;
public Color pathColor = Color.yellow;
public Color directionColor = Color.green;
[Header("动画参数")]
public string runAnimParam = "run";
public string mineAnimParam = "mine";
public string idleAnimParam = "idle";
[Header("头像")]
public Transform headUI;
// 头像跟随
[Header("Bone")]
public Transform targetBone;
public bool useSmoothing = true;
public float smoothingSpeed = 10f;
[Header("呼吸效果")]
public bool enableBreathing = true;
public float breathingAmplitude = 0.02f;
public float breathingFrequency = 1.2f;
public AnimationCurve breathingCurve = AnimationCurve.EaseInOut(0, 0, 1, 1);
[Header("变身特效")]
public Transform _fx_advancedToBasic;
public Transform _fx_basicToAdvanced;
public Transform _fx_advancedGather;
public Transform _fx_basicGather;
public Transform _fx_masterGather;
public Transform _fx_basicToMaster;
public Transform _fx_masterToBasic;
// 计算标准的变量
// 标准位置
[HideInInspector]
public int step = -1;
//
[HideInInspector]
public int col = -1 ;
//
// public Vector3 localOffset = new Vector3(0, 2f, 0);
private Vector3 localOffset;
private float localOffsetY;
private Vector3 velocity;
private Vector3 previousBonePosition;
private Vector3 currentOffset;
private float breathingTime;
private string basicAniParam = "basic";
private string advanceAniAnim = "advance";
private string masterAniAnim = "master";
// 用户积分
private TMP_Text _textNum;
private TMP_Text _textName;
// 调试信息
private Vector3 _debugTargetPosition;
private Vector3 _debugStartPosition;
private bool _isMoving = false;
private Vector3 _debugMoveDirection;
// 采集次数
private int _gatherNum;
public int GatherNumPerDuration { get; set; } = 3;
public bool IsMainCharacter { get; set; } = false;
//Task
private TaskCompletionSource<bool> _playMiningTcs;
private TaskCompletionSource<bool> _changeShowTypeTask;
private TaskCompletionSource<bool> _idleSpecialTask;
private TaskCompletionSource<bool> _playCelebrateTcs;
private int idleStateHash;
private int basicMoveHash;
// private int advancedMoveHash;
private int MasterMoveHash;
private int BasicGatheringHash;
private int BasicRunningHash;
private int BasicToAdvancedHash;
private int AdvancedRunningHash;
private int AdvancedToBasicHash;
private int AdvancedGatheringHash;
private int AdvancedIdleHash;
private int BasicToMasterHash;
private int MasterRunningHash;
private int MasterToBasicHash;
private int MasterGatheringHash;
private int MasterIdleHash;
private AudioClip audioBasic;
private AudioClip audioAdvanced;
private AudioClip audioMaster;
private Sound soundMove;
private void Awake()
{
if (!characterController)
characterController = GetComponent<CharacterController>();
if (!body)
body = transform.Find("body");
_textNum = transform.Find("ui/myself/myself/head/text_num").GetComponent<TMP_Text>();
//
_textName = transform.Find("ui/myself/myself/head/btn_head/text_name").GetComponent<TMP_Text>();
idleStateHash = Animator.StringToHash("Base Layer.Idle");
basicMoveHash = Animator.StringToHash("Base Layer.BasicMove");
MasterMoveHash = Animator.StringToHash("Base Layer.MasterMove");
BasicGatheringHash = Animator.StringToHash("Base Layer.BasicMove.BasicGathering");
BasicRunningHash = Animator.StringToHash("Base Layer.BasicMove.BasicRunning");
BasicToAdvancedHash = Animator.StringToHash("Base Layer.AdvancedMove.BasicToAdvanced");
AdvancedRunningHash = Animator.StringToHash("Base Layer.AdvancedMove.AdvancedRunning");
AdvancedToBasicHash = Animator.StringToHash("Base Layer.AdvancedMove.AdvancedToBasic");
AdvancedGatheringHash = Animator.StringToHash("Base Layer.AdvancedMove.AdvancedGathering");
AdvancedIdleHash = Animator.StringToHash("Base Layer.AdvancedMove.AdvancedIdle");
BasicToMasterHash = Animator.StringToHash("Base Layer.MasterMove.BasicToMaster");
MasterRunningHash = Animator.StringToHash("Base Layer.MasterMove.MasterRunning");
MasterToBasicHash = Animator.StringToHash("Base Layer.MasterMove.MasterToBasic");
MasterGatheringHash = Animator.StringToHash("Base Layer.MasterMove.MasterGathering");
MasterIdleHash = Animator.StringToHash("Base Layer.MasterMove.MasterIdle");
LoadAudio();
}
private async void LoadAudio()
{
audioBasic = await Addressables.LoadAssetAsync<AudioClip>("audio_ui_partnermining_npc_basicrun").Task;
audioAdvanced = await Addressables.LoadAssetAsync<AudioClip>("audio_ui_partnermining_npc_advancedrun").Task;
audioMaster = await Addressables.LoadAssetAsync<AudioClip>("audio_ui_partnermining_npc_masterrun").Task;
}
void AudioRelease()
{
Addressables.Release(audioBasic);
Addressables.Release(audioAdvanced);
Addressables.Release(audioMaster);
}
private void Start()
{
if (animator)
{
animator.GetComponent<CharacterAnimationReceiver>()?.SetCharacterController(this);
}
SetSpinPointText(0);
localOffset = new Vector3(headUI.position.x - targetBone.position.x , headUI.position.y - targetBone.position.y, 0);
localOffsetY = headUI.position.y - targetBone.position.y;
}
private void Update()
{
}
private void OnDestroy()
{
StopMoveAudio();
AudioRelease();
}
void StopMoveAudio()
{
if (soundMove)
{
soundMove.audioSource.Stop();
soundMove.ReturnPool();
soundMove = null;
}
}
private void LateUpdate()
{
// throw new NotImplementedException();
if (!targetBone) return;
UpdateBreathingEffect();
UpdateFollowPosition();
}
private void UpdateBreathingEffect()
{
if (!enableBreathing) return;
breathingTime += Time.deltaTime * breathingFrequency;
// 使用动画曲线获得更自然的呼吸
var breathingValue = breathingCurve.Evaluate((Mathf.Sin(breathingTime) + 1) * 0.5f);
// 应用呼吸偏移
var breathingOffset = Vector3.up * (breathingValue * breathingAmplitude);
currentOffset = localOffset + breathingOffset;
// localOffsetY
}
private void UpdateFollowPosition()
{
// var targetPosition = targetBone.position + currentOffset;// targetBone.TransformDirection(currentOffset);
var newPosition = new Vector3(headUI.position.x, targetBone.position.y + localOffsetY, headUI.position.z);
// // 预测性跟随
// if (predictiveFollowing)
// {
// var boneVelocity = (targetBone.position - previousBonePosition) / Time.deltaTime;
// targetPosition += boneVelocity * predictionStrength;
// }
// 平滑跟随
if (useSmoothing)
{
headUI.position = Vector3.SmoothDamp( headUI.position, newPosition, ref velocity, 1f / smoothingSpeed );
}
else
{
headUI.position = newPosition;
}
previousBonePosition = targetBone.position;
}
private string GetStateName()
{
var stateInfo = animator.GetCurrentAnimatorStateInfo(0);
var stateName = "NULL";
// 检查当前状态
if (stateInfo.fullPathHash == idleStateHash)
{
// Log("当前状态: Idle");
stateName = "Idle";
}
else if (stateInfo.fullPathHash == BasicRunningHash)
{
// Log("当前状态: BasicRunning");
stateName = "BasicRunning";
}
else if (stateInfo.fullPathHash == BasicGatheringHash)
{
// Log("当前状态: BasicGathering");
stateName = "BasicGathering";
}
else if (stateInfo.fullPathHash == BasicToAdvancedHash)
{
// Log("当前状态: BasicToAdvanced");
stateName = "BasicToAdvanced";
}
else if (stateInfo.fullPathHash == AdvancedRunningHash)
{
// Log("当前状态: AdvancedRunning");
stateName = "AdvancedRunning";
}
else if (stateInfo.fullPathHash == AdvancedToBasicHash)
{
// Log("当前状态: AdvancedToBasicHash");
stateName = "AdvancedToBasic";
}
else if (stateInfo.fullPathHash == AdvancedGatheringHash)
{
// Log("当前状态: AdvancedGathering");
stateName = "AdvancedGathering";
}
else if (stateInfo.fullPathHash == AdvancedIdleHash)
{
// Log("当前状态: AdvancedToBasicHash");
stateName = "AdvancedIdleHash";
}
else if (stateInfo.fullPathHash == BasicToMasterHash)
{
// Log("当前状态: BasicToMaster");
stateName = "BasicToMaster";
}
else if (stateInfo.fullPathHash == MasterRunningHash)
{
// Log("当前状态: MasterRunning");
stateName = "MasterRunning";
}
else if (stateInfo.fullPathHash == MasterGatheringHash)
{
// Log("当前状态: MasterGathering");
stateName = "MasterGathering";
}
else if (stateInfo.fullPathHash == MasterIdleHash)
{
stateName = "MasterIdleHash";
}
return stateName;
}
private void OnDrawGizmos()
{
if (body != null && showDebugInfo)
{
// 绘制角色位置和朝向
Gizmos.color = Color.green;
Gizmos.DrawWireSphere(body.position, 0.5f);
// 绘制前方向量(蓝色)
Gizmos.color = Color.blue;
Gizmos.DrawRay(body.position, body.forward * 2f);
// 绘制右方向量(红色)
Gizmos.color = Color.red;
Gizmos.DrawRay(body.position, body.right * 1.5f);
// 绘制上方向量(黄色,较短)
Gizmos.color = Color.yellow;
Gizmos.DrawRay(body.position, body.up * 1f);
// === 新增:移动调试可视化 ===
if (_isMoving)
{
// 绘制目标点
Gizmos.color = targetPointColor;
Gizmos.DrawWireSphere(_debugTargetPosition, 0.8f);
Gizmos.DrawCube(_debugTargetPosition + Vector3.up * 0.5f, Vector3.one * 0.3f);
// 绘制起始点
Gizmos.color = Color.cyan;
Gizmos.DrawWireCube(_debugStartPosition, Vector3.one * 0.4f);
if (showMovementPath)
{
// 绘制移动路径线
Gizmos.color = pathColor;
Gizmos.DrawLine(_debugStartPosition, _debugTargetPosition);
// 绘制当前位置到目标的连线
Gizmos.color = Color.white;
Gizmos.DrawLine(body.position, _debugTargetPosition);
// 绘制移动方向箭头
Vector3 directionArrow = _debugMoveDirection * 3f;
Gizmos.color = directionColor;
Gizmos.DrawRay(body.position + Vector3.up * 0.2f, directionArrow);
// 绘制箭头头部
Vector3 arrowEnd = body.position + Vector3.up * 0.2f + directionArrow;
Vector3 arrowSide1 = Quaternion.Euler(0, 30, 0) * (-directionArrow.normalized * 0.5f);
Vector3 arrowSide2 = Quaternion.Euler(0, -30, 0) * (-directionArrow.normalized * 0.5f);
Gizmos.DrawLine(arrowEnd, arrowEnd + arrowSide1);
Gizmos.DrawLine(arrowEnd, arrowEnd + arrowSide2);
}
}
// 显示四个主要方向(运行时)
if (Application.isPlaying)
{
Gizmos.color = Color.cyan;
// 显示四个主要方向
Gizmos.DrawRay(body.position + Vector3.up * 0.1f, Vector3.forward * 0.8f); // 前
Gizmos.DrawRay(body.position + Vector3.up * 0.1f, Vector3.back * 0.8f); // 后
Gizmos.DrawRay(body.position + Vector3.up * 0.1f, Vector3.left * 0.8f); // 左
Gizmos.DrawRay(body.position + Vector3.up * 0.1f, Vector3.right * 0.8f); // 右
}
// 显示默认朝向(编辑器中)
if (!Application.isPlaying)
{
Gizmos.color = Color.magenta;
// Unity默认前方向量
Gizmos.DrawRay(body.position, Vector3.forward * 1.5f);
}
}
#if UNITY_EDITOR
// 在编辑器中显示文字信息
if (body != null && showDebugInfo)
{
UnityEditor.Handles.color = Color.white;
string info = $"Current Rotation: {body.rotation.eulerAngles}\n";
info += $"Forward: {body.forward.ToString("F2")}\n";
info += $"Right: {body.right.ToString("F2")}";
if (_isMoving)
{
info += $"\n--- 移动调试 ---";
info += $"\n起点: {_debugStartPosition.ToString("F1")}";
info += $"\n目标: {_debugTargetPosition.ToString("F1")}";
info += $"\n当前: {body.position.ToString("F1")}";
info += $"\n方向: {_debugMoveDirection.ToString("F2")}";
float distance = Vector3.Distance(body.position, _debugTargetPosition);
info += $"\n剩余距离: {distance:F2}";
}
UnityEditor.Handles.Label(body.position + Vector3.up * 2f, info);
}
#endif
}
public void SetPlayerName(string buildPartnerDisplayName)
{
_textName.text = buildPartnerDisplayName;
}
public void SetHeadImage(string headImageUrl)
{
var iconHead = headUI?.Find("myself/myself/head/btn_head/mask/icon_head");
var image = iconHead?.GetComponent<Image>();
GContext.container.Resolve<IUIService>().SetHeadImage(image, headImageUrl);
}
// 看逻辑没有清理直接设置为0
public void SetSpinPointText(int point)
{
_textNum.text = point.ToString();
}
/// <summary>
/// 移动到指定的3D位置
/// </summary>
/// <param name="targetPosition">目标位置</param>
/// <param name="speed">移动速度0表示使用默认速度</param>
public async Task MoveToPosition_Old(Vector3 targetPosition, float speed = 0f)
{
if (speed <= 0f)
speed = moveSpeed;
var st = animator.GetInteger(ShowType);
Trace($"MoveToPositionStart target={targetPosition} showType={st}");
if (false)
{
LastTargetPosition = targetPosition;
// 设置调试信息
_debugStartPosition = body.position;
_debugTargetPosition = targetPosition;
}
_isMoving = true;
var startPos = body.position;
var direction = (targetPosition - startPos);
// 忽略Y轴方向只考虑水平面移动
direction.y = 0f;
_debugMoveDirection = direction.normalized;
// float bodyForward = Mathf.Atan2(body.forward.x, body.forward.z) * Mathf.Rad2Deg;
// 检查水平移动距离是否足够大
if (direction.magnitude > 0.01f)
{
// Log($"开始移动: 从 {startPos} 到 {targetPosition}, 方向: {_debugMoveDirection}");
// 设置角色朝向目标方向
await SetCharacterDirectionVector(direction);
}
else
{
Trace($"MoveSkippedTooClose distance={direction.magnitude:F3}");
_isMoving = false;
return;
}
// return;
// 开始移动动画
SetAnimationState("moving");
// 移动到目标位置
var bodyStandPosition = body.position;
bodyStandPosition.y = targetPosition.y;
while (Vector3.Distance(bodyStandPosition, targetPosition) > idleThreshold)
{
var moveDirection = (targetPosition - bodyStandPosition).normalized;
moveDirection.y = 0f;
_debugMoveDirection = moveDirection; // 更新当前移动方向
Vector3 moveVector = moveDirection * speed * Time.deltaTime;
if (characterController && characterController.enabled)
{
moveVector.y = -9.81f * Time.deltaTime;
try
{
characterController.Move(moveVector);
}
catch (Exception e)
{
LogError($"CharacterMoveFailed error={e.Message}");
break;
}
}
else
{
Vector3 newPos = transform.position + moveVector;
newPos.y = transform.position.y;
transform.position = newPos;
}
bodyStandPosition = body.position;
bodyStandPosition.y = targetPosition.y;
await Awaiters.NextFrame;
}
Vector3 finalPos = targetPosition;
finalPos.y = transform.position.y;
transform.position = finalPos;
_ = SetCharacterDirectionVector(Vector3.right);
Trace($"MoveToPositionComplete bodyPos={body.position} transformPos={transform.position}");
_isMoving = false;
}
// 前进几列
public async Task MoveCol(int column,Vector3 theUserPoint,float columnDistance, float speed = 0f)
{
Trace($"MoveColStart column={column} userPoint={theUserPoint} lastTarget={LastTargetPosition}");
if (speed <= 0f)
speed = moveSpeed;
if (column <= 0) return;
var distance = theUserPoint.x - LastTargetPosition.x ;
if (distance < 0)
{
distance = 0;
}
// 这个是什么玩意儿。。
LastTargetPosition = theUserPoint;
// distance = 0;
Trace($"MoveColResolved column={column} userPoint={theUserPoint} distance={distance} finalDistance={column * columnDistance + distance}");
var targetPosition = new Vector3(body.position.x + column * columnDistance + distance, body.position.y, body.position.z);
SetAnimationState("moving");
var bodyStandPosition = body.position;
while (Vector3.Distance(bodyStandPosition, targetPosition) > idleThreshold)
{
var moveDirection = (targetPosition - bodyStandPosition).normalized;
moveDirection.y = 0f;
// _debugMoveDirection = moveDirection; // 更新当前移动方向
var moveVector = moveDirection * speed * Time.deltaTime;
if (characterController && characterController.enabled)
{
moveVector.y = -9.81f * Time.deltaTime;
characterController.Move(moveVector);
}
else
{
var newPos = body.position + moveVector;
newPos.y = body.position.y;
body.position = newPos;
}
bodyStandPosition = body.position;
bodyStandPosition.y = targetPosition.y;
await Awaiters.NextFrame;
}
}
public async Task MoveToPosition(Vector3 targetPosition, float speed = 0f, float maxDuration = 10f)
{
speed = speed > 0f ? speed : moveSpeed;
var st = animator.GetInteger(ShowType);
Trace($"MoveToPositionStart target={targetPosition} showType={st}");
_isMoving = true;
var startPos = body.position;
var initialDir = Horizontal(targetPosition - startPos);
if (initialDir.sqrMagnitude <= 0.0001f)
{
Trace($"MoveSkippedTooClose distance={initialDir.sqrMagnitude:F3}");
_isMoving = false;
return;
}
await SetCharacterDirectionVector(initialDir);
SetAnimationState("moving");
var startPosition = body.position;
var bodyPos = body.position;
float elapsed = 0f;
while (!IsCloseHorizontally(bodyPos, targetPosition, idleThreshold))
{
elapsed += Time.deltaTime;
if (elapsed > maxDuration)
{
LogWarning($"MoveTimeout maxDurationSec={maxDuration}");
break;
}
// 計算當前水平移動方向
var moveDir = Horizontal(targetPosition - bodyPos).normalized;
_debugMoveDirection = moveDir;
// 執行一步移動,並獲得實際水平位移量(用於檢測卡住/進度)
var actualMove = DoMoveStep(moveDir, speed);
bodyPos = body.position;
if (IsStuck(actualMove, bodyPos, targetPosition, idleThreshold))
{
Trace($"MovementBlocked distance={Horizontal(targetPosition - bodyPos).magnitude}");
if (!TryUnstuck())
break;
await Awaiters.NextFrame;
}
if (IsMinimalProgress(elapsed, maxDuration, startPosition, bodyPos, targetPosition))
{
LogWarning($"MoveMinimalProgress elapsedSec={elapsed:F2} maxDurationSec={maxDuration}");
break;
}
await Awaiters.NextFrame;
}
// 最終貼位只調整XZ保持當前Y
SnapToTargetXZKeepY(targetPosition);
// 維持和原行為一致:最後朝向設為 Vector3.right
_ = SetCharacterDirectionVector(Vector3.right);
Trace($"MoveToPositionComplete bodyPos={body.position} transformPos={transform.position}");
_isMoving = false;
}
/* ====================== Helpers ====================== */
// 只保留水平方向
private static Vector3 Horizontal(in Vector3 v) => new Vector3(v.x, 0f, v.z);
// 水平方向是否在閾值內
private static bool IsCloseHorizontally(in Vector3 a, in Vector3 b, float threshold)
{
var diff = Horizontal(b - a);
return diff.sqrMagnitude <= threshold * threshold;
}
// 執行一步位移,返回實際的水平位移(用於卡住判斷)
private Vector3 DoMoveStep(in Vector3 moveDir, float speed)
{
var before = body.position;
Vector3 moveVector = moveDir * speed * Time.deltaTime;
if (characterController && characterController.enabled)
{
// 添加重力,與原代碼一致
moveVector.y = -9.81f * Time.deltaTime;
try
{
characterController.Move(moveVector);
}
catch (Exception e)
{
LogError($"CharacterMoveFailed error={e.Message}");
}
}
else
{
// 非CharacterController直接平移保持Y不變
var newPos = transform.position + moveVector;
newPos.y = transform.position.y;
transform.position = newPos;
}
var after = body.position;
var actual = Horizontal(after - before);
return actual;
}
// 是否卡住
private static bool IsStuck(in Vector3 actualMove, in Vector3 bodyPos, in Vector3 target, float idleThreshold)
{
return actualMove.magnitude < 0.001f && Horizontal(target - bodyPos).magnitude > idleThreshold * 2f;
}
// 嘗試解卡CharacterController 上抬一點;否則無法處理
private bool TryUnstuck()
{
if (characterController && characterController.enabled)
{
try
{
characterController.Move(Vector3.up * 0.1f);
return true;
}
catch (Exception e)
{
LogError($"UnstuckMoveFailed error={e.Message}");
return false;
}
}
return false;
}
// 進度過少時間過半但前進距離不到總距離的10%
private static bool IsMinimalProgress(float elapsed, float maxDuration, in Vector3 start, in Vector3 current, in Vector3 target)
{
if (elapsed <= maxDuration * 0.5f) return false;
float total = Horizontal(target - start).magnitude;
float progress = Horizontal(current - start).magnitude;
// 避免除0
if (total <= 0.0001f) return false;
return progress < total * 0.1f;
}
// 最終貼位對齊XZ保持當前Y
private void SnapToTargetXZKeepY(in Vector3 target)
{
var finalPos = new Vector3(target.x, transform.position.y, target.z);
transform.position = finalPos;
}
// FollowMOve
public async Task FollowToPosition(Vector3 targetPosition,float speed = 0f)
{
Trace($"FollowToPosition target={targetPosition}");
if (speed <= 0f)
{
speed = followSpeed;
}
await MoveToDestination(targetPosition, speed);
}
private async Task MoveToDestination(Vector3 targetPosition, float speed)
{
await MoveToPosition(targetPosition,speed);
FinishMove();
}
/// <summary>
/// 设置角色朝向方向(基于方向向量)
/// </summary>
/// <param name="direction">目标方向向量</param>
private async Task SetCharacterDirectionVector(Vector3 direction)
{
direction.y = 0f;
// 检查方向向量是否有效(在标准化之前)
if (direction.magnitude < 0.01f) return;
direction.Normalize();
// 标准Unity旋转计算
float targetYRotation = Mathf.Atan2(direction.x, direction.z) * Mathf.Rad2Deg;
Quaternion targetRotation = Quaternion.Euler(0f, targetYRotation - 90, 0f);
if (smoothRotation)
{
Quaternion startRotation = body.rotation;
float rotationTime = 0f;
while (rotationTime < rotationDuration)
{
rotationTime += Time.deltaTime;
float t = rotationTime / rotationDuration;
body.rotation = Quaternion.Slerp(startRotation, targetRotation, t);
await Awaiters.NextFrame;
}
}
body.rotation = targetRotation;
Trace($"SetDirection target={direction} targetY={targetYRotation:F1} finalRotation={targetRotation.eulerAngles}");
}
// private IEnumerator TriggerNextFrame(string state)
// {
// // animator.SetTrigger(BackToIdle);
// yield return null; // 等待一帧让Animator处理BackToIdle的触发
// // animator.SetTrigger(BasicGathering);
// SetAnimationState(state);
// }
public async Task PlayMining( /*float duration */ )
{
Trace("PlayMining");
SetAnimationState("gathering");
// await Awaiters.Seconds(duration);
// SetAnimationState("idle");
_gatherNum = 0;
_playMiningTcs = new TaskCompletionSource<bool>();
await _playMiningTcs.Task;
}
public int GetAnimatorShowType()
{
return animator?.GetInteger(ShowType) ?? 0;
}
public void SetIdle()
{
// animator.SetTrigger(BackToIdle);
// SetAnimationState("idle");
// _ = UpdateShowType(0);
var showType = animator.GetInteger(ShowType);
Trace($"SetIdle showType={showType}");
{
animator.SetInteger(CurrentStatus,0);
animator.SetTrigger(BackToIdle);
}
}
public void SetAnimationState(string state)
{
var stateName = GetStateName();
if (!animator) return;
if (!state.Equals("idle"))
{
animator.ResetTrigger(BackToIdle);
}
var showType = animator.GetInteger(ShowType);
Trace($"SetAnimationState state={state} currentState={stateName} showType={showType}");
switch (state)
{
case "moving":
animator.SetInteger(CurrentStatus,1);
if (showType == 0)
{
animator.SetTrigger(ToBasic);
// GContext.Publish(new EventUISound("audio_ui_partnermining_npc_basicrun"));
}else if (showType == 1)
{
animator.SetTrigger(AdvancedMove);
// GContext.Publish(new EventUISound("audio_ui_partnermining_npc_advancedrun"));
}else if (showType == 2)
{
animator.SetTrigger(MasterMove);
// GContext.Publish(new EventUISound("audio_ui_partnermining_npc_masterrun"));
}
PlayMoveSoundEffect();
break;
case "gathering":
animator.SetInteger(CurrentStatus,2);
StopMoveAudio();
if (showType == 0)
{
PlayBasicGathering();
}else if (showType == 1)
{
PlayAdvancedGathering();
}else if (showType == 2)
{
var status = animator.GetInteger(CurrentStatus);
Trace($"AnimatorStatusUpdate status={status}");
PlayMasterGathering();
}
break;
case "celebrate":
// StartCelebrate();
StopMoveAudio();
animator.SetInteger(CurrentStatus,3);
animator.SetTrigger(Celebrate);
break;
default:
throw new NotSupportedException();
}
}
private void PlayMasterGathering()
{
animator.SetTrigger(MasterGathering);
// GContext.Publish(new EventUISound("audio_ui_partnermining_target_mastergather"));
}
private void PlayAdvancedGathering()
{
animator.SetTrigger(AdvancedGathering);
// GContext.Publish(new EventUISound("audio_ui_partnermining_target_advancedgather"));
}
private void PlayBasicGathering()
{
animator.SetTrigger(BasicGathering);
// GContext.Publish(new EventUISound("audio_ui_partnermining_target_basicgather"));
}
private void StartCelebrate()
{
_playCelebrateTcs = new TaskCompletionSource<bool>();
}
// private float moveTypeduration = 0.25f;
public async Task UpdateShowType(int showType)
{
var oldShowType = animator.GetInteger(ShowType);
Trace($"UpdateShowType old={oldShowType} new={showType}");
if (oldShowType == showType)
{
return;
}
_changeShowTypeTask = new TaskCompletionSource<bool>();
animator.SetInteger(ShowType,showType);
switch (showType)
{
case 0:
animator.SetTrigger( BackToIdle);
if (oldShowType == 1)
{
_fx_advancedToBasic.gameObject.SetActive(true);
}
else
{
_fx_masterToBasic.gameObject.SetActive(true);
}
break;
case 1:
animator.SetTrigger(ToAdvance);
// await Awaiters.Seconds(0.2f);
_fx_basicToAdvanced.gameObject.SetActive(true);
GContext.Publish(new EventUISound("audio_ui_partnermining_npc_mastershow"));
break;
case 2:
animator.SetTrigger(ToMaster);
_fx_basicToMaster.gameObject.SetActive(true);
GContext.Publish(new EventUISound("audio_ui_partnermining_npc_mastershow"));
break;
}
// _ = UpdateShowTypePosition(oldShowType, showType,basePos);
await _changeShowTypeTask.Task;
}
public async Task WaitForCelebrateFinished()
{
_playCelebrateTcs = new TaskCompletionSource<bool>();
await _playCelebrateTcs.Task;
}
private TaskCompletionSource<bool> _idleTask;
public async Task WaitForIdleFinished()
{
_idleTask = new TaskCompletionSource<bool>();
await _idleTask.Task;
}
// 获取当前是否在移动
public bool IsMoving()
{
return animator && animator.GetBool(runAnimParam);
}
public void OnAnimEnter(string args)
{
if (args.Equals("Gather"))
{
_gatherNum += 1;
Trace($"AnimEnter args={args} gatherNum={_gatherNum} gatherPerDuration={GatherNumPerDuration}");
if (_gatherNum > GatherNumPerDuration)
{
return;
}
var showType = animator.GetInteger(ShowType);
switch (showType)
{
case 0:
_fx_basicGather.gameObject.SetActive(true);
GContext.Publish(new EventUISound("audio_ui_partnermining_target_basicgather"));
break;
case 1:
GContext.Publish(new EventUISound("audio_ui_partnermining_target_advancedgather"));
if (_gatherNum == 1)
{
_fx_advancedGather.gameObject.SetActive(true);
}
break;
case 2:
GContext.Publish(new EventUISound("audio_ui_partnermining_target_mastergather"));
if (_gatherNum == 1)
{
_fx_masterGather.gameObject.SetActive(true);
}
break;
}
}
if (args.Equals("Move"))
{
// PlayMoveSoundEffect();
}
if (args.Equals("Idle"))
{
FinishIdle();
}
}
private void PlayMoveSoundEffect()
{
StopMoveAudio();
if (!IsMainCharacter) return;
var showType = animator.GetInteger(ShowType);
switch (showType)
{
case 0:
soundMove = GContext.container.Resolve<ISoundService>().GetNewUISound(audioBasic);
break;
case 1:
soundMove = GContext.container.Resolve<ISoundService>().GetNewUISound(audioAdvanced);
break;
case 2:
soundMove = GContext.container.Resolve<ISoundService>().GetNewUISound(audioMaster);
break;
}
soundMove.audioSource.Play();
soundMove.audioSource.loop = true;
}
public void OnAnimEnd(string args)
{
var stateName = GetStateName();
// Log($"OnAnimEnd -> {args} {stateName}");
if (args.Equals("Gather"))
{
_fx_basicGather.gameObject.SetActive(false);
// StopGatheringEffect();
Trace($"AnimEnd args={args} gatherNum={_gatherNum}");
// GContext.Publish(new EventMiningOre{ Num = _gatherNum});
if (_gatherNum >= GatherNumPerDuration)
{
FinishGather();
}
}
else if (args.Equals("ShowType"))
{
Trace($"AnimEnd args={args}");
FinishUpdateShowType();
}
else if (args.Equals("Idle"))
{
// Log($"-----OnAnimEnd -> {args} ");
FinishIdle();
}
else if (args.Equals("IdleSpecial"))
{
Trace($"AnimEndToIdle args={args}");
FinishIdleSpecial();
}
else if (args.Equals("Celebrate"))
{
FinishCelebrate();
}
}
public void OnAnimEvent(string args)
{
if (args.Equals("Mining"))
{
GContext.Publish(new EventMiningOre{ Num = _gatherNum});
}
if (_gatherNum == 1)
{
GContext.Publish(new EventUISound("audio_ui_partnermining_resource_break01"));
}
else if (_gatherNum == 2)
{
GContext.Publish(new EventUISound("audio_ui_partnermining_resource_break02"));
}
else if (_gatherNum == 3)
{
GContext.Publish(new EventUISound("audio_ui_partnermining_resource_break03"));
}
}
private void StopGatheringEffect()
{
// 挖矿特效
_fx_basicGather.gameObject.SetActive(false);
_fx_advancedGather.gameObject.SetActive(false) ;
_fx_masterGather.gameObject.SetActive(false);
}
public async Task WaitIdleSpecialFinish()
{
Trace("WaitIdleSpecialFinish");
animator.SetInteger(CurrentStatus,0);
var showType = animator.GetInteger(ShowType);
if (showType != 0)
{
_idleSpecialTask = new TaskCompletionSource<bool>();
await _idleSpecialTask.Task;
Trace($"WaitIdleSpecialFinishState showType={showType} endIdle={EndIdle}");
animator.SetTrigger(EndIdle);
await UpdateShowType(0);
}
}
private void FinishIdle()
{
// Log("FinishIdle->");
var success = _idleTask?.TrySetResult(true) ?? false;
if (success)
{
Trace("FinishIdle result=success");
}
}
private void FinishIdleSpecial()
{
Trace("FinishedIdleSpecial");
var success = _idleSpecialTask?.TrySetResult(true) ?? false;
if (success)
{
Trace("FinishedIdleSpecial result=success");
}
}
private void FinishUpdateShowType()
{
Trace("FinishUpdateShowType");
var success = _changeShowTypeTask.TrySetResult(true);
if (!success)
{
Trace("SetTaskResultSkipped reason=AlreadyCompleted");
}
_fx_basicToAdvanced.gameObject.SetActive(false);
_fx_advancedToBasic.gameObject.SetActive(false);
_fx_basicToMaster.gameObject.SetActive(false);
_fx_masterToBasic.gameObject.SetActive(false);
}
private void FinishGather()
{
Trace("FinishGather");
_gatherNum = 0;
GatherNumPerDuration = -1;
var ret = _playMiningTcs?.TrySetResult(true) ?? false;
if (ret)
{
Trace("FinishGather result=true");
}
StopGatheringEffect();
SetIdle();
}
public void FinishMove()
{
var showType = animator.GetInteger(ShowType);
if (showType == 0)
{
animator.ResetTrigger(ToBasic);
}else if (showType == 1)
{
animator.ResetTrigger(AdvancedMove);
}else if (showType == 2)
{
animator.ResetTrigger(MasterMove);
}
StopMoveAudio();
SetIdle();
}
private void FinishCelebrate()
{
Trace("FinishCelebrate");
var ret = _playCelebrateTcs?.TrySetResult(true) ?? false;
if (ret)
{
Trace("FinishCelebrate result=true");
}
}
/// <summary>
/// 立即设置角色朝向(基于方向向量,不带动画)
/// </summary>
/// <param name="direction">目标方向向量</param>
public void SetDirectionVectorImmediate(Vector3 direction)
{
direction.y = 0f;
// 检查方向向量是否有效(在标准化之前)
if (direction.magnitude < 0.01f) return;
direction.Normalize();
// 标准Unity旋转计算
float targetYRotation = Mathf.Atan2(direction.x, direction.z) * Mathf.Rad2Deg;
body.rotation = Quaternion.Euler(0f, targetYRotation, 0f);
Trace($"SetDirectionImmediate target={direction} targetY={targetYRotation:F1}");
}
/// <summary>
/// 立即朝向目标位置(不带动画)
/// </summary>
/// <param name="targetPosition">目标位置</param>
public void LookAtPositionImmediate(Vector3 targetPosition)
{
Vector3 direction = (targetPosition - body.position).normalized;
SetDirectionVectorImmediate(direction);
}
/// <summary>
/// 重置到默认朝向
/// </summary>
public void ResetToDefaultRotation()
{
body.rotation = Quaternion.identity;
}
/// <summary>
/// 获取当前面向的方向
/// </summary>
/// <returns>1为右-1为左0为其他方向</returns>
public float GetCurrentDirection()
{
Vector3 forward = body.forward;
float dot = Vector3.Dot(forward, Vector3.right);
if (dot > 0.5f) return 1f;
if (dot < -0.5f) return -1f;
return 0f;
}
#if UNITY_EDITOR
// /// <summary>
// /// 编辑器辅助:重置到默认朝向
// /// </summary>
// [UnityEngine.ContextMenu("重置到默认朝向")]
// private void ResetToDefault()
// {
// ResetToDefaultRotation();
// }
//
// /// <summary>
// /// 编辑器辅助:测试朝向前
// /// </summary>
// [UnityEngine.ContextMenu("测试朝向前")]
// private void TestFaceForward()
// {
// SetDirectionVectorImmediate(Vector3.forward);
// }
//
// /// <summary>
// /// 编辑器辅助:测试移动到指定位置
// /// </summary>
// [UnityEngine.ContextMenu("测试移动(右前方)")]
// private async void TestMoveToPosition()
// {
// Vector3 testTarget = body.position + new Vector3(5f, 0f, 5f);
// Debug.Log($"测试移动到位置: {testTarget}");
// await MoveToPosition(testTarget, 3f);
// }
//
// /// <summary>
// /// 编辑器辅助:测试移动(向右)
// /// </summary>
// [UnityEngine.ContextMenu("测试移动(向右)")]
// private async void TestMoveRight()
// {
// Vector3 testTarget = body.position + new Vector3(3f, 0f, 0f);
// Debug.Log($"测试向右移动到: {testTarget}");
// await MoveToPosition(testTarget, 2f);
// }
//
// /// <summary>
// /// 编辑器辅助:测试移动(向前)
// /// </summary>
// [UnityEngine.ContextMenu("测试移动(向前)")]
// private async void TestMoveForward()
// {
// Vector3 testTarget = body.position + new Vector3(0f, 0f, 3f);
// Debug.Log($"测试向前移动到: {testTarget}");
// await MoveToPosition(testTarget, 2f);
// }
//
// /// <summary>
// /// 编辑器辅助:显示当前朝向信息
// /// </summary>
// [UnityEngine.ContextMenu("显示当前朝向")]
// private void ShowCurrentDirection()
// {
// Debug.Log($"=== 角色朝向信息 ===");
// Debug.Log($"当前旋转: {body.rotation.eulerAngles}");
// Debug.Log($"Forward: {body.forward.ToString("F3")}");
// Debug.Log($"Right: {body.right.ToString("F3")}");
// Debug.Log($"Up: {body.up.ToString("F3")}");
// }
//
// /// <summary>
// /// 编辑器辅助:验证旋转逻辑
// /// </summary>
// [UnityEngine.ContextMenu("验证旋转逻辑")]
// private void ValidateRotationLogic()
// {
// Debug.Log($"=== 标准Unity旋转逻辑验证 ===");
//
// // 测试四个主要方向
// Vector3[] testDirections =
// {
// Vector3.right, // 向右 (1, 0, 0)
// Vector3.left, // 向左 (-1, 0, 0)
// Vector3.forward, // 向前 (0, 0, 1)
// Vector3.back // 向后 (0, 0, -1)
// };
//
// string[] directionNames = { "右", "左", "前", "后" };
//
// for (int i = 0; i < testDirections.Length; i++)
// {
// Vector3 direction = testDirections[i];
// float expectedYRotation = Mathf.Atan2(direction.x, direction.z) * Mathf.Rad2Deg;
//
// Debug.Log($"方向{directionNames[i]}: " +
// $"输入向量{direction} -> " +
// $"Y旋转{expectedYRotation:F1}°");
// }
// }
#endif
private static void ELog(object t)
{
#if UNITY_EDITOR
Debug.Log($"<color=cyan>EventPartnerMiningCharacterController-> {t} </color>");
#endif
}
private static void LogWarning(object t)
{
Debug.LogWarning($"<color=yellow>EventPartnerMiningCharacterController-> {t} </color>");
}
private static void LogError(object t)
{
Debug.LogError($"<color=red>EventPartnerMiningCharacterController-> {t} </color>");
}
[System.Diagnostics.Conditional("UNITY_EDITOR")]
private static void Trace(object t)
{
if (!EnableTraceLogs) return;
ELog(t);
}
public async void Test()
{
ELog("Test");
// this.UpdateShowType(0);
FinishMove();
animator.SetTrigger(Celebrate);
// throw new NotImplementedException();
// await UpdateShowType(2);
// animator.SetTrigger("_toAdvance");
// animator.SetTrigger(MasterGathering);
// animator.SetTrigger(MasterMove);
// await Awaiters.Seconds(0.8f);
// animator.ResetTrigger(MasterMove);
// animator.SetTrigger(MasterGathering);
}
}
}