World Plugin
The Clay World plugin provides comprehensive game world management for both 2D and 3D games. It integrates canvas rendering, physics simulation, and SVG-based level loading into cohesive world components. The plugin offers separate implementations for 2D and 3D worlds while sharing common functionality like scene loading and entity management.
Getting Started
To use the Clay World plugin in your QML files:
import Clayground.World
Core Components
- ClayWorld2d - Complete 2D game world with Box2D physics, ClayCanvas rendering, and camera following
- ClayWorld3d - Complete 3D game world with Qt Quick 3D physics and WASD/orbit camera controls (desktop only:
QtQuick3D.Physicsis not in the Web Runtime) - ClayWorldBase - Shared base functionality for scene loading and entity management
- SceneLoader2d - Loads entities from SVG files into 2D worlds
- SceneLoader3d - Loads entities from SVG files into 3D worlds
- Minimap2d - Miniature map view of a 2D world
- Box3DBody - 3D physics-enabled box shape (desktop only, like ClayWorld3d)
Usage Examples
Basic 2D World
import QtQuick
import Clayground.World
import Clayground.Physics
ClayWorld2d {
id: gameWorld
anchors.fill: parent
// World setup
xWuMin: 0
xWuMax: 100
yWuMin: 0
yWuMax: 50
// Physics
gravity: Qt.point(0, 10)
// Camera follows player
observedItem: player
RectBoxBody {
id: player
xWu: 10
yWu: 10
widthWu: 2
heightWu: 2
color: "blue"
bodyType: Body.Dynamic
}
}
Level Loading from SVG
ClayWorld2d {
id: world
scene: "levels/level1.svg"
loadMapAsync: true
// Register components for SVG loading
components: new Map([
["Wall", wallComponent],
["Enemy", enemyComponent],
["Collectible", coinComponent]
])
Component {
id: wallComponent
RectBoxBody {
color: "#333333"
bodyType: Body.Static
}
}
Component {
id: enemyComponent
RectBoxBody {
color: "red"
bodyType: Body.Dynamic
}
}
onMapLoaded: {
console.log("Level loaded!")
}
}
3D World Setup
ClayWorld3d {
id: world3d
anchors.fill: parent
// World size
xWuMax: 200
zWuMax: 200
// Camera setup
observedObject: player3d
Box3DBody {
id: player3d
position: Qt.vector3d(50, 10, 50)
width: 20
height: 20
depth: 20
color: "orange"
}
// Load 3D scene
scene: "levels/3d_level.svg"
components: new Map([
["Platform", platform3dComponent]
])
}
Areas in a 3D World
A polygon in the map - a lake, a plaza, a building footprint - becomes a 3D
entity the same way a rectangle does: its description names a registered
component, and the loader hands over the ring in world coordinates plus the
SVG fill as color. A Poly3D needs nothing else.
ClayWorld3d {
id: world
scene: "levels/town.svg"
components: new Map([["Area", areaComponent]])
Component {
id: areaComponent
Poly3D { useToonShading: true }
}
}
Height is not a mechanism of its own: it rides the properties section every
loaded entity has, so a footprint that should stand up says so in its SVG
description.
{"component":"Area","properties":{"extrude":12}}
The scene coordinates a loader works in are Y-up with the origin at the map’s
bottom left, while a 3D world spans x: [0, xWuMax] and z: [0, zWuMax]. So
the map’s Y becomes zWuMax - y - the conversion SceneLoader3d::toWorldRing()
does, and the same one the rectangle handler applies to a rectangle’s center.
Seen from above, the world is the SVG as it was drawn.
A polygon whose description names no registered component is not dropped: it
reaches polygonLoaded with its scene points untouched, so a sandbox can build
something the loader would not have guessed - an area with holes, for instance,
which an SVG polygon cannot express on its own.
ClayWorld3d {
id: world
onPolygonLoaded: (id, groupId, points, fillColor, strokeColor, description) => {
let ring = points.map(p => Qt.vector2d(p.x, world.zWuMax - p.y));
pondComponent.createObject(world.root, {vertices: ring, color: fillColor});
}
}
See demo/Sandbox3dScene.qml and demo/areas.svg for both routes side by side.
Minimap Implementation
ClayWorld2d {
id: mainWorld
// ... world setup
}
Minimap2d {
world: mainWorld
width: 200
height: 150
anchors.right: parent.right
anchors.top: parent.top
anchors.margins: 20
// Map entity types to minimap representations
typeMapping: new Map([
["Player", playerDotComponent],
["Enemy", enemyDotComponent],
["Wall", wallRectComponent]
])
Component {
id: playerDotComponent
Rectangle {
color: "green"
radius: width * 0.5
}
}
Component {
id: enemyDotComponent
Rectangle { color: "red" }
}
}
Dynamic Entity Creation
ClayWorld2d {
id: world
function spawnEnemy(x, y) {
let enemy = enemyComponent.createObject(room, {
xWu: x,
yWu: y
})
return enemy
}
Component {
id: enemyComponent
RectBoxBody {
widthWu: 2
heightWu: 2
color: "red"
bodyType: Body.Dynamic
}
}
Timer {
interval: 5000
repeat: true
running: true
onTriggered: {
world.spawnEnemy(
Math.random() * world.xWuMax,
Math.random() * world.yWuMax
)
}
}
}
Custom Scene Loading
ClayWorld2d {
id: world
onPolylineLoaded: (id, groupId, points, fillColor, strokeColor, description) => {
// Handle patrol paths
if (description.includes("patrol_path")) {
createPatrolPath(id, points)
}
}
onGroupAboutToBeLoaded: (id, description) => {
console.log("Loading group:", id)
}
onMapEntityCreated: (obj, groupId, compName) => {
// Post-process loaded entities
if (compName === "Enemy") {
obj.target = player
}
}
}
Physics Configuration
ClayWorld2d {
// Platformer physics
gravity: Qt.point(0, 20)
timeStep: 1/60
// Top-down physics
// gravity: Qt.point(0, 0)
// Debug rendering
debugPhysics: true
debugRendering: true
// Pause physics
MouseArea {
anchors.fill: parent
onClicked: parent.physicsEnabled = !parent.physicsEnabled
}
}
Best Practices
-
Scene Organization: Use SVG groups to organize level elements logically.
-
Component Registration: Register all components before loading scenes.
-
Async Loading: Use
loadMapAsync: truefor large levels to prevent UI freezing. -
World Units: Always work in world units for consistency across different screen sizes.
-
Entity Management: Use the
roomcontainer for proper entity lifecycle management. -
Performance: Limit physics bodies and use static bodies where possible.
Technical Implementation
The Clay World plugin provides:
- Unified World Management: Common base for 2D and 3D worlds
- SVG Scene Loading: Parse SVG files with JSON metadata for game entities
- Physics Integration: Seamless Box2D (2D) and Qt Quick 3D Physics integration
- Camera Systems: Automatic following for 2D, orbit/WASD controls for 3D
- Async Loading: Non-blocking entity creation for smooth gameplay
- Component Factory: Map SVG descriptions to QML components
The plugin handles coordinate transformations, physics body creation, and proper parent-child relationships automatically, making it easy to create complex game worlds from simple SVG layouts.