Создаём пустой проект
Запустим canvas без игровых правил, ресурсов и FSM.
Теперь соберём минимальный проект: конфигурацию, одну сцену и её зависимости. Создадим папку и установим пакеты SDK:
mkdir sdk-game
cd sdk-game
npm init -y
npm pkg set type=module
npm pkg set 'scripts.dev=VITE_PLATFORM=web vite' 'scripts.build=VITE_PLATFORM=web vite build' 'scripts.typecheck=tsc --noEmit'
npm install --save-exact @gamedevland/engine
npm install --save-dev --save-exact @gamedevland/vite vite@8.0.16 typescript@5.9.3
mkdir -p game/src
Команды с VITE_PLATFORM=web показаны для macOS/Linux. В Windows ту же переменную можно задать через файл .env в корне проекта.
Файлы проекта
В vite.config.ts настроим сборку. Корнем браузерной части станет папка game, результат попадёт в dist:
import { createGameViteConfig } from '@gamedevland/vite';
export default ({ mode }: { readonly mode: string }) =>
createGameViteConfig({
mode,
repoRoot: import.meta.dirname,
});
Создадим game/index.html с контейнером и точкой входа. Такой способ подключения используется в index.html Block Puzzle:
<!doctype html>
<html lang="ru">
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1"><title>SDK Game</title></head>
<body style="margin:0"><div id="app"></div><script type="module" src="/src/main.ts"></script></body>
</html>
Даже пустой сцене требуется определение DI. Пока нечего регистрировать, поэтому методы остаются пустыми. Классы разместим в отдельных файлах внутри game/src.
AppGlobalDi.ts описывает общие регистрации игры. Пока оставим их пустыми:
import { GlobalDiDefinition } from '@gamedevland/engine/di';
export class AppGlobalDi extends GlobalDiDefinition {
protected override registerElements(): void {}
protected override registerComponents(): void {}
protected override registerCommands(): void {}
protected override registerServices(): void {}
}
В MainSceneDi.ts укажем имя сцены. Зависимостей у неё пока нет:
import { SceneDiDefinition } from '@gamedevland/engine/di';
export class MainSceneDi extends SceneDiDefinition {
override getSceneKey(): string { return 'main'; }
protected override registerProviders(): void {}
protected override registerServices(): void {}
}
В AppDi.ts объединим общие регистрации и зависимости сцен:
import { GameDiDefinition } from '@gamedevland/engine/di';
import { AppGlobalDi } from './AppGlobalDi';
import { MainSceneDi } from './MainSceneDi';
export class AppDi extends GameDiDefinition {
protected override createGlobalDefinition(): AppGlobalDi {
return new AppGlobalDi();
}
protected override createSceneDefinitions(): MainSceneDi[] {
return [new MainSceneDi()];
}
}
В main.ts соберём конфигурацию и передадим её движку:
import { Engine } from '@gamedevland/engine';
import { BaseScene } from '@gamedevland/engine/scenes';
import { AppDi } from './AppDi';
import type { GameConfig } from '@gamedevland/engine/core';
const config = {
system: { canvasBackground: '#1d57c8' },
assets: { bundles: [], preload: [] },
scenes: { initial: 'main', map: { main: { key: 'main', useClass: BaseScene } } },
di: new AppDi(),
} satisfies GameConfig;
await Engine.run('#app', config);
В корне создадим tsconfig.json:
{
"compilerOptions": {
"target": "ES2022",
"module": "ESNext",
"moduleResolution": "Bundler",
"strict": true,
"noEmit": true,
"skipLibCheck": true,
"lib": ["ES2022", "DOM"],
"types": ["vite/client"]
},
"include": ["game/src/**/*.ts"]
}
Выполним npm run typecheck, затем npm run dev. Ожидаемый результат — синий canvas без ошибок. preload: [] указан явно: без него движок пытается загрузить стандартные наборы common и layouts, которых здесь нет.