Skip to content

A 3D Block Building Game in QML

Qt Quick Rendering Engine

Here, at KDAB, we get to spend 10% of our time on learning what we don’t know or practicing and improving what we already know. Recently, I decided to use that time to learn more about the Qt Quick Rendering Engine. The best way to do so, I found, is to use it in a way it wasn’t intended to be used: for making simple 3D graphics — creating my own little 3D paintings, as one would in Minecraft, starting with a ground plane. I’d like to take this time to share with you how to play.

How to play

My mini voxel-editor should be using Isometric Perspective to display colorful cubes, which can be placed in a world. Therefore, I create a QML Rectangle that contains the transformations necessary for isometric perspective.

By combining three transforms, I create my ground plane:

ground plane

  • 0 — Starting with a normal rectangle
  • 1 — Rotating 45 degrees (around transform origin)
  • 2 — Downscaling of the original y-component
  • 3 — Flipping of the coordinate system towards the viewer

These actions can be achieved by combining three 4-by-4 matrices for each of the transformations, via multiplication. Note that, although QML comes with predefined Transform Items, I use raw matrices for each transform, to keep the code consistent.

    //The ISO perspective transform:
    transform: Matrix4x4 {
        id: isometric_perspective
        property real rotationZ: 45/360 * Math.PI * 2
        property matrix4x4 flipMatrix:
            Qt.matrix4x4(1,  0, 0, 0,
                         0, -1, 0, 0,
                         0,  0, 1, 0,
                         0,  0, 0, 1)
        property matrix4x4 rotationZMatrix:
            Qt.matrix4x4(Math.cos(rotationZ), -Math.sin(rotationZ), 0, 0,
                         Math.sin(rotationZ),  Math.cos(rotationZ), 0, 0,
                         0, 0, 1, 0,
                         0, 0, 0, 1)
        property real flatness: 0.5
        property matrix4x4 scaleYMatrix:
            Qt.matrix4x4(1,   0, 0, 0,
                         0, flatness, 0, 0,
                         0,   0, 1, 0,
                         0,   0, 0, 1)
        matrix: flipMatrix.times(scaleYMatrix).times(rotationZMatrix)
    }

Because all transforms originate in the top-right of my window, I position my world-origin to the bottom-center of my window, to reveal the full world inside of it. Since QML’s scene graph is hierarchical, all of its child Items (RectanglesCircles, …) are inherently in the right perspective and appear as lying on the ground of my iso-world. Both the empty world and the world filled with RectangleCircle, and a star Image can be seen here:

ground plane

Next would be the cubes. I already have the top faces, since they would be simple squares in my iso-world (just like the red rectangle in the image above). To “elevate” them to the cubes height (32px), I apply an offset of 32 in x, as well as in y, as a transform. Going up in the iso-world means adding the same value to x and y, thus moving straight out of the corner pointing towards you. Sometimes, this additional axis combined out of x and y is called the w axis, for the isometric perspectives. There is a certain ambiguity, still, since the viewer does not know if a shape was moved on the ground plane (a), or elevated (b). Small tricks in lighting, such as adding a shadow, can help here:

scene graph

For the side faces, I also would need an upwards-effect to make them rise from the bottom to the top of my cubes. This, I achieve with a transform matrix, increasing x as well as y. For the right side, this happens in the first row of the transform matrix. Conversely, the left side has this double increase in its second row.

scene graph

For the colors, I decided to simply make the top Qt.lighter(cubeColor), the left normal cubeColor, and the right Qt.darker(cubeColor). So, overall, it looks like light is coming from top left.

I found out that all MouseAreas are transformed, together with the visible Item (as they behave as transformed Rectangles). So, just by filling a MouseArea to each of the sides, I could make them clickable, individually, also working with all the transforms. To reuse the MouseArea, I refactored out a universal FaceArea, which allows you to create a cube at an arbitrarily chosen vector offset.

So, to give an example, I instantiate it with the vector Qt.vector3D(0, 0, 1), for the top face to create the new cube on top, when clicking.

     //FaceArea.qml
    MouseArea{
        property var creationOffset: Qt.vector3d(0, 0, 1)
        anchors.fill: parent
        acceptedButtons: Qt.AllButtons
        onWheel: {isoCube.color = Qt.hsla(Math.random(),0.8, 0.5, 1)}
        onClicked: {
            if (mouse.button & Qt.LeftButton) {
                isoWorld.createCubeAt(isoCube.xpos + creationOffset.x,
                                                  isoCube.ypos + creationOffset.y,
                                                  isoCube.level + creationOffset.z);
            } else {
                isoCube.destroy()
            }
        }
    }

There are 3 possibilities of interaction:

  • Wheel randomly changes the color
  • Normal left click adds a new cube on that face, using QML’s Dynamic Item Creation.
  • Right click deletes the cube.

Bonus: To make the whole thing a bit more vivid, I made it wobble up and down. I also added an indicator where new cubes are placed on the ground plane.

Here is the final result in a video:

Tip: To make the Cubes stack well, Z-ordering has to be done right. There are 3 simple rules:

  1. If it’s above, it is in front.
  2. If it has a lower x or y, it is in front (because it’s more towards the viewer)
  3. But “above” is way more important.

So, I simply set the z to 1000 * stacklevel – x – y, and it worked well.

Summary

Although QML is intended to be used with 2D graphics, mainly 2.5D, including interaction is well possible through Item-specific transforms. The QML Scene Graph implicitly allows nested transforms and makes perspective drawings an easy task.

I had lots of fun while playing and learning through this small example. Maybe it’s useful to you, or maybe it just makes you as happy as it made me.

The code for this mini Minecraft can be found and played with at: https://github.com/chsterz/isoworld

 

KDAB offers unique Qt expert services. Develop unique Qt and QML applications with KDAB’s expertise. For more information on our services surrounding Qt and QML, click here.

 

About KDAB

If you like this article and want to read similar material, consider subscribing via our RSS feed.

Subscribe to KDAB TV for similar informative short video content.

KDAB provides market leading software consulting and development services and training in Qt, C++ and 3D/OpenGL. Contact us.

Categories: How to / KDAB Blogs / KDAB on Qt / QML / Qt / QtDevelopment / Technical

Tags: /

3 thoughts on “A 3D Block Building Game in QML”

  1. The entire scene moving up and down in the video looks disorientating and annoying for usability.

    1. For usability, I can agree.
      Still, a static world was quite boring to look at 🙂
      So In the end, I still put in a bit of “wobble”.

Leave a Reply

Your email address will not be published. Required fields are marked *