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文件解析,解析结束触发槽函数获取地图数据,然后触发界面更新的逻辑

运行结果如下

相关推荐
巨人张2 小时前
C++零基础游戏----“大鱼吃小鱼”
java·c++·游戏
啃火龙果的兔子3 小时前
Pygame开发游戏流程详解
python·游戏·pygame
lxysbly3 小时前
安卓 PS1 模拟器,手机上也能玩经典 PlayStation 游戏
android·游戏·智能手机
chengpei1471 天前
Moonlight + Sunshine互联网串流方案介绍
游戏
向宇it1 天前
2025年技术总结 | 在Unity游戏开发路上的持续探索与沉淀
游戏·unity·c#·游戏引擎
数说故事1 天前
有哪些采集游戏大数据的工具推荐?数说聚合提供全网游戏阵地数据
游戏·数据采集·用户洞察
科技块儿1 天前
如何快速识别游戏安全运营中外挂与多开用户?
安全·游戏
前端小L1 天前
贪心算法专题(五):覆盖范围的艺术——「跳跃游戏」
数据结构·算法·游戏·贪心算法
前端小L1 天前
贪心算法专题(六):步步为营的极速狂飙——「跳跃游戏 II」
算法·游戏·贪心算法