defold-core

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Defold Core

Defold 核心

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).
组织、连接并验证 Defold 游戏:编辑器资源文件、Lua 脚本生命周期、 寻址与消息传递、运行时生成与世界加载,以及 Bob 构建并运行检查。 示例面向 Defold 1.14.0;它们使用 Bob 1.14.0 构建,并在 1.14.0 headless engine 上运行。现有项目保留其 Defold 版本(参见 版本)。

When to use

何时使用

  • Use when adding or fixing game objects, collections, scripts, input, messages, spawning or level loading in a Defold project (
    game.project
    in the root,
    .collection
    ,
    .go
    ,
    .script
    ,
    .gui_script
    files), 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.
  • 在 Defold 项目(根目录中的
    game.project
    、
    .collection
    、
    .go
    、
    .script
    、
    .gui_script
    文件)中添加或修复 game objects、collections、scripts、 input、messages、spawning 或 level loading 时使用,或者当 Bob 报告缺失资源时使用。
**何时不使用:**普通 Lua 或 LÖVE 项目(
love2d-core
);GUI 布局、sprites、 tilemaps 和超出参考资料指引范围的 physics depth;以及由下述相关 skills 处理的、 与引擎无关的设计。

Workflow

工作流

  1. 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.
  2. 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
    .lua
    modules. Editor files are protobuf text: keep their structure, and copy shapes from
    references/file-formats.md
    instead of guessing fields.
  3. 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 every
    ERROR
    (missing or wrong-case
    /paths
    , a
    game.project
    path without the compiled
    c
    suffix, a
    require
    whose file does not exist). Warnings cover files nothing uses yet, unfetched library dependencies and runtime-loaded files. Re-run until it exits 0.
  4. Build with Bob.
    java -jar bob.jar --root . --variant headless --archive resolve build
    needs JDK 25 and a
    bob.jar
    matching the project's Defold version. Exit 1 plus an
    ERROR:
    line means a broken reference or a Lua syntax error (with file and line);
    references/file-formats.md
    shows how to trace an error that names a generated file.
  5. 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.
  1. 先检查。 读取
    game.project
    (
    [bootstrap] main_collection
    、
    [input] game_binding
    、
    [project] dependencies
    )、主 collection,以及请求附近的 scripts。 遵循项目的文件夹布局、id 命名和消息名称;不要重新组织。记录 CI 或团队使用的 Defold/Bob 版本。
  2. 编辑正确的文件类型。 Game objects(
    .go
    )包含 components;collections (
    .collection
    )放置 game objects 和其他 collections;逻辑放在
    .script
    (game objects)、
    .gui_script
    (GUI)或共享
    .lua
    modules 中。Editor files 是 protobuf text:保持其结构,并从
    references/file-formats.md
    复制 shapes, 而不是猜测字段。
  3. 检查引用。 运行
    python scripts/check_defold_project.py <project-dir>
    (路径 相对于此 skill 文件夹;需要 Python 3.10+,无需安装 Defold)。修复每个
    ERROR
    (缺失或大小写错误的
    /paths
    、
    game.project
    中缺少 compiled
    c
    suffix 的 path, 或文件不存在的
    require
    )。Warnings 涵盖当前尚未使用的文件、未拉取的 library dependencies 和 runtime-loaded files。重新运行,直到其以 0 退出。
  4. 使用 Bob 构建。
    java -jar bob.jar --root . --variant headless --archive resolve build
    需要 JDK 25,以及与项目 Defold 版本匹配的
    bob.jar
    。退出码为 1 且出现
    ERROR:
    行,表示引用损坏或 Lua syntax error(会给出文件和行号);
    references/file-formats.md
    展示了如何追踪指向 generated file 的错误。
  5. 运行。 通过构建并不能证明游戏可以工作。在 timeout 下运行 headless bundle (
    references/build-and-run.md
    )并读取日志:
    ERROR:SCRIPT:
    行表示 Lua runtime errors,
    ERROR:GAMEOBJECT:
    行表示错误地址或 socket 冲突。测试完成时应打印一行 指定内容;仅凭退出码不能证明任何事情。Headless engine 没有键盘或鼠标,因此应在 editor 或 desktop build 中检查 input 变化,并说明哪些内容无法验证。

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
:
ini
[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.
game.project
是项目根目录中的 INI 文件。所有资源路径均为 root-absolute (
/main/player.go
)。在
game.project
中,它们指的是 compiled 文件,因此源路径 末尾要加
c
:
ini
[bootstrap]
main_collection = /main/main.collectionc

[input]
game_binding = /input/game.input_bindingc
Bob 只构建从
game.project
可达的内容;
/builtins/...
随 engine 提供。

Collections, game objects, components

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
.
text
undefined
Collection 放置 instances,并可按 instance 覆盖 script properties;game object 列出 file components 和 embedded components(转义后的
data:
strings)。完整且经过验证的 文件见
references/file-formats.md
。
text
undefined

main.collection (excerpt): place player.go and override its script's speed

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 } } }
undefined
instances { id: "player" prototype: "/main/player.go" component_properties { id: "player" properties { id: "speed" value: "250.0" type: PROPERTY_TYPE_NUMBER } } }
undefined

