verekia-architecture
from verekia/r3f-gamedev
⚛️ React Three Fiber Game Dev Recipes
19 stars0 forksUpdated Jan 21, 2026
npx skills add https://github.com/verekia/r3f-gamedev --skill verekia-architectureSKILL.md
Architecture
The core principle of R3F game development is separating game logic from rendering. React components are views, not the source of truth.
Systems vs Views
Systems contain all game logic:
- Movement, physics, collision detection
- Spawning and destroying entities
- State mutations (health, score, timers)
- AI and behavior
- Syncing Three.js objects with entity state
Views (React components) only render:
<PlayerEntity>,<EnemyEntity>wrap models withModelContainer, process any data needed and pass it as props to the model<PlayerModel>,<EnemyModel>are dumb and only render meshes via props- They don't contain core game logic, just visuals logic
- No
useFramein view components unless it is purely visual and should not be part of the core logic
Headless-First Mindset
Games should be capable of running entirely without a renderer:
┌─────────────────────────────────────────┐
│ Game Logic Layer │
│ (Systems, ECS, World State, Entities) │
└─────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────┐
│ View Layer (optional) │
│ React Three Fiber / DOM / Headless │
└─────────────────────────────────────────┘
This means:
- All state lives in the world/ECS, not in React components
- Systems iterate over entities and mutate state
- Views subscribe to state and render accordingly
- You could swap R3F for DOM elements or run tests headlessly
Miniplex: What NOT to Use
From miniplex-react:
ECS.Entity- Don't use this componentECS.Component- Don't use this componentECS.world- Don't access world through ECS, use direct importuseEntitieshook - Don't use this- Render props pattern - Don't use this
From miniplex core:
onEntityAdded/onEntityRemoved- Prefer using data and systems to trigger things (e.g., timers, flags).where()- Don't use predicate-based filtering, prefer iterating over all entities that have the component no matter its value. For example iterate over all entities that have health and filter out entities that have health < 0 in the system rather than querying entities where health < 0 (which would require reindexing).
Miniplex: Preferred Methods
Only use these:
world.add(entity)- Add a new entityworld.remove(entity)- Remove an entityworld.addComponent(entity, 'component', value)- Add component to existing entityworld.removeComponent(entity, 'component')- Remove component from entityworld.with('prop1', 'prop2')- Create queriescreateReactAPI(world)- GetEntitiescomponent for rendering
Entity Types and Queries
// lib/ecs.ts
import { World } from 'miniplex'
import createReactAPI from 'miniplex-react'
type Entity = {
position?: { x: number; y: number; z: number }
velocity?: { x: number; y: number; z: number }
isCharacter?: true
isEnemy?: true
three?: Object3D | null
}
export const world = new World<Entity>()
export const characterQuery = world.with('position', 'isCharacter', 'three')
export type CharacterEntity = (typeof characterQuery)['entities'][number]
// Only destructure Entities from React API
export const { Entities } = createReactAPI(world)
ModelContainer Pattern
Capture Three.js object references on entities using a wrapper component, allowing systems to manipulate objects directly.
Similar to the Redux container/component pattern:
*Entitycomponents are smart wrappers that connect entity data to the view*Modelcomponents are dumb and only responsible for rendering
┌─────────────────────────────────────────┐
│ PlayerEntity (smart) │
│ - Wraps with ModelContainer │
│ - Passes entity data as props │
│ │
│ ┌─────────────────────────────────┐ │
│ │ PlayerModel (dumb) │ │
│ │ - Pure rendering │ │
│ │ - Receives props │ │
│ │ - No knowledge of entities │ │
│ └─────────────────────────────────┘ │
└─────────────────────────────────────────┘
- Ref callback stores the Three.js object on the entity
- Cleanup function removes the reference when unmounted
- Systems access
entity.threedirectly inuseFrame - Models are reusable and testable in isolation
Entity as Props Pattern
The component passed to <Entities> receives the entity directly as props:
// Dumb component - only renders, no entity knowledge
const CharacterModel = () => (
<mesh>
<sphereGeometry />
<meshBasicMaterial color="blue" />
</mesh>
)
// Smart wrapper - connects entity to model via ModelContainer
const CharacterEntity = (entity: CharacterEntity) => (
<ModelContainer entity={entity}>
<CharacterModel />
</ModelContainer>
)
// entities/entities.tsx (contains <Entities> for all renderable entities)
const isCharacterQuery = world.
...
Repository
verekia/r3f-gamedevParent repository
Repository Stats
Stars19
Forks0
LicenseMIT License