Clay SVG Plugin

The Clay SVG plugin provides comprehensive SVG (Scalable Vector Graphics) support for Clayground applications. It enables reading SVG files to extract game objects, writing SVG files programmatically, and using SVG elements as image sources. This makes it perfect for level design, vector graphics assets, and data-driven game content.

Getting Started

To use the Clay SVG plugin in your QML files:

import Clayground.Svg

Core Components

  • SvgReader - Reads and parses SVG files, emitting signals for each shape found (rectangles, circles, polygons, polylines).
  • SvgWriter - Creates SVG files programmatically with support for basic shapes and metadata.
  • SvgImageSource - Provides access to individual SVG elements as image sources for use in QML Image components.
  • Usage Examples

    Reading SVG for Level Data

    
    import QtQuick
    import Clayground.Svg
    
    Item {
        SvgReader {
            id: levelReader
            source: "levels/level1.svg"
            
            onRectangle: (id, x, y, width, height, fillColor, strokeColor, description) => {
                // Parse description for game object type
                let data = JSON.parse(description)
                
                if (data.type === "platform") {
                    createPlatform(x, y, width, height)
                } else if (data.type === "enemy") {
                    createEnemy(x, y, width, height, data.enemyType)
                }
            }
            
            onCircle: (id, x, y, radius, fillColor, strokeColor, description) => {
                let data = JSON.parse(description)
                if (data.type === "coin") {
                    createCoin(x, y, radius)
                }
            }
            
            onPolyline: (id, points, fillColor, strokeColor, description) => {
                let data = JSON.parse(description)
                if (data.type === "patrol_path") {
                    createPatrolPath(id, points)
                }
            }
        }
    }
    

    Writing Game Data to SVG

    
    SvgWriter {
        id: mapWriter
        path: "output/game_map.svg"
        
        function saveGameState(world) {
            begin(world.width, world.height)
            
            // Save platforms
            for (let platform of world.platforms) {
                rectangle(
                    platform.x,
                    platform.y,
                    platform.width,
                    platform.height,
                    JSON.stringify({
                        type: "platform",
                        material: platform.material
                    })
                )
            }
            
            // Save collectibles
            for (let coin of world.coins) {
                circle(
                    coin.x,
                    coin.y,
                    coin.radius,
                    JSON.stringify({
                        type: "coin",
                        value: coin.value
                    })
                )
            }
            
            // Save paths
            for (let path of world.paths) {
                polyline(
                    path.points,
                    JSON.stringify({
                        type: "path",
                        name: path.name
                    })
                )
            }
            
            end()
        }
    }
    

    Using SVG Elements as Images

    
    import Clayground.Svg
    
    Item {
        SvgImageSource {
            id: svgAssets
            svgPath: "assets/game_sprites.svg"
            annotationRRGGBB: "FF00FF"  // Ignore magenta annotations
        }
        
        Image {
            source: svgAssets.source("player_idle")
            width: 64
            height: 64
        }
        
        Image {
            source: svgAssets.source("enemy_sprite")
            visible: svgAssets.has("enemy_sprite")
        }
    }
    

    Level Editor Integration

    
    Item {
        // Read level template
        SvgReader {
            id: templateReader
            source: "templates/base_level.svg"
            
            property var objects: []
            
            onBegin: {
                // Clear existing objects
                for (let obj of objects) obj.destroy()
                objects = []
            }
            
            onRectangle: (id, x, y, width, height, fillColor, strokeColor, description) => {
                let obj = gameObjectComponent.createObject(gameWorld, {
                    x: x,
                    y: y,
                    width: width,
                    height: height,
                    objectData: JSON.parse(description)
                })
                objects.push(obj)
            }
        }
        
        // Save edited level
        SvgWriter {
            id: levelSaver
            
            function saveLevel(filename) {
                path = filename
                begin(gameWorld.width, gameWorld.height)
                
                for (let obj of templateReader.objects) {
                    if (obj.shape === "rectangle") {
                        rectangle(
                            obj.x,
                            obj.y,
                            obj.width,
                            obj.height,
                            JSON.stringify(obj.objectData)
                        )
                    }
                }
                
                end()
            }
        }
    }
    

    Dynamic Asset Loading

    
    Item {
        property var svgSources: ({})
        
        function loadSvgAssets(category) {
            let source = svgSourceComponent.createObject(this, {
                svgPath: assets/${category}_sprites.svg
            })
            svgSources[category] = source
        }
        
        Component {
            id: svgSourceComponent
            SvgImageSource {}
        }
        
        function getAssetSource(category, assetId) {
            if (svgSources[category] && svgSources[category].has(assetId)) {
                return svgSources[category].source(assetId)
            }
            return ""
        }
    }
    

    SVG-Based Animation Frames

    
    SvgImageSource {
        id: animationFrames
        svgPath: "animations/character_walk.svg"
    }
    
    AnimatedImage {
        property int frame: 0
        source: animationFrames.source("walk_frame_" + frame)
        
        Timer {
            interval: 100
            repeat: true
            running: true
            onTriggered: {
                parent.frame = (parent.frame + 1) % 8
            }
        }
    }
    

    Best Practices

    1. Structured Metadata: Use JSON in description fields for structured game data. 2. Naming Conventions: Use consistent ID naming for SVG elements (e.g., "platform_01", "enemy_goblin"). 3. Coordinate Systems: Ensure your SVG coordinate system matches your game world units. 4. Layer Organization: Use SVG groups to organize different types of game objects. 5. Color Annotations: Use a specific color (like magenta) for editor-only annotations. 6. File Watching: The SvgReader automatically watches files for changes, useful during development.

    Technical Implementation

    The Clay SVG plugin provides:
  • Qt SVG Renderer: Uses Qt's SVG rendering for image provider functionality
  • Custom Parser: Implements streaming XML parser for efficient shape extraction
  • Simple SVG Writer: Uses the simple-svg-writer library for SVG generation
  • Image Provider: Registered as "claysvg" for accessing individual SVG elements
  • File Watching: Automatic file change detection for live reloading
  • Cache Management: Efficient caching of rendered SVG elements
  • The plugin supports standard SVG shapes (rectangles, circles, polygons, polylines) and preserves metadata through description attributes, making it ideal for level design workflows.