Script lifecycle,
self
and properties

Script 生命周期、
self
与 properties

lua
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.
lua
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
回调包括:
init
、
final
、
fixed_update
、
update
、
late_update
(每次
update
之后、render 之前)、
on_message
、
on_input
、
on_reload
。Game objects 之间的 update 顺序未指定。
fixed_update
每帧运行 0..N 次,频率由
[engine] fixed_update_frequency
决定;还需设置
[physics] use_fixed_timestep = 1
,使 physics steps 以相同频率运行,并将 physics 驱动的代码放在其中。
self
是 userdata:可以在其上存储 fields,但
pairs(self)
和
pprint(self)
不起作用。Property 类型包括:number、 boolean、hash、
msg.url
、
vmath.vector3
/
vector4
、
vmath.quat
、诸如
resource.atlas("/main/main.atlas")
的 resources,以及(自 1.13.2 起)strings。

Input bindings and the input stack

Input bindings 与 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.
.input_binding
将 devices 映射到 action 名称(
key_trigger
、
mouse_trigger
,以及 gamepad、touch 和 text triggers)。每个获取 focus 的 component 都位于 input stack 上; 最后获取 focus 的位于顶部并优先接收 input,而从
on_input
返回
true
会将 input 停止在这一层,这正是 pause menus 阻止 gameplay input 的方式。Key actions 会报告
pressed
和
released
;按上述方式自行跟踪 held keys。

Addressing and messages

寻址与 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.
lua
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
.
URL 的格式为
[socket:][path][#fragment]
:
"."
表示当前 game object,
"#"
表示当前 component,
"#sprite"
表示同级 component,
"enemy#ai"
表示 relative object,
"/manager#controller"
表示 absolute object,
"main:/loader#loader"
表示另一个 world 中的 object。Collections 会创建 naming scopes,因此 string id 会相对于 sender 的 collection 解析;pre-hashed id 始终是 absolute。
lua
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 会被排队并在 frame 稍后分发;table 上限为 2 KB。Bad address 会记录
ERROR:GAMEOBJECT: Instance '/x' could not be found when dispatching message
。

Factories and collection factories

Factories 与 collection factories

lua
-- 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 } }
.
lua
-- 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 和 rotation 默认取自 spawning object。
go.delete()
在 frame 结束时生效。 Collection factory 的 per-instance properties 以 id 为 key:
{ [hash("/leader")] = { hp = 5 } }
。

Collection proxies (load another world)

Collection proxies(加载其他 world)

