defold-core
Build and debug the core of a Defold game in Lua: game.project settings, collections, game objects and components, script lifecycle and self state, go.property, input focus and bindings, URLs and message passing, factories, collection factories and collection proxies, plus building and smoke-running with Bob. Use when a project has game.project or .collection, .go, .script, .gui_script or .input_binding files, or the request mentions Defold, msg.post, on_message, on_input, acquire_input_focus, factory.create, collection proxy, bob.jar, or a Defold build error.
How do I install this agent skill?
npx skills add https://github.com/gamedev-skills/awesome-gamedev-agent-skills --skill defold-coreIs this agent skill safe to install?
- Gen Agent Trust Hubpass
The skill provides tools and instructions for building and debugging Defold games using official resources. It includes a custom Python script for project validation and references official Defold build tools. A minor risk of indirect prompt injection exists if the agent processes malicious project files.
- Socketpass
No alerts
- Snykpass
Risk: LOW · No issues
What does this agent skill do?
Defold Core
Structure, wire and verify a Defold game: editor resource files, the Lua script lifecycle, addressing and messages, runtime spawning and world loading, and a Bob build-and-run check. Examples target Defold 1.14.0; they were built with Bob 1.14.0 and run on the 1.14.0 headless engine. An existing project keeps its Defold version (see Version).
When to use
- Use when adding or fixing game objects, collections, scripts, input, messages, spawning or
level loading in a Defold project (
game.projectin the root,.collection,.go,.script,.gui_scriptfiles), or when Bob reports a missing resource.
When not to use: plain Lua or LÖVE projects (love2d-core); GUI layout, sprites,
tilemaps and physics depth beyond the pointers in the references; engine-agnostic design
handled by the related skills below.
Workflow
- Inspect first. Read
game.project([bootstrap] main_collection,[input] game_binding,[project] dependencies), the main collection, and the scripts near the request. Follow the project's folder layout, id naming and message names; do not reorganize. Note which Defold/Bob version CI or the team uses. - Edit the right file type. Game objects (
.go) hold components; collections (.collection) place game objects and other collections; logic goes in.script(game objects),.gui_script(GUI) or shared.luamodules. Editor files are protobuf text: keep their structure, and copy shapes fromreferences/file-formats.mdinstead of guessing fields. - Check references. Run
python scripts/check_defold_project.py <project-dir>(path relative to this skill folder; Python 3.10+, no Defold install needed). Fix everyERROR(missing or wrong-case/paths, agame.projectpath without the compiledcsuffix, arequirewhose file does not exist). Warnings cover files nothing uses yet, unfetched library dependencies and runtime-loaded files. Re-run until it exits 0. - Build with Bob.
java -jar bob.jar --root . --variant headless --archive resolve buildneeds JDK 25 and abob.jarmatching the project's Defold version. Exit 1 plus anERROR:line means a broken reference or a Lua syntax error (with file and line);references/file-formats.mdshows how to trace an error that names a generated file. - Run it. A build does not prove the game works. Run a headless bundle under a timeout
(
references/build-and-run.md) and read the log:ERROR:SCRIPT:lines are Lua runtime errors,ERROR:GAMEOBJECT:lines bad addresses or socket clashes. Require a line your test prints when it finishes; the exit code alone proves nothing. The headless engine has no keyboard or mouse, so check input changes in the editor or a desktop build, and say what you could not verify.
Patterns
Project and resource references
game.project is an INI file in the project root. Resource paths everywhere are
root-absolute (/main/player.go). In game.project they name the compiled file, so the
source path gets a trailing c:
[bootstrap]
main_collection = /main/main.collectionc
[input]
game_binding = /input/game.input_bindingc
Bob builds only what is reachable from game.project; /builtins/... ships with the engine.
Collections, game objects, components
A collection places instances and can override script properties per instance; a game object
lists file components and embedded components (escaped data: strings). Full verified
files are in references/file-formats.md.
# main.collection (excerpt): place player.go and override its script's speed
instances {
id: "player"
prototype: "/main/player.go"
component_properties {
id: "player"
properties { id: "speed" value: "250.0" type: PROPERTY_TYPE_NUMBER }
}
}
Script lifecycle, self and properties
go.property("speed", 200) -- top level only, literal default; editable per instance
local LEFT = hash("left") -- hash ids once, not every frame
local RIGHT = hash("right")
local FIRE = hash("fire")
function init(self)
msg.post(".", "acquire_input_focus") -- no focus, no on_input calls
self.held = {} -- per-instance state lives on self
end
function update(self, dt)
local dir = (self.held[RIGHT] and 1 or 0) - (self.held[LEFT] and 1 or 0)
if dir ~= 0 then
local pos = go.get_position()
pos.x = pos.x + dir * self.speed * dt -- dt-scaled: same speed at any frame rate
go.set_position(pos)
end
end
function on_input(self, action_id, action)
if action_id == LEFT or action_id == RIGHT then
if action.pressed then self.held[action_id] = true
elseif action.released then self.held[action_id] = nil end
return true -- consumed: listeners lower on the stack skip it
elseif action_id == FIRE and action.pressed then
factory.create("#bulletfactory", go.get_position(), nil,
{ velocity = vmath.vector3(0, 600, 0) })
return true
end
end
function final(self)
msg.post(".", "release_input_focus")
end
Callbacks: init, final, fixed_update, update, late_update (after every update,
before render), on_message, on_input, on_reload. Update order between game
objects is unspecified. fixed_update runs 0..N times per frame at [engine] fixed_update_frequency; set [physics] use_fixed_timestep = 1 too so physics steps at the
same rate, and put physics-driven code there. self is userdata: store
fields on it, but pairs(self) and pprint(self) do not work. Property types: number,
boolean, hash, msg.url, vmath.vector3/vector4, vmath.quat, resources such as
resource.atlas("/main/main.atlas"), and (since 1.13.2) strings.
Input bindings and the input stack
.input_binding maps devices to action names (key_trigger, mouse_trigger, plus
gamepad, touch and text triggers). Every component that acquired focus sits on an input
stack; the last to acquire is on top and gets input first, and returning true from
on_input stops it there, which is how pause menus block gameplay input. Key actions report
pressed and released; track held keys yourself as above.
Addressing and messages
A URL is [socket:][path][#fragment]: "." is this game object, "#" this component,
"#sprite" a sibling component, "enemy#ai" a relative object, "/manager#controller" an
absolute one, "main:/loader#loader" an object in another world. Collections create naming
scopes, so a string id is resolved relative to the sender's collection; a pre-hashed id is
always absolute.
msg.post("/manager#controller", "enemy_spotted", { id = go.get_id() })
function on_message(self, message_id, message, sender)
if message_id == hash("enemy_spotted") then
print("from", sender, "id", message.id)
end
end
Messages are queued and dispatched later in the frame; the table is capped at 2 KB. A bad
address logs ERROR:GAMEOBJECT: Instance '/x' could not be found when dispatching message.
Factories and collection factories
-- factory component "#bulletfactory" -> one game object; properties override go.property values
local id = factory.create("#bulletfactory", pos, nil, { velocity = vmath.vector3(0, 600, 0) })
-- collectionfactory component "#wavefactory" -> table of collection ids -> new instance ids
local ids = collectionfactory.create("#wavefactory", vmath.vector3(480, 500, 0))
local leader = ids[hash("/leader")] -- e.g. hash: [/collection0/leader]
go.delete(ids, true) -- later: delete them and their children
Position and rotation default to the spawning object's. go.delete() takes effect at the
end of the frame. Per-instance properties for a collection factory are keyed by id:
{ [hash("/leader")] = { hp = 5 } }.
Collection proxies (load another world)
-- excerpt from the loader script; the collection's "#levelproxy" component is a collectionproxy
function init(self)
msg.post(".", "acquire_input_focus") -- else the loaded world gets no input
msg.post("#levelproxy", "async_load") -- or "load"
end
function on_message(self, message_id, message, sender)
if message_id == hash("proxy_loaded") then
msg.post(sender, "init")
msg.post(sender, "enable")
elseif message_id == hash("level_done") then
msg.post("#levelproxy", "unload") -- disables and finalizes first
elseif message_id == hash("proxy_unloaded") then
-- safe to load the next level here
end
end
The loaded world's socket is the name: of the loaded collection file, so scripts in it
reply with msg.post("main:/loader#loader", ...) and its own URLs read
level1:/level#level. set_time_step { factor = 0, mode = 0 } pauses a proxy world.
Version
game.project has no engine version key. Use the Defold editor or bob.jar version the
project is built with (CI often pins a release sha1), and library.defold_min_version when the
project is a library. java -jar bob.jar --version prints the version and sha1. Keep that
version; do not migrate unless asked.
Pitfalls
- Bob:
the bootstrap.main_collection '/main/main.collectio' can't be found→ the setting lacks the compiled suffix and Bob stripped the last letter. Write/main/main.collectionc(same forinput.game_binding→.input_bindingc). - Bob:
ERROR: build/default/_generated_….factory:0: the prototype '/main/x.go' can't be found→ an embedded component in some.gopoints at a missing file. Search the sources for the quoted path (/main/x.go), not the generated name, or run the checker. on_inputnever fires → the object never sentacquire_input_focus, the action name differs from the binding,input.game_bindingpoints elsewhere, or a listener higher on the stack returnstrue.- Input dead inside a proxy-loaded level → the game object holding the collection proxy did not acquire input focus.
already a socket with the same nameand the proxy never sendsproxy_loaded→ the loaded collection'sname:equals an already loaded world (often both are"main"or"default"). Give every proxied collection a unique name.fixed_updatenever runs →[engine] fixed_update_frequencyis 0 (default 60). For physics also set[physics] use_fixed_timestep = 1, as the manual requires both.module 'modules.util' not foundat run time although Bob succeeded → arequireused slashes (require "modules/util"). Use the dotted path from the project root,require "modules.util"for/modules/util.lua.tostring(id)printshash: [<unknown:…>]→ hashes keep their source text only in debug builds. Compare hashes, never their strings.- A second engine logs
Failed to initialize Remotery, or a test run never ends → the debug engine opens an engine service (port 8001), a log server and a profiler port, and waits forever withoutsys.exit. Run one engine at a time and always use a timeout.
References
references/file-formats.md— verified minimalgame.project,.collection,.go,.atlas,.input_bindingand script files, and how to trace build errors back to a source file; read before writing editor files by hand.references/build-and-run.md— JDK 25 andbob.jar, Bob commands, platforms and variants, a headless smoke test with timeout and log checks, and pointers for GUI, sprites, tilemaps and collision objects.scripts/check_defold_project.py— read-only reference checker;--helplists every check.
Related skills
input-systems— rebinding, gamepad support and buffering on top of Defold bindings.game-ui-ux— HUD and menu design to build with GUI scenes and.gui_script.physics-tuning— fixed-timestep and collision tuning for collision objects.camera-systems— follow/deadzone logic, typically applied inlate_update.save-systems— save formats and slots; pair with Defold'ssys.save/sys.load.game-feel— juice built fromgo.animateand timers.platformer,level-design— genre and layout guidance that binds to this skill.love2d-core— the other Lua engine in this catalog, for LÖVE projects.
How can the creator link this skill?
Add the canonical catalog link to the repository README so users can inspect current installs and available audits. The publishing guide covers the complete discovery path.
<a href="https://skillzs.dev/skills/gamedev-skills/awesome-gamedev-agent-skills/defold-core">View defold-core on skillZs</a>