How to read and create game objects from a script.
Covers the geode.cocos2d and geode.gd namespaces,
table-shaped values like points and sizes, and per-object fields.
geode.cocos2d holds engine classes, e.g.:
CCDirectorCCLabelBMFontCCSprite- action classes such as
CCMoveTo
geode.gd holds Geometry Dash classes, e.g.:
GameManagerGameStatsManagerMenuLayer
You usually begin from a singleton or a factory.
local cc2d = geode.cocos2d
local gd = geode.gd
local director = cc2d.CCDirector.sharedDirector()
local gm = gd.GameManager.get()Many classes provide a create factory. Always check the result, because it can be nil.
local label = cc2d.CCLabelBMFont.create("Hello, World!", "bigFont.fnt")
if not label then return end
label:setScale(0.5)Geode adds string IDs to CCNode.
When creating a node, always call :setID() and pass an id string:
- The id should start with your mod id as a prefix
- Use lowercase kebab-case
- Do not use spaces
Example: mod-id/my-node-id
String IDs let you find nodes with getChildByID and layouts instead of fragile child indexes,
which is how Geode mods stay compatible.
local cc2d = geode.cocos2d
local modId = geode.Mod.getID()
local sprite = cc2d.CCSprite.createWithSpriteFrameName(modId .. "/coolSprite.png")
if not sprite then return end
sprite:setID(modId .. "/cool-sprite")See the Geode nodetree tutorial.
Call methods with a colon. Read and write simple properties with a dot.
local pos = self.m_obPositionSome APIs return a CCArray. Use Cocos methods to read it.
objectAtIndex starts at index 0.
local lists = localLevelManager:getCreatedLists(0)
if not lists or lists:count() == 0 then return nil end
local first = lists:objectAtIndex(0)Some method arguments are small structs you pass as plain Lua tables with named keys.
- Point:
{ x = number, y = number } - Size:
{ width = number, height = number } - Color:
{ r = number, g = number, b = number } - Rect:
{ origin = point, size = size }
label:setPosition({ x = 100, y = 200 })
bg:setContentSize({ width = 170, height = 84 })Some generated APIs use Geode containers. Most by-value containers become plain table snapshots. Direct object and opaque-pointer vectors may use read-only sequence views.
- Scalar-key map:
{ [K]: V }. - Vector or set:
{ T }. - Direct object vector view:
{ Object? }. Entries can benil. - Pair record:
{ first = T1, second = T2 }. Always usefirstandsecond. - Pair-key map: entry list
{ { first = k1, second = k2, value = v } }because Lua cannot use tables as map keys. - Recursive containers apply these shapes at every level.
Sequence views use 1-based indexes and support #.
They reject writes.
Plain table snapshots follow Lua nil rules.
A nil indexed leaf creates a hole.
A nil value in a dictionary-shaped map removes that entry.
Map table iteration order and unordered container order are unspecified.
Writing a container field updates its native value recursively without whole-container assignment.
Some generated container fields are read-only.
Check the type stubs for the exact surface.
Read-only handler queues on input dispatchers use { Handler? } sequences and 1-based indexes.
Binding details for recursive containers, pairs, and ccCArray views:
Use type stubs for the exact stub type on each member.
Store your own data on an object with geode.fields(node) or the m_fields property.
Both return the same plain table for a live CCNode.
When the value is not a live CCNode, geode.fields returns an empty table.
Missing keys read as nil, so flags default to false in normal checks.
local fields = geode.fields(self)
fields.count = (fields.count or 0) + 1
if geode.fields(layer).myFlag then
-- only runs when the layer is live and the flag was set
endSome GD stats on level and manager objects are encrypted ints in the game
(m_attempts, m_stars, m_levelID, and others on GJGameLevel).
In Luau they appear as number fields. Read and write them like any other number field.
You can also use wrapper methods when the class has them (setAttempts, setLevelID, and similar).
print(level.m_levelID)
print(level.m_objectCount)
level:setAttempts(level.m_attempts + 1) -- level.m_attempts += 1See Codegen for binding policy.
GD and Geode enums are integer constant tables on geode.gd and geode. See enums.
The runtime tracks Lua-owned vs borrowed objects and handles C++ lifetime for you.
Luau does not bind retain() or release().
Manual ref counting fights LuauAPI bookkeeping and can crash the game, leak nodes, or break callback and geode.fields cleanup.
Keep a normal Luau reference when you need an object to stay alive. Use a local, a module table, or the scene graph. See the Bindings framework.
- Getting started
- globals
- LuauAPI mod guidelines
- hooks
- callbacks
- cocos
- mod
- UI and layouts
- enums
- type stubs
src/framework/stack/Types.hppsrc/framework/stack/ContainerTables.hppsrc/framework/view/ReadOnlyVectorView.hppsrc/framework/usertype/Fields.cpptools/luau_codegen/emit/luau_types/tools/luau_codegen/policy/fields.py