lua
-- 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.
lua
-- 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
已加载 world 的 socket 是 loaded collection 文件的
name:
,因此其中的 scripts 会以
msg.post("main:/loader#loader", ...)
回复,而其自身的 URLs 读作
level1:/level#level
。
set_time_step { factor = 0, mode = 0 }
会暂停 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.
game.project
没有 engine version key。使用项目构建时所采用的 Defold editor 或
bob.jar
版本(CI 通常会固定 release sha1);当项目是 library 时,使用
library.defold_min_version
。
java -jar bob.jar --version
会打印 version 和 sha1。 保留该版本;除非用户要求,否则不要迁移。

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 for
    input.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
    .go
    points at a missing file. Search the sources for the quoted path (
    /main/x.go
    ), not the generated name, or run the checker.
  • on_input
    never fires
    → the object never sent
    acquire_input_focus
    , the action name differs from the binding,
    input.game_binding
    points elsewhere, or a listener higher on the stack returns
    true
    .
  • 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 name
    and the proxy never sends
    proxy_loaded
    → the loaded collection's
    name:
    equals an already loaded world (often both are
    "main"
    or
    "default"
    ). Give every proxied collection a unique name.
  • fixed_update
    never runs
    →
    [engine] fixed_update_frequency
    is 0 (default 60). For physics also set
    [physics] use_fixed_timestep = 1
    , as the manual requires both.
  • module 'modules.util' not found
    at run time although Bob succeeded
    → a
    require
    used slashes (
    require "modules/util"
    ). Use the dotted path from the project root,
    require "modules.util"
    for
    /modules/util.lua
    .
  • tostring(id)
    prints
    hash: [<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 without
    sys.exit
    . Run one engine at a time and always use a timeout.
  • Bob:
    the bootstrap.main_collection '/main/main.collectio' can't be found
    → 该设置缺少 compiled suffix,导致 Bob 去掉最后一个字母。写入
    /main/main.collectionc
    (
    input.game_binding
    同理 →
    .input_bindingc
    )。
  • Bob:
    ERROR: build/default/_generated_….factory:0: the prototype '/main/x.go' can't be found
    → 某个
    .go
    中的 embedded component 指向了不存在的文件。在 sources 中搜索 带引号的路径(
    /main/x.go
    ),而不是 generated name,或运行 checker。
  • on_input
    从不触发
    → 对象从未发送
    acquire_input_focus
    、action 名称与 binding 不同、
    input.game_binding
    指向别处,或 input stack 中更高层的 listener 返回了
    true
    。
  • Proxy-loaded level 中的输入失效 → 持有 collection proxy 的 game object 未获取 input focus。
  • already a socket with the same name
    且 proxy 从不发送
    proxy_loaded
    → loaded collection 的
    name:
    与已加载 world 相同(常见情况是两者都为
    "main"
    或
    "default"
    )。为每个 proxied collection 设置唯一名称。
  • fixed_update
    从不运行
    →
    [engine] fixed_update_frequency
    为 0(默认值为 60)。 对于 physics,还需按 manual 要求同时设置
    [physics] use_fixed_timestep = 1
    。
  • 运行时出现
    module 'modules.util' not found
    ,尽管 Bob 构建成功
    → 某个
    require
    使用了斜杠(
    require "modules/util"
    )。使用从项目根目录开始的 dotted path:
    require "modules.util"
    对应
    /modules/util.lua
    。
  • tostring(id)
    输出
    hash: [<unknown:…>]
    → Hash 仅在 debug builds 中保留 source text。比较 hash,绝不要比较其字符串。
  • 第二个 engine 记录
    Failed to initialize Remotery
    ,或 test run 永不结束
    → Debug engine 会打开 engine service(port 8001)、log server 和 profiler port,并在没有
    sys.exit
    时永久等待。每次只运行一个 engine,并始终使用 timeout。

References

参考文献

  • references/file-formats.md
    — verified minimal
    game.project
    ,
    .collection
    ,
    .go
    ,
    .atlas
    ,
    .input_binding
    and 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 and
    bob.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;
    --help
    lists every check.
  • references/file-formats.md
    — 已验证的最小
    game.project
    、
    .collection
    、
    .go
    、
    .atlas
    、
    .input_binding
    和 script 文件,以及如何将 build errors 追溯到 source file;手工编写 editor files 前请先阅读。
  • references/build-and-run.md
    — JDK 25 和
    bob.jar
    、Bob commands、platforms 和 variants、带 timeout 和 log checks 的 headless smoke test,以及 GUI、sprites、 tilemaps 和 collision objects 的相关指引。
  • scripts/check_defold_project.py
    — 只读 reference checker;
    --help
    列出所有 checks。

Related skills

相关 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 in
    late_update
    .
  • save-systems
    — save formats and slots; pair with Defold's
    sys.save
    /
    sys.load
    .
  • game-feel
    — juice built from
    go.animate
    and 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.
  • input-systems
    — 基于 Defold bindings 进行按键重绑定、gamepad 支持和输入缓冲。
  • game-ui-ux
    — 使用 GUI scenes 和
    .gui_script
    构建 HUD 与菜单设计。
  • physics-tuning
    — 使用 collision objects 调优 fixed-timestep 和 collision。
  • camera-systems
    — follow/deadzone logic,通常在
    late_update
    中实现。
  • save-systems
    — 存档格式和槽位;与 Defold 的
    sys.save
    /
    sys.load
    配合使用。
  • game-feel
    — 使用
    go.animate
    和 timers 增强游戏手感。
  • platformer
    ,
    level-design
    — 与此 skill 配合使用的类型和关卡布局指导。
  • love2d-core
    — catalog 中的另一个 Lua engine,用于 LÖVE projects。