Минимальный запуск12 / 12

Создаём пустой проект

Запустим 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"]
}
01
npm run devVite читает game/index.html
02
main.tsПередаёт GameConfig в Engine.run
03
mainBaseScene создаёт корень сцены без layout и FSM
Полный пример создаёт пустой синий canvas. Вёрстку и правила добавим через API следующих уроков.

Выполним npm run typecheck, затем npm run dev. Ожидаемый результат — синий canvas без ошибок. preload: [] указан явно: без него движок пытается загрузить стандартные наборы common и layouts, которых здесь нет.

К программе курса →