diff --git a/Assets/PerfectWorld/Scene/AnimationTest.unity b/Assets/PerfectWorld/Scene/AnimationTest.unity index 2d16aa6a66..ce2ed17328 100644 --- a/Assets/PerfectWorld/Scene/AnimationTest.unity +++ b/Assets/PerfectWorld/Scene/AnimationTest.unity @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:2daf9dffc5bed73510959aebf1ee1b4b2fe326380d6417e1a262c919b651095e -size 70182 +oid sha256:11f16ce7ddd16e5c3de941b56358deb495efb2d5d4783c8d59c75aab0dad1975 +size 92966 diff --git a/Assets/PerfectWorld/Scripts/Managers/EC_HPWorkMove.cs b/Assets/PerfectWorld/Scripts/Managers/EC_HPWorkMove.cs index 7672d2d72a..000fb86a8b 100644 --- a/Assets/PerfectWorld/Scripts/Managers/EC_HPWorkMove.cs +++ b/Assets/PerfectWorld/Scripts/Managers/EC_HPWorkMove.cs @@ -875,11 +875,7 @@ namespace BrewMonster.Scripts vCurPos = m_pHost.m_MoveCtrl.GroundMove(m_vCurDir, fSpeed, fDeltaTime, m_pHost.m_fVertSpeed); if (!m_vCurDir.IsZero()) - { - //m_pHost.StartModelMove(m_vCurDir, GPDataTypeHelper.g_vAxisY, 100); - //m_pHost.ChangeModelTargetDirAndUp(m_vCurDir, GPDataTypeHelper.g_vAxisY); - UpdateFacingFromDelta(vCurPos); - } + OrientHostHorizontal(m_vCurDir); if (m_pHost.m_MoveCtrl.MoveBlocked() >= 3) { diff --git a/Assets/PerfectWorld/Scripts/Move/AutoPFImp/AutoMove/CMoveAgent.cs b/Assets/PerfectWorld/Scripts/Move/AutoPFImp/AutoMove/CMoveAgent.cs index 5199de3cb9..20fa085849 100644 --- a/Assets/PerfectWorld/Scripts/Move/AutoPFImp/AutoMove/CMoveAgent.cs +++ b/Assets/PerfectWorld/Scripts/Move/AutoPFImp/AutoMove/CMoveAgent.cs @@ -3,76 +3,59 @@ using System.Collections.Generic; using UnityEngine; // Filename : CMoveAgent.cs -// Creator : ported/simplified from C++ (AutoPFImp/AutoMoveImp/MoveAgent.*) +// Creator : ported from C++ (AutoPFImp/AutoMoveImp/MoveAgent.*) // Date : 2026/01/09 namespace AutoMove { /// - /// Minimal MoveAgent: loads movemap and provides A* path on layer0 rmap. - /// 最小 MoveAgent:加载 movemap,并在第0层 rmap 上执行 A* 路径搜索。 + /// MoveAgent: A* on layer0 rmap + COptimizePath (matches C++ CMoveAgent flow). /// public class CMoveAgent { - // Debug switch (enable temporarily while validating routes). - // 调试开关(验证寻路时可临时开启)。 - // NOTE: must not be const, otherwise Unity compiler warns about unreachable code. - // 注意:不要用 const,否则 Unity 编译会报“不可达代码”警告。 private static bool DEBUG_AUTOPF = false; + public abstract class BrushTest { - // from.y/to.y store DH in original engine. - // 原版中 from.y/to.y 存储 DH(相对地形高度差)。 public abstract bool Collide(Vector3 from, Vector3 to); } private readonly CMoveMap m_pMoveMap = new CMoveMap(); - private Vector3 m_vOriginOverride; + private COptimizePath m_pPathOptimizer; private Vector2Int m_ptStart; private Vector2Int m_ptGoal; private int m_iLayerStart; private int m_iLayerGoal; - private readonly List m_path3D = new List(1024); - public bool Load(string basePathNoExt, Func resolver, Vector3? originOverride) { - // basePathNoExt corresponds to "maps\\\\movemap\\r.._..-c.._..-l0" in C++. - // basePathNoExt 对应 C++ 的 "maps\\\\movemap\\r.._..-c.._..-l0"。 string cfgName = basePathNoExt; if (!cfgName.EndsWith(".cfg", StringComparison.OrdinalIgnoreCase)) - { cfgName += ".cfg"; - } byte[] cfgBytes = resolver(cfgName); if (cfgBytes == null) - { return false; - } - // Resolver for referenced files: in cfg, references are baseName + ".prmap" etc. - // cfg 内引用:baseName + ".prmap" 等。 bool ok = m_pMoveMap.Load(cfgBytes, resolver, basePathNoExt); if (!ok) - { return false; - } if (originOverride.HasValue) - { - m_vOriginOverride = originOverride.Value; - m_pMoveMap.SetOrigin(m_vOriginOverride); - } + m_pMoveMap.SetOrigin(originOverride.Value); + CreateOptimizer(); return true; } + void CreateOptimizer() + { + m_pPathOptimizer = new COptimizePath(); + } + public bool IsReady() { - // Original C++ requires MultiCluGraph; here we only require layer0 RMap. - // 原版 C++ 依赖 MultiCluGraph;这里最小实现只要求第0层 RMap。 var layer0 = m_pMoveMap.GetLayer(0); return layer0 != null && layer0.GetRMap() != null; } @@ -98,8 +81,6 @@ namespace AutoMove public int WhichLayer(Vector3 vWld, float dH, out float layerDist) { - // Minimal: single layer 0 if passable. - // 最小实现:如果可通行则使用单层0。 layerDist = 0.0f; var pt = TransWld2Map(vWld); var layer0 = m_pMoveMap.GetLayer(0); @@ -116,31 +97,72 @@ namespace AutoMove return true; } - public int GetPathCount() => m_path3D.Count; + public List Get2DPath() + { + return m_pPathOptimizer?.GetPath(); + } - /// - /// Matches C++ CMoveAgent::GetOptimizeCatchCount — lookahead window for path following. - /// 对应 C++:无 PathOptimizer 时为 0,FindNearest/Farthest 仅在单步索引上工作。 - /// - public int GetOptimizeCatchCount() => 0; + public int GetPathCount() + { + if (m_pPathOptimizer != null) + { + var path = m_pPathOptimizer.GetPath(); + if (path != null) + return path.Count; + } + return 0; + } + + public int GetOptimizeCatchCount() + { + return m_pPathOptimizer != null ? m_pPathOptimizer.GetCatchCount() : 0; + } + + public Vector3 Get2DPathNode(int index) + { + var path2d = Get2DPath(); + if (path2d == null || index < 0 || index >= path2d.Count) + return Vector3.zero; + PathNode pathNode = path2d[index]; + Vector2 ptWld = m_pMoveMap.TransMap2Wld(pathNode.ptMap.x, pathNode.ptMap.y); + return new Vector3(ptWld.x, 0f, ptWld.y); + } public Vector3 Get3DPathNode(int index) { - if (index < 0 || index >= m_path3D.Count) return Vector3.zero; - return m_path3D[index]; + var path2d = Get2DPath(); + if (path2d == null || index < 0 || index >= path2d.Count) + return Vector3.zero; + PathNode pathNode = path2d[index]; + Vector2 ptWld = m_pMoveMap.TransMap2Wld(pathNode.ptMap.x, pathNode.ptMap.y); + float dh = m_pMoveMap.GetDH(pathNode.layer, (int)pathNode.ptMap.x, (int)pathNode.ptMap.y); + return new Vector3(ptWld.x, dh, ptWld.y); } - public System.Collections.Generic.List GetFullPath() + /// Ported from C++ CMoveAgent::Optimize. + public bool Optimize(int moveIndex) { - return new System.Collections.Generic.List(m_path3D); + if (m_pPathOptimizer == null) + return false; + if (!m_pPathOptimizer.NeedOptimize(moveIndex)) + return false; + m_pPathOptimizer.StepOptimize(); + return true; + } + + public List GetFullPath() + { + var path = new List(); + var path2d = Get2DPath(); + if (path2d == null) + return path; + for (int i = 0; i < path2d.Count; i++) + path.Add(Get2DPathNode(i)); + return path; } public bool Search(int nMaxExpand = -1) { - // A* on layer0 rmap - // 在第0层 rmap 上执行 A* - m_path3D.Clear(); - var layer0 = m_pMoveMap.GetLayer(0); var rmap = layer0?.GetRMap(); if (rmap == null) return false; @@ -149,30 +171,18 @@ namespace AutoMove if (w <= 0 || h <= 0) return false; if (!InBounds(m_ptStart, w, h) || !InBounds(m_ptGoal, w, h)) - { return false; - } - // If goal or start is not passable, try to find nearest passable (original MoveAgent does this). - // 如果起点或终点不可通行,尝试寻找最近可通行点(原版 MoveAgent 会这样做)。 if (!rmap.GetPixel(m_ptStart.x, m_ptStart.y)) { if (!TryFindNearestPassable(rmap, m_ptStart, w, h, 64, out var newStart)) - { - if (DEBUG_AUTOPF) Debug.LogWarning($"[CMoveAgent] start not passable and no nearest passable found. start={m_ptStart}"); return false; - } - if (DEBUG_AUTOPF) Debug.Log($"[CMoveAgent] Adjust start {m_ptStart} -> {newStart}"); m_ptStart = newStart; } if (!rmap.GetPixel(m_ptGoal.x, m_ptGoal.y)) { if (!TryFindNearestPassable(rmap, m_ptGoal, w, h, 64, out var newGoal)) - { - if (DEBUG_AUTOPF) Debug.LogWarning($"[CMoveAgent] goal not passable and no nearest passable found. goal={m_ptGoal}"); return false; - } - if (DEBUG_AUTOPF) Debug.Log($"[CMoveAgent] Adjust goal {m_ptGoal} -> {newGoal}"); m_ptGoal = newGoal; } @@ -184,8 +194,6 @@ namespace AutoMove open.Push(m_ptStart, Heuristic(m_ptStart, m_ptGoal)); int expands = 0; - //ToDo: need use another method to caculate the value of maxExpand - //800000 is a magic number, need to be optimized int maxExpand = nMaxExpand > 0 ? nMaxExpand : 800000; while (open.Count > 0 && expands < maxExpand) @@ -228,16 +236,12 @@ namespace AutoMove if (rmap == null) return false; - // Check origin first - // 先检查原点 if (origin.x >= 0 && origin.x < w && origin.y >= 0 && origin.y < h && rmap.GetPixel(origin.x, origin.y)) { best = origin; return true; } - // Expand square rings - // 按方形“圈”扩展搜索 for (int r = 1; r <= maxRadius; r++) { int minX = Math.Max(0, origin.x - r); @@ -245,14 +249,11 @@ namespace AutoMove int minY = Math.Max(0, origin.y - r); int maxY = Math.Min(h - 1, origin.y + r); - // Top/bottom edges for (int x = minX; x <= maxX; x++) { TryConsider(x, minY); TryConsider(x, maxY); } - - // Left/right edges (excluding corners already checked) for (int y = minY + 1; y <= maxY - 1; y++) { TryConsider(minX, y); @@ -285,28 +286,35 @@ namespace AutoMove private void ReconstructPath(Dictionary cameFrom, Vector2Int cur) { - List rev = new List(1024) { cur }; + var rev = new List(1024) { cur }; while (cameFrom.TryGetValue(cur, out var prev)) { cur = prev; rev.Add(cur); } - rev.Reverse(); - // Convert to world positions (y will be resolved by host terrain in movement). - // 转为世界坐标(y 由移动逻辑/地形解析)。 + + var initPath = new List(rev.Count); for (int i = 0; i < rev.Count; i++) { - m_path3D.Add(Map2Wld(rev[i])); + initPath.Add(new PathNode + { + ptMap = new Vector2(rev[i].x, rev[i].y), + layer = m_iLayerStart, + }); } + + if (m_pPathOptimizer == null) + CreateOptimizer(); + + // C++: m_pPathOptimizer->SetupOptimize(GetMoveMap(), path); + m_pPathOptimizer.SetupOptimize(m_pMoveMap, initPath); } private static bool InBounds(Vector2Int p, int w, int h) => p.x >= 0 && p.x < w && p.y >= 0 && p.y < h; private static int Heuristic(Vector2Int a, Vector2Int b) { - // Octile distance * 10 - // 八方向启发式(octile)*10 int dx = Mathf.Abs(a.x - b.x); int dy = Mathf.Abs(a.y - b.y); int min = Math.Min(dx, dy); @@ -316,8 +324,6 @@ namespace AutoMove private static int Cost(Vector2Int a, Vector2Int b) { - // Diagonal=14, straight=10 - // 斜向=14,直向=10 int dx = Mathf.Abs(a.x - b.x); int dy = Mathf.Abs(a.y - b.y); return (dx + dy == 2) ? 14 : 10; @@ -337,33 +343,13 @@ namespace AutoMove public void ResetSearch() { - // Çå³ýµ±Ç°ËÑË÷״̬ - //if (m_iStat == PF_STATE_UNKNOWN) - //{ - // return; - //} - //if (m_pPfAlg) - //{ - // m_pPfAlg->Reset(); - //} - //if (m_pPathOptimizer) - //{ - // m_pPathOptimizer.Reset(); - //} - m_ptStart.x = m_ptStart.y = 0; + m_ptStart = Vector2Int.zero; m_iLayerStart = -1; - m_ptGoal.x = m_ptGoal.y = 0; + m_ptGoal = Vector2Int.zero; m_iLayerGoal = -1; - //m_pBrushTest = null; - - //m_iStat = PF_STATE_UNKNOWN; - m_path3D.Clear(); + m_pPathOptimizer?.Reset(); } - /// - /// Very small min-heap for A*. - /// A* 用的小型最小堆。 - /// private sealed class MinHeap { private struct Node @@ -419,5 +405,3 @@ namespace AutoMove } } } - - diff --git a/Assets/PerfectWorld/Scripts/Move/AutoPFImp/AutoMove/CMoveMap.cs b/Assets/PerfectWorld/Scripts/Move/AutoPFImp/AutoMove/CMoveMap.cs index 70c4441a47..d4a8aa0139 100644 --- a/Assets/PerfectWorld/Scripts/Move/AutoPFImp/AutoMove/CMoveMap.cs +++ b/Assets/PerfectWorld/Scripts/Move/AutoPFImp/AutoMove/CMoveMap.cs @@ -56,8 +56,13 @@ namespace AutoMove public Vector2 TransMap2Wld(int x, int y) { - float wx = m_vOrigin.x + (x + 0.5f) * m_fPixelSize; - float wz = m_vOrigin.z + (y + 0.5f) * m_fPixelSize; + return TransMap2Wld((float)x, (float)y); + } + + public Vector2 TransMap2Wld(float mapX, float mapY) + { + float wx = m_vOrigin.x + (mapX + 0.5f) * m_fPixelSize; + float wz = m_vOrigin.z + (mapY + 0.5f) * m_fPixelSize; return new Vector2(wx, wz); } diff --git a/Assets/PerfectWorld/Scripts/Move/AutoPFImp/AutoMove/COptimizePath.cs b/Assets/PerfectWorld/Scripts/Move/AutoPFImp/AutoMove/COptimizePath.cs new file mode 100644 index 0000000000..e75975f261 --- /dev/null +++ b/Assets/PerfectWorld/Scripts/Move/AutoPFImp/AutoMove/COptimizePath.cs @@ -0,0 +1,298 @@ +using System.Collections.Generic; +using UnityEngine; + +// Ported from C++ OptimizePath.h / OptimizePath.cpp (Kui Wu, 2007). + +namespace AutoMove +{ + /// Line sampler for map-space path optimization (C++ CLine). + sealed class CLine + { + Vector2 m_from; + Vector2 m_dir; + int m_count; + + public void Init(Vector2 from, Vector2 dir) + { + m_from = from; + m_dir = dir; + float len = m_dir.magnitude; + if (len > 1e-8f) + { + m_dir.x /= len; + m_dir.y /= len; + } + m_count = 0; + } + + public void Init(Vector2 from, int dirX, int dirY) + { + Init(from, new Vector2(dirX, dirY)); + } + + public Vector2 Next() + { + m_count++; + return new Vector2(m_from.x + m_dir.x * m_count, m_from.y + m_dir.y * m_count); + } + + public int GetCount() => m_count; + public Vector2 GetFrom() => m_from; + public void Reset() => m_count = 0; + } + + /// Path optimizer — port of C++ COptimizePath. + public sealed class COptimizePath + { + const int LookAhead = 60; + const int LookStep = 3; + + CMoveMap m_pMoveMap; + int m_mapWidth; + int m_mapHeight; + readonly Dictionary m_lookUp = new Dictionary(4096); + readonly List m_path = new List(1024); + int m_curIndex = -1; + int m_catchCount = 10; + int m_curLayer = -1; + + public void Reset() + { + m_pMoveMap = null; + m_mapWidth = 0; + m_mapHeight = 0; + m_lookUp.Clear(); + m_path.Clear(); + m_curIndex = -1; + m_curLayer = -1; + } + + public int GetCatchCount() => m_catchCount; + + public List GetPath() => m_path; + + public bool NeedOptimize(int moveIndex) + { + if (m_curIndex < m_path.Count + && moveIndex < m_path.Count + && m_curIndex - moveIndex < m_catchCount) + { + if (moveIndex > m_curIndex) + { + SetFootprintRange(m_curIndex, moveIndex - 1, 0); + m_curIndex = moveIndex; + } + return true; + } + return false; + } + + public void SetupOptimize(CMoveMap pMoveMap, List initPath, int catchCount = 10) + { + m_path.Clear(); + if (initPath != null && initPath.Count > 0) + m_path.AddRange(initPath); + + m_pMoveMap = pMoveMap; + m_mapWidth = pMoveMap != null ? pMoveMap.GetMapWidth() : 0; + m_mapHeight = pMoveMap != null ? pMoveMap.GetMapLength() : 0; + m_curIndex = 0; + m_catchCount = catchCount; + m_curLayer = -1; + + if (m_path.Count == 0) + return; + + CheckLayer(); + } + + public void StepOptimize() + { + CheckLayer(); + int step = m_catchCount * 2; + if (step <= 0) + return; + + int i = 0; + while (i < step && m_curIndex < m_path.Count) + { + LocalOptimize(); + i++; + m_curIndex++; + } + } + + void CheckLayer() + { + if (m_path.Count == 0 || m_curIndex < 0 || m_curIndex >= m_path.Count) + return; + + if (m_path[m_curIndex].layer == m_curLayer) + return; + + m_curLayer = m_path[m_curIndex].layer; + m_lookUp.Clear(); + + int i = m_curIndex; + while (i < m_path.Count && m_path[i].layer == m_curLayer) + { + SetFootprint(m_path[i].ptMap, (short)(i + 1)); + i++; + } + } + + void LocalOptimize() + { + int toIndex = Mathf.Min(m_curIndex + LookAhead, m_path.Count - 1); + var line = new CLine(); + Vector2 dir; + int newCount = -1; + + while (toIndex - m_curIndex > LookStep) + { + if (GetFootprint(m_path[toIndex].ptMap) == 0) + { + toIndex -= LookStep; + continue; + } + if (m_path[toIndex].layer != m_curLayer) + { + toIndex -= LookStep; + continue; + } + + dir = m_path[toIndex].ptMap - m_path[m_curIndex].ptMap; + if ((int)dir.x == 0 && (int)dir.y == 0) + { + PathIntersect(toIndex); + return; + } + + line.Init(m_path[m_curIndex].ptMap, dir); + if (LineTo(line, m_path[toIndex].ptMap)) + { + newCount = line.GetCount(); + break; + } + toIndex -= LookStep; + } + + if (newCount > 0) + { + line.Reset(); + AddPathPortion(line, toIndex - m_curIndex, newCount); + } + } + + CBitImage GetRMap(int layer) + { + if (m_pMoveMap == null) + return null; + var layerMap = m_pMoveMap.GetLayer(layer); + return layerMap?.GetRMap(); + } + + bool LineTo(CLine line, Vector2 to) + { + int toX = (int)to.x; + int toY = (int)to.y; + int curX = (int)line.GetFrom().x; + int curY = (int)line.GetFrom().y; + + CBitImage pRMap = GetRMap(m_curLayer); + if (pRMap == null) + return false; + + int lastX = curX; + int lastY = curY; + + while (curX != toX || curY != toY) + { + Vector2 cur = line.Next(); + curX = (int)cur.x; + curY = (int)cur.y; + + if (!pRMap.GetPixel(curX, curY)) + return false; + + bool bNeighbor1 = pRMap.GetPixel(lastX, curY); + bool bNeighbor2 = pRMap.GetPixel(curX, lastY); + + if (toX == lastX && toY == curY) + break; + if (toX == curX && toY == lastY) + break; + + if (curX != lastX && curY != lastY && (!bNeighbor1 || !bNeighbor2)) + return false; + + lastX = curX; + lastY = curY; + } + + return true; + } + + void PathIntersect(int toIndex) + { + SetFootprintRange(m_curIndex + 1, toIndex, 0); + int removeCount = toIndex - m_curIndex; + if (removeCount > 0 && m_curIndex + 1 < m_path.Count) + m_path.RemoveRange(m_curIndex + 1, removeCount); + } + + void AddPathPortion(CLine line, int oldCount, int newCount) + { + SetFootprintRange(m_curIndex + 1, m_curIndex + oldCount, 0); + + if (oldCount > newCount) + { + m_path.RemoveRange(m_curIndex + 1, oldCount - newCount); + } + else if (oldCount < newCount) + { + int insertCount = newCount - oldCount; + for (int k = 0; k < insertCount; k++) + m_path.Insert(m_curIndex + 1, new PathNode()); + } + + int index = m_curIndex + 1; + while (line.GetCount() < newCount && index < m_path.Count) + { + var node = m_path[index]; + node.ptMap = line.Next(); + node.layer = m_curLayer; + m_path[index] = node; + index++; + } + + SetFootprintRange(m_curIndex + 1, m_curIndex + newCount, 1); + } + + int GetLookUpKey(int x, int y) => y * m_mapWidth + x; + + int GetFootprint(int x, int y) + { + return m_lookUp.TryGetValue(GetLookUpKey(x, y), out short v) ? v : 0; + } + + int GetFootprint(Vector2 pt) => GetFootprint((int)pt.x, (int)pt.y); + + void SetFootprint(int x, int y, int val) + { + int key = GetLookUpKey(x, y); + if (val == 0) + m_lookUp.Remove(key); + else + m_lookUp[key] = (short)val; + } + + void SetFootprint(Vector2 pt, int val) => SetFootprint((int)pt.x, (int)pt.y, val); + + void SetFootprintRange(int fromIndex, int toIndex, int val) + { + for (int i = fromIndex; i <= toIndex && i < m_path.Count; ++i) + SetFootprint(m_path[i].ptMap, val); + } + } +} diff --git a/Assets/PerfectWorld/Scripts/Move/AutoPFImp/AutoMove/COptimizePath.cs.meta b/Assets/PerfectWorld/Scripts/Move/AutoPFImp/AutoMove/COptimizePath.cs.meta new file mode 100644 index 0000000000..86d0edecc0 --- /dev/null +++ b/Assets/PerfectWorld/Scripts/Move/AutoPFImp/AutoMove/COptimizePath.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 96e45cded56a0bc4188f329f2cf3b676 \ No newline at end of file diff --git a/Assets/PerfectWorld/Scripts/Move/AutoPFImp/AutoMove/PathNode.cs b/Assets/PerfectWorld/Scripts/Move/AutoPFImp/AutoMove/PathNode.cs new file mode 100644 index 0000000000..885435e2d2 --- /dev/null +++ b/Assets/PerfectWorld/Scripts/Move/AutoPFImp/AutoMove/PathNode.cs @@ -0,0 +1,22 @@ +using UnityEngine; + +namespace AutoMove +{ + /// Matches C++ AutoMove::PathNode (PfConstant.h). + public struct PathNode + { + public Vector2 ptMap; + public int layer; + + public Vector2Int GetPtI() + { + return new Vector2Int(Mathf.FloorToInt(ptMap.x), Mathf.FloorToInt(ptMap.y)); + } + + public void SetI(Vector2Int pt, int iLayer) + { + ptMap = new Vector2(pt.x, pt.y); + layer = iLayer; + } + } +} diff --git a/Assets/PerfectWorld/Scripts/Move/AutoPFImp/AutoMove/PathNode.cs.meta b/Assets/PerfectWorld/Scripts/Move/AutoPFImp/AutoMove/PathNode.cs.meta new file mode 100644 index 0000000000..0cf163da7d --- /dev/null +++ b/Assets/PerfectWorld/Scripts/Move/AutoPFImp/AutoMove/PathNode.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 98c1e0dc325b8e64bbda287a0128aeed \ No newline at end of file diff --git a/Assets/PerfectWorld/Scripts/Move/CECIntelligentRoute.cs b/Assets/PerfectWorld/Scripts/Move/CECIntelligentRoute.cs index e9d1e10c32..a772cd9a43 100644 --- a/Assets/PerfectWorld/Scripts/Move/CECIntelligentRoute.cs +++ b/Assets/PerfectWorld/Scripts/Move/CECIntelligentRoute.cs @@ -19,7 +19,7 @@ namespace BrewMonster.Scripts /// public sealed class CECIntelligentRoute { - private const bool DEBUG_AUTOPF = true; + private const bool DEBUG_AUTOPF = false; /// cos(5°) for FindFarthestNode — same as C++ EC_IntelligentRoute.cpp. private static readonly float s_cos5Deg = Mathf.Cos(5f * Mathf.PI / 180f); public enum SearchResult @@ -376,9 +376,22 @@ namespace BrewMonster.Scripts // Determine layers (minimal: layer0 or invalid). // 确定层(最小实现:层0或无效)。 + float startDh = 0f; + float endDh = 0f; + var world = EC_Game.GetGameRun()?.GetWorld(); + if (world != null) + { + A3DVECTOR3 n = GPDataTypeHelper.g_vAxisY; + if (world.TryGetTerrainHeight(start, ref n, out float startTer)) + startDh = start.y - startTer; + n = GPDataTypeHelper.g_vAxisY; + if (world.TryGetTerrainHeight(end, ref n, out float endTer)) + endDh = end.y - endTer; + } + float dist; - int startLayer = agent.WhichLayer(new Vector3(start.x, start.y, start.z), 0.0f, out dist); - int endLayer = agent.WhichLayer(new Vector3(end.x, end.y, end.z), 0.0f, out dist); + int startLayer = agent.WhichLayer(new Vector3(start.x, start.y, start.z), startDh, out dist); + int endLayer = agent.WhichLayer(new Vector3(end.x, end.y, end.z), endDh, out dist); if (startLayer < 0) startLayer = 0; if (endLayer < 0) endLayer = 0; @@ -466,7 +479,8 @@ namespace BrewMonster.Scripts m_iCurDest = FindNextNode(pos, m_iCurDest + 1); m_dist2CurDest = (GetCurDest() - pos).MagnitudeH(); - // C++ calls agent->Optimize(m_iCurDest) here; Unity CMoveAgent has no path optimizer yet. + agent.Optimize(m_iCurDest); + m_dist2CurDest = (GetCurDest() - pos).MagnitudeH(); } A3DVECTOR3 GetNodePos(int iNode) @@ -489,17 +503,26 @@ namespace BrewMonster.Scripts if (agent == null) return new A3DVECTOR3(0f, 0f, 0f); Vector3 v = agent.Get3DPathNode(iNode); - // C++ adds GetTerrainHeight to Y; Unity path nodes use movemap/world height from loader. - return new A3DVECTOR3(v.x, v.y, v.z); + var pos = new A3DVECTOR3(v.x, v.y, v.z); + var world = EC_Game.GetGameRun()?.GetWorld(); + if (world != null) + { + A3DVECTOR3 n = GPDataTypeHelper.g_vAxisY; + if (world.TryGetTerrainHeight(pos, ref n, out float terY)) + pos.y = terY; + } + return pos; } A3DVECTOR3 GetNodePosXZ(int iNode) { if (IsIdle()) return new A3DVECTOR3(0f, 0f, 0f); - A3DVECTOR3 p = GetNodePosNoCheck(iNode); - p.y = 0f; - return p; + RangedMoveAgent? pCurAgent = GetCurAgent(); + if (pCurAgent == null || pCurAgent.Value.agent == null) + return new A3DVECTOR3(0f, 0f, 0f); + Vector3 v = pCurAgent.Value.agent.Get2DPathNode(iNode); + return new A3DVECTOR3(v.x, v.y, v.z); } int FindNearestNode(A3DVECTOR3 curPos, int iNodeFrom) diff --git a/Assets/PerfectWorld/Scripts/NPC/CECModel.cs b/Assets/PerfectWorld/Scripts/NPC/CECModel.cs index 1d715caad5..e797a8c2ca 100644 --- a/Assets/PerfectWorld/Scripts/NPC/CECModel.cs +++ b/Assets/PerfectWorld/Scripts/NPC/CECModel.cs @@ -1366,7 +1366,14 @@ public class CECModel { return false; } - + if(m_skeletonBuilder == null ) + { + return false; + } + if(pChild.m_skeletonBuilder == null) + { + return false; + } Transform hook = m_skeletonBuilder.GetHook(szHookName, true); Transform hangger = pChild.m_skeletonBuilder.GetHook(szCCName, true); if (hook == null || hangger == null) diff --git a/Assets/PerfectWorld/Scripts/Players/CECHostNavigatePlayer.cs b/Assets/PerfectWorld/Scripts/Players/CECHostNavigatePlayer.cs index 09b7c9f058..c6748bff39 100644 --- a/Assets/PerfectWorld/Scripts/Players/CECHostNavigatePlayer.cs +++ b/Assets/PerfectWorld/Scripts/Players/CECHostNavigatePlayer.cs @@ -193,7 +193,7 @@ namespace BrewMonster.Scripts m_pNavigateModel = new CECModel(); m_pNavigateModel.m_pPlayerModel = instance; SkeletonBuilder skeletonBuilder = instance.GetComponentInChildren(true); - NamedAnimancerComponent animancer = instance.GetComponent(); + NamedAnimancerComponent animancer = instance.GetComponentInChildren(); if (skeletonBuilder != null && animancer != null) { @@ -214,7 +214,6 @@ namespace BrewMonster.Scripts CameraController.Instance.UpdateFollowObject(m_pNavigateModel.transform); TickInvoker.Instance.RegisterTickable(this); // Clone host player model/equipment // 克隆宿主玩家模型与装备 - BMLogger.Log($"CECHostNavigatePlayer::InitAsync, debugStep: {debugStep++}"); bool loaded = await LoadFrom(m_pHostPlayer, false); if (loaded) SetupNavigatePlayerVisual(); @@ -278,6 +277,7 @@ namespace BrewMonster.Scripts base.Tick(dwDeltatime); if (!m_bNavigateModelApplied /*&& IsAllResReady()*/) { + //TOdo: implement IsAllresReady ApplyNavigateModel(); // if(!IsShapeChanged()) // ApplyNavigateModel(); @@ -365,8 +365,11 @@ namespace BrewMonster.Scripts private bool ApplyNavigateModel() { - // if ( !GetMajorModel() || m_pNavigateModel == null || m_bNavigateModelApplied) - // return false; + if ( !GetMajorModel() || m_pNavigateModel == null || m_bNavigateModelApplied) + { + BMLogger.Log($"GetMajorModel(){GetMajorModel() == true } m_NavigateMode {m_pNavigateModel == null} m_bNavigateModelApplied {m_bNavigateModelApplied} "); + return false; + } A3DVECTOR3 vCurPos = GetPos(); diff --git a/Assets/PerfectWorld/Scripts/World/CECWorld.cs b/Assets/PerfectWorld/Scripts/World/CECWorld.cs index a929005c48..a7293d0eb6 100644 --- a/Assets/PerfectWorld/Scripts/World/CECWorld.cs +++ b/Assets/PerfectWorld/Scripts/World/CECWorld.cs @@ -32,9 +32,25 @@ namespace BrewMonster.Scripts.World return m_pOnmtMan; } public float GetTerrainHeight(A3DVECTOR3 vPos, ref A3DVECTOR3 pvNormal /* NULL */) + { + A3DVECTOR3 normal = pvNormal; + if (!TryGetTerrainHeight(vPos, ref normal, out float height)) + return vPos.y; + pvNormal = normal; + return height; + } + + /// False when terrain is not loaded yet (e.g. during early AutoPF Search). + public bool TryGetTerrainHeight(A3DVECTOR3 vPos, ref A3DVECTOR3 pvNormal, out float height) { A3DTerrain2 pTerrain = GetTerrain(); - return pTerrain.GetPosHeight(vPos, ref pvNormal); + if (pTerrain == null) + { + height = vPos.y; + return false; + } + height = pTerrain.GetPosHeight(vPos, ref pvNormal); + return true; } public float GetWaterHeight(A3DVECTOR3 vPos)