
Bring Godot to React Native ๐ฎ. Create immersive 3D experiences or interactive games directly within React Native.
- Screenshots
- Features
- Device Support
- Requirements
- Installation
- Quick Start
- API Reference
- Project Setup
- Limitations & Known Issues
- Contributing
- License
- ๐๏ธ Native C++ JSI performance - Direct JavaScript to native bindings
- ๐ฅ GPU-accelerated rendering - Metal (iOS) and OpenGL/Vulkan support
- โ Full React Native compatibility - Supports old and new architecture
- ๐ฎ Complete Godot integration - Access all Godot variants and features
- ๐งโโ๏ธ Runtime GDScript compilation - Create and execute scripts dynamically
- ๐ฆ Easy project import - Simple workflow to bring Godot projects to RN
- ๐ Bidirectional communication - React Native โ Godot messaging
iOS support is implemented, full Android support is coming soon.
Platform | Supported |
---|---|
iOS Device | โ |
iOS Simulator | โ |
Android Device | ๐ง |
Android Emulator | ๐ง |
- Godot 4.4.1 (https://godotengine.org/)
- React Native 0.70+
- iOS 12.0+ / Android API 21+
npm install react-native-godot
# or
yarn add react-native-godot
Wrap your app with GodotProvider
to initialize Godot properly:
// App.tsx
import React from 'react';
import { GodotProvider } from 'react-native-godot';
import MyGameScreen from './MyGameScreen';
export default function App() {
return (
<GodotProvider>
<MyGameScreen />
</GodotProvider>
);
}
// MyGameScreen.tsx
import React, { useEffect, useState } from 'react';
import { GodotView, useGodot, useGodotRef } from 'react-native-godot';
const MyGameScreen = () => {
const godotRef = useGodotRef();
const { Vector3, Vector2 } = useGodot();
const [isGodotReady, setIsGodotReady] = useState(false);
useEffect(() => {
// Start Godot rendering (call once in your app)
GodotView.startDrawing();
return () => {
// Stop Godot rendering when component unmounts
GodotView.stopDrawing();
};
}, []);
useEffect(() => {
if (!isGodotReady || !godotRef.current) {
return;
}
// Use Godot variants
const position = Vector3(1, 2, 3);
console.log('Position Y:', position.y);
// Get nodes from your scene
const playerNode = godotRef.current.getRoot()?.getNode('Player');
playerNode?.call('jump', 10);
// Send data to Godot
godotRef.current.emitMessage({
type: 'player_spawn',
position: position,
health: 100
});
}, [isGodotReady]);
return (
<GodotView
ref={godotRef}
style={{ flex: 1 }}
source={require('./assets/game.pck')}
scene="res://main.tscn"
onReady={() => setIsGodotReady(true)}
onMessage={(instance, message) => {
console.log('Message from Godot:', message);
}}
/>
);
};
export default MyGameScreen;
The main component for embedding Godot scenes in React Native. You can use multiple GodotView
components on the same screen - each will render a different scene but they all share the same Godot engine instance.
Prop | Type | Description |
---|---|---|
source |
string | ImageSourcePropType |
Path to your .pck file |
scene |
string |
Scene path (e.g., "res://main.tscn") |
style |
StyleProp<ViewStyle> |
React Native style object |
onReady |
(instance: GodotViewRef) => void |
Called when Godot is ready |
onMessage |
(instance: GodotViewRef, message: any) => void |
Called when receiving messages from Godot |
Method | Description |
---|---|
getRoot(): Node |
Get the root node of the loaded scene |
emitMessage(message: any): void |
Send a message to Godot scripts |
pause(): void |
Pause the Godot instance |
resume(): void |
Resume the Godot instance |
isReady(): boolean |
Check if Godot is ready |
Method | Description |
---|---|
GodotView.startDrawing(): void |
Start Godot rendering engine (call once per app) |
GodotView.stopDrawing(): void |
Stop Godot rendering engine |
Single GodotView Example:
const MyGame = () => {
const godotRef = useGodotRef();
useEffect(() => {
// Start rendering when app launches
GodotView.startDrawing();
return () => GodotView.stopDrawing();
}, []);
return (
<GodotView
ref={godotRef}
source={require('./game.pck')}
scene="res://main.tscn"
onReady={(instance) => {
console.log('Godot ready!');
instance.emitMessage({ type: 'game_start' });
}}
onMessage={(instance, message) => {
console.log('From Godot:', message);
}}
/>
);
};
Multiple GodotView Example:
const MultiSceneApp = () => {
const gameRef = useGodotRef();
const uiRef = useGodotRef();
const minimapRef = useGodotRef();
useEffect(() => {
// Start rendering once for all GodotView instances
GodotView.startDrawing();
return () => GodotView.stopDrawing();
}, []);
return (
<View style={{ flex: 1 }}>
{/* Main game view */}
<GodotView
ref={gameRef}
source={require('./game.pck')}
scene="res://game_world.tscn"
style={{ flex: 1 }}
/>
{/* UI overlay */}
<GodotView
ref={uiRef}
source={require('./ui.pck')}
scene="res://hud.tscn"
style={{
position: 'absolute',
top: 0,
left: 0,
width: '100%',
height: 100
}}
/>
{/* Minimap */}
<GodotView
ref={minimapRef}
source={require('./game.pck')}
scene="res://minimap.tscn"
style={{
position: 'absolute',
top: 20,
right: 20,
width: 150,
height: 150
}}
/>
</View>
);
};
๐ก Note: All GodotView
instances share the same Godot engine, so you only need to call GodotView.startDrawing()
once per app, regardless of how many views you have. Use pause()
and resume()
on individual view instances to control which scenes are actively rendering.
Example: Controlling individual scenes:
// Pause the minimap when not needed
minimapRef.current?.pause();
// Resume it later
minimapRef.current?.resume();
// Pause game but keep UI active
gameRef.current?.pause();
// UI continues running for menus, etc.
All Godot variant types are available with full method and property support:
Available Types:
AABB | Basis | Color | Plane | Projection | Quaternion | Rect2 | Rect2i | Transform2D | Transform3D | Vector2 | Vector2i | Vector3 | Vector3i | Vector4 | Vector4i
Usage:
const { Vector3, Color, Transform3D } = useGodot();
// Create variants
const position = Vector3(1, 2, 3);
const color = Color(1, 0, 0, 1); // Red
const transform = Transform3D();
// Use methods and properties
console.log('Distance:', position.length());
console.log('Normalized:', position.normalized());
console.log('Red component:', color.r);
Complete documentation: Godot Variant Types
Create and compile GDScript at runtime, then attach to dynamically created nodes:
const { Script, Node } = useGodot();
// Create and compile a script
const script = Script();
const success = script.setSourceCode(`
extends Node
@onready var health = 100
func _ready():
print("Dynamic script loaded!")
func take_damage(amount: int) -> int:
health -= amount
return health
func heal(amount: int):
health += amount
print("Healed for ", amount, " HP")
`);
if (success) {
// Create node and attach script
const dynamicNode = Node();
dynamicNode.setScript(script);
dynamicNode.setName("DynamicPlayer");
// Add to scene
godotRef.current?.getRoot()?.addChild(dynamicNode);
// Call script methods
const remainingHealth = dynamicNode.call("take_damage", 25);
console.log('Health remaining:', remainingHealth);
// Alternative syntax with TypeScript casting
(dynamicNode as any).heal(10);
}
Method | Description |
---|---|
Script() |
Create a new empty script |
setSourceCode(source: string): boolean |
Set and compile GDScript source code |
Method | Description |
---|---|
Node() |
Create a new empty node |
getNode(path: string): Node | null |
Get child node by path |
getParent(): Node | null |
Get parent node |
getChildren(): Node[] |
Get all child nodes |
getChildCount(): number |
Get number of child nodes |
addChild(child: Node) |
Add a child node |
setName(name: string) |
Set the node's name |
setScript(script: Script) |
Attach a script to the node |
call(method: string, ...args: any[]): any |
Call a method defined in the attached script |
๐ก Tip: For better TypeScript ergonomics, you can call script methods directly using (node as any).methodName(args)
instead of node.call("methodName", args)
.
Access and interact with nodes from your loaded Godot scenes:
useEffect(() => {
if (!isGodotReady || !godotRef.current) return;
// Get the root node
const root = godotRef.current.getRoot();
// Navigate the scene tree
const player = root?.getNode('Player');
const ui = root?.getNode('UI/HealthBar');
// Access node hierarchy
const parent = player?.getParent();
const children = player?.getChildren();
const siblingCount = parent?.getChildCount();
// Call methods defined in the node's GDScript
player?.call('set_health', 100);
ui?.call('update_display', 100, 100);
// Alternative direct method calls
(player as any)?.jump(15);
(ui as any)?.show_damage_effect();
}, [isGodotReady]);
Send messages from React Native to your Godot scripts:
// Send structured data to Godot
godotRef.current?.emitMessage({
type: 'player_action',
action: 'attack',
target: 'enemy_1',
position: Vector3(10, 0, 5),
damage: 50
});
Receive messages in React Native from Godot scripts:
<GodotView
onMessage={(instance, message) => {
console.log('Received from Godot:', message);
// Handle different message types
switch (message.type) {
case 'game_over':
showGameOverScreen(message.score);
break;
case 'level_complete':
advanceToNextLevel();
break;
case 'item_collected':
updateInventory(message.item);
break;
}
}}
/>
extends Node
@onready var rn_singleton = Engine.get_singleton("ReactNative")
func _ready():
if rn_singleton:
# Listen for messages from React Native
rn_singleton.on_message(_on_react_native_message)
func _on_react_native_message(message: Dictionary):
print("Message from React Native: ", message)
match message.type:
"player_action":
handle_player_action(message)
"game_state_change":
update_game_state(message.state)
func send_to_react_native(data: Dictionary):
if rn_singleton:
rn_singleton.emit_message(data)
func _on_enemy_defeated():
send_to_react_native({
"type": "enemy_defeated",
"enemy_id": "goblin_1",
"exp_gained": 50
})
To use your existing Godot project in React Native:
- Add export preset configuration
Create export_presets.cfg
in your Godot project directory:
[preset.0]
name="main"
platform="iOS"
runnable=true
advanced_options=false
dedicated_server=false
custom_features=""
export_filter=""
include_filter="project.godot"
exclude_filter=""
export_path=""
encryption_include_filters=""
encryption_exclude_filters=""
encrypt_pck=false
encrypt_directory=false
script_export_mode=2
[preset.0.options]
export/distribution_type=1
binary_format/architecture="universal"
binary_format/embed_pck=false
custom_template/debug=""
custom_template/release=""
debug/export_console_wrapper=0
display/high_res=true
- Generate PCK file
Run the provided script (modify path for your OS):
./gen-pck PROJECT_FOLDER_PATH
- Add to React Native project
Move the generated .pck
file to your React Native assets
folder.
- Include project.godot in iOS
Add your project.godot
file to your Xcode project bundle.
Add PCK file support to your metro.config.js
:
const config = getDefaultConfig(__dirname);
// Add pck files as assets
config.resolver.assetExts.push('pck');
module.exports = config;
When importing textures or 3D models, avoid using VRAM Compressed
format as it may not export properly in PCK files.
Currently, you cannot swap PCK assets at runtime. You need to restart the app to load a new PCK file. This appears to be a Godot engine limitation that we're investigating.
- iOS Simulator is not supported due to architecture differences
- Android support is in development
We welcome contributions! The core development happens in a private repository, but if you'd like to contribute:
- Open an issue to discuss your idea
- Contact us at
[email protected]
for access to the private repo - Experience with Godot Engine, C++, and React Native is preferred
- Knowledge of Bazel is a plus
Copyright Calico Games 2024. All rights reserved.
This library is released under a Custom License:
- โ Free for non-commercial use - Personal, educational, or open-source projects
- ๐ผ Commercial use requires license - Companies/individuals with >$50,000 annual revenue need a commercial license
- โ No redistribution - Cannot be redistributed, repackaged, or resold
We support the Godot Foundation by sharing revenue from commercial licenses.
For commercial licensing: [email protected]
- Special thanks to the Godot Engine contributors
- Huge appreciation to Migeran for their invaluable help