Skip to content

Latest commit

 

History

History
215 lines (170 loc) · 8.25 KB

File metadata and controls

215 lines (170 loc) · 8.25 KB

📦 Arquitetura de Plugins

Visão geral da arquitetura de plugins do VibeGame, baseada em ECS (Entity Component System) com bitecs.

Conceitos Fundamentais

Plugin

Ponto de entrada principal. Um plugin registra systems, components, recipes e config em uma única interface Plugin:

export interface Plugin {
  readonly systems?: readonly System[];
  readonly recipes?: readonly Recipe[];
  readonly components?: Record<string, Component>;
  readonly config?: Config;
  readonly initialize?: (state: State) => void | Promise<void>;
}

Plugins são registrados em defaults.ts via DefaultPlugins array.

Component

Dados puros (sem lógica) armazenados em SOA (Struct of Arrays) — plain objects com typed arrays:

import { MAX_ENTITIES } from '../../core/ecs/constants';

export const Fog = {
  mode: new Float32Array(MAX_ENTITIES),
  density: new Float32Array(MAX_ENTITIES),
  colorR: new Float32Array(MAX_ENTITIES),
  colorG: new Float32Array(MAX_ENTITIES),
  colorB: new Float32Array(MAX_ENTITIES),
} as const;

Tipos: Float32Array, Uint8Array, Uint16Array, Uint32Array, Int8Array, Int32Array.

System

Função update(state: State) que roda todo frame (ou a cada fixed tick). Sistemas leem/escrevem Components via queries:

export const FogSystem: System = {
  group: 'draw',        // 'setup' | 'simulation' | 'fixed' | 'draw'
  after: [CameraSyncSystem],  // ordenação opcional
  update(state: State) {
    const entities = fogQuery(state.world);
    // ...
  },
};

Grupos de execução (em ordem): setupfixed (physics) → simulationdraw (render).

Recipe

Atalho para criar entidades com um conjunto pré-definido de components + overrides:

export const playerRecipe: Recipe = {
  name: 'player',
  components: ['player', 'transform', 'body', 'collider', 'character-controller', 'input-state'],
  overrides: {
    'body.type': PLAYER_BODY_DEFAULTS.type,
    'collider.radius': PLAYER_COLLIDER_DEFAULTS.radius,
  },
};

Config

Configuração declarativa do plugin, com 6 seções opcionais:

Seção Tipo Descrição
defaults Record<string, Record<string, number>> Valores padrão para components
enums Record<string, Record<string, EnumMapping>> Mapeia strings → números no XML
adapters Record<string, Record<string, Adapter>> Converte valores XML/JSON para o component
parsers Record<string, Parser> Parsers customizados para elementos XML
shorthands Record<string, Record<string, ShorthandMapping>> Atalhos de atributos
validations ValidationRule[] Regras de validação para recipes

Exemplo de adapter (converte cor hex #ff0000 em R/G/B float):

function fogColorAdapter(entity: number, value: string, state: State): void {
  const num = parseInt(value.slice(1), 16);
  Fog.colorR[entity] = ((num >> 16) & 0xff) / 255;
  Fog.colorG[entity] = ((num >> 8) & 0xff) / 255;
  Fog.colorB[entity] = (num & 0xff) / 255;
}

Estrutura de um Plugin

plugins/
└── meu-plugin/
    ├── plugin.ts        # Exporta MeuPlugin: Plugin (obrigatório)
    ├── components.ts    # defineComponent() para dados ECS
    ├── systems.ts       # Lógica por frame (queries + update)
    ├── recipes.ts       # Atalhos de criação de entidades
    ├── index.ts         # Re-exports públicos
    └── context.md       # Notas de contexto (opcional, para AI agents)

Template de Novo Plugin

1. Component (components.ts)

import { MAX_ENTITIES } from '../../core/ecs/constants';

export const MeuComponent = {
  ativo: new Uint8Array(MAX_ENTITIES),
  valor: new Float32Array(MAX_ENTITIES),
  alvo: new Uint32Array(MAX_ENTITIES),
} as const;

2. System (systems.ts)

import { defineQuery } from '../../core';
import type { State, System } from '../../core';
import { MeuComponent } from './components';

const query = defineQuery([MeuComponent]);

export const MeuSystem: System = {
  group: 'simulation',
  update(state: State) {
    for (const eid of query(state.world)) {
      if (MeuComponent.ativo[eid] === 1) {
        MeuComponent.valor[eid] += state.time.deltaTime;
      }
    }
  },
};

3. Recipe (recipes.ts)

import type { Recipe } from '../../core';

export const meuRecipe: Recipe = {
  name: 'meu-component',
  components: ['meu-component', 'transform'],
  overrides: {
    'meu-component.ativo': 1,
    'meu-component.valor': 0,
  },
};

4. Plugin (plugin.ts)

import type { Plugin } from '../../core';
import { MeuComponent } from './components';
import { MeuSystem } from './systems';
import { meuRecipe } from './recipes';

export const MeuPlugin: Plugin = {
  systems: [MeuSystem],
  recipes: [meuRecipe],
  components: { 'meu-component': MeuComponent },
  config: {
    defaults: {
      'meu-component': { ativo: 1, valor: 0, alvo: 0 },
    },
  },
};

5. Registro (defaults.ts)

Adicione MeuPlugin ao array DefaultPlugins.

6. Re-export (index.ts)

export { MeuComponent } from './components';
export { MeuPlugin } from './plugin';
export { meuRecipe } from './recipes';
export { MeuSystem } from './systems';

Plugins Existentes

Plugin Descrição Complexidade
transforms Posição/rotação/escala 3D Baixa
physics Física (Rapier) + colliders Alta
rendering Three.js renderer, câmeras, cenas Alta
player Controle de jogador (movimento, pulo, câmera) Média
input Teclado/mouse/gamepad Média
orbit-camera Câmera orbital com zoom Média
player-controller Câmera em terceira pessoa (ThirdPersonCamera) Média
fog Neblina volumétrica + fog exp/linear Média
water Água com física, nado, reflexos Alta
terrain Terreno procedural com heightmaps Alta
gltf-xml Carregamento de modelos GLB/GLTF Alta
animation Sistema de animação Média
tweening Interpolações suaves (tweens) Baixa
spawner <SpawnGroup>, <GameObject place="…"> no terreno — context.md Média
startup Execução deferida pós-inicialização Baixa
debug Debug overlays (wireframes, etc.) Baixa
sky Skybox equirectangular + IBL (PMREM) Média
audio Áudio espacial (Howler, <AudioSource>) — docs/AUDIO.md Média
postprocessing Bloom, SMAA, dithering, tonemapping (registry) Alta