You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Raylib C++ Tilemap碰撞异常问题求助

Tilemap碰撞问题修复方案

核心问题分析

你的代码里有几个关键逻辑错误,直接导致碰撞不稳定:

  • Tile坐标搞反了:循环里i遍历行数(y方向)、j遍历列数(x方向),但创建Tile时把x设成i*32、y设成j*32,整个地图行列颠倒,碰撞检测位置完全错位。
  • 碰撞检测顺序错误:先移动角色再检测碰撞,碰到障碍再拉回位置。这种"先动后改"的逻辑在速度快时,角色步长可能超过Tile宽度,直接穿过障碍。
  • 循环break不彻底:检测到碰撞时只跳出内层j循环,外层i循环还会继续,可能覆盖之前的碰撞判断。
  • 按键逻辑冗余:用UP/DOWN这类一次性布尔值控制移动,按住按键时每帧重复触发,导致移动卡顿或判断混乱。

修复步骤

  1. 修正Tile坐标
    把Tile的x/y对应正确:列(j)对应x轴,行(i)对应y轴,改成Rectangle floor{ j * 32, i * 32, 32, 32 };

  2. 重构碰撞检测逻辑
    改成"预判移动位置→检测碰撞→决定是否移动"的流程:

    • 先计算移动后的角色碰撞矩形
    • 检测预判矩形是否和障碍Tile碰撞
    • 无碰撞才执行移动,彻底避免穿模
  3. 统一碰撞矩形
    提前定义角色碰撞矩形,避免每次移动重复创建,保证检测一致性。

  4. 优化按键处理
    直接用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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.21 14:05:56