Files
2026-04-06 14:31:09 +07:00

1477 lines
51 KiB
C#
Raw Permalink 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.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Text;
using System.Threading.Tasks;
using UnityEngine;
using CSNetwork.GPDataType;
using BrewMonster.Scripts.Task;
using BrewMonster.Network;
using UnityEngine.AddressableAssets;
namespace BrewMonster.Scripts
{
//////////////////////////////////////////////////////////////////////////
//
// Helper functions for vector/yaw calculations
//
//////////////////////////////////////////////////////////////////////////
// Helper class for navigation calculations // 导航计算辅助类
public static class NavigateHelper
{
// Convert vector to yaw angle // 将向量转换为偏航角
public static float glb_VectorToYaw(A3DVECTOR3 v)
{
A3DVECTOR3 v1 = v;
v1.y = 0.0f;
v1.Normalize();
return Mathf.Rad2Deg * (float)Mathf.Atan2(v1.z, v1.x);
}
// Convert yaw to vector // 将偏航角转换为向量
public static A3DVECTOR3 glb_YawToVector(float fYaw)
{
float fRad = Mathf.Deg2Rad * fYaw;
A3DVECTOR3 v;
v.x = 10.0f * Mathf.Cos(fRad);
v.z = 10.0f * Mathf.Sin(fRad);
v.y = 0.0f;
v.Normalize();
return v;
}
// Clamp yaw to [-180.0f, 180.0f] // 将偏航角限制在 [-180.0f, 180.0f] 范围内
public static void glb_ClampYaw(ref float fYaw)
{
if (fYaw >= -180.0f && fYaw <= 180.0f)
{
// Below clamp calculation will bring little error to original number, this will
// cause problem in yaw's equal comparing. So if original yaw has been in valid
// range, return directly.
// 下面的限制计算会给原始数字带来小的误差,这会在偏航角的相等比较中造成问题。
// 所以如果原始偏航角已经在有效范围内,直接返回。
return;
}
float fInv = 1.0f / 360.0f;
fYaw += 180.0f;
if (fYaw < 0.0f)
{
int n = (int)(-fYaw * fInv) + 1;
fYaw = fYaw + 360.0f * n;
}
else
{
int n = (int)(fYaw * fInv);
fYaw = fYaw - 360.0f * n;
}
fYaw -= 180.0f;
//UnityEngine.Debug.Assert(fYaw >= -180.0f && fYaw <= 180.0f);
}
// Get pitch from vector // 从向量获取俯仰角
public static float glb_GetPitch(A3DVECTOR3 v)
{
return Mathf.Rad2Deg * Mathf.Acos(v.y);
}
// Rotate position around axis // 围绕轴旋转位置
public static A3DVECTOR3 a3d_RotatePosAroundAxis(A3DVECTOR3 vPos, A3DVECTOR3 vAxis, float fRad)
{
// C++: a3d_RotatePosAroundAxis uses A3DMATRIX4 RotateAxis(axis, rad) then matrix * v
// 原版:a3d_RotatePosAroundAxis 使用 A3DMATRIX4 RotateAxis(axis, rad) 然后 matrix * v
Vector3 axis = new Vector3(vAxis.x, vAxis.y, vAxis.z);
if (axis.sqrMagnitude < 1e-12f)
{
return vPos;
}
Quaternion rotation = Quaternion.AngleAxis(Mathf.Rad2Deg * fRad, axis.normalized);
Vector3 result = rotation * new Vector3(vPos.x, vPos.y, vPos.z);
return new A3DVECTOR3(result.x, result.y, result.z);
}
}
//////////////////////////////////////////////////////////////////////////
//
// CECNavigateCtrl class
//
//////////////////////////////////////////////////////////////////////////
public class CECNavigateCtrl
{
public enum NavigateEvent
{
EM_PREPARE = 0,
EM_BEGIN = 1,
EM_END = 2,
}
// Navigation info structure // 导航信息结构
public struct INFO
{
public int taskID;
public int bezierID;
public float speed;
public float angleWithH;
public bool bezierDir; // false: fix, true: bezier // false: 固定, true: 贝塞尔
public string strModelPath;
}
// Config info list, vector type // 配置信息列表,vector类型
private List<INFO> m_configInfo = new List<INFO>();
private CECHostPlayer m_pHost;
// Bezier walker // 贝塞尔行走器
private CECBezierWalker m_pBezierWalker;
// Force navigate state // 强制导航状态
private bool m_bForceNavigateState;
// Current force navigate info // 当前强制导航信息
private INFO m_curNavigateInfo;
// Corresponding task ID // 对应的任务ID
private int m_taskID;
public CECNavigateCtrl(CECHostPlayer pHost)
{
m_pHost = pHost;
m_pBezierWalker = null;
m_bForceNavigateState = false;
m_taskID = 0;
// Try to load default force-navigate config (Addressables) on creation.
// 尝试在创建时加载默认强制导航配置(Addressables)。
EnsureDefaultConfigLoaded();
}
private const string DEFAULT_FORCE_NAVIGATE_CONFIG_ADDRESS = "Assets/Addressable/force_navigate.txt";
private bool m_bConfigLoaded = false;
// Decode raw file bytes: UTF-8 (optional BOM), else strict UTF-8; on invalid UTF-8 fall back to GBK (code page 936) for legacy PC exports.
// 解码原始文件字节:UTF-8(可选 BOM),否则严格 UTF-8;非法 UTF-8 时回退到 GBK(代码页 936)以兼容老 PC 导出。
private static string DecodeNavigateConfigText(byte[] bytes)
{
if (bytes == null || bytes.Length == 0)
{
return string.Empty;
}
int offset = 0;
if (bytes.Length >= 3 && bytes[0] == 0xEF && bytes[1] == 0xBB && bytes[2] == 0xBF)
{
offset = 3;
}
int len = bytes.Length - offset;
var utf8Strict = new UTF8Encoding(encoderShouldEmitUTF8Identifier: false, throwOnInvalidBytes: true);
try
{
return utf8Strict.GetString(bytes, offset, len);
}
catch (DecoderFallbackException)
{
Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);
return Encoding.GetEncoding(936).GetString(bytes, offset, len);
}
}
// Ensure default config is loaded (safe to call multiple times).
// 确保默认配置已加载(可重复调用)。
private void EnsureDefaultConfigLoaded()
{
if (m_bConfigLoaded)
{
return;
}
// Prefer Addressables (same as other config loads in this project).
// 优先使用 Addressables(与本项目其他配置加载方式一致)。
if (LoadConfigAddressable(DEFAULT_FORCE_NAVIGATE_CONFIG_ADDRESS))
{
m_bConfigLoaded = true;
return;
}
// Fallback to file path loading (dev/debug).
// 回退到文件路径加载(开发/调试)。
if (LoadConfig(DEFAULT_FORCE_NAVIGATE_CONFIG_ADDRESS))
{
m_bConfigLoaded = true;
}
}
// Load configuration from Addressables text asset.
// 从 Addressables 文本资源加载配置。
public bool LoadConfigAddressable(string address)
{
try
{
Addressables.InitializeAsync().WaitForCompletion();
var ta = Addressables.LoadAssetAsync<TextAsset>(address).WaitForCompletion();
if (ta == null || ta.bytes == null || ta.bytes.Length == 0)
{
Debug.LogWarning($"CECNavigateCtrl::LoadConfigAddressable, failed to load '{address}'");
return false;
}
string text = DecodeNavigateConfigText(ta.bytes);
if (string.IsNullOrEmpty(text))
{
Debug.LogWarning($"CECNavigateCtrl::LoadConfigAddressable, decoded empty text '{address}'");
return false;
}
return LoadConfigFromText(text);
}
catch (Exception ex)
{
Debug.LogWarning($"CECNavigateCtrl::LoadConfigAddressable exception: {ex.Message}");
return false;
}
}
// Load configuration from raw text (supports quoted CSV lines in force_navigate.txt).
// 从原始文本加载配置(支持 force_navigate.txt 中的带引号 CSV 行)。
public bool LoadConfigFromText(string text)
{
try
{
m_configInfo.Clear();
if (string.IsNullOrEmpty(text))
{
return false;
}
using var reader = new StringReader(text);
string rawLine;
int lineNumber = 0;
while ((rawLine = reader.ReadLine()) != null)
{
lineNumber++;
string line = rawLine.Trim();
if (string.IsNullOrEmpty(line) || line.StartsWith("//"))
{
continue;
}
// force_navigate.txt lines are often wrapped in quotes:
// "32220,-1001,30,30.0,1,Models\...\xxx.ecm"
// force_navigate.txt 的行通常被双引号包裹:
// "32220,-1001,30,30.0,1,Models\...\xxx.ecm"
if (line.Length >= 2 && line[0] == '"' && line[^1] == '"')
{
line = line.Substring(1, line.Length - 2);
}
string[] parts = line.Split(',');
if (parts.Length < 6)
{
Debug.LogWarning($"CECNavigateCtrl::LoadConfigFromText, invalid line {lineNumber}: '{rawLine}'");
return false;
}
// Trim each field (also remove accidental quotes around individual fields).
// 修剪每个字段(也移除单独字段意外的引号)。
for (int i = 0; i < parts.Length; i++)
{
parts[i] = parts[i].Trim().Trim('"');
}
INFO navi = new INFO();
navi.taskID = int.Parse(parts[0], CultureInfo.InvariantCulture);
navi.bezierID = int.Parse(parts[1], CultureInfo.InvariantCulture);
navi.speed = float.Parse(parts[2], CultureInfo.InvariantCulture);
navi.angleWithH = float.Parse(parts[3], CultureInfo.InvariantCulture);
navi.bezierDir = (int.Parse(parts[4], CultureInfo.InvariantCulture) == 1);
navi.strModelPath = parts[5];
m_configInfo.Add(navi);
}
return m_configInfo.Count > 0;
}
catch (Exception ex)
{
Debug.LogWarning($"CECNavigateCtrl::LoadConfigFromText exception: {ex.Message}");
return false;
}
}
// Load configuration // 加载配置
public bool LoadConfig(string szFile)
{
try
{
string filePath = szFile;
// Handle Unity asset paths (relative to Assets folder) // 处理Unity资源路径(相对于Assets文件夹)
if (filePath.StartsWith("Assets/"))
{
// Convert to absolute path // 转换为绝对路径
string projectPath = Application.dataPath.Replace("/Assets", "");
filePath = Path.Combine(projectPath, filePath);
}
// Handle relative paths from Application.dataPath // 处理相对于Application.dataPath的相对路径
else if (!Path.IsPathRooted(filePath))
{
filePath = Path.Combine(Application.dataPath, filePath);
}
if (!File.Exists(filePath))
{
Debug.LogError($"CECNavigateCtrl::LoadConfig, failed to open file {filePath} (original: {szFile})");
return false;
}
byte[] raw = File.ReadAllBytes(filePath);
string text = DecodeNavigateConfigText(raw);
return LoadConfigFromText(text);
}
catch (Exception)
{
return false;
}
}
// Get navigation info // 获取导航信息
public bool GetNavigateInfo(int task, ref INFO info)
{
if (task <= 0) return false;
for (int i = 0; i < m_configInfo.Count; i++)
{
if (m_configInfo[i].taskID == task)
{
info = m_configInfo[i];
return true;
}
}
return false;
}
// Prepare navigation // 准备导航
public async void OnPrepareNavigate(int task)
{
m_bForceNavigateState = true;
m_taskID = task;
// TODO: Implement CECUIHelper.GetGameUIMan().SetShowAllPanlesFlag(false);
// CECUIHelper::GetGameUIMan()->SetShowAllPanlesFlag(false);
INFO naviInfo = new INFO();
if (GetNavigateInfo(task, ref naviInfo))
{
Debug.Log($"CECNavigateCtrl::OnPrepareNavigate, GetNavigateInfo: {task} success");
m_curNavigateInfo = naviInfo;
// Set navigate model file // 设置导航模型文件
CECHostNavigatePlayer player = m_pHost.GetNavigatePlayer();
player.SetNavigateModelFile(m_curNavigateInfo.strModelPath);
await player.Init();
}
else
{
m_pHost.GetTaskInterface().GiveUpTask((uint)m_taskID);
}
}
// Begin navigation // 开始导航
public void OnBeginNavigate()
{
// C++:
// CECScene* pScene = g_pGame->GetGameRun()->GetWorld()->GetScene();
// CECBezier* pBezier = dynamic_cast<CECBezier*>(pScene->GetBezierObjectByGlobalID(m_curNavigateInfo.bezierID));
// if (!pBezier) { GiveUpTask; return; }
//
// 原版:
// 从Scene按GlobalID取Bezier;取不到则放弃任务并返回
CECBezier pBezier = CECBezierNavigateLoader.GetBezierObjectByGlobalID(m_curNavigateInfo.bezierID);
if (pBezier == null)
{
if (m_pHost != null && m_pHost.GetTaskInterface() != null)
{
m_pHost.GetTaskInterface().GiveUpTask((uint)m_taskID);
}
return;
}
if (m_pBezierWalker == null)
{
m_pBezierWalker = new CECBezierWalker();
}
// Bind bezier and start walking // 绑定贝塞尔曲线并开始行走
m_pBezierWalker.BindBezier(pBezier);
m_pBezierWalker.SetSpeed(m_curNavigateInfo.speed);
m_pBezierWalker.StartWalk(false, true);
// Create and start work // 创建并开始工作
if (m_pHost != null && m_pHost.GetWorkMan() != null)
{
CECHPWorkNavigate pWork = m_pHost.GetWorkMan().CreateWork(CECHPWork.Host_work_ID.WORK_FORCENAVIGATEMOVE) as CECHPWorkNavigate;
if (pWork != null)
{
pWork.BeginNavigate();
m_pHost.GetWorkMan().StartWork_p2(pWork);
}
}
}
// End navigation // 结束导航
public void OnEndNavigate()
{
if (!m_bForceNavigateState) return;
m_taskID = 0;
// Finish running work // 完成运行中的工作
if (m_pHost != null && m_pHost.GetWorkMan() != null)
{
m_pHost.GetWorkMan().FinishRunningWork(CECHPWork.Host_work_ID.WORK_FORCENAVIGATEMOVE);
CameraController.Instance.UpdateFollowObject(m_pHost.PointCam);
}
// TODO: Implement UI helper
// CECUIHelper::GetGameUIMan()->SetShowAllPanlesFlag(true);
m_bForceNavigateState = false;
}
// Check if in force navigate state // 检查是否在强制导航状态
public bool IsInForceNavigateState()
{
return m_bForceNavigateState;
}
// Get bezier walker // 获取贝塞尔行走器
public CECBezierWalker GetBezierWalker()
{
return m_pBezierWalker;
}
// Get current navigate info // 获取当前导航信息
public INFO GetCurrentNavigateInfo()
{
return m_curNavigateInfo;
}
}
//////////////////////////////////////////////////////////////////////////
//
// CECHPWorkNavigate class
//
//////////////////////////////////////////////////////////////////////////
public class CECHPWorkNavigate : CECHPWork
{
// Move flag // 移动标志
private bool m_bMove;
// Move speed // 移动速度
private float m_fSpeed;
public CECHPWorkNavigate(CECHPWorkMan pWorkMan) : base(Host_work_ID.WORK_FORCENAVIGATEMOVE, pWorkMan)
{
m_dwMask = Work_mask.MASK_FORCENAVIGATE;
m_dwTransMask = Work_mask.MASK_STAND;
Reset();
}
// Reset work // 重置工作
public override void Reset()
{
base.Reset();
m_bMove = false;
}
// Copy work data // 复制工作数据
public override bool CopyData(CECHPWork pWork)
{
if (!base.CopyData(pWork))
return false;
CECHPWorkNavigate pSrc = pWork as CECHPWorkNavigate;
if (pSrc == null)
return false;
m_fSpeed = pSrc.m_fSpeed;
m_bMove = pSrc.m_bMove;
return true;
}
// Begin navigation // 开始导航
public void BeginNavigate()
{
Debug.Log($"[CECHPWorkNavigate] BeginNavigate: Setting m_bMove=true");
m_bMove = true;
}
// Stop move // 停止移动
public void Finish()
{
Reset();
}
// On first tick // 第一次tick时
protected override void OnFirstTick()
{
// TODO: Implement GetNavigatePlayer
// A3DVECTOR3 dir = m_pHost->GetNavigatePlayer()->GetDir();
// dir.Normalize();
CECHostNavigatePlayer pNavigatePlayer = m_pHost.GetNavigatePlayer();
if (pNavigatePlayer == null)
{
return;
}
A3DVECTOR3 dir = pNavigatePlayer.GetDir();
dir.Normalize();
// TODO: Implement GetNavigateCtrl
// CECNavigateCtrl* pNaviCtrl = m_pHost->GetNavigatePlayer()->GetNavigateCtrl();
CECNavigateCtrl pNaviCtrl = pNavigatePlayer.GetNavigateCtrl();
if (pNaviCtrl == null)
{
return;
}
CECNavigateCtrl.INFO naviInfo = pNaviCtrl.GetCurrentNavigateInfo();
A3DVECTOR3 vRight = A3DVECTOR3.CrossProduct(GPDataTypeHelper.g_vAxisY, dir);
dir = NavigateHelper.a3d_RotatePosAroundAxis(dir, vRight, Mathf.Deg2Rad * naviInfo.angleWithH);
float fDesYaw = NavigateHelper.glb_VectorToYaw(dir);
NavigateHelper.glb_ClampYaw(ref fDesYaw);
// TODO: Implement camera control
// m_pHost->GetCameraCtrl()->SetDgree(90.f-fDesYaw);
float fDesPitch = NavigateHelper.glb_GetPitch(dir);
// TODO: Implement camera control
// m_pHost->GetCameraCtrl()->SetPitch(90.f-fDesPitch);
}
// Tick routine // Tick例程
public override bool Tick(float dwDeltaTime)
{
// TODO: Implement GetNavigateCtrl
// CECNavigateCtrl* pNaviCtrl = m_pHost->GetNavigatePlayer()->GetNavigateCtrl();
// CECBezierWalker* pBezierWalker = pNaviCtrl->GetBezierWalker();
CECHostNavigatePlayer pNavigatePlayer = m_pHost.GetNavigatePlayer();
CECNavigateCtrl pNaviCtrl = pNavigatePlayer != null ? pNavigatePlayer.GetNavigateCtrl() : null;
CECBezierWalker pBezierWalker = pNaviCtrl != null ? pNaviCtrl.GetBezierWalker() : null;
if (!m_bMove || pBezierWalker == null) return true;
// Do not use EC_Game.GetRealTickTime() here: it is (unscaledDeltaTime - 0.018s)*1000 for mining tuning
// and goes negative on typical frames (<18ms), corrupting m_iTimeCnt and Bezier param f.
// 此处勿用 EC_Game.GetRealTickTime():其为挖矿偏移后的毫秒增量,帧快于 ~18ms 时为负,会破坏时间轴与 f。
int dwRealTime = Mathf.Max(1, Mathf.RoundToInt(Time.unscaledDeltaTime * 1000f));
// TODO: Implement IsWalking
// if (pBezierWalker->IsWalking()) {
if (pBezierWalker.IsWalking())
{
pBezierWalker.Tick(dwRealTime);
// Get updated position from bezier walker // 从贝塞尔行走器获取更新的位置
A3DVECTOR3 vCurPos = pBezierWalker.GetPos();
// Get direction from bezier walker // 从贝塞尔行走器获取方向
A3DVECTOR3 vDir = pBezierWalker.GetDir();
vDir.Normalize();
A3DVECTOR3 vRight = A3DVECTOR3.CrossProduct(GPDataTypeHelper.g_vAxisY, vDir);
A3DVECTOR3 vUp = A3DVECTOR3.CrossProduct(vDir, vRight);
A3DVECTOR3 vDirH = A3DVECTOR3.CrossProduct(vRight, GPDataTypeHelper.g_vAxisY);
vUp.Normalize();
// TODO: Implement GetNavigatePlayer
// CECHostNavigatePlayer* pClone = m_pHost->GetNavigatePlayer();
CECHostNavigatePlayer pClone = m_pHost.GetNavigatePlayer();
if (pClone != null)
{
pClone.SetPos(new Vector3(vCurPos.x, vCurPos.y, vCurPos.z));
pClone.SetDirAndUp(new A3DVECTOR3(vDir.x, vDir.y, vDir.z), new A3DVECTOR3(vUp.x, vUp.y, vUp.z));
pClone.ResetHookPosition();
if(pNaviCtrl.GetCurrentNavigateInfo().bezierDir)
{
pClone.ChangeModelMoveDirAndUp(vDir, vUp);
}
else
{
pClone.ChangeModelMoveDirAndUp(vDirH, GPDataTypeHelper.g_vAxisY);
}
}
}
else
{
if (m_bMove)
{
m_bMove = false;
}
else
{
//UnityEngine.Debug.Assert(false);
}
// Set finish flag // 设置完成标志
if (m_pHost != null && m_pHost.GetTaskInterface() != null)
{
m_pHost.GetTaskInterface().SetForceNavigateFinishFlag(true);
}
}
base.Tick(dwDeltaTime);
return true;
}
}
//////////////////////////////////////////////////////////////////////////
//
// Forward declarations / Stub classes for dependencies
//
//////////////////////////////////////////////////////////////////////////
// CECBezierWalker class - Converted from C++ // CECBezierWalker类 - 从C++转换
public class CECBezierWalker
{
private CECBezier m_pBezier; // Bezier route data // 贝塞尔路径数据
private bool m_bForward; // Forward flag // 前进标志
private float m_fSpeed; // Moving speed // 移动速度
private int m_iTotalTime; // Total time of whole bezier route // 整个贝塞尔路径的总时间
private int m_iTimeCnt; // Time counter // 时间计数器
private int m_iCurSeg; // Current segment // 当前段
private int m_iCurSegTime; // Total time of current segment // 当前段的总时间
private int m_iPassSegTime; // Total time of passed segments // 已通过段的总时间
private bool m_bLoop; // Loop flag // 循环标志
private bool m_bWalking; // true, is walking // true,正在行走
private bool m_bPause; // Pause flag // 暂停标志
private bool m_bForwardStop; // Stop flag // 停止标志
private bool m_dbgWarnedZeroSeg; // One-shot empty-spline warning // 空路径单次警告
public CECBezierWalker()
{
m_pBezier = null;
m_fSpeed = 1.0f;
m_iTotalTime = 0;
m_iTimeCnt = 0;
m_bForward = true;
m_iCurSeg = 0;
m_iCurSegTime = 0;
m_iPassSegTime = 0;
m_bLoop = true;
m_bWalking = false;
m_bPause = false;
m_bForwardStop = false;
}
// Bind bezier route // 绑定贝塞尔路径
public bool BindBezier(CECBezier pBezier)
{
m_pBezier = pBezier;
// Stop at default position // 在默认位置停止
m_iTimeCnt = 0;
m_iCurSeg = 0;
m_iPassSegTime = 0;
m_bForwardStop = false;
m_bWalking = false;
m_bPause = false;
m_dbgWarnedZeroSeg = false;
return true;
}
// Start walk // 开始行走
public bool StartWalk(bool bLoop, bool bForward)
{
if (m_pBezier == null)
{
return false;
}
m_bLoop = bLoop;
m_bForward = bForward;
m_iTimeCnt = 0;
m_iCurSeg = bForward ? 0 : m_pBezier.GetSegmentNum() - 1;
CECBezierSeg pSeg = m_pBezier.GetSegment(m_iCurSeg);
if (pSeg != null)
{
float fInvSpeed = 1000.0f / m_fSpeed;
m_iCurSegTime = (int)(pSeg.GetSegLength() * fInvSpeed);
}
else
{
m_iCurSegTime = 0;
}
m_iPassSegTime = 0;
m_bWalking = true;
m_bPause = false;
return true;
}
// Pause walk // 暂停行走
public void Pause(bool bPause)
{
if (bPause)
{
if (m_bWalking)
{
m_bPause = true;
m_bWalking = false;
}
}
else
{
if (m_bPause)
{
m_bPause = false;
m_bWalking = true;
}
}
}
// Move speed // 移动速度
public void SetSpeed(float fSpeed)
{
if (fSpeed <= 0.0f)
{
//UnityEngine.Debug.Assert(false);
return;
}
m_fSpeed = fSpeed;
// Calculate total time // 计算总时间
if (m_pBezier != null)
{
int iNumSeg = m_pBezier.GetSegmentNum();
float fInvSpeed = 1000.0f / m_fSpeed;
m_iTotalTime = 0;
for (int i = 0; i < iNumSeg; i++)
{
CECBezierSeg pSeg = m_pBezier.GetSegment(i);
if (pSeg != null)
{
m_iTotalTime += (int)(pSeg.GetSegLength() * fInvSpeed);
}
}
}
}
public void SetForwardFlag(bool bForward)
{
if (!m_bWalking && !m_bPause)
{
m_bForward = bForward;
return;
}
int iDeltaTime = m_iPassSegTime + m_iCurSegTime - m_iTimeCnt;
m_iPassSegTime = m_iTotalTime - (m_iPassSegTime + m_iCurSegTime);
m_iTimeCnt = m_iPassSegTime + iDeltaTime;
m_bForward = bForward;
}
// Tick routine // Tick例程
public bool Tick(int iDeltaTime)
{
if (m_pBezier == null || !m_bWalking)
{
return true;
}
int iNumSeg = m_pBezier.GetSegmentNum();
if (iNumSeg == 0)
{
if (!m_dbgWarnedZeroSeg)
{
m_dbgWarnedZeroSeg = true;
}
return true;
}
float fInvSpeed = 1000.0f / m_fSpeed;
if (iDeltaTime < 0)
{
iDeltaTime = Mathf.Max(1, Mathf.RoundToInt(Time.unscaledDeltaTime * 1000f));
}
if (m_iTotalTime > 0)
{
iDeltaTime = iDeltaTime % m_iTotalTime;
}
m_iTimeCnt += iDeltaTime;
if (m_bForward)
{
while (m_iTimeCnt >= m_iPassSegTime + m_iCurSegTime)
{
if (m_iCurSeg + 1 >= iNumSeg)
{
if (m_bLoop)
{
m_iCurSeg = 0;
m_iPassSegTime = 0;
m_iTimeCnt -= m_iTotalTime;
if (m_iTimeCnt < 0)
m_iTimeCnt = 0;
}
else
{
m_iTimeCnt = m_iPassSegTime + m_iCurSegTime;
m_bWalking = false;
m_bForwardStop = true;
break;
}
}
else
{
m_iCurSeg++;
m_iPassSegTime += m_iCurSegTime;
}
CECBezierSeg pSeg = m_pBezier.GetSegment(m_iCurSeg);
if (pSeg != null)
{
m_iCurSegTime = (int)(pSeg.GetSegLength() * fInvSpeed);
}
else
{
m_iCurSegTime = 0;
break;
}
}
}
else
{
while (m_iTimeCnt >= m_iPassSegTime + m_iCurSegTime)
{
if (m_iCurSeg - 1 < 0)
{
if (m_bLoop)
{
m_iCurSeg = iNumSeg - 1;
m_iPassSegTime = 0;
m_iTimeCnt -= m_iTotalTime;
if (m_iTimeCnt < 0)
m_iTimeCnt = 0;
}
else
{
m_iTimeCnt = m_iPassSegTime + m_iCurSegTime;
m_bWalking = false;
m_bForwardStop = false;
break;
}
}
else
{
m_iCurSeg--;
m_iPassSegTime += m_iCurSegTime;
}
CECBezierSeg pSeg = m_pBezier.GetSegment(m_iCurSeg);
if (pSeg != null)
{
m_iCurSegTime = (int)(pSeg.GetSegLength() * fInvSpeed);
}
else
{
m_iCurSegTime = 0;
break;
}
}
}
return true;
}
// Get current position // 获取当前位置
public A3DVECTOR3 GetPos()
{
if (m_pBezier == null)
{
//UnityEngine.Debug.Assert(false);
return new A3DVECTOR3(0, 0, 0);
}
int iSeg;
float f;
bool bForward;
if (m_bWalking || m_bPause)
{
if (m_iCurSegTime > 0)
{
f = (float)(m_iTimeCnt - m_iPassSegTime) / m_iCurSegTime;
f = Mathf.Clamp01(f);
}
else
{
f = 0.0f;
}
bForward = m_bForward;
iSeg = m_iCurSeg;
}
else
{
bForward = true;
if (m_bForwardStop)
{
f = 1.0f;
iSeg = m_pBezier.GetSegmentNum() - 1;
}
else
{
f = 0.0f;
iSeg = 0;
}
}
CECBezierSeg pSeg = m_pBezier.GetSegment(iSeg);
if (pSeg != null)
{
return pSeg.Bezier(f, bForward);
}
return new A3DVECTOR3(0, 0, 0);
}
// Get current direction // 获取当前方向
public A3DVECTOR3 GetDir()
{
if (m_pBezier == null)
{
//UnityEngine.Debug.Assert(false);
return new A3DVECTOR3(0, 0, 1);
}
int iSeg;
float f;
bool bForward;
if (m_bWalking || m_bPause)
{
if (m_iCurSegTime > 0)
{
f = (float)(m_iTimeCnt - m_iPassSegTime) / m_iCurSegTime;
f = Mathf.Clamp01(f);
}
else
{
f = 0.0f;
}
bForward = m_bForward;
iSeg = m_iCurSeg;
}
else
{
bForward = true;
if (m_bForwardStop)
{
f = 1.0f;
iSeg = m_pBezier.GetSegmentNum() - 1;
}
else
{
f = 0.0f;
iSeg = 0;
}
}
CECBezierSeg pSeg = m_pBezier.GetSegment(iSeg);
if (pSeg != null)
{
return pSeg.Vector(f, bForward);
}
return new A3DVECTOR3(0, 0, 1);
}
// Debug: timeline mirrors GetPos/GetDir parameter f (expect 0…1 while walking). // 调试:与 GetPos 一致的段内参数 f(行走时期望 0…1)。
public void GetTimelineDebug(out int numSeg, out int curSeg, out int timeCnt, out int passSegTime, out int curSegTime, out float paramF, out bool walking, out bool forwardStop, out bool loop)
{
numSeg = m_pBezier != null ? m_pBezier.GetSegmentNum() : 0;
curSeg = m_iCurSeg;
timeCnt = m_iTimeCnt;
passSegTime = m_iPassSegTime;
curSegTime = m_iCurSegTime;
walking = m_bWalking;
forwardStop = m_bForwardStop;
loop = m_bLoop;
paramF = 0f;
if (m_pBezier == null)
{
return;
}
if (m_bWalking || m_bPause)
{
if (m_iCurSegTime > 0)
{
paramF = Mathf.Clamp01((float)(m_iTimeCnt - m_iPassSegTime) / m_iCurSegTime);
}
}
else if (m_bForwardStop)
{
paramF = 1.0f;
}
}
public bool IsWalking() { return m_bWalking; }
public bool IsPause() { return m_bPause; }
public bool GetForwardFlag() { return m_bForward; }
public float GetSpeed() { return m_fSpeed; }
public int GetTotalTime() { return m_iTotalTime; }
public void SetLoopFlag(bool bLoop) { m_bLoop = bLoop; }
public bool GetLoopFlag() { return m_bLoop; }
}
//////////////////////////////////////////////////////////////////////////
//
// CECBezierPoint class - Bezier curve point // 贝塞尔曲线点类
//
//////////////////////////////////////////////////////////////////////////
public class CECBezierPoint
{
private A3DVECTOR3 m_vPos; // Position // 位置
private A3DVECTOR3 m_vDir; // Direction // 方向
public CECBezierPoint()
{
m_vPos = new A3DVECTOR3(0, 0, 0);
m_vDir = new A3DVECTOR3(0, 0, 1);
}
public A3DVECTOR3 GetPos() { return m_vPos; }
public A3DVECTOR3 GetDir() { return m_vDir; }
public void SetPos(A3DVECTOR3 pos) { m_vPos = pos; }
public void SetDir(A3DVECTOR3 dir) { m_vDir = dir; }
}
//////////////////////////////////////////////////////////////////////////
//
// CECBezierSeg class - Bezier curve segment // 贝塞尔曲线段类
//
//////////////////////////////////////////////////////////////////////////
public class CECBezierSeg
{
private A3DVECTOR3 m_vAnchorHead; // Head anchor point // 头部锚点
private A3DVECTOR3 m_vAnchorTail; // Tail anchor point // 尾部锚点
private int m_nHeadPoint; // Head point index // 头部点索引
private int m_nTailPoint; // Tail point index // 尾部点索引
private float m_fLength; // Segment length // 段长度
private CECBezierPoint[] m_pListPoint; // Reference to point list // 点列表的引用
public CECBezierSeg()
{
m_vAnchorHead = new A3DVECTOR3(0, 0, 0);
m_vAnchorTail = new A3DVECTOR3(0, 0, 0);
m_nHeadPoint = 0;
m_nTailPoint = 0;
m_fLength = 0.0f;
m_pListPoint = null;
}
public void Init(CECBezierPoint[] pListPt)
{
m_pListPoint = pListPt;
}
// Calculate Bezier curve position at parameter u [0,1] // 计算参数u [0,1]处的贝塞尔曲线位置
public A3DVECTOR3 Bezier(float u, bool bForward)
{
// C++: use a,b,c arrays form
// 原版:使用 a,b,c 数组形式计算
A3DVECTOR3 pt1, pt2, control1, control2;
if (bForward)
{
pt1 = m_pListPoint[m_nHeadPoint].GetPos();
control1 = m_vAnchorHead;
control2 = m_vAnchorTail;
pt2 = m_pListPoint[m_nTailPoint].GetPos();
}
else
{
pt1 = m_pListPoint[m_nTailPoint].GetPos();
control1 = m_vAnchorTail;
control2 = m_vAnchorHead;
pt2 = m_pListPoint[m_nHeadPoint].GetPos();
}
float[] a = new float[3];
float[] b = new float[3];
float[] c = new float[3];
float[] p0 = { pt1.x, pt1.y, pt1.z };
float[] p1 = { control1.x, control1.y, control1.z };
float[] p2 = { control2.x, control2.y, control2.z };
float[] p3 = { pt2.x, pt2.y, pt2.z };
for (int i = 0; i < 3; i++)
{
c[i] = 3.0f * (p1[i] - p0[i]);
b[i] = 3.0f * (p2[i] - p1[i]) - c[i];
a[i] = p3[i] - p0[i] - c[i] - b[i];
}
A3DVECTOR3 pos = new A3DVECTOR3();
pos.x = (a[0] * u * u * u) + (b[0] * u * u) + (c[0] * u) + p0[0];
pos.y = (a[1] * u * u * u) + (b[1] * u * u) + (c[1] * u) + p0[1];
pos.z = (a[2] * u * u * u) + (b[2] * u * u) + (c[2] * u) + p0[2];
return pos;
}
// Calculate direction vector at parameter u [0,1] // 计算参数u [0,1]处的方向向量
public A3DVECTOR3 Vector(float u, bool bForward)
{
// C++ implementation
// 原版实现
A3DVECTOR3 headspot, tailspot;
if (bForward)
{
headspot = m_pListPoint[m_nHeadPoint].GetDir();
tailspot = m_pListPoint[m_nTailPoint].GetDir();
}
else
{
headspot = m_pListPoint[m_nTailPoint].GetDir();
tailspot = m_pListPoint[m_nHeadPoint].GetDir();
}
if (u <= 0.0f) return headspot;
if (u >= 1.0f) return tailspot;
float m1 = headspot.Magnitude();
float m2 = tailspot.Magnitude();
A3DVECTOR3 hN, tN;
A3DVECTOR3.Normalize(headspot, out hN);
A3DVECTOR3.Normalize(tailspot, out tN);
float dot = A3DVECTOR3.DotProduct(hN, tN);
if (dot > 1.0f) return headspot;
if (dot < -1.0f) dot = -1.0f;
float r = Mathf.Acos(dot);
r = r * u;
A3DVECTOR3 axis = A3DVECTOR3.CrossProduct(headspot, tailspot);
// matrix * headspot (use quaternion equivalent)
A3DVECTOR3 rotated = NavigateHelper.a3d_RotatePosAroundAxis(headspot, axis, r);
A3DVECTOR3.Normalize(rotated, out rotated);
float len = m1 + (m2 - m1) * u;
rotated = rotated * len;
if (bForward) return rotated;
return -rotated;
}
public A3DVECTOR3 GetAnchorHead() { return m_vAnchorHead; }
public A3DVECTOR3 GetAnchorTail() { return m_vAnchorTail; }
public int GetHeadPoint() { return m_nHeadPoint; }
public int GetTailPoint() { return m_nTailPoint; }
public float GetSegLength() { return m_fLength; }
public void SetAnchorHead(A3DVECTOR3 v) { m_vAnchorHead = v; }
public void SetAnchorTail(A3DVECTOR3 v) { m_vAnchorTail = v; }
public void SetHeadPoint(int index) { m_nHeadPoint = index; }
public void SetTailPoint(int index) { m_nTailPoint = index; }
public void SetSegLength(float length) { m_fLength = length; }
}
//////////////////////////////////////////////////////////////////////////
//
// CECBezier class - Bezier curve route // 贝塞尔曲线路径类
//
//////////////////////////////////////////////////////////////////////////
public class CECBezier
{
private int m_nObjectID; // Object ID // 对象ID
private int m_iGlobalID; // Global ID used to link bezier routes // 用于链接贝塞尔路径的全局ID
private int m_iNextGlobalID; // Next bezier route's global ID // 下一个贝塞尔路径的全局ID
private int m_nNumPoint; // Number of points // 点数
private int m_nNumSeg; // Number of segments // 段数
private string m_strActName; // Action name // 动作名称
private CECBezierPoint[] m_pListPoint; // Point list // 点列表
private CECBezierSeg[] m_pListSeg; // Segment list // 段列表
public CECBezier()
{
m_nObjectID = 0;
m_iGlobalID = -1;
m_iNextGlobalID = -1;
m_nNumPoint = 0;
m_nNumSeg = 0;
m_strActName = "";
m_pListPoint = null;
m_pListSeg = null;
}
~CECBezier()
{
Release();
}
public void Release()
{
m_pListPoint = null;
m_pListSeg = null;
m_nNumPoint = 0;
m_nNumSeg = 0;
}
// Load bezier from binary stream (navigate.txt) // 从二进制流加载Beziernavigate.txt
public bool Load(BinaryReader br)
{
// C++: DWORD version; int objectId; (version>=2: globalID,nextGlobalID) (version>=3: ReadString actName)
// 原版:DWORD version; int objectId;version>=2globalID,nextGlobalID)(version>=3ReadString 动作名)
uint dwVersion = br.ReadUInt32();
m_nObjectID = br.ReadInt32();
if (dwVersion >= 2)
{
m_iGlobalID = br.ReadInt32();
m_iNextGlobalID = br.ReadInt32();
}
if (dwVersion >= 3)
{
int len = br.ReadInt32();
if (len > 0)
{
byte[] buf = br.ReadBytes(len);
m_strActName = Encoding.GetEncoding(936).GetString(buf);
}
else
{
m_strActName = string.Empty;
}
}
int ptNum = br.ReadInt32();
m_pListPoint = new CECBezierPoint[ptNum];
m_nNumPoint = ptNum;
for (int i = 0; i < ptNum; i++)
{
var vPos = new A3DVECTOR3(br.ReadSingle(), br.ReadSingle(), br.ReadSingle());
var vDir = new A3DVECTOR3(br.ReadSingle(), br.ReadSingle(), br.ReadSingle());
m_pListPoint[i] = new CECBezierPoint();
m_pListPoint[i].SetPos(vPos);
m_pListPoint[i].SetDir(vDir);
}
int segNum = br.ReadInt32();
m_nNumSeg = segNum;
if (segNum > 0)
{
m_pListSeg = new CECBezierSeg[segNum];
for (int i = 0; i < segNum; i++)
{
var vAnchorHead = new A3DVECTOR3(br.ReadSingle(), br.ReadSingle(), br.ReadSingle());
var vAnchorTail = new A3DVECTOR3(br.ReadSingle(), br.ReadSingle(), br.ReadSingle());
int headIndex = br.ReadInt32();
int tailIndex = br.ReadInt32();
float length = br.ReadSingle();
m_pListSeg[i] = new CECBezierSeg();
m_pListSeg[i].Init(m_pListPoint);
m_pListSeg[i].SetAnchorHead(vAnchorHead);
m_pListSeg[i].SetAnchorTail(vAnchorTail);
m_pListSeg[i].SetHeadPoint(headIndex);
m_pListSeg[i].SetTailPoint(tailIndex);
m_pListSeg[i].SetSegLength(length);
}
}
return true;
}
public int GetObjectID() { return m_nObjectID; }
public void SetObjectID(int id) { m_nObjectID = id; }
public void SetGlobalID(int iID) { m_iGlobalID = iID; }
public int GetGlobalID() { return m_iGlobalID; }
public void SetNextGlobalID(int iID) { m_iNextGlobalID = iID; }
public int GetNextGlobalID() { return m_iNextGlobalID; }
public int GetSegmentNum() { return m_nNumSeg; }
public CECBezierSeg GetSegment(int n)
{
if (n >= 0 && n < m_nNumSeg && m_pListSeg != null)
{
return m_pListSeg[n];
}
return null;
}
// Assign points and segments (for loading/creation) // 分配点和段(用于加载/创建)
public void Assign(int nNumPt, int nNumSeg)
{
Release();
m_nNumPoint = nNumPt;
m_nNumSeg = nNumSeg;
if (nNumPt > 0)
{
m_pListPoint = new CECBezierPoint[nNumPt];
}
if (nNumSeg > 0)
{
m_pListSeg = new CECBezierSeg[nNumSeg];
// Initialize segments with point list reference // 使用点列表引用初始化段
for (int i = 0; i < nNumSeg; i++)
{
m_pListSeg[i] = new CECBezierSeg();
if (m_pListPoint != null)
{
m_pListSeg[i].Init(m_pListPoint);
}
}
}
}
public void AddBezierPoint(CECBezierPoint pt, int i)
{
if (i >= 0 && i < m_nNumPoint && m_pListPoint != null)
{
m_pListPoint[i].SetPos(pt.GetPos());
m_pListPoint[i].SetDir(pt.GetDir());
}
}
public void AddBezierSeg(CECBezierSeg seg, int i)
{
if (i >= 0 && i < m_nNumSeg && m_pListSeg != null)
{
m_pListSeg[i].Init(m_pListPoint);
m_pListSeg[i].SetAnchorHead(seg.GetAnchorHead());
m_pListSeg[i].SetAnchorTail(seg.GetAnchorTail());
m_pListSeg[i].SetHeadPoint(seg.GetHeadPoint());
m_pListSeg[i].SetTailPoint(seg.GetTailPoint());
m_pListSeg[i].SetSegLength(seg.GetSegLength());
}
}
public void SetOffset(A3DVECTOR3 vOffset)
{
if (m_pListPoint != null)
{
for (int i = 0; i < m_nNumPoint; i++)
{
A3DVECTOR3 pos = m_pListPoint[i].GetPos();
pos.x += vOffset.x;
pos.y += vOffset.y;
pos.z += vOffset.z;
m_pListPoint[i].SetPos(pos);
}
}
}
}
//////////////////////////////////////////////////////////////////////////
//
// navigate.txt loader (equivalent to CECScene::LoadBezierNavigate + GetBezierObjectByGlobalID)
// navigate.txt加载器(等价于 CECScene::LoadBezierNavigate + GetBezierObjectByGlobalID
//
//////////////////////////////////////////////////////////////////////////
public static class CECBezierNavigateLoader
{
// NAVIGATEBEZIERFILEHEADER
// 导航Bezier文件头
private struct NAVIGATEBEZIERFILEHEADER
{
public int iVersion; // Version // 版本
public int iNumBezier; // Number of bezier route // Bezier数量
}
private static bool s_loaded;
private static readonly Dictionary<int, CECBezier> s_beziers = new Dictionary<int, CECBezier>();
public static CECBezier GetBezierObjectByGlobalID(int iGlobalID)
{
if (!s_loaded)
{
LoadBezierNavigate("Assets/Addressable/navigate.txt");
}
return s_beziers.TryGetValue(iGlobalID, out var bezier) ? bezier : null;
}
public static bool LoadBezierNavigate(string address)
{
if (s_loaded)
{
return true;
}
try
{
Addressables.InitializeAsync().WaitForCompletion();
var ta = Addressables.LoadAssetAsync<TextAsset>(address).WaitForCompletion();
if (ta == null)
{
return false;
}
using var ms = new MemoryStream(ta.bytes, false);
using var br = new BinaryReader(ms);
NAVIGATEBEZIERFILEHEADER header;
header.iVersion = br.ReadInt32();
header.iNumBezier = br.ReadInt32();
s_beziers.Clear();
for (int i = 0; i < header.iNumBezier; i++)
{
var pBezier = new CECBezier();
if (!pBezier.Load(br))
{
return false;
}
// C++: m_BezierForceNavigateTab.put((int)pBezier->GetGlobalID(), pBezier);
// 原版:按GlobalID存入表
s_beziers[pBezier.GetGlobalID()] = pBezier;
}
s_loaded = true;
return true;
}
catch
{
return false;
}
}
}
}