Can I create/manipulate GO self.variables in LUA module functions (SOLVED)

I want to create a set of variables in LUA module functions by calling function on_init so the code is cleaner and easier to read. (like variables for hitbox and movement)
Also, I want to manipulate them inside module functions. (calculations on movement without polluting main go script)
If I try to define self.variables in module function I get errors - “attempt to index global ‘self’ (a nil value)”.
If I try to manipulate any defined self.variable in init I get errors - “attempt to index global ‘self’ (a nil value)”.
If I use self.variables defined in init after such function call - (“attempt to perform arithmetic on field ‘hitbox_b’ (a nil value)”)
Sorry, I’m still trying to understand LUA.
I’m working on a collision system that seems most of the defolders doesn’t think to do. For the moment I want to keep it a secret.

I hope this site would be helpful for you:

It helps me a lot with understanding self and modules. I think you’ll find your answers in last chapters of this site. You can either pass self table to the function or try an approach where you are creating an “instance” of a module in your GO’s script, something like this:

local animator = require "modules.animator"
self.anim = animator.create(self, something)

And use functions from this module by implicitly calling it with : like this:

self.anim:play(animator.IDLE)

But if it’s only about simple calculations don’t complicate it and use simple function that gets self.variable and save a return variable from the function to this self.variable again:

self.pos = newPos(self.pos, self.destination) 

If there will be problems, show us a code maybe :wink:

2 Likes

Well, first I want is like this:

function init(self)
    set_hitbox(-6, 5,18,0)
end
--inside module
function set_hitbox(hitbox_l, hitbox_r, hitbox_t, hitbox_b)
	self.hitbox_l = hitbox_l
	self.hitbox_r = hitbox_r
	self.hitbox_t = hitbox_t
	self.hitbox_b = hitbox_b
	self.hitbox_hc = math.ceil((self.hitbox_r + self.hitbox_l)/2)
	self.hitbox_vc = math.ceil((self.hitbox_t + self.hitbox_b)/2)
end

Then I want use those variables like those functions were inside GO script:

function ground_check(solidMap, solidLayer, x, y)
	local t = tile_id(solidMap, solidLayer, x*VIEW_SCALE, (y + self.hitbox_b -1)*VIEW_SCALE)
	return t ~= 0 and t ~= nil
end

Or manipulate them, so I could have decoupled variables from GO script, making coding just necessary things and leave rest to module functions.

Oh, I found workaround, by also passing self to function so I can set:

function set_hitbox(id, hitbox_l, hitbox_r, hitbox_t, hitbox_b)
	id.hitbox_l = hitbox_l
	id.hitbox_r = hitbox_r
	id.hitbox_t = hitbox_t
	id.hitbox_b = hitbox_b
	id.hitbox_hc = math.ceil((id.hitbox_r + id.hitbox_l)/2)
	id.hitbox_vc = math.ceil((id.hitbox_t + id.hitbox_b)/2)
end

I really want to give something back to community and I thing this collision system will be interesting to many. I hope it should be way better performing than using built in Box2D, but it’s only for retro feel physics.

You should really try to understand “Using metatables” from the site I posted. Then use it to create a self.physics instance of the module in the init function and use it later.
The good approach could be when you’ll create an instance self.hitbox and self.ground and others and then create one instance of the world that will include those entities:

self.world = physics.createWorld(dt, self.ground, self.hitbox1, self.hitbox2) 
self.world:update(dt) 

Then in update call one function which takes as parameters only dt and implicitly self module, and inside this function calculate the outcome depending on interactions.

Or more separated approach, when you check and resolve collisions one by one, something I could imagine would be used like this:

self.hitbox:checkCollisionsWith(self.ground)
self.hitbox:resolveAllCollisions(dt) 

It would be really, really hard though, imo, but I hope you’ll mange to make it :wink:

Maybe checkout Low Rex Adventure and Platypus by @britzl :wink:

2 Likes

I recommend you keep ‘self’ named ‘self’ everywhere, not change it to ‘id’ in your module, especially since ‘id’ means a specific different thing in Defold.

3 Likes

If you are interested, I’m using this module:

-- Thanks AJirenius for inspiration
-- https://forum.defold.com/t/accessing-internal-state-self-of-another-game-object-solved/2389/10

local M = {}

local gop_tables = {}

function M.get(id, key)
	id = id or msg.url().path
	if type(id) == "string" then id = go.get_id(id) end
	if not pcall(hash_to_hex, id) then return nil end

	if gop_tables[id] == nil then	gop_tables[id] = {} end
	local result = gop_tables[id]

	key = key or nil
	if key and gop_tables[id][key] then
		result = gop_tables[id][key]
	elseif key and not gop_tables[id][key] then
		result = nil
	end
	
	return result
