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

SFML 3(C++)多层横版游戏碰撞异常及优化方案咨询

SFML 3 横版游戏:移动卡顿与碰撞异常修复方案

问题概述

使用SFML 3和C++开发多层横版卷轴游戏,开发初期遇到两个核心问题:

  • 角色移动时视觉卡顿
  • 层碰撞异常:仅顶部、左侧碰撞生效,底部、右侧碰撞完全失效
    已尝试用占位碰撞盒+角色Sprite模拟Unity Collider,甚至将Layer1的Sprite裁剪为正方形,问题仍未解决。

问题根源分析

1. 移动卡顿原因

  • Camera类设计错误:Camera内部维护了独立的sf::RenderWindow实例,和Window类的渲染窗口不是同一个对象,调用cameraFollow时设置的View完全无效,导致相机跟随逻辑失效,视觉上出现卡顿。
  • 重复创建层实例:Game::collisionHit方法每次调用都会新建SIDESCROLLINGBACKGROUND_LAYER1对象,重复加载纹理,造成不必要的性能开销,引发卡顿。

2. 碰撞异常原因

  • 碰撞逻辑完全错误:当前用contains检测点是否在矩形内,而非矩形相交检测,无法覆盖所有碰撞情况。
  • 混淆Local/Global Bounds:使用Layer1.getLocalBounds()(未考虑缩放、位置的局部边界)进行碰撞判断,导致坐标计算错误。
  • 碰撞返回值逻辑颠倒:collisionHit返回true时禁止移动,但当前碰撞函数的返回逻辑和实际需求完全相反。
  • 未做轴分离处理:同时处理X/Y轴移动,导致碰撞检测时无法精准判断单侧碰撞。

解决方案

一、修复移动卡顿

1. 重构Camera类

移除Camera内部的sf::RenderWindow,改为返回sf::View给Window类设置:

// Camera.hpp
#ifndef CAMERA_H
#define CAMERA_H

#include <SFML/Graphics.hpp>
#include "Game.hpp"

class Camera{
public:
    sf::View cameraFollow(const Game& game);
    Camera();
    ~Camera();

private:
    sf::View view;
};

#endif
// Camera.cpp
#include "Camera.hpp"

sf::View Camera::cameraFollow(const Game& game){
    sf::Vector2f playerPos = game.placeHolderPlayer.getPosition();
    view.setCenter(playerPos);
    view.setSize(sf::Vector2f(sf::VideoMode::getDesktopMode().size));
    return view;
}

Camera::Camera(){}
Camera::~Camera(){}

2. 全局复用层实例

在Window的run方法中创建一次SIDESCROLLINGBACKGROUND_LAYER1,通过参数传递给Game的碰撞检测方法,避免重复加载纹理:

// 修改Game::collisionHit方法,接收层实例引用
bool Game::collisionHit(float dt, sf::Vector2f dir, const SIDESCROLLINGBACKGROUND_LAYER1& layer1);

// Window::run中调用
game.handleInput(dt, Layer1);

二、修复碰撞异常(改用AABB轴对齐碰撞)

使用SFML自带的sf::FloatRect::intersects方法实现标准AABB碰撞检测,分X/Y轴单独处理,确保各方向碰撞都能生效。

1. 修正Game的碰撞处理逻辑

// Game.hpp 修改handleInput和collisionHit的参数
void handleInput(float dt, const SIDESCROLLINGBACKGROUND_LAYER1& layer1);
bool collisionHit(float dt, sf::Vector2f dir, const SIDESCROLLINGBACKGROUND_LAYER1& layer1);

