Raycaster远离(0,0)点时墙体检测异常问题求助
射线投射器大地图穿墙问题排查与修复
问题现象
- 16×16瓦片、64×64尺寸的地图下,射线投射器运行正常;切换为60×60尺寸地图后,部分墙体无法被射线检测到。
- 玩家位于坐标
(2013.69,2043.34)时,射线可正常检测墙体;移动至(2124.59,2215.76)后,射线出现穿墙现象。 - 尝试通过让玩家静止、滚动地图的方式缓解,但画面流畅度差。
核心原因
问题源于浮点数大数计算精度丢失:
float类型仅支持6-7位十进制有效精度,当玩家坐标达到2000+时,小数部分精度被稀释,导致射线坐标、瓦片索引计算出现偏差。- 射线步进的浮点累加误差随距离增大而放大,射线位置偏离实际应检测的墙体瓦片。
- 原代码中瓦片索引的边界判断存在漏洞,可能跳过合法瓦片检测或访问越界内存。
代码问题分析
核心代码片段及问题点:
void castRay(Map* &mp) { int map_x, map_y, map_point, dof; // 问题1:float精度不足,无法准确表示大数坐标 float ray_x, ray_y, ray_angle, ray_offset_x, ray_offset_y; float v_distance = 1e9, h_distance = 1e9, wall_distance; float vx, vy, hx, hy; int tile_size = mp->tileSize; ray_angle = player_angle-30*D; if(ray_angle > 2*PI) ray_angle -= 2*PI; else if(ray_angle < 0) ray_angle += 2*PI; for(int r = 0; r < 60; r++) { // Checking horizontal line float aTan = -1/tan(ray_angle); dof = 0; if(ray_angle > PI) { // 问题2:大数下的整数转换会丢失精度,导致初始射线位置错误 ray_y = (((int)center_y/mp->tileSize)*mp->tileSize)-0.0001; ray_x = center_x + (center_y-ray_y)*aTan; ray_offset_y = -mp->tileSize; ray_offset_x = -ray_offset_y*aTan; } // ... 其余射线初始化代码 while(dof < max(mp->Map_Width,mp->Map_Height)) { map_x = (int)ray_x/mp->tileSize; map_y = (int)ray_y/mp->tileSize; map_point = map_y*mp->Map_Width+map_x; hx = ray_x; hy = ray_y; h_distance = sqrt((ray_x-center_x)*(ray_x-center_x)+(ray_y-center_y)*(ray_y-center_y)); // 问题3:边界判断不严谨,未检查map_x/map_y是否在地图合法范围内 if(map_point > 0 && map_point < mp->Map_Size && mp->Map_Tilemap[map_point] == 2) { break; } else { ray_x += ray_offset_x; ray_y += ray_offset_y; dof++; } } // ... 垂直射线检测代码(存在同样的精度和边界问题) // ... 3D渲染代码 } }
修复方案
1. 提升计算精度
将所有存储坐标、角度的float替换为double,利用其15-17位的十进制有效精度,缓解大数下的精度丢失:
// 替换所有float为double double ray_x, ray_y, ray_angle, ray_offset_x, ray_offset_y; double v_distance = 1e9, h_distance = 1e9, wall_distance; double vx, vy, hx, hy;
2. 修正边界判断逻辑
直接检查map_x和map_y的合法性,避免越界和错误检测:
// 替换原if判断 if(map_x >= 0 && map_x < mp->Map_Width && map_y >= 0 && map_y < mp->Map_Height && mp->Map_Tilemap[map_point] == 2) { break; }
3. 优化地图坐标体系(根治方案)
放弃玩家坐标无限增大的设计,改为玩家静止、地图滚动模式:
- 玩家始终保持在屏幕中心对应的局部坐标(比如
(tile_size*32, tile_size*32))。 - 维护地图偏移量
map_offset_x、map_offset_y,根据玩家移动方向更新。 - 计算射线时,用局部坐标加偏移量获取全局瓦片索引,确保所有计算坐标处于小数值范围,彻底避免大数精度问题。
4. 优化射线步进计算
提前缓存三角函数值,减少重复计算;用整数运算辅助瓦片定位,降低浮点累加误差。
内容的提问来源于stack exchange,提问作者Duong Tran
相关产品推荐
相关产品推荐

