C++游戏开发之旅 10
问题概述:
上一章,我们实现了将Tiled中的背景图层渲染到了屏幕上,为关卡铺设了远景。现在,我们要开始构建玩家能够站立、行走、跳跃的实体世界。这章我们继续深入LevelLoader,实现对瓦片层(Tiled Layer)的解析和加载。瓦片层是Tiled地图的核心,是由一个网格单元组成的二维数组,每个单元格可以放置一个来自图块集(Tileset)的瓦片。这比简单的图片层要复杂。
1.Tiled瓦片系统核心概念
首先我们需要知道Tiled是如何进行组织瓦片数据的。

-
地图大小:height × width 91 × 29
-
瓦片大小:tileheight × tilewidth 16 × 16
-
地图像素大小:height × tileheight × width × tilewidth = 91 × 16 × 29 × 16
-
Data数组:指定每个地图区域放入什么图块
- 长度为地图大小
- 数值为瓦片索引(0代表无瓦片)
-
瓦片索引(gid):全局唯一,计算方式 gid = firstgid + 图集内id
核心术语
-
图块集(Tileset) - 包含多个瓦片的资源集合,可以是一张包含所有瓦片的大地图,也可以是多个独立图片的集合。Tiled会将图块集的数据保存在一个单独的
.tsj文件中。 -
全局ID(GID) - Tiled会给一个地图中所有图块集里的每一个瓦片分配一个从1开始、独一无二的ID,这就是GID。
-
firstgid - 在地图文件
.tmj中,每个引用的图块集中都有一个firstgid属性。它表示这个图块集的第一个瓦片在整个地图中的GID是多少。 -
局部ID - 一个瓦片在所属图块集中的内部ID
-
换算关系 -
LID = GID - firstgid,通过公式,我们可以从地图文件数据中读到一个GID时,就可以判断出它属于哪一个图块集,并且知道是该图块集中的第几个瓦片。
Tiled地图解析思路
解析地图文件流程:
1.加载图块集
- 遍历地图文件中的
tilesets数组 - 根据
source字段找到对应的.tsj文件并解析 - 将其数据与
firstgid一起缓存起来
2.加载图层
- 遍历
layers数组 - 如果是
tilelayer:- 遍历该层的
data数组,里面存放了每个网格单元的GID - 对于每一个GID,使用缓存的图块集数据来查找它对应的图片、源矩形和自定义属性
- 将这些信息组装成一个
TileInfo对象 - 所有
TileInfo对象被存入一个std::vector中 - 最后,创建一个包含这个vector的
TileLayerComponent游戏对象
- 遍历该层的
2.TileLayerComponent:瓦片世界的容器
我们通过新建一个组件来持有和渲染整个瓦片层。
tilelayer_component.h
#pragma once
#include "component.h"
#include "../render/sprite.h"
#include <vector>
#include <glm/vec2.hpp>
namespace engine::core {
class Context;
}
namespace engine::component {
enum class TileType{
EMPTY,
NORMAL,
SOLID,
// ...
};
struct TileInfo {
render::Sprite sprite; // 精灵视图
TileType type; // 瓦片类型
TileInfo(render::Sprite sprite = render::Sprite(), TileType type = TileType::EMPTY) : sprite(std::move(sprite)), type(type) {}
};
/**
* @brief 瓦片层组件
* 用于存储瓦片信息,并可以渲染瓦片层
*/
class TileLayerComponent final : public Component {
friend class engine::object::GameObject;
private:
glm::ivec2 map_size_; // 地图尺寸(瓦片数)
glm::ivec2 tile_size_; // 单个瓦片尺寸
std::vector<TileInfo> tiles_; // 存储所有瓦片信息 以行主序存储
glm::vec2 offset_ = {0.0f,0.0f}; // 瓦片层一般不会旋转或是缩放,不引入Transform,一般也不会有偏移 保持0就行
bool is_hidden_ = false;
public:
TileLayerComponent() = default;
/**
* 构造函数
* @param map_size 地图尺寸(瓦片数)
* @param tile_size 单个瓦片尺寸
* @param tiles 瓦片信息
*/
TileLayerComponent(glm::ivec2 map_size, glm::ivec2 tile_size, std::vector<TileInfo>&& tiles);
/**
* @brief 获取瓦片信息
* @param pos 瓦片坐标(0 <= x < map_size_.x, 0 <= y < map_size_.y)
* @return const TileInfo* 指向瓦片信息的指针,如果坐标超出范围则返回nullptr
*/
const TileInfo* getTileInfoAt(glm::ivec2 pos) const;
/**
* @brief 获取瓦片类型
* @param pos 瓦片坐标(0 <= x < map_size.x, 0 <= y < map_size.y)
* @return TileType 瓦片类型,如果坐标超出范围则返回TileType::EMPTY
*/
TileType getTileTypeAt(glm::ivec2 pos) const;
/**
* @brief 根据世界坐标获取瓦片类型
* @param world_pos 世界坐标
* @return TileType 瓦片类型,如果坐标超出范围则返回TileType::EMPTY
*/
TileType getTileTypeAtWorldPos(const glm::vec2& world_pos) const;
// getter and setter
const glm::ivec2& getTileSize() const { return tile_size_; }
const glm::ivec2& getMapSize() const { return map_size_; }
glm::vec2 getWorldSize() const { return glm::vec2(map_size_.x * tile_size_.x, map_size_.y * tile_size_.y); }
bool isHidden() const { return is_hidden_; }
void setHidden(bool hidden) { is_hidden_ = hidden; }
protected:
void init() override;
void update(float, engine::core::Context&) override{}
void render(engine::core::Context&) override;
};
}
核心设计说明
-
TileType --- 表示瓦片的类型,这个在碰撞的时候需要知道,比如SOLID,表示地表、墙的瓦片类型。
-
TileInfo --- 结构体,包含单个瓦片的所有信息
- 表示视图渲染的
Sprite - 用于逻辑判断的
TileType
- 表示视图渲染的
-
tiles_ --- 使用一个
std::vector数组来以行主序的方式存放瓦片层的所有瓦片信息TileInfo -
render() --- 遍历
tiles_,计算每个瓦片在世界中的位置,调用Renderer::drawSprite绘制
tilelayer_component.cpp
#include "tilelayer_component.h"
#include "../core/context.h"
#include "../render/renderer.h"
#include <spdlog/spdlog.h>
namespace engine::component {
TileLayerComponent::TileLayerComponent(glm::ivec2 map_size, glm::ivec2 tile_size, std::vector<TileInfo> &&tiles)
: map_size_(map_size), tile_size_(tile_size), tiles_(std::move(tiles))
{
if(tiles_.size() != static_cast<size_t>(map_size_.x * map_size_.y)){
spdlog::error("瓦片层组件瓦片数量不匹配");
tiles_.clear();
map_size_ = {0,0};
}
spdlog::trace("瓦片层组件创建");
}
const TileInfo *TileLayerComponent::getTileInfoAt(glm::ivec2 pos) const
{
if(pos.x < 0 || pos.x >= map_size_.x || pos.y < 0 || pos.y >= map_size_.y){
spdlog::error("超出地图大小索引");
return nullptr;
}
return &tiles_[static_cast<size_t>(pos.y * map_size_.x + pos.x)];
}
TileType TileLayerComponent::getTileTypeAt(glm::ivec2 pos) const
{
if (const auto *tile_info = getTileInfoAt(pos)) {
return tile_info->type;
}
return TileType::EMPTY;
}
TileType TileLayerComponent::getTileTypeAtWorldPos(const glm::vec2 &world_pos) const
{
glm::vec2 relative_position = world_pos - offset_;
int tile_x = static_cast<int>(std::floor(relative_position.x / tile_size_.x));
int tile_y = static_cast<int>(std::floor(relative_position.y / tile_size_.y));
return getTileTypeAt(glm::ivec2(tile_x, tile_y));
}
void TileLayerComponent::init()
{
if(!owner_) {
spdlog::error("瓦片层组件没有拥有者");
return;
}
spdlog::trace("瓦片层组件初始化完成");
}
void TileLayerComponent::render(engine::core::Context& context)
{
if(tile_size_.x <= 0 || tile_size_.y <= 0) {
return;
}
for (int y = 0; y < map_size_.y; ++y) {
for (int x = 0; x < map_size_.x; ++x) {
int index = static_cast<size_t>(y * map_size_.x + x);
if (tiles_[index].type != TileType::EMPTY) {
const auto &tile_info = tiles_[index];
// 计算左上角的位置
glm::vec2 position = {
offset_.x + static_cast<float>(x) * tile_size_.x,
offset_.y + static_cast<float>(y) * tile_size_.y
};
// 如果遇到瓦片大小不一致的情况,则调整位置
if(tile_info.sprite.getSourceRect()->h != tile_size_.y){
position.y -= (tile_info.sprite.getSourceRect()->h - static_cast<float>(tile_size_.y));
}
context.getRenderer().drawSprite(context.getCamera(), tile_info.sprite, position);
}
}
}
}
}
3.LevelLoader添加载入瓦片层的函数
接下来需要为LevelLoader添加加载图块集和瓦片层的能力。
engine/scene/level_loader.h
#pragma once
#include <string>
#include <nlohmann/json.hpp>
#include <glm/vec2.hpp>
#include <map>
namespace engine::component{
struct TileInfo;
}
namespace engine::scene {
class Scene;
class LevelLoader final {
private:
std::string map_path_; // 关卡地图路径
glm::ivec2 map_size_; // 关卡地图大小 瓦片填充个数
glm::ivec2 tile_size_; // 关卡地图中每个瓦片的大小
std::map<int,nlohmann::json> tileset_data_; // 关卡地图中的瓦片数据,需要将瓦片数据集给先缓存下来
// 对应的是firstgid --- 瓦片数据
public:
LevelLoader() = default;
/**
* @brief 加载关卡数据到指定的 Scene 对象对
* @param map_path 关卡文件路径
* @param scene 需要加载关卡数据的 Scene 对象
* @return 加载成功返回 true,否则返回 false
*/
bool loadLevel(const std::string& map_path, Scene& scene);
private:
void loadImageLayer(const nlohmann::json& json, Scene& scene);
void loadTileLayer(const nlohmann::json& json, Scene& scene);
void loadObjectLayer(const nlohmann::json& json, Scene& scene);
// 获取瓦片数据集中的属性
component::TileInfo getTileInfoByGid(int gid);
// 加载瓦片数据集
void loadTilesets(const std::string& tileset_path, int firstgid);
/**
* 解析路径,将地图路径和相对路径合并
* 地图路径:"assets/maps/level1.tmj"
* 相对路径:"../textures/Layers/back.png"
* 结果路径:"assets/maps/../textures/Layers/back.png
* maps/../ 抵消了
*/
// 显然这部分获得绝对路径是有点问题,这只是对于地图文件了,后面比如瓦片数据集文件获取就需要处理,这里进行修改以适配该情况
std::string resolvePath(const std::string& image_path);
std::string resolvePath(const std::string& relative_path, const std::string& file_path);
};
}
关键更新说明
新增成员变量
- **map_size_ , tile_size_ ** --- 存储地图的全局信息
- **std::map<int, nlohmann::json> tileset_data_ ** --- 缓存所有图块集的数据,以键值对的形式{firstgid, json对象},
新增方法
- getTileInfoByGid() --- 根据GID获取瓦片信息
- loadTileset() --- 加载图块集数据,将图块集数据放到
std::map<int, nlohmann::json> tileset_data_中 - loadTileLayer() --- 加载瓦片层
engine/scene/level_loader.cpp
#include "level_loader.h"
#include <nlohmann/json.hpp>
#include <spdlog/spdlog.h>
#include <fstream>
#include <filesystem>
#include "scene.h"
#include "../core/context.h"
#include "../resource/resource_manager.h"
#include "../object/game_object.h"
#include "../component/transform_component.h"
#include "../component/parallax_component.h"
#include "../component/tilelayer_component.h"
namespace engine::scene {
bool LevelLoader::loadLevel(const std::string &map_path, Scene &scene)
{
map_path_ = map_path;
// 1.加载地图文件
std::ifstream file(map_path_);
if (!file.is_open()) {
spdlog::error("打开地图文件失败: {}", map_path_);
return false;
}
// 2.解析地图文件
nlohmann::json json_data;
try{
file >> json_data;
} catch (const nlohmann::json::parse_error &e) {
spdlog::error("解析地图文件失败: {}", e.what());
return false;
}
// 3.获取地图的基本信息
map_size_ = glm::vec2(json_data.value("width", 0),json_data.value("height", 0));
tile_size_ = glm::vec2(json_data.value("tilewidth", 0),json_data.value("tileheight", 0));
// 4.加载图块集
if(json_data.contains("tilesets") && json_data["tilesets"].is_array()){
for (const auto& tileset_json : json_data["tilesets"]) {
if(tileset_json.contains("source") && tileset_json.contains("firstgid") &&
tileset_json["source"].is_string() && tileset_json["firstgid"].is_number_integer()){
auto source = tileset_json["source"].get<std::string>();
auto firstgid = tileset_json["firstgid"]; // 不使用.get<int>() 也可以,因为支持隐式转换
auto tileset_path = resolvePath(source, map_path_); // 得到瓦片集的绝对路径
loadTilesets(tileset_path, firstgid);
}
}
}
// 5.加载图层
if(!json_data.contains("layers") || !json_data["layers"].is_array() ){
spdlog::error("地图文件缺少图层信息或者没有数组layers, {}", map_path_);
return false;
}
for (const auto& layer_json : json_data["layers"]) {
std::string layer_type = layer_json.value("type", "none");
if(!layer_json.value("visible",true)){
spdlog::debug("图层{}不可见,跳过", layer_json.value("name", "none"));
continue;
}
if(layer_type == "imagelayer"){
loadImageLayer(layer_json, scene);
} else if (layer_type == "tilelayer") {
loadTileLayer(layer_json, scene);
} else if (layer_type == "objectgroup") {
loadObjectLayer(layer_json, scene);
} else {
spdlog::warn("未知的图层类型: {}", layer_type);
}
}
spdlog::info("加载地图文件成功: {}", map_path_);
return true;
}
void LevelLoader::loadImageLayer(const nlohmann::json &layer_json, Scene &scene)
{ // 获取相对路径,这里会自动处理\/
const auto& image_path = layer_json.value("image", "");
if (image_path.empty()) {
spdlog::warn("图层{}缺少图片路径", layer_json.value("name", "none"));
return;
}
/* 我们知道,resourceManager中的getTexture是通过绝对路径来获取图片的,我们需要将相对路径
转换为绝对路径以此去适配它 */
const std::string& texuture_id = resolvePath(image_path);
if (texuture_id.empty()) {
spdlog::warn("图片路径{}解析失败", image_path);
return;
}
// 获取图片层的名字
auto name = layer_json.value("name", "none");
// 获取图片层的偏移量
glm::vec2 offset = glm::vec2(layer_json.value("offsetx", 0), layer_json.value("offsety", 0));
// 获取视差因子
glm::vec2 scroll_factor = glm::vec2(layer_json.value("parallaxx", 1.0f), layer_json.value("parallaxy", 1.0f));
// 是否平铺
glm::bvec2 repeat = glm::bvec2(layer_json.value("repeatx", false), layer_json.value("repeaty", false));
// 创建一个游戏对象 注意顺序,最先添加的组件是Transform
auto obj = std::make_unique<object::GameObject>(name);
obj->addComponent<component::TransformComponent>(offset);
obj->addComponent<component::ParallaxComponent>(texuture_id,scroll_factor,repeat);
scene.addGameObject(std::move(obj));
spdlog::info("加载图片层成功: {}", name);
}
void LevelLoader::loadTileLayer(const nlohmann::json &json, Scene &scene)
{
if(!json.contains("data") || !json["data"].is_array()){
spdlog::error("图层{}数据异常", json.value("name","none"));
return;
}
// TileInfo Vector (瓦片数量 = 地图宽度 * 地图高度)
std::vector<engine::component::TileInfo> tiles;
tiles.reserve(map_size_.x * map_size_.y);
// 获取data
const auto& data = json["data"];
for (const auto& gid : data) {
tiles.push_back(getTileInfoByGid(gid));
}
// 添加游戏对象
auto name = json.value("name", "none");
auto obj = std::make_unique<object::GameObject>(name);
obj->addComponent<component::TileLayerComponent>(map_size_, tile_size_, std::move(tiles));
scene.addGameObject(std::move(obj));
spdlog::info("加载瓦片层成功: {}", name);
}
void LevelLoader::loadObjectLayer(const nlohmann::json &json, Scene &scene)
{
}
component::TileInfo LevelLoader::getTileInfoByGid(int gid)
{
if(gid == 0){
return component::TileInfo();
}
// 需要找比gid小的最大的firstgid
auto it = tileset_data_.upper_bound(gid);
if(it == tileset_data_.begin()){
spdlog::error("找不到对应的图块集");
return component::TileInfo();
}
--it; // 退一步
const auto& tileset_json = it->second; // 获取图块集json对象
auto localgid = gid - it->first; // 获取相对gid
// 这里需要考虑两种情况,一种是图块集是单个图片,一种是图块集是多个图片拼接的
if(tileset_json.contains("image")){ // 单个图片,image属性在外边
//获取图片路径
const auto& textureid = resolvePath(tileset_json["image"].get<std::string>(), tileset_json["file_path"].get<std::string>());
// 可以计算出图片网格中的坐标
auto coordinate_x = localgid % tileset_json["columns"].get<int>();
auto coordinate_y = localgid / tileset_json["columns"].get<int>();
// 确定源矩形
SDL_FRect texture_rect = {
static_cast<float>(coordinate_x * tile_size_.x),
static_cast<float>(coordinate_y * tile_size_.y),
static_cast<float>(tile_size_.x),
static_cast<float>(tile_size_.y)
};
engine::render::Sprite sprite(textureid, texture_rect);
return component::TileInfo(sprite,engine::component::TileType::NORMAL);
} else { // 多图片情况 image路径在tiles里
for(auto& tile_data : tileset_json["tiles"]){
if(tile_data["id"] == localgid){
// 获取图片路径
const auto& textureid = resolvePath(tile_data["image"].get<std::string>(), tileset_json["file_path"].get<std::string>());
// 多图片获取的是整张图片 但你还是得确定源矩形
auto image_width = tile_data.value("imagewidth", 0);
auto image_height = tile_data.value("imageheight", 0);
SDL_FRect texture_rect = {
static_cast<float>(tile_data.value("x", 0)),
static_cast<float>(tile_data.value("y", 0)),
static_cast<float>(tile_data.value("width", image_width)),
static_cast<float>(tile_data.value("height", image_height))
};
engine::render::Sprite sprite(textureid, texture_rect);
return component::TileInfo(sprite,engine::component::TileType::NORMAL);
}
}
}
return component::TileInfo();
}
void LevelLoader::loadTilesets(const std::string &tileset_path, int firstgid)
{
std::ifstream file(tileset_path);
if (!file.is_open()) {
spdlog::error("打开图块集文件失败: {}", tileset_path);
return;
}
// 1.解析图块集文件
nlohmann::json json_data;
try {
file >> json_data;
} catch (const nlohmann::json::parse_error &e) {
spdlog::error("解析图块集文件失败: {}", e.what());
return;
}
// 2.获取图块集的基本信息
// 将路径放进去,为了后面解析图片路径需要,此时这个json对象中有一个路径,也就是文件所在的绝对路径
json_data["file_path"] = tileset_path;
tileset_data_[firstgid] = std::move(json_data);
spdlog::info("图块集{}加载成功, firstgid:{}", tileset_path, firstgid);
}
std::string LevelLoader::resolvePath(const std::string &image_path)
{
try {
// 这里可以获得地图map_path_的父路径assets/maps/level1.tmj ---> assets/maps/
auto map_dir = std::filesystem::path(map_path_).parent_path();
// 合并路径,获得绝对路径
auto abs_path = map_dir / image_path;
// 规范化处理
abs_path = std::filesystem::canonical(abs_path);
return abs_path.string();
} catch (const std::filesystem::filesystem_error &e) {
spdlog::error("解析路径失败: {}", e.what());
return "";
}
}
std::string LevelLoader::resolvePath(const std::string &relative_path, const std::string &file_path)
{
try {
// 获取文件所在的父路径
auto file_dir = std::filesystem::path(file_path).parent_path();
// 合并路径,获得绝对路径、
auto abs_path = file_dir / relative_path;
// 规范化处理
abs_path = std::filesystem::canonical(abs_path);
return abs_path.string();
} catch (const std::filesystem::filesystem_error &e) {
spdlog::error("解析路径失败: {}", e.what());
return "";
}
}
}
4.渲染细节
我们发现游戏在渲染瓦片时,非常容易在瓦片之间出现细微的裂缝,这是因为GPU默认使用线性插值(SDL_SCALEMODE_LINEAR)来缩放纹理,导致瓦片边缘的像素与临近的透明像素“混合”,从而产生半透明的边缘。
解决方案:最近邻插值
我们需要告诉SDL使用使用最近邻插值(SDL_SCALEMODE_NEAREST),这种模式会选择最接近的像素颜色,保持像素画的锐利边缘。
更新engine/resource/texture_manager.cpp
SDL_Texture *TextureManager::loadTexture(const std::string &file_path)
{
// 首先检查是否已经加载过
auto it = textures_.find(file_path);
if(it != textures_.end()){
return it->second.get();
}
// 没有加载过,则加载
spdlog::debug("加载纹理:{}", file_path);
SDL_Texture* raw_texture = IMG_LoadTexture(sdl_renderer_, file_path.c_str());
if(!raw_texture){
spdlog::error("TextureManager 加载纹理失败->文件路径 {} 异常,错误信息 {}", file_path,SDL_GetError());
return nullptr;
}
// 设置纹理缩放模式为最近邻
if(!SDL_SetTextureScaleMode(raw_texture, SDL_SCALEMODE_NEAREST)){
spdlog::warn("无法设置纹理缩放模式为最近邻模式");
}
// 将纹理放入缓存
textures_.emplace(file_path, std::unique_ptr<SDL_Texture, SDLTextureDeleter>(raw_texture));
spdlog::debug("TextureManager 加载纹理成功->文件路径 {}", file_path);
return raw_texture;
}
5.最终效果
GameScene的代码几乎不需要改变,其会自动处理新增的瓦片层逻辑。
运行结果


