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

运行结果如下

相关推荐
the3clipse19 小时前
视频编码技术如何赋能游戏性能优化:从硬件隔离到AI驱动的带宽革命
游戏·性能优化·音视频·视频编码·硬件加速·ai编码·自适应编码
jeffsonfu1 天前
生成对抗网络(GAN)原理:生成器与判别器的“猫鼠游戏”
人工智能·游戏·生成对抗网络
解决小子1 天前
【游戏作品设计解析】类魂游戏设计解析
游戏·业界资讯·游戏策划·游戏设计
k4m7v2pz2 天前
用中文编程语言写游戏:玄铁语言生命游戏的 macOS 适配实录
游戏·macos·玄铁·生命游戏·中文编程语言·玄铁中文编程
JQweryuiop8 天前
中小型游戏陪练工作室数字化管理实践:基于七彩屋电竞护航小程序订单、履约与绩效系统的落地复盘
大数据·游戏·小程序·架构
乌萨达9 天前
低配电脑玩手游怎么设置?4GB、8GB内存与模拟器选择指南
游戏·电脑·安卓模拟器·雷电模拟器
林川~0110 天前
Unity 万能物理检测工具:射线检测 / 范围检测 / 层级过滤 / 编辑器可视化(可直接拿去用)
游戏·unity·c#·射线检测·通用工具
一孤程11 天前
游戏测试专题第七篇:游戏自动化测试实战
游戏·测试
一孤程12 天前
游戏测试专题第八篇:游戏安全测试与反外挂
游戏·测试
河南花仙子科技13 天前
小游戏社交裂变功能的高阶设计思路
大数据·科技·游戏