碰撞体积偏移了

05/10攻略
现在我明确了:
row/col 是建筑的左上角(1-based 网格坐标)
渲染锚点在建筑矩形的底边中心:cx = (col-1 + sizeW*0.5) * ts, cy = (row-1 + sizeH) * ts
碰撞标记当前是从 (row, col) 到 (row+sizeH-1, col+sizeW-1) — 即整个建筑占用的网格区域
现在让我深入理解碰撞偏移的根因。问题的核心是:在等距视角下,角色碰撞坐标和建筑碰撞网格之间的映射是否对齐。让我看看角色的碰撞坐标是怎么转换到网格的。
现在我理解了完整的坐标映射关系。让我分析问题的根因:
角色位置:worldX = (col-1)*ts + ts*0.5, worldY = (row-1)*ts + ts*0.5 — 这是格子中心。
碰撞检测 toGrid:c = math.floor(px / ts) + 1, r = math.floor(py / ts) + 1 — 这将内容坐标转回网格行列。
建筑碰撞标记:b.row 到 b.row + sizeH - 1,b.col 到 b.col + sizeW - 1 — 直接用建筑的 row/col(左上角)。
这个逻辑在内容坐标系中是完全正确的。碰撞应该在正确的位置。
问题可能出在角色位置本身和渲染位置之间的偏差。让我再看看角色渲染和建筑渲染的对比。
关键点是:用户看到的是屏幕上的效果。在屏幕上:
建筑图像通过 counter-rotation 显示为直立
角色通过 counter-rotation 也显示为直立
但建筑的视觉占地面积和网格碰撞区域在屏幕上的投影可能不一致
让我仔细看建筑图像的 drawSize 计算和实际网格尺寸的对比。
现在我理解了完整的画面。让我仔细分析偏移原因。
关键发现:建筑渲染有两个部分:
建筑图像:以 (cx, cy) 即底边中心为锚点,经过 counter-rotation 后直立绘制
网格轮廓线:直接在 (x, y, w, h) 内容空间画矩形 — 没有经过 counter-rotation
这意味着网格轮廓线在等距视角下显示为菱形(钻石形),而建筑图像显示为直立的矩形。这两者在视觉上是不对齐的。
碰撞的核心问题:角色的 worldX/worldY 是格子中心的内容坐标。碰撞检测使用 math.floor(px / ts) + 1 转换回网格坐标。这个映射是正确的——在内容坐标系中,碰撞和建筑的网格占用是对齐的。
但用户看到的偏移是因为:建筑图片是 counter-rotated 后直立显示的,占据的屏幕空间与碰撞网格在屏幕上的投影不一致。
建筑图片用了 drawSize = min(w, h) * 1.4,比实际网格大 40%。加上 counter-rotation,在 45° 视角下:
建筑图片的视觉中心偏离了网格碰撞区域的视觉中心
网格菱形的某一侧有空碰撞区域,另一侧建筑图片溢出碰撞区域
这是一个视觉 vs 逻辑的错位。解决方案不是调整碰撞逻辑(碰撞逻辑在内容空间是正确的),而是需要让碰撞感觉上与视觉建筑位置一致。
最简单的方法是:在 ResolveMove 中,对建筑的碰撞检测增加一个偏移量,让碰撞区域在视觉上对齐建筑图片。
让我思考具体的偏移方向和大小。在 45° 旋转的等距视角下:
内容空间的 (+X, 0) 方向在屏幕上指向右下
内容空间的 (0, +Y) 方向在屏幕上指向左下
建筑锚点在网格底边中心 (x + w*0.5, y + h)
建筑图片从锚点向上延伸(counter-rotation后直立)
用户说碰撞偏右。在屏幕上偏右,对应内容空间中碰撞区域相对视觉建筑偏向 +X 方向或 -Y 方向。
实际上,更好的方法是理解建筑图片的视觉占地和网格碰撞区域的视觉投影之间的差异。但这个差异是由旋转角度决定的,旋转角度是可变的(用户可以旋转地图)。
更好的方案:不在网格碰撞中修正偏移,而是将碰撞从"网格格子"改为"距离建筑锚点"的方式。但这会改变整个碰撞系统的设计。
让我重新思考。实际上,树木也使用同样的 counter-rotation 渲染,但树木的碰撞是单个格子,中心对齐,所以问题不明显。建筑是多格子(如 3x3),碰撞区域是一个矩形网格,而视觉建筑图片可能与这个矩形的位置不完全重合。
让我看看具体的建筑尺寸和图片比例关系。
好的,所有建筑都是 2x2 或 2x3 或 3x3。让我用 2x2 的建筑来具体分析偏移。
2x2 建筑,tileSize = 64:
网格范围(内容坐标):x = (col-1)*64, y = (row-1)*64, w = 128, h = 128
锚点:cx = x + 64, cy = y + 128 — 底边中心
drawSize = min(128, 128) * 1.4 = 179.2 — 建筑图片比网格大 40%
图片绘制范围(counter-rotated 坐标):(-89.6, -179.2) 到 (89.6, 0) — 以锚点为原点
建筑图片的视觉中心在 counter-rotated 坐标的 (0, -89.6) 处,即锚点上方 89.6 像素。
而网格碰撞区域的中心在内容坐标 (x + 64, y + 64) = (cx, cy - 64)。锚点在 (cx, cy) = (cx, y + 128)。所以网格中心在锚点上方 64 像素。
建筑图片的视觉中心在锚点上方 89.6,网格碰撞中心在锚点上方 64。两者在垂直方向上差了 25.6 像素(在 counter-rotated 空间中)。
但这不是主要问题。主要问题是水平方向的偏移。
让我重新思考。在 45° 旋转的等距视角中:
网格碰撞区域在内容空间是一个正方形,从 (x, y) 到 (x+128, y+128)。在等距投影下,这个正方形变成一个菱形。
建筑图片通过 counter-rotation 在屏幕上显示为直立矩形,锚定在菱形的底部顶点(因为 cy = y + h 是网格底边,对应菱形的底部顶点)。
所以建筑图片从菱形底部顶点直直向上延伸,而碰撞菱形的宽度对称分布在底部顶点两侧。
在 45° 视角下:
菱形的底部顶点就是 (cx, cy) 的屏幕位置
菱形向左扩展到 (x, y+h) 的屏幕位置 — 这对应菱形的左顶点
菱形向右扩展到 (x+w, y+h) — 不对,让我重新画
等一下,我需要用具体的数学来计算。45° 旋转 + Y 轴 0.5 压缩:
对于一个在内容空间 (px, py) 的点,屏幕坐标为:
screenX = px * cos(45°) - py * sin(45°)
screenY = (px * sin(45°) + py * cos(45°)) * 0.5
让我计算一个 2x2 建筑(col=5, row=5, tileSize=64):
网格四角内容坐标:
左上 (256, 256)
右上 (384, 256)
右下 (384, 384)
左下 (256, 384)
锚点(底边中心):(320, 384)
用 cos(45°) = sin(45°) = 0.707:
左上 → screen: (0, 181)
右上 → screen: (90.5, 226.3)
右下 → screen: (0, 271.5)
左下 → screen: (-90.5, 226.3)
锚点 → screen: (-45.2, 248.9)
所以菱形是上下对称的,锚点不在菱形底部!锚点在底边中心的位置经过旋转后不在菱形的顶点,而是在菱形的左下边的中点。
这就是问题!建筑锚点 (cx, cy) = (x + w*0.5, y + h) 在内容空间中是底边中心,但经过 45° 旋转和 Y 压缩后,它映射到菱形的左边偏下的位置。建筑图片从这个位置直直向上延伸(因为 counter-rotation),所以视觉上建筑偏向左侧。
这意味着用户看到的效果是:建筑图片偏左,碰撞区域偏右 — 与用户反馈完全一致!
解决方案:需要将碰撞检查的参考点从建筑的网格中心调整为建筑图片的视觉中心。
但最实用的修复方式是:给碰撞加一个与旋转角度相关的偏移。不过旋转角度是变化的,这意味着碰撞偏移也需要随旋转改变。
更好的方案是:不使用纯网格碰撞,而是改用基于建筑锚点的距离碰撞。或者,最简单的方案是:将角色的碰撞坐标从 worldX/worldY 改为使用角色的视觉锚点。
等等,实际上角色也经过了同样的 counter-rotation 渲染。角色的 worldX/worldY 也会在屏幕上映射到菱形内的某个位置。碰撞检查是在内容坐标系中做的,所有对象在内容坐标系中应该是对齐的。
让我重新思考…
真正的问题:碰撞在内容坐标系中是正确的。但用户看到的"穿墙"其实是建筑图片溢出了网格范围(因为 drawSize = min(w,h) * 1.4,图片比网格大 40%),加上 counter-rotation 后图片向特定方向溢出。所以角色可以走在看起来是建筑一部分的区域,但这些区域在碰撞网格之外。
对于 2x2 建筑(w = h = 128),drawSize = 179.2,比网格大出 51.2 像素。图片以锚点(底边中心)为基准,向上延伸 179.2 像素,向左右各延伸 89.6 像素。
而网格只有 128 像素宽(向锚点左右各 64 像素)。所以图片在水平方向上超出网格 89.6 - 64 = 25.6 像素每侧。
但 counter-rotation 后,这个超出是在屏幕空间的,不是内容空间。在等距视角下,屏幕左右对应的内容方向是斜的,所以超出部分的"投影"可能在一个方向上更明显。
让我想一个更实际的解决方案。用户需要碰撞和视觉对齐。最简单的方法是:扩大碰撞区域,让碰撞覆盖建筑图片的视觉范围。由于图片比网格大 40%,可以在建筑周围额外标记一圈格子。
但这可能太保守了。更好的方法是:减小建筑图片的 drawSize 或 扩大碰撞判定范围。
让我回头看看用户的截图描述。用户说在 45° 视角下:
右侧有一大块空碰撞区域
左侧可以穿过建筑
这个不对称说明问题不仅仅是图片比网格大。让我更仔细地计算。
在 45° 旋转中:
角色在网格中心 (worldX, worldY) → 碰撞检测 toGrid 得到 (row, col)
建筑在 (b.row, b.col) 到 (b.row+sizeH-1, b.col+sizeW-1) 标记为 blocked
如果角色在建筑的正左方(内容空间),角色的 col = b.col - 1,不会触碰碰撞。在屏幕上,内容空间的"正左方"对应 45° 旋转后的左上方。
如果角色在建筑的正下方(内容空间),角色的 row = b.row + sizeH,不会触碰碰撞。在屏幕上,内容空间的"正下方"对应 45° 旋转后的右下方。
没问题,内容空间的碰撞是对的! 问题一定是建筑图片溢出。
让我确认:用户说的"偏右"指的是碰撞区域在屏幕上相对建筑图片偏右。建筑图片通过 counter-rotation 在锚点位置直立显示。锚点位于 (cx, cy) 的屏幕位置。
在 45° 旋转下,(cx, cy) = (x + w*0.5, y + h) 在内容空间的底边中心。这个点在屏幕上映射到网格菱形的某个位置。
建筑图片从锚点向上画,视觉上覆盖的区域主要在锚点正上方(屏幕方向)。
而碰撞菱形的中心在内容空间的 (x + w*0.5, y + h*0.5) — 这在屏幕上位于锚点的右上方(因为 y 减小在 45° 视角下向右上移动)。
所以碰撞菱形的视觉中心在建筑图片的右上方,这就解释了为什么碰撞偏右!
修复方案:调整碰撞标记位置。对于每栋建筑,碰撞区域需要向"内容空间的 +Y 方向"偏移一些(这在屏幕上等效于向左偏移),使碰撞区域的视觉中心和建筑图片的视觉中心对齐。
但在网格碰撞系统中很难做精确的亚格偏移。更实用的方案是:
方案 A - 调整碰撞参考点:在 IsAreaBlocked 中对建筑碰撞增加偏移。但碰撞系统是通用的,不区分建筑和树。
方案 B - 扩大建筑碰撞区域:在建筑周围额外标记一格(尤其是在 Y+方向,即屏幕左侧方向),让碰撞区域更好地覆盖建筑图片。
方案 C - 角色碰撞位置偏移:在 ResolveMove 中,角色碰撞点相对于角色内容坐标做一个依赖旋转角度的偏移。
实际上让我重新分析。问题核心是:
建筑图片在 counter-rotation 后是直立的,它的视觉底部在锚点位置
锚点在内容空间的 (x + w/2, y + h),即网格底边中心
在 45° 旋转下,这个底边中心在屏幕上位于菱形的左下顶点附近
建筑图片从这里直直向上画,所以建筑主体在菱形的左侧
但碰撞是整个菱形,包括右侧空区域
所以修复方案应该是:让碰撞区域也偏向左侧。在内容空间中,"屏幕左侧"大约对应 +Y 方向。
让我试一个更简单的方案:直接在 _RenderOneBuilding 中调整锚点到网格中心而非底边中心,让图片以网格中心为锚点画。这样建筑图片和碰撞区域的视觉中心就对齐了。
但是这样会影响深度排序(depth sort 基于 footY)。不过碰撞和渲染可以分别处理。
实际上,最直接的修复方式是:不改渲染逻辑,而是让角色的碰撞判定点从 (worldX, worldY) 调整一下。
等等,我觉得我过度分析了。让我回到代码里找一个最小侵入的修复。
实际上树木碰撞正常工作了(用户没有报告树木穿越问题),所以差异只在建筑。树木是 1x1 格子,锚点在格子中心。建筑是 NxM 格子,锚点在底边中心。
让我换个思路。问题可能是 drawSize 的 1.4 倍缩放导致建筑图片明显偏离碰撞区域。让我检查这个 1.4 倍是否正确地让图片对齐碰撞区域。
对于 2x2 建筑,网格是 128x128。建筑图片 drawSize = 179.2(1.4倍)。
Counter-rotation 后,图片以锚点(底边中心)为基准:
图片宽 179.2,左右各 89.6
图片高 179.2,全部在锚点上方
但网格在内容空间中锚点的位置是底边中心。网格宽 128,在内容空间中锚点左右各 64。图片超出 25.6 每侧。
图片高 179.2,全部向上。网格在锚点上方 128(= h)。图片超出 51.2 向上。
这些超出在 counter-rotation 后是"屏幕方向"的超出。而碰撞在内容空间中不超出。所以在视觉上,建筑图片总是比碰撞区域大,任何角度都有差异。
最佳修复方案:
给碰撞检测增加一个视觉补偿偏移。在 ResolveMove 中,将角色坐标转换到网格前,加一个与地图旋转角度相关的偏移。这样碰撞判定点与角色视觉位置同步。
但碰撞需要传入旋转角度……不太优雅。
更好的方案:扩大建筑碰撞区域,在建筑网格周围额外加一层碰撞缓冲。这样即使图片溢出,角色也无法走到溢出区域。
2
9