I’m working on my game, and I decided to write a custom (pseudo) class for my monsters, called Monster. It has a .new method, and several other methods relating to the functionality of the monster model.
In the constructor method, .new, I clone the monster model, assign it a unique EnemyId, and add it to the Workspace. I also add the new Monster to a table outside of the class called monsters. This keeps track of all the monsters added to the game.
Code:
table.insert(monsters, self);
I also have a method that’s outside the actual class that is used to lookup a monster object with its EnemyId.
function Monster.LookUp(enemyId: string): MonsterType
print(monsters);
for i,v in pairs(monsters) do
if v.EnemyId == enemyId then
return v;
end
end
end
I use this in hit detection to find the Monster object whose model was hit and use the Monster:Damage() method on it to handle what happens when a monster gets damaged. The thing is, this function always returns nil, and the table always prints to nil.
local Monster = {}
Monster.__index = Monster
function Monster.new(name: string, region: string)
local self = setmetatable({}, Monster)
self.Name = name
self.EnemyId = nil;
self.Health = settings.Gameplay.EnemyHP[self.Name]
self.PlayerFoundEvent = Instance.new("BindableEvent")
self.PlayerFound = self.PlayerFoundEvent.Event
self.Model = nil
local enemies = {}
for i,v in pairs(EnemyStorage:GetChildren()) do
if v.Name == self.Name then
self.Model = v:Clone();
for i,v in pairs(Enemies:GetChildren()) do
if v.Name == name then
table.insert(enemies, v)
end
end
local idStringVal = self.Model.EnemyId
idStringVal.Value = `{self.Name}{(#enemies)+1}`
self.EnemyId = idStringVal.Value
end
end
local reg = settings.Gameplay.Regions.MonsterSpawns[region]
self.Model:PivotTo(Utils:RandomPositionInReg3(reg))
self.Model.Parent = Enemies
local human: Humanoid = self.Model:FindFirstChild("Enemy");
table.insert(monsters, self);
human.Died:Connect(function() self:Damage(100) end)
coroutine.resume(coroutine.create(function()
self:Wander();
end))
return self
end
In the monster table is there an __index metamethod pointing back at the monster table (Monster.__index = Monster), and in the monster table is there a damage method?
And in your original post, when you say the table always prints to nil, are you talking about when printing the monster table, it prints nil? Or do you mean Monster.Lookup returns nil? Or something else?
In the Monster.Lookup method, I tell it to print the monsters table so I can make sure the table is populated. Printing the monsters table returns nil, and the Lookup function returns nil also.
I assume this is happening because you require the module from 2 different scripts. Basically, 2 instances of the module are created, that have a different copy of monsters. A simple solution is to make monsters a global variable and mention it this way:
_G.monsters = _G.monsters or {} --if monsters doesn't exist, create it
local monsters = _G.monsters --just a reference
Also I assume the table is empty not nil, because if the function returns nil it means it can enter the for loop, else it would throw an error.
Just to clarify, printing monsters it outputs nil, not an unpopulated table? Calling pairs with no arguments should give you an error saying argument 1 missing or nil.
I see in your code above that you aren’t ever declaring a monsters table also. But then again calling table.insert with no arguments should give you an error. Is that above? Can you try printing the monsters table directly after inserting self into it?
Well didn’t know that, last time I used modules I had issues similar to this due to modules acting weird each time they were required by a different script.
Also I think the issue is that on each require, the thing reruns, so the table gets its original/default value again, which is {}.
It doesn’t rerun. It only gets run on the first require call assuming it’s required from the same environment (from the command line, from server scripts, client scripts…)
I’m going to offer another solution since the current solution to this is very unsatisfying.
Reckless use of _G is dangerous, and can get very unorganized when used so unsparingly. The best solution is to set up the table of monsters inside the monster class itself.
When simulating classes in Lua, you can use the dot syntax to simulate both your constructor and static methods (a lot of writing in Lua is about using conventions to convey intended usage in spite of the rather loose syntactic capabilities).
local monsters = {}
local Monster = {}
-- your monster class
-- I recommend providing ways to interact with the monster list rather than always using the list itself. Nevertheless:
function Monster.getList()
return monsters
end
function Monster.find(identifier)
return monsters[identifier]
end
This way, you can access your monsters list anywhere you’ve required the Monster module. Please refrain from using _G so recklessly. It gets very risky/dangerous the bigger your game gets.
Very briefly, it makes program state unpredictable.
if a method in one of the objects triggers a side effect which changes the value of the shared global state, then you no longer know what the starting state is when you execute a method in the other object. You can now no longer predict what output you’ll get when you execute the method
Further, global state hurts the readability of your code. If your code has an external dependency that isn’t explicitly introduced into the code then whoever gets the job of maintaining your code will have to go looking for it to figure out where it came from.
Thanks for pointing this out, I’m not very familiar with OOP in Lua but this seems like a better solution than mine(btw it reminds me of the way objects are done in Java).
@FreakingHulk If the above reply is working, it should be marked a solution instead.
Yep. Since Lua doesn’t offer native class functionality, we can’t exactly write a class like we normally would in Java, C#, C++, etc. There are quite a few ways to simulate this in Lua, but I think the easiest and most efficient way is to use metatables to allow for small, specific tables (your objects) to index/access information from big, general tables (your classes).