How to use Lua Modules?

So, the question is simple (or not?) - how to use Modules? I mean, I’ve read tutorial, but I have a lack of knowledge of working with functions and their arguments, returning values, etc. So I need a little bit of help here. :slight_smile:

I have some code which I want to share with two or more objects (movement of different types of mobs). So, I don’t want just copypaste code to each of new types of mobs, here Modules come in handy. But I am a little bit stuck.

Module code:

function init(self)
	self.direction = 1 -- -1 - left, 1 - right
	self.id = go.get_id()
end

function update(self, dt)
	local p = go.get_position()
	-- apply the speed
	p.x = p.x + self.speed * dt * self.direction
	go.set_position(p)
	if p.x == 204 or p.x == 39 then		
		p.y = p.y - 56
		self.direction = self.direction*-1
		if self.direction == -1 then
			sprite.set_hflip("#mob1", true)
			sprite.set_hflip("#mob2", true)
		else 
			sprite.set_hflip("#mob1", false)
			sprite.set_hflip("#mob2", false)
		end
		go.set_position(p)
	end
	--arrive to Reactor
	if p.x == 192 and p.y == 25 then
		msg.post("/reactor#behavior_TurretReactor", "do_damageReactor")
		go.delete()
	end
	--draw HP
	msg.post("@render:", "draw_text", {text = "" .. self.hp, position = vmath.vector3(p.x - 10, p.y+30, p.z)})
	--msg.post("@render:", "draw_text", {text = "" .. self.id, position = vmath.vector3(p.x - 10, p.y+50, p.z)})
	--delete mobs if 0 hp
	if self.hp <= 0 then
		go.delete()
	end

end

function on_message(self, message_id, message, sender)
	--damage from laser
	if message_id == hash("do_damage") then
		self.hp = self.hp - message.damage
			--msg.post("@render:", "draw_text", {text = "dmg" .. message.damage, position = vmath.vector3(p.x - 10, p.y+30, p.z)})
	end
end

Mob’s script:

hpIncrement = 0

function init(self)
	self.speed = 30
	self.hp = math.random(1000+hpIncrement, 1700+hpIncrement) + hpIncrement
	local id = timer.seconds(2, function(self, id)
  	hpIncrement = hpIncrement + 10
    end)
end

function update(self, dt)
	if self.hp <= 0 then
		money = money + 10
	end
end

I know that I need to name functions in Module and just paste functions in mob’s script, but I don’t quite understand which arguments and return values must be.

I don’t think you need a module for this, just keep it as a regular script. You can use multiple script components on the same object and expose some variables as script properties so the base behavior script is customizable. For example, in your “module code”, you can add go.property("speed", 30) and then you can set that to whatever you want for each mob, in the editor. Or you can set it from another script with something like, go.set("#mobModule, "speed", 150). You can do the same thing with the HP. Make it a property on the “module” component but calculate the initial value and set it from the “mob” component.

You could use a Lua module for some of this, but I think it would just make things more complicated for no particular reason.

[Edit] Here’s how I might modify your script to be more usable for different enemies (all in one script).

(I wasn’t quite sure what you were doing with the “hpIncrement”, so I just put in something simple)

"Module" (click to show)
go.property("speed", 30)
go.property("hpMin", 1000)
go.property("hpMax", 1700)
go.property("money", 10)


function init(self)
	self.direction = 1 -- -1 - left, 1 - right
	self.id = go.get_id()
	self.hp = math.random(self.hpMin, self.hpMax)
end

function update(self, dt)
	local p = go.get_position()
	-- apply the speed
	p.x = p.x + self.speed * dt * self.direction
	go.set_position(p)
	if p.x == 204 or p.x == 39 then
		p.y = p.y - 56
		self.direction = self.direction*-1
		if self.direction == -1 then
			sprite.set_hflip("#mob1", true)
			sprite.set_hflip("#mob2", true)
		else
			sprite.set_hflip("#mob1", false)
			sprite.set_hflip("#mob2", false)
		end
		go.set_position(p)
	end
	--arrive to Reactor
	if p.x == 192 and p.y == 25 then
		msg.post("/reactor#behavior_TurretReactor", "do_damageReactor")
		go.delete()
	end
	--draw HP
	msg.post("@render:", "draw_text", {text = "" .. self.hp, position = vmath.vector3(p.x - 10, p.y+30, p.z)})
	--delete mobs if 0 hp
	if self.hp <= 0 then
		money = money + self.money
		go.delete()
	end
end

function on_message(self, message_id, message, sender)
	--damage from laser
	if message_id == hash("do_damage") then
		self.hp = self.hp - message.damage
		--msg.post("@render:", "draw_text", {text = "dmg" .. message.damage, position = vmath.vector3(p.x - 10, p.y+30, p.z)})
	end
end

And that will give you this in the editor. You can make a different GO for each enemy with totally different settings on the script.


[Edit 2] It looks like you’re using some global variables. That’s the part that I would replace with a module. Global variables are just way too easy to lose track of and cause conflicts and hard-to-find bugs. If you put global stuff in a module instead then you know exactly where it is and where it comes from, and you can even use the “referencing file” feature in Editor 2 to see what scripts are using the module. (such an awesome feature.)

So, modules are just dictionary-style(key = value) tables with stuff in them. You define them like this:

local M = {} -- define a table for the module contents. 
-- calling it 'M' is just an easy convention

M.money = 0 -- define some variable in the module table

function M.myFunc(stuff) -- define a function of the module
    stuff = stuff * 10
    return stuff
end

return M -- give the table to scripts that `require` it

Then any script that wants to access the contents of the module just has to write:

local myMod = require "main.module_filename"
-- assuming the module file is "module_filename.lua" in the "main" folder

You can set it’s local name to whatever you want. Generally you want to use the same name in every script that uses it. And then you just access the module like any other key-value table.

function init(self)
    self.money = myMod.myFunc(self.money) -- multiply my money by 10
    myMod.money = myMod.money + self.money -- add my money to the 'global' money pool
    print(myMod.money)
end
2 Likes

Thank you, kind sir, for such a detailed answer! I will defenetily use properties for my mobs.

About the global variables. Yes, you are right, here I have a mistake with global var. Thank you for pointing it out. I have the main.script with money variable and I need to send message there from my mob script to increment it. Am I right that your approach with tables is kinda similar to message passing here? I mean, I can use either of these two methods to implement adding of money. Which one is better? And when one should use Modules instead of approach with properties for example?

Script properties are useful for several reasons: 1) They get exposed in the editor and allows game designers and other non devs to more easily tweak values or configure game objects 2) They can be queries using go.get()

I prefer to create modules when I have code that is really reusable and I want to reuse from multiple places. Things such as path finding algorithms, abstraction of logic when contacting a backend service, shared data structures and so on.

3 Likes