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

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();
}

问题根源

这是对象切片和默认拷贝行为导致的纹理资源错误覆盖:

  1. CreateHazards函数中,先创建默认构造的Hazard数组,再通过赋值操作hazards[i] = Hazard(...)生成临时Hazard对象。
  2. 父类MovingObject的拷贝是浅拷贝,临时对象的Texture句柄会被复制到数组元素中,但临时对象销毁时会调用析构函数卸载纹理,导致数组元素中的Texture变成无效的悬空句柄。
  3. 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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.26 02:47:05