A SvelteKit-based mobile game engine template for building 2D casual/puzzle games. PWA-first with all the systems you need to build production-ready mobile games.
gu (game units)# Clone this template
git clone https://github.com/yourusername/sveltegame.git my-game
cd my-game
# Install dependencies
npm install
# Start development server
npm run dev
# Run tests
npm test
src/
├── lib/
│ ├── engine/ # Game engine systems
│ │ ├── core/ # GameContainer, viewport, units
│ │ ├── scenes/ # SceneManager, scene store
│ │ ├── state/ # Game state store factory
│ │ ├── save/ # SaveManager with migrations
│ │ ├── input/ # InputManager (touch/gestures)
│ │ ├── tween/ # Tween engine with easings
│ │ ├── audio/ # SoundManager
│ │ ├── haptics/ # HapticManager
│ │ ├── achievements/ # AchievementManager
│ │ ├── analytics/ # AnalyticsManager
│ │ └── platform/ # PlayStore hooks
│ └── components/ # UI components
├── routes/ # SvelteKit pages
└── app.css # Global styles with CSS variables
The GameContainer wraps your game and provides responsive scaling:
<script>
import { GameContainer } from '$engine';
</script>
<GameContainer>
<!-- Your game content -->
</GameContainer>
Use game units for consistent sizing across devices:
<script>
import { gu, guh } from '$engine';
</script>
<div style="width: {gu`50`}; height: {guh`30`};">
<!-- This will be 50% of game width, 30% of game height -->
</div>
Define scenes and navigate between them:
<script>
import { SceneManager, createSceneStore } from '$engine';
import MenuScene from './MenuScene.svelte';
import GameScene from './GameScene.svelte';
const scenes = [
{ id: 'menu', component: MenuScene },
{ id: 'game', component: GameScene, onEnter: () => console.log('Game started!') }
];
</script>
<SceneManager {scenes} initialScene="menu" />
Navigate from within a scene:
<script>
import { getContext } from 'svelte';
const sceneStore = getContext(Symbol.for('scene-store'));
function startGame() {
sceneStore.goto('game', { type: 'fade', duration: 300 });
}
</script>
Create typed game stores:
import { createGameStore } from '$engine';
interface GameState {
score: number;
level: number;
lives: number;
}
const gameStore = createGameStore<GameState>({
score: 0,
level: 1,
lives: 3
});
// Update state
gameStore.patch({ score: 100 });
// Subscribe to changes
gameStore.subscribe(state => console.log('Score:', state.score));
Persist game data with automatic migrations:
import { createSaveManager } from '$engine';
const saveManager = createSaveManager<SaveData>('my-game-save', {
version: 2,
migrations: [
{
version: 2,
migrate: (data) => ({ ...data, newField: 'default' })
}
]
});
// Save
saveManager.save({ score: 100, level: 5 });
// Load with default
const data = saveManager.loadOrDefault({ score: 0, level: 1 });
Handle touch gestures:
import { createInputManager } from '$engine';
const input = createInputManager(element);
input.onTap((pos) => console.log('Tapped at', pos));
input.onSwipe((dir, velocity) => console.log('Swiped', dir));
input.onDrag((start, current, delta) => console.log('Dragging'));
input.onPinch((scale, center) => console.log('Pinch zoom', scale));
// Cleanup
onDestroy(() => input.destroy());
Animate values with easing:
import { createTween, easeOutBounce } from '$engine';
const tween = createTween({
from: 0,
to: 100,
duration: 1000,
easing: easeOutBounce,
onUpdate: (value) => {
element.style.transform = `translateY(${value}px)`;
},
onComplete: () => console.log('Done!')
});
tween.start();
Play sounds and music:
import { createSoundManager } from '$engine';
const sound = createSoundManager({ soundsPath: '/sounds/' });
// Preload sounds
await sound.preload(['click', 'success', 'background']);
// Play effects
sound.play('click');
sound.play('success', 0.5); // 50% volume
// Background music
sound.playMusic('background', true); // loop
// Volume control
sound.setVolume(0.8);
sound.setMuted(true);
Provide tactile feedback:
import { createHapticManager } from '$engine';
const haptic = createHapticManager();
haptic.tap(); // Quick tap feedback
haptic.success(); // Success pattern
haptic.error(); // Error pattern
haptic.heavy(); // Strong vibration
Track player achievements:
import { createAchievementManager } from '$engine';
const achievements = createAchievementManager([
{ id: 'first_win', name: 'First Victory', description: 'Win your first game' },
{ id: 'score_100', name: 'Century', description: 'Score 100 points' }
], 'my-game-achievements');
// Unlock
if (achievements.unlock('first_win')) {
// Show notification - this was newly unlocked
}
// Check progress
const progress = achievements.getProgress();
console.log(`${progress.unlocked}/${progress.total} (${progress.percentage}%)`);
// Listen for unlocks
achievements.onUnlock((achievement) => {
showAchievementToast(achievement);
});
Track game events:
import { createAnalyticsManager, createConsoleAnalyticsProvider } from '$engine';
const analytics = createAnalyticsManager();
// Use console provider for development
analytics.setProvider(createConsoleAnalyticsProvider());
// Track events
analytics.levelStart(5);
analytics.levelComplete(5, 1000, 3);
analytics.levelFail(5, 'out_of_moves');
analytics.purchase('gem_pack', 4.99, 'USD');
// Custom events
analytics.track({ name: 'custom_event', params: { foo: 'bar' } });
The template is configured as a PWA with:
manifest.json with game metadataTo customize, edit static/manifest.json and add your icons to static/icons/.
When ready to publish to the Play Store, wrap with Capacitor:
npm install @capacitor/core @capacitor/android
npx cap init
npx cap add android
Then implement the PlayStoreHooks interface with Capacitor plugins for:
npm run dev # Start dev server
npm run build # Build for production
npm run preview # Preview production build
npm run test # Run tests in watch mode
npm run test:run # Run tests once
npm run check # Type check
Customize the look by overriding CSS custom properties:
:root {
--color-bg: #1a1a2e;
--color-surface: #16213e;
--color-primary: #e94560;
--color-secondary: #0f3460;
--color-text: #ffffff;
--color-text-muted: #a0a0a0;
}
MIT