Best practice for modularity

Hello everyone I am just discovering Defold and I have some questions concerning the best way to pass information through scripts.

In my example I have some kind of scripts that reads keyboard inputs with “on_input” and transform them into a vector.

Another script would be responsible to make the game object move with “update” depending on the vector.

I want to have two scripts to be able to branch another kind of input into the moving script (an AI for example).

What would be the best way to communicate my vector to my second script ?
By the way, is it a good practice in Defold to do this kind of decoupling ? Maybe there is a nicer way to achieve what I’m looking for, or some tutorial that goes through this example ?

Thanks everyone and sorry for the newbie question :wink:

If i understand you correctly, you want movement algorithm decoupled from “usual game object script”?
You can use lua modules for that: https://www.defold.com/manuals/modules/
You have “on_input.script” which msg received input from player to “player_object.script” which calls something from “movement.lua”, like movement.new_position(position, direction, speed, dt) to get new position. And you can reuse “movement module” now for some bot scripts feeding it with some calculated data, etc.

Thanks for the reply !

I’m not sure to understand your answer.

AI ---------> Movement <---------Input

Movement can take from AI or Input depending on the game object.
Let’s say each of these are a unique script.
Using modules I imagine I should declare a “require” in the Movement script as it will need to get information from AI or Input depending on the game object (e.g. player or enemy).
But then I don’t understand how to switch the “require” depending on the different cases.

It’s possible I’m completely missing something ! Feel free to tell me.

I was talking about something like that.
movement.lua

local M = {}

function M.directional_movement(position, direction, speed)
	return coroutine.create(function(dt)
		while true do
			position = position + (dt * speed) * direction
			coroutine.yield(position)
		end
	end)
end

return M

Ooops, updated code, reread if you already did it. input.script:

function init(self)
	msg.post(".", "acquire_input_focus")
	self.direction = vmath.vector3(0,0,0)
	self.new_input = false
end

function on_input(self, action_id, action)
	if action_id == hash("left") and action.pressed then
		self.direction.x = self.direction.x - 1
		self.new_input = true
	end
	if action_id == hash("right") and action.pressed then
		self.direction.x = self.direction.x + 1
		self.new_input = true
	end
	if action_id == hash("up") and action.pressed then
		self.direction.y = self.direction.y + 1
		self.new_input = true
	end
	if action_id == hash("down") and action.pressed then
		self.direction.y = self.direction.y - 1
		self.new_input = true
	end
	if self.new_input then
		self.direction = vmath.normalize(self.direction)
		msg.post("#player", "input", { direction = self.direction })
		self.new_input = false
	end
end

player.script

local movement = require("main.movement")

function init(self)
	self.speed = 50
	self.move = nil 
end

function update(self, dt)
	if self.move then
		local new_position = select(2, coroutine.resume(self.move, dt))
		go.set_position(new_position)
	end
end

function on_message(self, message_id, message)
	if message_id == hash("input") then
		local position = go.get_position()
		self.move = movement.directional_movement(position, message.direction, self.speed)
	end
end

bot.script

local movement = require("main.movement")

function init(self)
	self.speed = 10
	self.move = nil 
end

local function direction_to_player(self) 
	-- here find direction to player in form of normalized vector3
end

local function follow_player(self)
	-- call this function when you want bot start following player
	-- collision, msg whaterver you want
	-- then
	self.move = movement.directional_movement(go.get_position(), direction_to_player(self), self.speed)
end

function update(self, dt)
	if self.move then
		local new_position = select(2, coroutine.resume(self.move, dt))
		go.set_position(new_position)
	end
end

If you keep same movement coroutine interface - function(dt) returns vector3, then you can swap movement behavior on the fly, like that:

if something then
	self.move = movement.directional_movement(go.get_position(), direction_to_player(self), self.speed)
else
	self.move = movement.another_fancy_movement_alghoritm(what_ever_you_want_args)
end

Super cool I will dig into your answer and see how to adapt it to my need !
Juste one thing I don’t get yet. Is it normal there is a reference to “#player” in input.script ?

msg.post("#player", "input", { direction = self.direction })

Will that reference to the script player.script and only this script ? Or is there something I don’t understand ?

You need to dig more into messaging https://www.defold.com/manuals/message-passing and adressing https://www.defold.com/manuals/addressing
In my example i have one game object (player.go with id “player”) with two scripts attached (input.script and player.script (id “player” too)). “#player” url is relative, means “”(nothing) current game object (current id “player”) “#” component “player” id. If you duplicate player.go (“Add Game Object File”) each input.script will message to own player.script, because “” will means in one instance “player” and “player1” in another.

Ok thanks this will definitely help me !