// Game.cpp
void Game::handleInput(float dt, const SIDESCROLLINGBACKGROUND_LAYER1& layer1){
    sf::Vector2f dir(0.f, 0.f);

    if (sf::Keyboard::isKeyPressed(sf::Keyboard::W)) dir.y -= 1.f;
    if (sf::Keyboard::isKeyPressed(sf::Keyboard::S)) dir.y += 1.f;
    if (sf::Keyboard::isKeyPressed(sf::Keyboard::A)) dir.x -= 1.f;
    if (sf::Keyboard::isKeyPressed(sf::Keyboard::D)) dir.x += 1.f;

    if (dir.x != 0.f || dir.y != 0.f) {
        dir = dir.normalized();
    }

    // 分X/Y轴单独检测碰撞,避免同时移动导致的穿透
    sf::Vector2f moveAmount = dir * moveSpeed * dt;

    // 先检测X轴碰撞
    sf::Vector2f tempPos = placeHolderPlayer.getPosition();
    placeHolderPlayer.move(moveAmount.x, 0.f);
    if (collisionHit(dt, {moveAmount.x, 0.f}, layer1)) {
        // 碰撞发生,回退X轴位置
        placeHolderPlayer.setPosition(tempPos);
    } else {
        // X轴无碰撞,更新Sprite位置
        playerSprite.move(moveAmount.x, 0.f);
    }

    // 再检测Y轴碰撞
    tempPos = placeHolderPlayer.getPosition();
    placeHolderPlayer.move(0.f, moveAmount.y);
    if (collisionHit(dt, {0.f, moveAmount.y}, layer1)) {
        // 碰撞发生,回退Y轴位置
        placeHolderPlayer.setPosition(tempPos);
    } else {
        // Y轴无碰撞,更新Sprite位置
        playerSprite.move(0.f, moveAmount.y);
    }
}

bool Game::collisionHit(float dt, sf::Vector2f dir, const SIDESCROLLINGBACKGROUND_LAYER1& layer1){
    // 获取玩家碰撞盒的全局边界
    sf::FloatRect playerBounds = placeHolderPlayer.getGlobalBounds();
    // 计算移动后的玩家碰撞盒
    sf::FloatRect playerNextBounds = playerBounds;
    playerNextBounds.left += dir.x;
    playerNextBounds.top += dir.y;

    // 获取层的全局碰撞盒
    sf::FloatRect layerBounds = layer1.Layer1.getGlobalBounds();

    // 检测两个矩形是否相交
    return playerNextBounds.intersects(layerBounds);
}

2. 移除SIDESCROLLINGBACKGROUND_LAYER1中的无效碰撞逻辑

// SidescrollingBackground_Layer1.cpp 删除原collision方法,层类只负责渲染和存储自身Sprite

3. 修正Window::run中的调用逻辑

void Window::run() {
    Camera camera;
    SIDESCROLLINGBACKGROUND_LAYER1 Layer1;
    Game game;
    sf::Clock clock;

    while (renderer.isOpen()) {
        // 获取相机View并设置
        sf::View gameView = camera.cameraFollow(game);
        renderer.setView(gameView);

        float dt = clock.restart().asSeconds();

        // 事件处理
        while (const std::optional<sf::Event> event = renderer.pollEvent()) {
            if (event->is<sf::Event::Closed>()) {
                renderer.close();
            }
            if (const sf::Event::KeyPressed* keyPress = event->getIf<sf::Event::KeyPressed>()) {
                if (keyPress->code == sf::Keyboard::Escape) {
                    renderer.close();
                }
            }
        }

        update();
        // 传入Layer1实例给handleInput
        game.handleInput(dt, Layer1);

        renderer.clear(sf::Color::Blue);
        Layer1.render(renderer);
        game.render(renderer);
        renderer.display();
    }
}

额外优化建议

  • 给placeHolderPlayer设置半透明填充色,方便调试碰撞盒位置:placeHolderPlayer.setFillColor(sf::Color(255,0,0,100));,并在Game::render中绘制它
  • 将所有资源加载逻辑统一放到初始化阶段,避免运行时重复加载
  • 限制dt的最大值(比如dt = std::min(dt, 0.016f);),防止帧率骤降时移动幅度过大导致碰撞穿透

内容的提问来源于stack exchange,提问作者Parker Bladh

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.01 13:22:29