I’m pretty new to the Roblox Studio game engine, and I’ve been learning the system for about almost 2 weeks now, by developing a combat game. So far, I’ve been managing a Player’s stats by having them separated into two main modules:
a DataManager to store save data using ProfileStore
a StatManager stored in Characters to interact with the environment (take damage, modify movespeed, import attack stats for abilities, etc)
I was wondering if there was a better way to manage these stats while still using ModuleScripts and being easy to access from both Server and Local scripts. My current method uses ModuleScripts stored inside a character for access just by having a player/NPC in functions to know the stats of something, here’s an example:
-- example on getting the module from hitting an enemy
local StatManager = require(character:FindFirstChild("StatManager"))
-- code happens and i eventually have to import attack hitbox data
hitbox.Size = abilityData.Hitbox.Size
hitbox.Angle = abilityData.Hitbox.Angle
hitbox.CFrame = clientCFrame
hitbox.Offset = abilityData.Hitbox.Origin
hitbox.Shape = abilityData.Hitbox.Shape
The issue with my current method is that my autocomplete cannot detect the Module data from StarterCharacterScripts, making development a lot less efficient. Please share more about ways to be more efficient while staying easily scalable. Thanks!
So far this is how it looks, I think with getting the module from a character script is what’s making the autocomplete not consistent so im trying to find a more efficient universal way to handle stats. Since my current main way of damaging a character goes like:
Client sends Remote → Server makes Hitbox based on Client CFrame and Client’s StatManager attack data → any Hitbox that gets hit gets their StatManager:TakeDamage() function activated
I would suggest moving the ModuleScript into ReplicatedStorage. There is 1 disadvantage from this though, and that is that 1 module will handle every player, so you’d either need a :GetStatManager(Player) method, or modify your the methods and stuff to work with a player argument
Other solution that is less effort is overwriting the type like this
local StatManager = require(character:FindFirstChild("StatManager")) :: typeof(require(game.StarterPlayer.StatManager))
The type system is weird sometimes though, and overwriting types sometimes require defining a new variable, but in this case I don’t think that is an issue
The reason the type cannot be resolved is because the module is retrieved at runtime. This is the same reason why you don’t have autocomplete to find the StatManager module inside the player (but you can still use dot notation, :FindFirstChild() here isn’t useful as, if the module isn’t found, an error will happen anyway)
Yeah, I just tested that the type system refuses to work for modules like this. I think a more long-term solution would be to moving the ModuleScript into ReplicatedStorage and defining a PlayerList and a Stats object…
Although I’m now curious on how I should handle NPC stats, since I’m plannng this to be either an open world RPG or a dungeon crawler… Do I just make a different module for NPC list (going to be harder since NPC names will be duplicate)?
Also how would these approaches compare in terms of performance, I’m quite curious
If the type system refuses to play nice, you can try this
local StatManager : typeof(require(game.StarterPlayer.StatManager)) = require(character:FindFirstChild("StatManager"))
or
local Module = require(character:FindFirstChild("StatManager"))
local StatManager = Module :: typeof(require(game.StarterPlayer.StatManager))
or
local Module = require(character:FindFirstChild("StatManager"))
local StatManager : typeof(require(game.StarterPlayer.StatManager)) = Module
How different will the two be? If they are similar, use the same module
To get the corresponding stats from an npc or player, you can use the npc (the model instance or whatever) and the player instance (or the character of the player, but that one is destroyed when the player dies) as the key to a table. Lua tables can take anything as a key, including roblox instances, another table or even itself
(it would be nice to know what you used and know before coming to the roblox engine, like are you familiar with lua?, etc)
The location of the module, or having two modules, basically wont change much. What does have a small (and often insignificant) effect on performance is using roblox instances to store stuff, such as the value objects (IntValue, NumberValue, StringValue, …), or Attributes. Keeping data in pure lua with module scripts is the best for performance, and performance will not be a concern
Either method you chose from, the bottleneck will be computationally expensive functions you make, if you make them, rather than the implementation details
Wow this method actually works this time, good to know…
But you may have a great point they will be mostly similar in the way you can take/heal damage and deal status effects, for other stat related handlers i think i can just separate into some different modules…
I learned Roblox Lua like almost a decade ago as my first language but I wasnt very good with programming back then, so I stopped doing dev. But now I’m in college and I have a slightly better understanding with the programming workflow from other languages (C, C++, Python, JS/TS, HTML, CSS, and the Vue framework)
Yeah this is also very quite true I should learn about which functions are expensive to run and make the logic more compact requiring less loops and stuff