Raylib纹理加载异常:玩家与Hazard均显示玩家纹理问题
问题:Hazard对象错误显示玩家纹理
程序运行时,玩家和Hazard对象均显示玩家纹理。尽管通过调试确认CreateHazards函数创建的Hazard对象使用了正确的Hazard纹理路径,但渲染结果仍不符合预期。
相关代码
#include <iostream> #include <raylib.h> #include <random> int getRandomInt(int start, int end) { std::random_device rd; std::mt19937 gen(rd()); std::uniform_int_distribution<> dis(start, end); return dis(gen); } class MovingObject { public: Texture2D Texture; Rectangle Geometry; Vector2 Position; float Velocity; char SpriteSheetCount; char CurrentSpriteNumber = 0; float UpdateTime{1.0 / 12.0}; int Frame{0}; float CurrentTime{0}; std::string TexturePath; MovingObject() { } MovingObject(const char *texturePath, float velocity, char spriteSheetCount) : Velocity(velocity), SpriteSheetCount(spriteSheetCount), TexturePath(texturePath) { Texture = LoadTexture(texturePath); } ~MovingObject() { UnloadTexture(Texture); } void SetSprite(char number = 0) { CurrentSpriteNumber = number; Geometry.x = number * Geometry.width; } void SetVelocity(float velocity) { Velocity = velocity; } virtual void Move(float dT = 1, float accelerationRatio = 1) = 0; }; class Player : public MovingObject { public: float JumpVelocity = -40000; Player(const char *texturePath, short windowWidth, short windowHeight, float velocity, char spriteSheetCount = 6) : MovingObject(texturePath, velocity, spriteSheetCount) { Geometry = { 0.0, 0.0, (float)Texture.width / spriteSheetCount, (float)Texture.height}; Position = {(float)windowWidth / 2 - Texture.width / 2, (float)windowHeight - Texture.height}; }; bool IsInAir(int windowHeight) { return Position.y < windowHeight - Geometry.height; } bool IsNearTheGround(int windowHeight, int offset = 50) { return Position.y + offset < windowHeight - Geometry.height; } void Move(float dT = 1, float accelerationRatio = 1) { Position.y += Velocity * dT * accelerationRatio; } void Jump(float dT = 1) { Velocity += JumpVelocity * dT; } }; class Hazard : public MovingObject { public: Hazard() { } Hazard(const char *texturePath, short windowWidth, short windowHeight, float velocity, char spriteSheetCount = 8) : MovingObject(texturePath, velocity, spriteSheetCount) { Geometry = {0.0, 0.0, (float)Texture.width / spriteSheetCount, (float)Texture.height}; Position = {(float)windowWidth, (float)windowHeight - Texture.height / spriteSheetCount}; } void Move(float dT = 1, float accelerationRatio = 1) { Position.x += Velocity * dT * accelerationRatio; } void ResetPosition(short windowWidth, float offset = 0) { Position.x = windowWidth + offset; } }; void HandlePlayerMovement(Player &player, short windowHeight, short g, float dT, int accelerationRation) { if (!player.IsInAir(windowHeight)) { player.Velocity = 0; } else { player.SetVelocity(player.Velocity + (g * dT)); } if (IsKeyPressed(KEY_SPACE)) { if (!player.IsNearTheGround(windowHeight)) { player.Jump(dT); } } player.Move(dT, accelerationRation); } void HandleHazardsMovement(Hazard *hazards, int hazardCount, short windowWidth, float dT, float accelerationRatio) { for (int i = 0; i < hazardCount; i++) { hazards[i].Move(dT, accelerationRatio); if (hazards[i].Position.x + hazards[i].Geometry.width <= 0) { hazards[i].ResetPosition(windowWidth + getRandomInt(0, 500)); }; } } void HandlePlayerAnimations(Player &player, short windowHeight, float dT) { if (player.IsInAir(windowHeight)) { player.Frame = 2; player.SetSprite(player.Frame); } else { player.CurrentTime += dT; if (player.CurrentTime >= player.UpdateTime) { player.CurrentTime = 0.0; player.SetSprite(player.Frame); player.Frame = player.Frame > 5 ? 0 : player.Frame + 1; } } }; void HandleHazardsAnimations(Hazard *hazards, int hazardCount, float dT) { for (int i = 0; i < hazardCount; i++) { hazards[i].CurrentTime += dT; if (hazards[i].CurrentTime >= hazards[i].UpdateTime) { hazards[i].CurrentTime = 0.0; hazards[i].SetSprite(hazards[i].Frame); hazards[i].Frame = hazards[i].Frame > 7 ? 0 : hazards[i].Frame + 1; } } } Hazard *CreateHazards(int count, short windowWidth, short windowHeight, float offsetStart = -100, float offsetEnd = 100) { Hazard *hazards = new Hazard[count]; for (int i = 0; i < count; i++) { hazards[i] = Hazard("./correct/path/to/hazard/texture", windowWidth / 2, windowHeight / 2, -60); } return hazards; }; void DrawHazards(Hazard *hazards, int hazardCount, Color color = WHITE) { for (int i = 0; i < hazardCount; i++) { DrawTextureRec(hazards[i].Texture, hazards[i].Geometry, hazards[i].Position, color); } } int main() { const short windowWidth{1024}; const short windowHeight{720}; const short g{1'000}; const char hazardCount = 6; InitWindow(windowWidth, windowHeight, "Dasher"); SetTargetFPS(60); Player player{"./correct/path/to/player/texture", windowWidth, windowHeight, 0}; Hazard *hazards = CreateHazards(hazardCount, windowWidth, windowHeight, -200, 400); float accelerationRatio{1}; while (!WindowShouldClose()) { if (accelerationRatio <= 2.5) { accelerationRatio += 0.0001; } const float dT{GetFrameTime()}; BeginDrawing(); ClearBackground(WHITE); HandlePlayerAnimations(player, windowHeight, dT); HandleHazardsAnimations(hazards, hazardCount, dT); DrawTextureRec(player.Texture, player.Geometry, player.Position, WHITE); HandlePlayerMovement(player, windowHeight, g, dT, accelerationRatio); DrawHazards(hazards, hazardCount); HandleHazardsMovement(hazards, hazardCount, windowWidth, dT, accelerationRatio); EndDrawing(); } delete[] hazards; CloseWindow(); }
问题根源
这是对象切片和默认拷贝行为导致的纹理资源错误覆盖:
CreateHazards函数中,先创建默认构造的Hazard数组,再通过赋值操作hazards[i] = Hazard(...)生成临时Hazard对象。- 父类
MovingObject的拷贝是浅拷贝,临时对象的Texture句柄会被复制到数组元素中,但临时对象销毁时会调用析构函数卸载纹理,导致数组元素中的Texture变成无效的悬空句柄。 - Raylib中,渲染无效纹理句柄时会 fallback 到最近加载的有效纹理(即玩家纹理),所以Hazard最终显示的是玩家纹理。
修复方案
方案1:避免临时对象,直接在数组内存中构造对象
修改CreateHazards函数,用placement new直接在分配的内存中构造Hazard:
Hazard *CreateHazards(int count, short windowWidth, short windowHeight, float offsetStart = -100, float offsetEnd = 100) { // 分配原始内存 Hazard *hazards = static_cast<Hazard*>(operator new[](count * sizeof(Hazard))); for (int i = 0; i < count; i++) { // 直接在内存中构造Hazard对象 new (&hazards[i]) Hazard("./correct/path/to/hazard/texture", windowWidth, windowHeight, -60); } return hazards; };
销毁数组时要手动调用析构函数:
// 替换main中的delete[] hazards; for (int i = 0; i < hazardCount; i++) { hazards[i].~Hazard(); } operator delete[](hazards);
方案2:禁用拷贝操作,改用vector管理对象
在MovingObject类中禁用拷贝构造和赋值运算符,从根源避免对象切片:
class MovingObject { public: // ... 原有成员 ... // 禁用拷贝构造 MovingObject(const MovingObject&) = delete; // 禁用赋值操作 MovingObject& operator=(const MovingObject&) = delete; // ... 原有成员函数 ... };
修改CreateHazards返回std::vector<Hazard>:
#include <vector> std::vector<Hazard> CreateHazards(int count, short windowWidth, short windowHeight, float offsetStart = -100, float offsetEnd = 100) { std::vector<Hazard> hazards; hazards.reserve(count); for (int i = 0; i < count; i++) { hazards.emplace_back("./correct/path/to/hazard/texture", windowWidth, windowHeight, -60); } return hazards; };
更新main中的变量和相关函数参数:
// main中替换Hazard* hazards = ... std::vector<Hazard> hazards = CreateHazards(hazardCount, windowWidth, windowHeight, -200, 400); // 比如修改HandleHazardsMovement的参数 void HandleHazardsMovement(std::vector<Hazard>& hazards, short windowWidth, float dT, float accelerationRatio) { for (auto& hazard : hazards) { hazard.Move(dT, accelerationRatio); if (hazard.Position.x + hazard.Geometry.width <= 0) { hazard.ResetPosition(windowWidth + getRandomInt(0, 500)); }; } }
额外建议:添加纹理加载检查
在MovingObject构造函数中检查纹理是否加载成功,提前排查资源问题:
MovingObject(const char *texturePath, float velocity, char spriteSheetCount) : Velocity(velocity), SpriteSheetCount(spriteSheetCount), TexturePath(texturePath) { Texture = LoadTexture(texturePath); if (Texture.id == 0) { std::cerr << "加载纹理失败:" << texturePath << std::endl; // 可添加默认纹理或终止程序的逻辑 } }
内容的提问来源于stack exchange,提问作者Hayk
相关产品推荐
相关产品推荐

