A React Native game library for building high-performance sliding block puzzles. Slide colored segments on a grid, fill rows to clear them, and watch blocks cascade. Built with Skia, Reanimated, and RxJS—60fps on iOS and Android.
This is a library package, not a standalone app. Install it in your React Native project and integrate the game with your own UI, assets, and logic.
npm install react-native-sliding-blocks
# or
yarn add react-native-sliding-blocksInstall these in your app (they are peer dependencies):
| Package | Purpose |
|---|---|
react |
UI framework |
react-native |
Mobile runtime |
@shopify/react-native-skia |
2D graphics & canvas |
react-native-reanimated |
Animations |
react-native-gesture-handler |
Touch and gestures |
react-native-worklets |
UI thread worklets |
rxjs |
Reactive streams |
Requirements: Node.js ≥ 22.11.0
import { SlidingBlocks } from 'react-native-sliding-blocks'
function GameScreen() {
return (
<SlidingBlocks
config={{
rowsCount: 10,
columnsCount: 8,
padding: 16,
explosionPoolSize: 80,
keysSize: 5
}}
callbacks={{
onScoreChange: (score) => console.log('Score:', score),
onGameOver: (score) => console.log('Game over:', score),
onRemovingStart: () => playClearSound(),
onFitComplete: ({ hadActualFit }) => hadActualFit && playSlideSound()
}}
/>
)
}The library uses Skia-drawn blocks and a solid background by default. No assets required.
All-in-one component. Pass config, callbacks, and optional assets/theme.
<SlidingBlocks
config={SlidingBlocksConfig}
callbacks?: SlidingBlocksCallbacks
assets?: SlidingBlocksAssets
theme?: Partial<SlidingBlocksTheme>
settings?: SlidingBlocksSettingsOverrides
engine?: IGameEngine
initialState?: GameStateSnapshot | null
onGameStateChange?: (state: GameStateSnapshot) => void
blockRenderMode?: 'skia' | 'image'
showFinishOption?: boolean
onLoadProgress?: (progress: number) => void
onLoadComplete?: () => void
/>| Prop | Type | Description |
|---|---|---|
config |
SlidingBlocksConfig |
Required. Grid layout: rowsCount, columnsCount, padding, explosionPoolSize, keysSize |
callbacks |
SlidingBlocksCallbacks |
Score, game over, pause, restart, sound hooks, etc. |
assets |
SlidingBlocksAssets |
Block PNGs, background image. Omit for Skia fallbacks |
theme |
Partial<SlidingBlocksTheme> |
Overlay, score bar, block colors |
settings |
SlidingBlocksSettingsOverrides |
Block, explosion, checkerboard, explosionPresets, animations (durations in ms), feedback (opacity values). Tune for performance: explosionPresets.explosionEnabled: false disables explosion; circlesOnly: true uses circles-only particles; set animations.* to 0 to disable animations. |
engine |
IGameEngine |
Optional pre-created engine |
initialState |
GameStateSnapshot | null |
Restore from persisted state; host loads from storage to resume after app kill |
onGameStateChange |
(state: GameStateSnapshot) => void |
Called when state changes; host should persist (e.g. AsyncStorage). Snapshot includes gameOver flag; host typically clears when game over. |
blockRenderMode |
'skia' | 'image' |
'skia' = draw blocks (default), 'image' = PNG assets |
showFinishOption |
boolean |
Show "Finish" in pause overlay; use with onFinish |
const ref = useRef<SlidingBlocksHandle>(null)
<SlidingBlocks ref={ref} config={...} callbacks={...} />
// Then:
ref.current?.pause()
ref.current?.resume()
ref.current?.restart()
ref.current?.isPaused() // boolean
ref.current?.getGameState() // GameStateSnapshot for persistenceBuild custom layouts (e.g. custom score bar, different ordering).
import { useSlidingBlocks } from 'react-native-sliding-blocks'
function CustomGameScreen() {
const { Root, ScoreBar, GameArea, ref } = useSlidingBlocks({
config: { rowsCount: 10, columnsCount: 8, padding: 16, explosionPoolSize: 80, keysSize: 5 },
callbacks: { onScoreChange, onGameOver }
})
return (
<Root>
<MyCustomScoreBar />
<GameArea blockRenderMode="skia" onLoadComplete={() => setReady(true)} />
</Root>
)
}Returns:
Root— Wrapper; must wrapScoreBarandGameAreaScoreBar— Default score barGameArea— Game canvas (blocks, grid, overlays)ref—SlidingBlocksHandle(pause, resume, restart, isPaused, getGameState)
For fully custom layouts, use useComposableSlidingBlocksContext() inside Root to access layout and shared values.
GameRootView— Minimal wrapper when you need full control over layout and bridge.createGameEngine(config, host?, options?)— Create a React-agnostic engine for headless testing or custom integration.optionscan includeonRowAdded,animOverrides(removeFadeMs,itemDropMs),initialState(restore from persisted state), andonGameStateChange(called when state changes for host persistence).GameStateSnapshot,isSnapshotCompatible(snapshot, config),getLayoutVersion(config)— Types and helpers for game state persistence. UseisSnapshotCompatiblebefore resuming to ensure saved state matches current layout.PreloaderOverlay— Loading overlay; optionalfillAnimationDurationMsprop. Usesettings.animations.loadingBarFillMsfor consistency.scheduleIdle,cancelIdle,GESTURE_SENSITIVITY, layout constants (TOP_PAUSE, SCORE_BAR, etc.) — Exported for advanced use.
The library provides serializable game state for host-side persistence. Persistence (e.g. AsyncStorage) is the host's responsibility.
| Export | Purpose |
|---|---|
GameStateSnapshot |
Serializable type: rows, score, multiplier, layoutVersion, gameOver |
getGameState() |
Engine / handle method — returns current state |
initialState prop |
Pass loaded state to resume after app kill |
onGameStateChange |
Callback — persist when state changes; host typically clears when gameOver: true |
isSnapshotCompatible(snapshot, config) |
Validate saved state matches current layout before resume |
Example flow:
- On mount: load from storage → if valid and not game over → pass as
initialState. - On state change:
onGameStateChange(state)→ persist to storage. - On game over: clear stored state (host decides).
- Use
isSnapshotCompatibleso layout changes (e.g. settings) don't corrupt restore.
type SlidingBlocksConfig = {
rowsCount: number // e.g. 10
columnsCount: number // e.g. 8
padding: number // screen padding (px)
explosionPoolSize: number
keysSize: number // block types (colors)
}| Callback | When |
|---|---|
onScoreChange |
After each row clear |
onGameOver |
Game ends |
onPause / onResume |
User pauses/resumes |
onRestart |
User restarts (from overlay) |
onFinish |
User taps "Finish" in pause overlay |
onGestureStart / onGestureEnd |
Pan starts/ends |
onRemovingStart / onRemovingEnd |
Row clear animation starts/ends; payload { hasSuper: boolean } |
onFitStart / onFitComplete |
Slide/snap animation; onFitComplete({ hadActualFit }) for slide sound (hadActualFit = true when blocks actually moved) |
onRowAdded |
New row added at top |
| Asset | Description |
|---|---|
blockImages |
Map of color hex → [1×1, 1×2, 1×3, 1×4] image sources (e.g. require(...)) |
backgroundImage |
Full-screen background image source |
Omit for fallbacks: solid background, Skia-drawn blocks.
Partial overrides merged with defaults. All optional.
| Category | Fields | Description |
|---|---|---|
block |
radius, borderWidth, borderColor, frostHighlightColor, frostHighlightHeightRatio, superGradientColors, superGradientSteps |
Block appearance |
explosion |
radius, baseParticleSize, riseHeight, fallDistance, pictureSize |
Explosion particles |
checkerboard |
defaultBaseColor, defaultDarkOpacity, defaultLightOpacity |
Grid styling |
explosionPresets |
particleCount, trajectoryPresetCount, shapePresetCount, circlesOnly, explosionEnabled |
Explosion variety. Set explosionEnabled: false to disable. circlesOnly: true for faster rendering. |
animations |
completeSnapMs, itemDropMs, willRemovePulseMs, removeFadeMs, removeExplosionMs, gameOverInMs, gameOverOutMs, pauseOverlayMs, loadingBarFillMs |
Animation durations (ms). removeFadeMs = block opacity fade; removeExplosionMs = explosion particles. |
feedback |
blockIdle, willRemovePulseMin, ghostActive, indicatorActive |
Opacity values (0–1) for blocks, ghost, indicator |
Performance presets are host-defined. Create presets as SlidingBlocksSettingsOverrides (e.g. blockRenderMode, explosionPresets, animations) and apply via your settings store. The example app defines four presets—extra-low, low, fine, good—in example/src/settings/performancePresets.ts and exposes them as one-tap buttons with a "Custom settings" section for manual overrides.
- Grid — Configurable columns × rows (e.g. 8×10)
- Blocks — Colored segments (1–4 cells). Drag left/right to reposition.
- Clearing — Fill a row completely to clear it; blocks above drop.
- Super segments — Rare black segments; when cleared, also wipe overlapping blocks in adjacent rows for combos.
- Score & multiplier — Animated display.
The package uses MVVM with RxJS and Reanimated. The engine is React-agnostic; a bridge wires RxJS streams to SharedValues. No React commits during gameplay—all rendering is driven by SharedValues and Skia.
For a deep dive, see src/CONCEPTS.md.
| Layer | Path | Responsibility |
|---|---|---|
| Engine | engine/ |
Pure game logic, RxJS, no React |
| Bridge | bridge/ |
RxJS → SharedValues binding |
| UI | ui/ |
React components, Skia, SharedValues |
react-native-sliding-blocks/
├── src/ # Library source
│ ├── bridge/ # RxJS → SharedValues (useEngineBridge, GestureCompletionOrchestrator)
│ ├── engine/ # Game logic, RxJS, no React
│ ├── ui/ # React components, Skia, contexts
│ ├── constants/ # Layout constants, game config
│ ├── types/ # Settings, layout types
│ ├── config.ts
│ ├── CONCEPTS.md # Architecture guide
│ └── index.ts
├── example/ # Example React Native app
└── package.json
An example app lives in example/—it demonstrates:
- Declarative and composable APIs
- Settings screen with performance presets (extra-low, low, fine, good) and a collapsible "Custom settings" section for manual overrides
- Game state persistence (AsyncStorage) with a Resume button when stored state exists
- Sounds, themes, block images
# From repo root
yarn install
yarn example # Install example deps
yarn example:start # Start Metro
yarn example:ios # Run iOS (in another terminal)
yarn example:android # Run Android (in another terminal)Developing the library? The example uses Metro's custom resolver so changes in src/ trigger hot reload. See DEVELOPMENT.md.
Requires React Native environment setup.
| Command | Description |
|---|---|
yarn lint |
Lint library source |
yarn example |
Install example dependencies |
yarn example:start |
Start Metro |
yarn example:start:dev |
Start Metro with --reset-cache (use if changes don't appear) |
yarn example:ios |
Run on iOS |
yarn example:android |
Run on Android |
| Layer | Technology |
|---|---|
| Framework | React Native |
| Graphics | Skia (@shopify/react-native-skia) |
| Animations | React Native Reanimated |
| Gestures | React Native Gesture Handler |
| State / Streams | RxJS |
MIT