qml练习:绘制rgp游戏地图(1)

接下来我打算使用qml进行一个简单的rpg游戏编写,以此来练习qml

1要做一个rpg游戏首先要知道一个网格系统的概念,游戏中使用网格系统来达到图块资源的重复利用,更重要的是把地图分为一个个的小方块能够更好的处理我们的地图,对于程序后续编写的便利性很重要。

我们这里采用tiled来进行地图的绘制,程序中使用手动解析的方式进行图块信息的解析,使用qml进行画面渲染和每一帧时间间隙的控制

首先我们选择tiled绘制一个基本的土块地图

绘制地图我们要知道应该知道想要的地图大小是多少,考虑到rpg游戏要略微大于整体的屏幕并且尽可能的容纳更多东西,这里使用32.32的长宽进行构建地图

然后我们可以设置好地图之后,保存为json文档就可以开始编码工作了

1.业务方面新建一个MapLoading类和MapRenderer渲染类

这个是进行地图加载的核心逻辑先检测url是否存在,如果存在就转化为doc,然后根据json内容转化为object,这个类主要是为了接受信号进行处理,继承 QObject

复制代码
bool MapLoading::loadMap(const QString &url) {
        m_currentMapPath = url; // 设置当前地图路径
        
        QFile file(url);
        if (!file.open(QIODevice::ReadOnly)) {
            emit loadError(tr("Failed to open map file"));
            return false;
        }
        
        QByteArray data = file.readAll();
        QJsonDocument doc = QJsonDocument::fromJson(data);
        if (doc.isNull()) {
            emit loadError(tr("Invalid JSON format"));
            return false;
        }
        
        QJsonObject root = doc.object();
        
        // 解析地图基本信息
        m_mapData.width = root["width"].toInt();
        m_mapData.height = root["height"].toInt();
        m_mapData.tileWidth = root["tilewidth"].toInt();
        m_mapData.tileHeight = root["tileheight"].toInt();
        
        // 清空现有数据
        m_mapData.layers.clear();
        m_mapData.tilesets.clear();
        
        // 解析tilesets
        QJsonArray tilesetsArray = root["tilesets"].toArray();
        for (const QJsonValue &tilesetValue : tilesetsArray) {
            TileSet tileset;
            if (parseTileset(tilesetValue.toObject(), tileset)) {
                m_mapData.tilesets.append(tileset);
            }
        }
        
        // 解析layers
        QJsonArray layersArray = root["layers"].toArray();
        for (const QJsonValue &layerValue : layersArray) {
            TileLayer layer;
            if (parseLayer(layerValue.toObject(), layer)) {
                m_mapData.layers.append(layer);
            }
        }
        
        emit mapLoaded();
        return true;
    }

MapRenderer作为一个渲染类负责对画面进行渲染,继承了QQuickPaintedItem,主要是因为需要重写绘制函数,普通的控件或者qml控件很难实现

复制代码
 void MapRenderer::paint(QPainter *painter) {
        if (!m_mapData) return;
        
        painter->setRenderHint(QPainter::Antialiasing);
        
        // 绘制每个可见图层
        for (const TileLayer &layer : m_mapData->layers) {
            if (!layer.visible) continue;
            
            for (int y = 0; y < layer.height; y++) {
                for (int x = 0; x < layer.width; x++) {
                    int tileId = layer.data[y * layer.width + x];
                    if (tileId == 0) continue; // 0表示空图块
                    
                    // 查找对应的tileset
                    TileSet* targetTileset = nullptr;
                    for (TileSet &tileset : m_mapData->tilesets) {
                        if (tileId >= tileset.firstGid && 
                            (tileset.tileCount == 0 || tileId < tileset.firstGid + tileset.tileCount)) {
                            targetTileset = &tileset;
                            break;
                        }
                    }
                    
                    if (targetTileset && !targetTileset->imagePath.isEmpty()) {
                        // 加载图块集图像
                        static QHash<QString, QImage> imageCache;
                        if (!imageCache.contains(targetTileset->imagePath)) {
                            QImage img(targetTileset->imagePath);
                            if (img.isNull()) {
                                qWarning() << "Failed to load image:" << targetTileset->imagePath;
                                continue;
                            }
                            imageCache[targetTileset->imagePath] = img;
                        }
                        
                        const QImage &tilesetImage = imageCache[targetTileset->imagePath];
                        
                        // 计算图块在tileset中的位置
                        int localId = tileId - targetTileset->firstGid;
                        int tileX = (localId % targetTileset->columns) * targetTileset->tileWidth;
                        int tileY = (localId / targetTileset->columns) * targetTileset->tileHeight;
                        
                        QRect sourceRect(tileX, tileY, targetTileset->tileWidth, targetTileset->tileHeight);
                        QRectF targetRect(x * m_mapData->tileWidth, 
                                         y * m_mapData->tileHeight,
                                         m_mapData->tileWidth,
                                         m_mapData->tileHeight);
                        
                        painter->drawImage(targetRect, tilesetImage, sourceRect);
                    }
                }
            }
        }
    }

    void MapRenderer::setMapData(MapData* data) {
        if (m_mapData != data) {
            m_mapData = data;
            if (m_mapData) {
                setWidth(m_mapData->width * m_mapData->tileWidth);
                setHeight(m_mapData->height * m_mapData->tileHeight);
            }
            update();
            emit mapDataChanged();
        }
    }

界面方面使用自定义qml

复制代码
// MapComponent.qml
import QtQuick 2.15
import RpgGame 

Item {
    id: mapContainer
    
    property string mapUrl: ""
    
    Component.onCompleted: {
        if (mapUrl) {
            mapLoader.loadMap(mapUrl);
        }
    }
    
    MapLoading {
        id: mapLoader
        onMapLoaded: {
            mapRenderer.mapData = mapLoader.getMapData();
        }
        onLoadError: {
            console.error("Map loading error:", error);
        }
    }
    
    MapRenderer {
        id: mapRenderer
        anchors.fill: parent
    }
}

整体逻辑为: 当我们调用mapLoader.loadMap(mapUrl)会进行map的json文件解析,解析结束触发槽函数获取地图数据,然后触发界面更新的逻辑

运行结果如下

相关推荐
星辰徐哥19 小时前
Unity基础:游戏对象的激活与隐藏:SetActive方法详解
游戏·unity·lucene
CS创新实验室20 小时前
CS实验室行业报告:游戏行业就业分析报告
大数据·游戏
王杨游戏养站系统21 小时前
王杨游戏蜘蛛养站系统:提交百度站长工具平台教程!
游戏·百度·游戏下载站养站系统·游戏养站系统
码界筑梦坊21 小时前
112-基于Flask的游戏行业销售数据可视化分析系统
开发语言·python·游戏·信息可视化·flask·毕业设计·echarts
maaath21 小时前
【maaath】Flutter for OpenHarmony 游戏中心应用实战开发
flutter·游戏·华为·harmonyos
阿斯加德D1 天前
植物大战僵尸拼接版下载2026最新版完整及游戏内容详解
游戏
源码老李1 天前
独立游戏AI音乐指南:用Suno AI让游戏拥有灵魂
人工智能·游戏·ai编程
_守一2 天前
UE DS+Nakama进行游戏服务器开发(1)源码编译nakama
服务器·游戏
魔士于安2 天前
Unity 超市总动员 超市收银台 超市货架 超市购物手推车 超市常见商品
游戏·unity·游戏引擎·贴图·模型
zh路西法2 天前
【Unity实现Oneshot胶卷显形】游戏窗口化与Win32API的使用
游戏·unity·游戏引擎