将图片缩放模式设置为最近邻后效果好了很多!
本章总结
-
完成了TileLayerComponent组件的设计,我们有一个TileInfo,有一个sprite和tiletype的成员变量,组件中有一个vector数组,存放TileInfo,实现对于瓦片层的渲染
-
了解Tiled瓦片系统的核心概念(GID、Tileset、LID等等)
-
升级了LevelLoader以支持图块集和瓦片层加载,修改了resolvePath()函数
-
解决了像素画瓦片渲染的缝隙问题
遇到问题
1.在加载图块集数据的时候,没有将文件的路径放到tile_data_的map容器的json对象当中。
在加载图块集数据的时候,我们是从地图文件中去找的,有一个source和firstgid,这个source是图块集文件相对地图文件的路径,因此,我们通过resolvePath()函数获得绝对路径,这个得到的路径就算图块集的路径,有了这个路径后,就可以往tileset_data_中存储数据了,当然我们还需要将这个路径存放到json对象中。因为后面我们会从图块集数据中读取图片路径,这个resolvePath()函数需要一个相对路径(图块集中图片所在的相对路径),一个文件路径(图块集当前文件的路径,也就是存的路径)。
2.nlohmann::json的不完整定义问题
问题出现在level_loader.h,起因是我在头文件中是这样引入的#include <nlohmann/json_fwd.hpp>,然后我的LevelLoader中有一个成员变量std::map<int,nlohmann::json> tileset_data_,这是存储瓦片集数据的,对应于一个 firstgid 和 json 对象数据,然而,我的构造函数LevelLoader()=default写在了头文件里,所以在构造函数初始化的时候肯定会报错,因为std::map初始化的时候需要知道完整的nlohmann::json的定义,但是你并没有引入#include <nlohmann/json.hpp>的完整定义,所以报错了,这是个小细节问题。
3.程序运行遇到了Cannot access value of empty optional的问题,这是为什么?
因为在level_loader.cpp中的getTileInfoByGid函数处理多图片情况我是这样写的:
else { // 多图片情况 image路径在tiles里
for(auto& tile_data : tileset_json["tiles"]){
if(tile_data["id"] == localgid){
// 获取图片路径
const auto& textureid = resolvePath(tile_data["image"].get<std::string>(), tileset_json["file_path"].get<std::string>());
// 多图片获取的是整张图片
engine::render::Sprite sprite(textureid);
return component::TileInfo(sprite,engine::component::TileType::NORMAL);
}
}
}
可以看到我的Sprite类在进行初始化时,源矩形没有传参数,默认是std::nullopt,这会造成TileLayerComponent.cpp中的render函数出现异常判断,可以看到下面的代码
if (tiles_[index].type != TileType::EMPTY) {
const auto &tile_info = tiles_[index];
// 计算左上角的位置
glm::vec2 position = {
offset_.x + static_cast<float>(x) * tile_size_.x,
offset_.y + static_cast<float>(y) * tile_size_.y
};
// 如果遇到瓦片大小不一致的情况,则调整位置
if(tile_info.sprite.getSourceRect()->h > tile_size_.y){
position.y -= (tile_info.sprite.getSourceRect()->h - static_cast<float>(tile_size_.y));
}
context.getRenderer().drawSprite(context.getCamera(), tile_info.sprite, position);
}
因为可能出现瓦片图大于网格瓦片的情况,我们需要对精灵图进行调整,也就是瓦片信息的精灵源矩形大于网格瓦片大小,tile_info.sprite.getSourceRect()->h > tile_size_.y,这个时候 y 方向要减去瓦片网格size的高,但是基于前面的代码,你获取的时候这个源矩形就为std::nullopt了,就报错了。
4.渲染render函数错位了

if(tile_info.sprite.getSourceRect()->h > tile_size_.y){ // > 得改成 !=
position.y -= (tile_info.sprite.getSourceRect()->h - static_cast<float>(tile_size_.y));
}
context.getRenderer().drawSprite(context.getCamera(), tile_info.sprite, position);
我太fw了啊,只是考虑了瓦片图片大于网格瓦片的大小,而如果小于网格瓦片也会造成错位,比如精灵的源矩形是15×10,那么希望其在16×16的瓦片网格中y方向的位置应该是6,也就是一个网格中的下部分。

浙公网安备 33010602011771号