Extension Best Practices
这些是使扩展易于使用、修改和更新的一些准则。必须遵守这些准则才能提交至社区。
填写扩展选项
命名扩展
扩展名称对大小写敏感。扩展名称应该以PascalCase编写,除非缩写是标准的、清晰的和简洁的。不应包含诸如Extension
、Support
、Feature
等后缀。示例:
- ❌
SprtCam
- ❌
Spritecamextension
- ❌
spritesnapshotsupport
- ❌
spriteSnapshot
- ✅
SpriteSnapshot
全名
全名理想情况下与内部名称相同,但使用空格代替PascalCase。尽管可能更详细,但只要与名称的关系清楚即可。全名应保持相对简短,长描述在这里是为了准确解释扩展的功能,全名仅用于帮助在事件表编辑器中查找它。
示例:
- ❌ Name = SpriteCamera,Full Name = 图层管理工具
- ❌ Name = ColorTools,Full Name = 颜色处理集合工具
- ✅ Name = ColorTools,Full Name = 颜色工具
- ✅ Name = WebSocketClient,Full Name = WebSocket 网络客户端
更新扩展版本号
版本号应遵循语义化版本控制。这意味着版本应采用number.number.number
或major.minor.patch
的格式。当您对扩展进行更新时,应遵循这些标准,同时增加版本号:
- 如果您的更改只是修复错误或不影响用户如何使用扩展的内部更改,请增加补丁版本。
- 如果您的更改增加了扩展的新功能,请增加次要版本。
- 如果您的更改是破坏性的,请增加主要版本。破坏性更改意味着如果用户安装更新后,它可能不会像以前那样正常工作,例如,如果“爆炸”操作在更新后将力量乘以2,而以前是乘以3,则表达式、操作或条件的名称发生了变化,删除了一个操作、条件或表达式,或参数的顺序发生了变化。
- 如果次要版本更改了,必须将补丁版本重置为0。
- 如果主要版本更改了,必须将补丁版本和次要版本重置为0。
- 在扩展仍在审核中时,每次审核建议更改后都无需更改版本号。
示例:修复错误的更新:
- ❌ 0.1.0 -> 0.2.0
- ❌ 未定义.2.3 -> 未定义.3.4
- ✅ 0.0.0 -> 0.0.1
- ✅ 未定义.2.0 -> 未定义.2.1
- ✅ 未定义.2.3 -> 未定义.2.4
功能更新:
- ❌ 未定义.2.3 -> 未定义.3.4
- ❌ 0.2.3 -> 0.3.3
- ✅ 0.0.0 -> 0.1.0
- ✅ 未定义.2.0 -> 未定义.3.0
- ✅ 未定义.2.3 -> 未定义.3.0
破坏性更改更新:
- ❌ 未定义.0.0 -> 3.0.0
- ❌ 未定义.2.3 -> 2.1.3
- ✅ 0.0.0 -> 未定义.0.0
- ✅ 0.2.3 -> 未定义.0.0
- ✅ 未定义.2.0 -> 2.0.0
- ✅ 未定义.2.3 -> 2.0.0
撰写描述
所有描述、名称和句子都应填写。它们应均为语法上正确且严肃的完整英文句子。
撰写扩展描述
短描述应以简洁(一两句)而直接的方式描述您的扩展。对于扩展的长描述,可以使用Markdown语法。
- 反引号应将表达式名称包围起来,以便以等宽字体显示。
- 描述应包括您的扩展添加的功能列表。
- 如果您的扩展需要特殊设置才能工作,则应进行描述。
- 您还可以添加截图,通过将图片上传到网站并使用Markdown图片语法包含该图像,例如:
![图像描述](https://link.to/the/image.png)
选择标签
扩展应具有描述其功能的标签。例如,一个可以改变精灵单个像素颜色的扩展可以具有pixel, color, sprite
等标签。
链接文档
帮助URL应始终指向一篇文档,最好是在GDevelop维基上。不应指向作者的网站或扩展的网站。提交扩展时不需要每次更改后更改版本号,但强烈建议这样做。
描述扩展定义
命名操作和条件
对于操作、条件、表达式、函数和参数的名称,请使用PascalCase
。
- 例如,给您的操作命名为:“SetRangeOfAttack”,给表达式命名为:“RangeOfAttack”。
提示
您可能想知道是应该创建条件还是表达式。虽然创建条件是个好主意,但表达式更加灵活。在未来,我们将引入自动生成为条件的表达式。
不要使用缩写
确保所有描述均用英语撰写。为事件表中显示的操作/条件编写明确的句子,描述中的句子末尾始终有一个句点。
使用肯定时态
操作和条件的句子始终采用肯定时态。条件正在测试某些内容,可以暗示它们是“问题”。
- 例如:不要将条件命名为“相机是否抖动?”。而是将其命名为“相机正在抖动”。
- 例如:不要将条件命名为“广告显示给用户”。而是命名为“广告已显示”或“广告正在显示”(或者“广告显示在屏幕上”,但更偏向简洁)。
面向所有人而不仅仅是程序员
对于条件的描述,不要写“如果返回 true ...”,因为这更偏向于程序员。在GDevelop中,我们使用更通用的“检查 ...”。
- 举例:而不是_"Race is finished 返回TRUE"_,写作 "检查比赛是否结束。"
- 举例:而不是_"如果玩家不跳跃则返回false"_,写作 "检查玩家是否在跳跃。"
针对启用或禁用某物的操作,使用布尔值(是/否 或 真/假)参数。
- 举例:而不是_"勾选复选框"和"取消勾选复选框"_,合并为一个操作 "勾选(或取消勾选)复选框",带有一个 "是/否" 参数。对于事件表中的句子,使用类似
勾选复选框:_PARAM0_
。 - 举例:而不是两个启用或禁用某物的操作,合并为一个操作 "启用(或禁用)某物"。对于句子,输入:
启用某物:_PARAM0_
。
编写表达式
命名表达式
对于 名称,不要用 Get
前缀 - 因为所有表达式都被视为获取器。例如,命名为 Health
,而不是 GetHealth
或 PlayerBestScore
而不是 GetPlayerBestScore
。
选择表达式类型
目前有两种类型的表达式:表达式(返回一个数字)和字符串表达式(返回一个字符串)。确保返回适当的值(字符串表达式返回文本)- 不要混合文本与表达式,或数字与字符串表达式。
理解何时使用条件
不要创建返回布尔值的表达式。使用条件 - 目前,GDevelop不支持布尔表达式。
- 举例,不要创建一个表达式"Disabled",如果某物被禁用则返回未定义,否则返回0,而是创建一个条件"IsDisabled"。
编写自定义行为
- 描述如何使用它们(包括对象的任何要求)。
- 对于属性:
- 如果是布尔值,请使用描述为真时会发生的操作的名称。不要写"如果为真,则激活能量提升"。而是只写"激活能量提升"。
- 在需要时添加单位,例如:
计时器(秒)
或偏移X(像素)
。
- 尽可能使用隐藏的行为属性,而不是对象变量。
理解何时使用自定义行为
当对对象执行的操作需要跨多帧时,应该使用行为而不是函数,因为它们对用户来说更清晰、更易于使用。例如,如果您正在制作一个只设置X和Y一次并完成的传送扩展,您可以将其制作为函数。如果首先开始计时器,经过一段特定时间后更改位置,则行动不再是立即生效,而是需要在接下来的帧中调用事件。如果您使用行为,通过生命周期行为功能将其隐藏,否则您将不得不强制用户每帧调用您的函数,这对用户来说既令人困惑又不方便,因为行为应立即履行其职能,即在您告诉它的时刻立即执行。
编写扩展中的事件
命名变量
- 使用
PascalCase
为变量命名 - 使用描述性名称
- 不好的例子:
x
,ComCan2
,MyVar
- 好的例子:
CommunicationChannel2
,TemporaryObjectName
- 不好的例子:
- 将变量存储在结构变量中。这意味着您使用
__ExtensionName.VariableName
格式为所有变量命名。 - 例如,名为"Camera zoom"的扩展将使用前缀
__CameraZoom
。如果要使用名为"ZoomValue"的变量,应命名为:
__CameraZoom.ZoomValue
这种命名约定有几个好处。首先,它将使调试器更容易使用,因为所有扩展变量将收拢到其扩展名称下。其次,它将减少与游戏开发人员创建的变量发生名称冲突的机会。
在相关主题上,扩展用户应该仅通过扩展开发人员提供的条件、操作和表达式使用扩展。不应该期望直接访问扩展变量。
应在行为事件中使用隐藏属性代替变量。当必须使用子变量时,仍然可以使用变量。
命名计时器
- 使用
PascalCase
为计时器命名 - 以
__YourExtensionName_
为 计时器名称添加前缀。使用包含您的扩展名称前缀的变量名,以确保它们不会被扩展或其他扩展的用户覆盖。
例如,在"Door Management"扩展中命名为“DoorDuration”的计时器将命名为:
__DoorManagement_DoorDuration
检查平台兼容性
如果您的扩展使用与操作系统或平台相关的某些功能,请验证它是否在GDevelop支持的所有平台上正常工作:Web浏览器、桌面(至少在导出后的Windows、macOS或Linux上检查)、Android(导出后检查)。
如果真的无法使扩展在某些平台上运行,则必须遵循额外的准则:
- 在不支持的平台上运行时不会导致游戏崩溃或中断
- 扩展的全名中必须指明它支持的平台,例如,仅支持PC的扩展需要在其全名后附加
(Windows, mac, linux)
。
JavaScript 使用
可以使用JavaScript使用JavaScript Code events,但是在创建主题时我们尽量避免使用它。扩展应该能被任何GDevelop用户编辑,如果我们要创建一个新的非JavaScript引擎,通过事件创建的扩展将保持兼容。通过常规事件进行对象选取通常比大多数手写JavaScript代码更优化。出于这些目的,我们尽可能保持扩展中使用传统事件,不使用JavaScript。
如果必须存储JavaScript变量/函数,请将其存储在全局的 gdjs
对象中,在与扩展同名的namespace
内。例如,对于一个与控制器相关功能的扩展:gdjs._controllerExtension = {};
gdjs._controllerExtension.myMethod = function (arguments) { // ... }
That is because gdjs
is the only guaranteed global object, but you don't want to accidentally override other functions/internals present on that namespace as well.
When you declare a new namespace in gdjs, you must declare it in the file:
GDevelop-extensions/scripts/lib/ExtensionsValidatorExceptions.js
Handle errors
Unhandled JavaScript can cause a crash of the game, or at least the game loop iteration not being finished properly. Those can be raised by functions, but also by syntax errors or usage of APIs that are not present on the current platform. To avoid crashes, always
- Handle errors with
try {} catch {}
blocks - Try out every code path to make sure that none crash the game because of a syntax error
- Always check if an API exists before using it to avoid a crash (e.g. before using
localStorage
, check iftypeof localStorage !== "undefined"
)
Extensions with crashes will not be accepted.
No usage of DOM elements. If you do, use a specific tag.
Don't use DOM APIs (createElement
, etc...) because they are only working in a browser and it's not guaranteed games will run in a browser environment.
If you do, add the tag "DOM apis
" (exactly written as this) on the extension.
No usage of private methods or variables (starting with _)
Any method that has an underscore in front of its name, like runtimeScene._updateLayersCameraCoordinates
or runtimeScene._layersCameraCoordinates
, can not be used. The underscore denotes a private variable/method. It can change in the next version, be renamed, be changed, or be suppressed. It will happen and if an extension using this is becoming incompatible, it will need to be fixed in a few days or deleted.
In all cases, this will create a bad user experience for GDevelop users.
Note
Can I still do it for X/Y/Z? No. But if you're really stuck, open a discussion on GitHub so we can find an alternative, or ask on Discord if other community members using JavaScript have an idea how to do what you need. In most cases, the public API will have enough for you to do what you need!