end

function M.set(id, key, value)
	id = id or go.get_id()
	if type(id) == "string" then id = go.get_id(id) end
	if not pcall(hash_to_hex, id) then return nil end

	if gop_tables[id] == nil then gop_tables[id] = {} end

	key = tostring(key)
	gop_tables[id][key] = value
end

return M

And i do this in init() of my objects (passing parent_id in factory.create as a property)

gop = require 'main.gop'
go.property("parent_id", hash(""))

function init(self)	
	self.gop = gop.get()
	self.gop.id = go.get_id()
	self.gop.parent = gop.get(self.parent_id)
end

Hope it helps!

2 Likes

Thank you for help!
@Pawel Platypus example made a bit more idea about modules but when it goes function inside a function “functionseption” I can’t digest that at the moment. It’s the first time I see such a concept of saving function inside a variable.
@kgoya, thanks for the example, but some stuff confused me.

function M.set(id, key, value)
	id = id or go.get_id() --does it means function has reference to calling instance?
1 Like

@kgoya, thanks for the example, but some stuff confused me.

function M.set(id, key, value)
	id = id or go.get_id() --does it means function has reference

No, it is only if you do not provide an id (gop.get()) then it will get the data for the object from which it is called.
So in your GO script: gop.get() is equivalent to gop.get(go.get_id())

1 Like

@kgoya I thought that any variable that is set outside functions are constants and can’t be changed. But I see that M.set() is manipulating gop_tables.

 if gop_tables[id] == nil then gop_tables[id] = {} end

	key = tostring(key)
	gop_tables[id][key] = value

Haven’t tried to manipulate them anywhere.

No problem, I bet you’ll get used to it soon :smiley: Lua is a great language, in which you can program as you like. I would say, that you can perceive Lua as the basic principle - that everything is a key-value table, even functions :wink:

1 Like

Damn, even simple thing doesn’t work.

--test.lua    
local M = {}
    function M:create()
    	self.v1 = 3
    	self.v2 = 5
    	print(self.v1)
    end
    function M:add()
    	return self.v1 + self.v2
    end
    function M:multiply()
    	return self.v1 * self.v2
    end
    return M
--instance script    
local m = require "test"
    function init(self)
    	msg.post(".", "acquire_input_focus")
    	m.create()
    	self.add = nil
    	self.multiply = nil
    end
    function on_input(self, action_id, action)
    	if action_id == hash("mb_left") and action.pressed then
    		self.add = m.add()
    		print(self.add)
    	elseif action_id == hash("mb_right") and action.pressed then
    		self.multiply = m.multiply()
    		print(self.multiply)
    	end
    end
ERROR:SCRIPT: /test.lua:3: attempt to index local 'self' (a nil value)
stack traceback:
	/test.lua:3: in function 'create'
	/player.script:4: in function </player.script:2>
ERROR:SCRIPT: /test.lua:8: attempt to index local 'self' (a nil value)
stack traceback:
	/test.lua:8: in function 'add'
	/player.script:25: in function </player.script:23>
ERROR:SCRIPT: /test.lua:11: attempt to index local 'self' (a nil value)
stack traceback:
	/test.lua:11: in function 'multiply'
	/player.script:28: in function </player.script:23>

Don’t get it, I thought that’s the same as example in manual.

Replace by m:create(), m:add(), m:multiply()

2 Likes

Thank you! Thank you! @TheKing0x9
Are those variables unique to instance calling those functions, I don’t see that mentioned?
EDIT: just tested - self on instance and table are different. And using same script on 2 different instances returned that self in table are the same. Meaning it’s useless to create instance individual calculations.

1 Like

If you wrote this all without the colon notation it would be easier to understand. The colon is just a shortcut that saves you from typing a few letters.

If you define a function with a colon, it just adds an invisible ‘self’ argument.

function m:create()
end
-- is exactly the same as:
function m.create(self)
end

If you call a function with a colon, it passes in the table it was called on as the first argument, ‘self’.

m:create()
-- is exactly the same as:
m.create(m)

-- it is NOT:
m.create(self) -- which is probably what you want to be doing...?

All you need to do is write module functions with ‘self’ as the first argument (like the engine callbacks: init, update, etc), and then pass in the ‘self’ from your script when you call them. ‘self’ can (and must) be passed around just like any other variable. It is a local variable inside the engine callback functions.

6 Likes