The Clay Storage plugin provides persistent local storage capabilities for Clayground applications. It offers a simple key-value store interface built on top of Qt’s LocalStorage module, making it easy to save and retrieve game data, settings, and player progress.

Getting Started

To use the Clay Storage plugin in your QML files:

import Clayground.Storage

Core Components

  • KeyValueStore - A persistent key-value storage component using SQLite database for saving game data, settings, and player progress.

Usage Examples

Basic Storage Operations

import QtQuick
import Clayground.Storage

Item {
    KeyValueStore {
        id: storage
        name: "MyGameData"
    }
    
    Component.onCompleted: {
        // Store data
        storage.set("playerName", "Hero")
        storage.set("highScore", "10000")
        storage.set("level", "5")
        
        // Retrieve data
        let name = storage.get("playerName", "Unknown")
        let score = storage.get("highScore", "0")
        
        // Check existence
        if (storage.has("level")) {
            console.log("Level data exists")
        }
        
        // Remove data
        storage.remove("tempData")
    }
}

Game Settings

KeyValueStore {
    id: settings
    name: "GameSettings"
    
    function saveSoundSettings(enabled, volume) {
        set("soundEnabled", enabled.toString())
        set("soundVolume", volume.toString())
    }
    
    function loadSoundSettings() {
        return {
            enabled: get("soundEnabled", "true") === "true",
            volume: parseFloat(get("soundVolume", "1.0"))
        }
    }
    
    function saveGraphicsQuality(quality) {
        set("graphicsQuality", quality)
    }
    
    function loadGraphicsQuality() {
        return get("graphicsQuality", "medium")
    }
}

Player Progress

Item {
    KeyValueStore {
        id: progressStore
        name: "PlayerProgress"
    }
    
    function saveProgress(level, checkpoint, inventory) {
        progressStore.set("currentLevel", level.toString())
        progressStore.set("checkpoint", checkpoint)
        progressStore.set("inventory", JSON.stringify(inventory))
        progressStore.set("lastSaved", new Date().toISOString())
    }
    
    function loadProgress() {
        if (!progressStore.has("currentLevel")) {
            return null
        }
        
        return {
            level: parseInt(progressStore.get("currentLevel", "1")),
            checkpoint: progressStore.get("checkpoint", "start"),
            inventory: JSON.parse(progressStore.get("inventory", "[]")),
            lastSaved: progressStore.get("lastSaved", "")
        }
    }
    
    function resetProgress() {
        progressStore.remove("currentLevel")
        progressStore.remove("checkpoint")
        progressStore.remove("inventory")
        progressStore.remove("lastSaved")
    }
}

High Score Table

KeyValueStore {
    id: scoreStore
    name: "HighScores"
    
    function saveHighScores(scores) {
        // Save array of score objects
        set("highScores", JSON.stringify(scores))
    }
    
    function loadHighScores() {
        let data = get("highScores", "[]")
        return JSON.parse(data)
    }
    
    function addHighScore(name, score) {
        let scores = loadHighScores()
        scores.push({
            name: name,
            score: score,
            date: new Date().toISOString()
        })
        
        // Sort by score descending
        scores.sort((a, b) => b.score - a.score)
        
        // Keep only top 10
        scores = scores.slice(0, 10)
        
        saveHighScores(scores)
        return scores
    }
}

User Preferences

Item {
    KeyValueStore {
        id: prefStore
        name: "UserPreferences"
    }
    
    // Complex preferences object
    property var preferences: ({
        controls: {
            jumpKey: Qt.Key_Space,
            leftKey: Qt.Key_A,
            rightKey: Qt.Key_D
        },
        display: {
            fullscreen: false,
            resolution: "1920x1080"
        },
        gameplay: {
            difficulty: "normal",
            hints: true
        }
    })
    
    function savePreferences() {
        prefStore.set("preferences", JSON.stringify(preferences))
    }
    
    function loadPreferences() {
        if (prefStore.has("preferences")) {
            let loaded = JSON.parse(prefStore.get("preferences", "{}"))
            // Merge with defaults to handle new preferences
            preferences = Object.assign(preferences, loaded)
        }
    }
}

Migration and Versioning

KeyValueStore {
    id: versionedStore
    name: "GameDataV2"
    
    property string currentVersion: "2.0"
    
    Component.onCompleted: {
        let storedVersion = get("dataVersion", "1.0")
        
        if (storedVersion < currentVersion) {
            migrateData(storedVersion, currentVersion)
        }
        
        set("dataVersion", currentVersion)
    }
    
    function migrateData(fromVersion, toVersion) {
        console.log("Migrating data from", fromVersion, "to", toVersion)
        
        if (fromVersion === "1.0" && toVersion === "2.0") {
            // Perform migration
            let oldScore = get("score", "0")
            set("player.score", oldScore)
            remove("score")
        }
    }
}

Best Practices

  1. Database Names: Use descriptive, unique names for different storage purposes to avoid conflicts.

  2. Data Serialization: Use JSON.stringify/parse for complex data structures.

  3. Default Values: Always provide sensible defaults in the get() method.

  4. Error Handling: The storage operations are synchronous and wrapped in transactions for safety.

  5. Data Types: Remember that all values are stored as strings, so convert numbers and booleans accordingly.

  6. Performance: Avoid excessive read/write operations in loops or animations.

Technical Implementation

The KeyValueStore component:

  • SQLite Backend: Uses Qt’s LocalStorage module which provides SQLite database
  • Automatic Initialization: Creates the key-value table on first use
  • Transaction Safety: All operations are wrapped in database transactions
  • Simple API: Provides a clean key-value interface hiding SQL complexity
  • Persistent Storage: Data persists between application sessions
  • Platform Support: Works on all platforms supported by Qt LocalStorage

The storage location depends on the platform:

  • Desktop: User’s application data directory
  • Mobile: App-specific secure storage area