Skip to main content

State

The State element provides persistent state management across animation frames.

Access

const draw = (K: KlintContext) => {
// Access the State element
K.State.set("frameCount", K.frame);
const lastFrame = K.State.get("frameCount");
}

Methods

set(key, value, callback?)

set(key: string, value: any, callback?: (key: string, value: any) => void) => void

Stores a value with an optional callback.

// Basic usage
K.State.set("score", 100);

// With callback
K.State.set("playerPosition", { x: 200, y: 300 }, (key, value) => {
console.log(`Updated ${key}:`, value);
});

get(key, callback?)

get(key: string, callback?: (key: string, value: any) => void) => any

Retrieves a stored value with optional callback.

const position = K.State.get("playerPosition");

// With callback
const score = K.State.get("score", (key, value) => {
if (value > 1000) console.log("High score!");
});

has(key)

has(key: string) => boolean

Checks if a key exists in state.

if (!K.State.has("gameStarted")) {
K.State.set("gameStarted", true);
K.State.set("startTime", K.time);
}

delete(key, callback?)

delete(key: string, callback?: (key: string) => void) => void

Removes a key from state with optional callback.

K.State.delete("tempData");

// With callback
K.State.delete("oldScore", (key) => {
console.log(`Removed ${key}`);
});

log()

log() => Map<string, any>

Returns the entire state Map for debugging.

console.log("Current state:", K.State.log());

Common Patterns

Initialization

const draw = (K: KlintContext) => {
// Initialize state on first frame
if (!K.State.has("initialized")) {
K.State.set("initialized", true);
K.State.set("particles", []);
K.State.set("score", 0);
K.State.set("gameMode", "menu");
}

// Use state
const gameMode = K.State.get("gameMode");
if (gameMode === "menu") {
drawMenu(K);
} else if (gameMode === "playing") {
drawGame(K);
}
}

Persistent Animation Data

const draw = (K: KlintContext) => {
// Initialize particles
if (!K.State.has("particles")) {
const particles = [];
for (let i = 0; i < 50; i++) {
particles.push({
x: Math.random() * K.width,
y: Math.random() * K.height,
vx: (Math.random() - 0.5) * 4,
vy: (Math.random() - 0.5) * 4,
life: 1
});
}
K.State.set("particles", particles);
}

// Update particles
const particles = K.State.get("particles");
particles.forEach(particle => {
particle.x += particle.vx;
particle.y += particle.vy;
particle.life -= 0.01;

// Respawn dead particles
if (particle.life <= 0) {
particle.x = Math.random() * K.width;
particle.y = Math.random() * K.height;
particle.life = 1;
}
});

// Draw particles
K.background("#111");
particles.forEach(particle => {
K.fillColor(`rgba(255, 255, 255, ${particle.life})`);
K.circle(particle.x, particle.y, 3);
});
}

Game State Management

const draw = (K: KlintContext) => {
const { mouse, onClick } = KlintMouse();

// Initialize game state
if (!K.State.has("gameState")) {
K.State.set("gameState", {
mode: "menu",
score: 0,
level: 1,
lives: 3,
enemies: [],
powerups: []
});
}

const game = K.State.get("gameState");

// Handle click events
onClick(() => {
if (game.mode === "menu") {
game.mode = "playing";
game.startTime = K.time;
} else if (game.mode === "gameOver") {
// Reset game
game.mode = "menu";
game.score = 0;
game.level = 1;
game.lives = 3;
}
});

// Game logic based on mode
switch (game.mode) {
case "menu":
drawMenu(K, game);
break;
case "playing":
updateGame(K, game);
drawGame(K, game);
break;
case "gameOver":
drawGameOver(K, game);
break;
}
}

function updateGame(K, game) {
// Spawn enemies
if (!K.State.has("lastEnemySpawn")) {
K.State.set("lastEnemySpawn", K.time);
}

if (K.time - K.State.get("lastEnemySpawn") > 2) {
game.enemies.push({
x: K.width,
y: Math.random() * K.height,
speed: 2 + game.level * 0.5
});
K.State.set("lastEnemySpawn", K.time);
}

// Update enemies
game.enemies = game.enemies.filter(enemy => {
enemy.x -= enemy.speed;
return enemy.x > -50;
});

// Check for level progression
if (game.score > game.level * 100) {
game.level++;
}
}

State with Validation

const draw = (K: KlintContext) => {
// Initialize with validation
if (!K.State.has("settings")) {
const defaultSettings = {
volume: 0.5,
difficulty: "normal",
showFPS: false
};

K.State.set("settings", defaultSettings, (key, value) => {
// Validate settings
if (value.volume < 0 || value.volume > 1) {
console.warn("Volume must be between 0 and 1");
value.volume = Math.max(0, Math.min(1, value.volume));
}

if (!["easy", "normal", "hard"].includes(value.difficulty)) {
console.warn("Invalid difficulty, resetting to normal");
value.difficulty = "normal";
}
});
}

// Use settings
const settings = K.State.get("settings");

// Show FPS if enabled
if (settings.showFPS) {
K.fillColor("white");
K.textSize(16);
K.text(`FPS: ${Math.round(1000 / K.deltaTime)}`, 20, 30);
}
}

Undo/Redo System

const draw = (K: KlintContext) => {
const { mouse, onClick } = KlintMouse();

// Initialize history
if (!K.State.has("history")) {
K.State.set("history", {
states: [{ points: [] }],
currentIndex: 0
});
}

const history = K.State.get("history");
const currentState = history.states[history.currentIndex];

// Add point on click
onClick(() => {
const newState = {
points: [...currentState.points, { x: mouse.x, y: mouse.y }]
};

// Remove any states after current index (for branching)
history.states = history.states.slice(0, history.currentIndex + 1);

// Add new state
history.states.push(newState);
history.currentIndex++;

// Limit history size
if (history.states.length > 50) {
history.states.shift();
history.currentIndex--;
}
});

// Undo/Redo with keyboard (simplified)
// In real app, you'd use proper key event handling

K.background("#222");

// Draw all points
currentState.points.forEach((point, i) => {
K.fillColor(`hsl(${i * 10}, 70%, 60%)`);
K.circle(point.x, point.y, 10);
});

// UI
K.fillColor("white");
K.textSize(14);
K.text(`Points: ${currentState.points.length}`, 20, 30);
K.text(`History: ${history.currentIndex}/${history.states.length - 1}`, 20, 50);
}

Notes

  • State persists across all animation frames until manually deleted
  • Use State for data that needs to survive between draw calls
  • State is instance-specific - different Klint components have separate state
  • Perfect for game state, animation data, user preferences
  • State callbacks are useful for validation and logging
  • Consider using meaningful key names to avoid conflicts
  • For complex state, consider storing objects rather than individual values
  • State is kept in memory - large datasets should be managed carefully