I'm having a tough time properly setting variables such as money, and part ownership, to specific players. I know how to get the UserId of a player touching a block. I've managed to make a single-player tycoon by setting ownership of a door and then checking UserId of different parts when players touch them. My problem is that this only works with one plot, I'm having trouble cloning parts and connecting them to only a single player without using global variables. For instance, when I touch a door a button spawns that, when touched, either tells you you don't own it, or buys a dropper. This works fine if I only have one door, and one plot of droppers. If i add a second door or plot they link in with the first set through the global variable I've set. If I set it as just a local variable, there's no way to get the button to recognize an owner as far as I know. If someone would nudge me in the right direction I would greatly appreciate it.
Hey syntaxjedi,
Wonderful question! I appreciate your effort to understand things in good enough detail to see this problem. It is for this reason that I would love to give you an explanation:
Scripts in ROBLOX obviously can't share data with each other directly, so people often circumvent this by using _G or things like IntValues and NumberValues. These ways work, but there are two problems with these approaches:
They're messy: these methods involve exposing data to all scripts, not just the ones that actually need the data. This opens the door for encapsulation problems, which I will explain in a moment...
They're insecure: anyone who has the ability to see your game's hierarchy -- usually by way of external programs, like exploits, -- will be able to quickly find these exposed values and will be free to change them!
Now, encapsulation is a practice that involves restricting access for variables only where they are intended to be accessed. Here's an example:
Points = 10 ... function AddPoints() newPoints = Points + 50 ... end DoStuff() ... print(newPoints) --> 60
Oh no! Variable newPoints
is accessible outside AddPoints! This may be immediately noticeable in a small script, but in a large script, you may forget that it already exists, and use it again, which can lead to very big problems down the line that will take lots of time to resolve.
What can we do? Use the local
keyword. Local is something called an access modifier. It limits where a variable can be accessed. Using local, we can restrict newPoints
to AddPoints, preventing unintentional behavior further down the road. Observe:
local Points = 10 ... local function AddPoints() local newPoints = Points + 50 ... end DoStuff() ... print(newPoints) --> nil
Wonderful! This doesn't just apply to variables in scripts, though. You might end up using _G or IntValues and ObjectValues to share data among scripts, which will cause the same problem!
Now, you may be wondering why I took the time to explain encapsulation, since it doesn't exactly answer your question. You may even already know what local
is, and probably have a working knowledge of encapsulation.
I went through all this to solidify in your mind what you've deduced and ultimately lead you to ask this wonderful question. IntValues and _G suffer from these same encapsulation problems, and overcoming these problems by using loads of different names or spreading them out in weird schemes only adds to confusion and messiness. *There has to be a better way,* you wonder.
And you're right.
BindableFunctions are a way for two scripts to talk to each other. A script can call another script's BindableFunction and find out if that script's tycoon has the same owner! How do we use them, though? Here's how:
Make a BindableFunction, and put it in the model that contains all the tycoons; name it PlayerOwnsTycoon
.
Make a script in ServerScriptService
that, when PlayerOwnsTycoon
is invoked, checks each tycoon's owner and returns true if a player already owns a tycoon. Write like so:
local Tycoons = path.To.Tycoon.FolderOrModel; local OwnsTycoon = Tycoons:WaitForChild("PlayerOwnsTycoon") function OwnsTycoon.OnInvoke(player) for i, tycoon in ipairs(Tycoons:GetChildren()) do local owner = tycoon.GetOwner:Invoke() if (owner == player) then return true end end end
local Owner = nil local Door = path.To.Door local GetOwner = path.To.GetOwner local OwnsTycoon = path.To.OwnsTycoon -- Returns the owner of the tycoon, or nil if there is none. local function GetOwner.OnInvoke() return Owner end -- Set the owner if other is a character part, AND if the player does not already own a tycoon local function OnTouched(other) local player = game.Players:GetPlayerFromCharacter(other.Parent) -- When we invoke OwnsTycoon, the script we made earlier will look through each tycoon, and return true if the player already has a tycoon. By using not, we're saying: "Hey, if this is true, then don't set the owner!" if (player and not OwnsTycoon:Invoke(player)) then Owner = player; end end Door.Touched:Connect(OnTouched)
By now, you have the basis for:
This method is much more secure than using ObjectValues, and it will be rather robust as well. What's more, you can use BindableFunctions to share things like points, tycoon progress, etc... anything you like!
Here are links to various similar objects that will help you make custom events too, or even allow local and server scripts to talk to each other:
If you can learn to use all these effectively, you'll be well on your way to creating very efficient and manageable games!
Have a nice day, syntaxjedi, and best of luck with your tycoon project!
Warm regards,
tkcmdr
Edit: Improved formatting