# N28S(No more global functions in scripts)

**URL:** <https://forum.defold.com/t/n28s-no-more-global-functions-in-scripts/82118>\
**Category:** The Defoldmine\
**Created:** [December 19, 2025, 5:18am UTC](https://forum.defold.com/t/n28s-no-more-global-functions-in-scripts/82118 "2025-12-19T05:18:04Z")\
**Posts on this page:** 5\
**Page:** 1

<div class="post-metadata">

**Author:** ![d954mas](https://forum-defold.b-cdn.net/user_avatar/forum.defold.com/d954mas/32/5401_2.png) [@d954mas](https://forum.defold.com/u/d954mas)\
**Post date:** [December 19, 2025, 5:18am UTC](https://forum.defold.com/t/n28s-no-more-global-functions-in-scripts/82118/1 "2025-12-19T05:18:04Z")

</div>

# N28S(No more global functions in scripts)

> **[GitHub - d954mas/defold-n28s](https://github.com/d954mas/defold-n28s)**
>
> Contribute to d954mas/defold-n28s development by creating an account on GitHub.

This repository demonstrates how to use **n28s** (No More Global Functions in Scripts) inside a Defold project. The helper module (`n28s.lua`) lets you keep your gameplay logic encapsulated in a regular Lua table instead of spreading Defold lifecycle callbacks (`init`, `update`, `on_input`, etc.) over the global scope. It is a tiny drop-in utility inspired by [Lerg’s approach](https://gist.github.com/Lerg/888c7df3844ab38cc39ef81d1c786da4) and trimmed down for simplicity.

Keeping the script APIs on a table also makes auto-completion and type hints work better in editors such as VS Code or JetBrains IDEs—the tooling understands methods on explicit tables far more reliably than anonymous global callbacks with implicit `self`.

Defold injects a userdata `self` into callbacks by default, but storing your state on a plain Lua table and passing that around keeps all lookups inside Lua and is measurably faster than going through the userdata wrapper.

## What the sample does

- Loads `n28s.lua` from the project root.
- Defines a script table in `main/main.script`, registers it with `N28S.register`, and delegates the Defold callbacks through that table.
- Shows how to acquire input focus, set a fixed-fit projection, and react to the `touch` input action without leaking globals.

```lua
local N28S = require "n28s"

local Script = {}

function Script:init(go_self)
	self.go_self = go_self
	msg.post(".", "acquire_input_focus")
	msg.post("@render:", "use_fixed_fit_projection", { near = -1, far = 1 })
end

function Script:on_input(action_id, action)
	if action_id == hash("touch") and action.pressed then
		print("Touch!")
	end
end

N28S.register(Script)

```

## Using n28s in your own scripts

1. Copy `n28s.lua` into your project (keep it somewhere in the Lua module search path, e.g., the project root or `libs/`).
2. `require "n28s"` inside any script file.
3. Place lifecycle functions (`init`, `update`, `fixed_update`, `on_message`, `on_input`, `on_reload`, `final`) as methods on a Lua table.
4. Call `N28S.register(MyScriptTable)` once. The helper asserts that no global callbacks are already defined, ensuring a single source of truth per script file.

This pattern keeps state localized, enables reuse across game objects, and makes Lua tooling (linters, unit tests, IDE completion) easier to apply since the script logic is regular table methods instead of hidden global functions.

## Folder layout

- `main/main.script` – example game object script that uses n28s.
- `n28s.lua` – the helper module described above.
- `game.project`, `assets`, `input` – standard Defold desktop template files.

Feel free to extend the main script or replace it with your own gameplay logic. As long as you register your table with `N28S.register`, Defold will keep calling into your encapsulated script without cluttering the global namespace.

---

<div class="post-metadata">

**Author:** ![iotaHum](https://forum-defold.b-cdn.net/letter_avatar_proxy/v4/letter/i/977dab/32.png) [@iotaHum](https://forum.defold.com/u/iotaHum)\
**Post date:** [December 19, 2025, 10:59am UTC](https://forum.defold.com/t/n28s-no-more-global-functions-in-scripts/82118/2 "2025-12-19T10:59:22Z")

</div>

Nice; two salient points you make:

(1) “Defold injects a userdata self into callbacks by default, but storing your state on a plain Lua table and passing that around keeps all lookups inside Lua and is measurably faster than going through the userdata wrapper.”

Please can you give a link to where in the Defold engine source code this is done.

(2) “gameplay logic encapsulated in a regular Lua table instead of spreading Defold lifecycle callbacks over the global scope.”

Does the following approach also do this and how does it differ functionally from your code?:

```lua
-- ignore the self parameter, use this instead:
local go_self = {}

￼function init(self)
  go_self.dt = 0.0
end

￼function update(self,dt)
  go_self.dt = go_self.dt + dt
  print(go_self.dt)
end

```

My motivation for asking is improvement of my understanding of the what and how. Thanks for the code and post.

---

<div class="post-metadata">

**Author:** ![d954mas](https://forum-defold.b-cdn.net/user_avatar/forum.defold.com/d954mas/32/5401_2.png) [@d954mas](https://forum.defold.com/u/d954mas)\
**Post date:** [December 19, 2025, 1:56pm UTC](https://forum.defold.com/t/n28s-no-more-global-functions-in-scripts/82118/3 "2025-12-19T13:56:44Z")

</div>

> [@iotaHum](#):
>
> Please can you give a link to where in the Defold engine source code this is done.

```lua
---self is userdata
function init(self)

end

```

> [@iotaHum](#):
>
> Does the following approach also do this and how does it differ functionally from your code?:
> 
> ```lua
> -- ignore the self parameter, use this instead:
> local go_self = {}
> 
> ￼function init(self)
> go_self.dt = 0.0
> end
> 
> ￼function update(self,dt)
> go_self.dt = go_self.dt + dt
> print(go_self.dt)
> end
> 
> ```

Looks same is i do. But you still have global function. I use n28s because vscode and idea don’t like and don’t understand global functions in scripts.

```lua
--i just move table functions to global. So for engine when it load script file it will see global functions. But in vscode it will be looked as lua module
function M.register(script)
	assert(not _G.init, "global init already exist")
	assert(not script.__n28s_inited, "script already inited")
	script.__n28s_inited = true
	if script.init then _G.init = function (go_self) script:init(go_self) end end

	if script.update then
		local f = script.update
		_G.update = function (_, dt) f(script, dt) end
	end

```

---

<div class="post-metadata">

**Author:** ![iotaHum](https://forum-defold.b-cdn.net/letter_avatar_proxy/v4/letter/i/977dab/32.png) [@iotaHum](https://forum.defold.com/u/iotaHum)\
**Post date:** [December 20, 2025, 9:23am UTC](https://forum.defold.com/t/n28s-no-more-global-functions-in-scripts/82118/4 "2025-12-20T09:23:40Z")

</div>

My question was unclear, I am wondering where in the Defold C++/C code that the callbacks are. No worries as I should be able to work it out for myself, when I get a chance to look.

---

<div class="post-metadata">

**Author:** ![d954mas](https://forum-defold.b-cdn.net/user_avatar/forum.defold.com/d954mas/32/5401_2.png) [@d954mas](https://forum.defold.com/u/d954mas)\
**Post date:** [December 20, 2025, 1:04pm UTC](https://forum.defold.com/t/n28s-no-more-global-functions-in-scripts/82118/5 "2025-12-20T13:04:09Z")

</div>

Sorry i don’t understant.

Here is c++ code where global function is handled by defold

> <https://github.com/defold/defold/blob/9eaa88cf5ee23360ab92a8c2a1187f30ba7c7b82/engine/gameobject/src/gameobject/gameobject_script.cpp#L2352C38-L2352C59>
