Website | Code Examples | Discord | Web Demo
Safe, low-latency and fast sandbox for the Godot game engine.
Godot Sandbox lets players run untrusted code safely. Write gameplay in SafeGDScript, a safe GDScript-dialect executed inside a memory-safe sandbox with limits and execution timeouts. Restricted programs cannot access the host beyond what you explicitly allow, making it safe to load mods, user-generated content and scripts from other players. All Godot platforms are supported.
-
Automatic (Recommended): Download the plugin from the official Godot Asset Store using the AssetLib tab in Godot by searching for Godot Sandbox.
-
Manual: Download the latest github release and move only the addons folder into your project addons folder.
SafeGDScript (.sgd) is the default language for sandboxed code. It is compatible with and can replace GDScript in your projects. It is a GDScript-dialect with the same syntax, and some additions (like structs). It is generally much faster than GDScript: Bintr/JIT 5.0x, Interpreter 1.6x.
VS Code users can install the SafeGDScript extension for syntax highlighting and basic editor support.
SafeGDScript supports reusable traits. A trait can contribute state, constants, enums, signals and concrete/static methods, while abstract methods state what the using class must provide:
uses Damageable
trait Damageable:
var health: int = 100
func take_damage(amount: int) -> void:
health -= amount
@abstract func on_death() -> void
func on_death() -> void:
queue_free()Traits are nominal for SafeGDScript classes (value is Damageable) and may be
used as type hints. Foreign Godot objects can satisfy a trait structurally by
providing every instance method declared by it. Disable that compatibility path
with sandbox/safe_gdscript/trait_structural_fallback for strictly nominal matching.
struct Item:
var name: String
var value: int
var dropped_at: Vector2?
func try_stack(other: Item) -> bool:
if self.name != other.name:
return false
self.value += other.value
return true
func drop(at: int | Vector2) -> void:
if at is int:
self.dropped_at = Vector2(at, 0)
else:
self.dropped_at = at
var inventory: Array[Item] = []
func add_item(item_name: String, item_value: int) -> bool:
var item := Item(item_name, item_value)
for stored in inventory:
if stored.try_stack(item):
return true
inventory.append(item)
return false
func _ready():
add_item("coin", 1)
add_item("gem", 5)
add_item("coin", 1)
inventory[0].drop(Vector2(64, 32))SafeGDScript also supports await:
func cutscene(player_knocked : Signal) -> String:
$Gate.text = "The gate is sealed."
await get_tree().create_timer(0.8).timeout
await player_knocked
return "opened"The host receives a Signal and awaits it like any other coroutine: var result = await $Director.cutscene(knocked). See examples/async for a complete example.
A function decorated with @test becomes a test case:
@test func test_stacking_merges_equal_items() -> void:
assert(add_item("coin", 1) == false)
assert(add_item("coin", 1) == true, "the second coin stacks")Right-click a node/script in the editor for Run Tests or Run Test at Cursor, or run all tests headlessly:
godot --headless --path . -s addons/godot_sandbox/run_sgd_tests.gd
See examples/modding for a complete mod loader with a hostile-mod audit.
For modding with traits, see examples/modding_typed.
For maximum performance, you can also write sandboxed programs in C++ or Rust. They use the same sandbox and the same restrictions. See the code examples repository and the demo repository.
- Sandboxed C++ is 2.5-10x faster than GDScript by default, 5-50x with binary translation
- Enable full binary translation for maximum performance on all platforms (including locked down iOS, Web, Switch etc.)
- JIT builds are available in the Releases section for Windows, macOS, Android and Linux
Using typed variables in SafeGDScript will help the compiler optimize:
--- logic CPU dispatch ---
case ns/emulated instr vs ref vs base
SafeGDScript (sandbox) 115.4 5.36x -4.1%
GDScript (engine) 618.0 1.00x -7.2%
In the modding example that implements a virtual CPU, we gained 5x over GDScript by using typed variables.
-
Assign an ELF script resource directly to a node. Constructs a shared sandbox among all instances with that script, maximum scalability, call functions and attach signals like GDScript
-
Or, create a Sandbox node and assign the ELF resource to it. One sandbox per node, with auto-completion from other GDScripts using @export
The commercial studio version of the addon is at https://plus.libriscv.no/. It has a custom backend for translating GDScript to highly efficient native machine code, supports obfuscation and includes modding support. Studios receive access to a Godot module in source form, which exports native machine code that is faster than idiomatic GDScript: https://plus.libriscv.no/demo/.
Requirements:
If you want to contribute to this repo, here are steps on how to build locally:
./build.shYou can also use scons similar to how godot-cpp addons are built.
Thanks goes to these wonderful people (emoji key):
Alf-André Walla 💻 |
K. S. Ernest (iFire) Lee 💻 🔬 |
Dragos Daian 💻 |
This project follows the all-contributors specification. Contributions of any kind welcome!
The Jenova Framework has a built-in C++ compiler, and supports writing C++ in the Godot editor with hot-reloading support.