Raylib C++ Tilemap碰撞异常问题求助
Tilemap碰撞问题修复方案
核心问题分析
你的代码里有几个关键逻辑错误,直接导致碰撞不稳定:
- Tile坐标搞反了:循环里
i遍历行数(y方向)、j遍历列数(x方向),但创建Tile时把x设成i*32、y设成j*32,整个地图行列颠倒,碰撞检测位置完全错位。 - 碰撞检测顺序错误:先移动角色再检测碰撞,碰到障碍再拉回位置。这种"先动后改"的逻辑在速度快时,角色步长可能超过Tile宽度,直接穿过障碍。
- 循环break不彻底:检测到碰撞时只跳出内层
j循环,外层i循环还会继续,可能覆盖之前的碰撞判断。 - 按键逻辑冗余:用
UP/DOWN这类一次性布尔值控制移动,按住按键时每帧重复触发,导致移动卡顿或判断混乱。
修复步骤
修正Tile坐标
把Tile的x/y对应正确:列(j)对应x轴,行(i)对应y轴,改成Rectangle floor{ j * 32, i * 32, 32, 32 };重构碰撞检测逻辑
改成"预判移动位置→检测碰撞→决定是否移动"的流程:- 先计算移动后的角色碰撞矩形
- 检测预判矩形是否和障碍Tile碰撞
- 无碰撞才执行移动,彻底避免穿模
统一碰撞矩形
提前定义角色碰撞矩形,避免每次移动重复创建,保证检测一致性。优化按键处理
直接用IsKeyDown每帧判断移动状态,去掉冗余的一次性布尔值,让移动更流畅。
完整修复代码
#include<iostream> #include<raylib.h> struct AnimData { Rectangle rec; Vector2 pos; int frame; float updateTime; float runningTime; }; const int sWidth = 10; // 列数(x方向) const int sHeight = 7; // 行数(y方向) // 封装碰撞检测函数,减少重复代码 bool CheckCollisionWithWalls(Rectangle playerRec) { extern Rectangle rec[sWidth][sHeight]; extern int type[sWidth][sHeight]; for (int i = 0; i < sHeight; i++) { for (int j = 0; j < sWidth; j++) { if (type[j][i] == 1 && CheckCollisionRecs(playerRec, rec[j][i])) { return true; } } } return false; } int main() { InitWindow(800, 400, "My first game"); Rectangle rec[sWidth][sHeight]; int type[sWidth][sHeight] = { {1,1,1,1,1,1,1,1,1,1}, {1,0,0,0,0,1,0,0,1,0}, {1,0,0,0,0,0,0,0,0,0}, {1,0,0,0,0,0,0,0,0,0}, {1,0,0,0,0,0,0,0,0,0}, {1,0,0,0,0,0,0,0,0,0}, {1,0,0,0,0,0,0,0,0,0} }; // 修正Tile坐标:j对应x轴,i对应y轴 for (int i = 0; i < sHeight; i++) { for (int j = 0; j < sWidth; j++) { Rectangle floor{ j * 32, i * 32, 32, 32 }; rec[j][i] = floor; } } int playerSpeed = 128; bool isMovingUp = false, isMovingDown = false, isMovingLeft = false, isMovingRight = false; Image playerImg = LoadImage("Cat 01-1.png"); Texture2D playerTex = LoadTextureFromImage(playerImg); AnimData playerData{ {200.0, 125.0, playerImg.width / 3, playerImg.height / 4}, {200, 125}, 0, 1.0f / 9.0f, 0 }; // 统一角色碰撞矩形,避免重复创建 Rectangle playerCollRec = { playerData.pos.x, playerData.pos.y, playerImg.width / 3, playerImg.height / 4 }; bool isShoot = false; Rectangle bulletRec{ playerData.pos.x, playerData.pos.y, 4.f, 4.f }; Vector2 cameraOffset{ 800 / 2.0f, 400 / 2.0f }; Camera2D camera = { 0 }; camera.target = { playerData.pos.x + playerCollRec.width/2, playerData.pos.y + playerCollRec.height/2 }; camera.offset = cameraOffset; camera.rotation = 0.0f; camera.zoom = 1.0f; SetTargetFPS(60); while (!WindowShouldClose()) { const float dt = GetFrameTime(); BeginDrawing(); ClearBackground(BLACK); // 每帧更新移动状态 isMovingUp = IsKeyDown(KEY_W); isMovingDown = IsKeyDown(KEY_S); isMovingLeft = IsKeyDown(KEY_A); isMovingRight = IsKeyDown(KEY_D); // 静止状态重置动画帧 if (!isMovingUp && !isMovingDown && !isMovingLeft && !isMovingRight) { playerData.rec.x = 32; } // 处理移动:先预判位置,再检测碰撞 if (isMovingUp) { playerData.rec.y = 96; Rectangle tempRec = playerCollRec; tempRec.y -= playerSpeed * dt; if (!CheckCollisionWithWalls(tempRec)) { playerData.pos.y -= playerSpeed * dt; playerCollRec.y = playerData.pos.y; } } if (isMovingDown) { playerData.rec.y = 0; Rectangle tempRec = playerCollRec; tempRec.y += playerSpeed * dt; if (!CheckCollisionWithWalls(tempRec)) { playerData.pos.y += playerSpeed * dt; playerCollRec.y = playerData.pos.y; } } if (isMovingLeft) { playerData.rec.y = 32; Rectangle tempRec = playerCollRec; tempRec.x -= playerSpeed * dt; if (!CheckCollisionWithWalls(tempRec)) { playerData.pos.x -= playerSpeed * dt; playerCollRec.x = playerData.pos.x; } } if (isMovingRight) { playerData.rec.y = 64; Rectangle tempRec = playerCollRec; tempRec.x += playerSpeed * dt; if (!CheckCollisionWithWalls(tempRec)) { playerData.pos.x += playerSpeed * dt; playerCollRec.x = playerData.pos.x; } } // 处理射击:用dt保证子弹速度稳定 if (IsKeyPressed(KEY_SPACE)) { isShoot = true; bulletRec.x = playerData.pos.x + playerCollRec.width/2; bulletRec.y = playerData.pos.y + playerCollRec.height/2; } if (isShoot) { bulletRec.x += 200 * dt; if (bulletRec.x > 800) isShoot = false; } // 更新动画帧 playerData.runningTime += dt; if (playerData.runningTime >= playerData.updateTime && (isMovingUp || isMovingDown || isMovingLeft || isMovingRight)) { playerData.runningTime = 0.0; playerData.rec.x = playerData.frame * playerData.rec.width; playerData.frame++; if (playerData.frame > 4) playerData.frame = 0; } // 更新相机目标,保持角色居中 camera.target = { playerData.pos.x + playerCollRec.width/2, playerData.pos.y + playerCollRec.height/2 }; BeginMode2D(camera); // 绘制Tilemap for (int i = 0; i < sHeight; i++) { for (int j = 0; j < sWidth; j++) { if (type[j][i] == 1) { DrawRectangle((int)rec[j][i].x, (int)rec[j][i].y, 32, 32, GRAY); } else { DrawRectangle((int)rec[j][i].x, (int)rec[j][i].y, 32, 32, DARKGREEN); } } } // 绘制角色和碰撞框 DrawTextureRec(playerTex, playerData.rec, playerData.pos, WHITE); DrawRectangleLines((int)playerCollRec.x, (int)playerCollRec.y, (int)playerCollRec.width, (int)playerCollRec.height, RED); DrawText(TextFormat("x %.1f , y %.1f", playerData.pos.x, playerData.pos.y), playerData.pos.x - 40, playerData.pos.y - 40, 16, WHITE); // 绘制子弹 if (isShoot) { DrawRectangle((int)bulletRec.x, (int)bulletRec.y, 4, 4, BLUE); } EndMode2D(); EndDrawing(); } // 释放资源,避免内存泄漏 UnloadTexture(playerTex); UnloadImage(playerImg); CloseWindow(); return 0; }
额外优化建议
- 给角色碰撞框加一点内缩(比如
playerCollRec.width -= 4),避免边缘贴墙时的卡顿感 - 可以把Tilemap数据放到外部文件(如TXT),方便编辑大地图
- 增加角色动画的方向判断,避免左右移动时动画反向
内容的提问来源于stack exchange,提问作者Chr2she
相关产品推荐
相关产品推荐

