I disagree with my own stance from 2021. I wrote that before I started building anything more than small projects.
It’s still true that generalizable code should be moved into modules, but I admit that the rest of what I said about keeping code in script files and using go.property() for sharing data was mostly wrong and will lead to problems when a project grows large enough. @Vow’s take becomes more and more vital as a project grows.
Of course, with very small projects, it’s no big deal either way.
I am not a code architecture expert, so don’t take my words too seriously.
I really like @Vow’s technical journal in general and the last entry on modules / scripts in particular. In my humble opinion, some interactions with the engine (messages, creation / deletion of game objects) need to be done in a script.
For example, a factory.create in a module function only works if the factory url is in the same collection as the script that called the module function. (At least, I understand it that way). It is difficult to respect this restriction when writing code in modules.
Therefore, although it is tempting to move most of the code into modules (in a not-so-small project), care must be taken when dividing the code into scripts/modules.
Yeah, that’s a good point to bring up. I do occasionally run into the same problem.
Sockets are one of the reasons to add another script to the project. Drivers should know which functions are safe to call for the socket they live in, which is at least resonable based on the fact that driver code will never be called by an unexpected module function in the context of the wrong socket.
In my experience, you typically only need one script component per collection proxy. Messages should be used to send commands and data to the GUI or other proxy collections. Don’t make logic on messages, it is hard to understand and debug asynchronous logic
The main disadvantage is that you may need to create an editor for your game or use code to describe levels.
This is how looks controller.script in my games.
local N28S = require "libs.n28s"
local INPUT = require "libs.input"
local SM = require "libs.sm.scene_manager"
local CONTEXTS = require "libs.contexts_manager"
local GAME = require "game.game_world"
local DEFS = require "game.balance.defs.defs"
local POINTER = require "libs.pointer_lock"
local HASHES = require "libs.hashes"
local CONSTANTS = require "libs.constants"
local SOUNDS = require "libs.sounds"
---@class ScriptGame
local Script = {}
function Script:init()
CONTEXTS:register(CONTEXTS.NAMES.GAME, self)
INPUT.acquire(self, nil, SM:get_scene_by_name(SM.SCENES.GAME))
self.fixed_updates = 0
if (html_utils) then
-- Player see loading. So we can collect garbage and user can't see it.
collectgarbage("collect")
timer.delay(0, false, function ()
html_utils.hide_bg()
html_utils.focus()
end)
end
GAME:change_location(DEFS.LOCATIONS.BY_ID.JAM_1.id)
SOUNDS:liveupdate_load_game_sound()
end
function Script:fixed_update(dt)
if self.fixed_updates > 4 then --0,1,2,3
return
end
self.fixed_updates = self.fixed_updates + 1
GAME:fixed_update(dt)
end
function Script:update(dt)
self.fixed_updates = 0
self:check_pointer_lock()
GAME:update(dt)
end
function Script:check_pointer_lock()
if CONSTANTS.IS_MOBILE_DEVICE then return end
if SM:is_working() then return end
if GAME.state.editor_visible then
POINTER.unlock_cursor()
end
if SM:get_top()._name == SM.SCENES.GAME then
if html5 and not POINTER.need_locked and not POINTER.locked and not GAME.state.editor_visible then
SM:show(SM.MODALS.SETTINGS)
end
else
POINTER.unlock_cursor()
end
end
function Script:final()
INPUT.release(self)
CONTEXTS:unregister(CONTEXTS.NAMES.GAME)
GAME:final()
end
function Script:on_input(action_id, action)
if (action_id == HASHES.INPUT.ESCAPE and action.pressed) then
if (not SM:is_working() and SM:get_top()._name == SM.SCENES.GAME) then
SM:show(SM.MODALS.SETTINGS)
end
end
end
N28S.register(Script)