Im making a Bee Swarm Simulator type game, and i’ve been using Bindable Events/ Remote Events when a Player collects Pollen so the Server can then give the Pollen/Honey to the Player by running checks first.
I’m planning on making an Event Listener of some kind that would maybe store the Player’s Position every few seconds?
And check all the Fields in the Player’s radius, if the Player is close to the Field then communicate to my Module to then keep track of the Player so the Player can collect Pollen freely, or something like this.
I would just love some ideas on how i could make this, because right now i don’t know how to do this neither have i done this before.
I also try to avoid things like ReplicatedStorage because it can be easy for Exploiters to find clues to how your game logic is made and exploit the game that way.
Thanks for reading.
local function ReduceFlower(Requester,Flowers,PollenToBeGathered)
for i = 1, #Flowers do
local FlowerExists = SearchFlower(Flowers[i])
local RequesterRootPart
if Requester and FlowerExists then --If Requester Exists and Flower exists.
local FieldBox = FlowerExists.Parent.Parent.FieldBox
local FlowerVariables = CreatedFlowers[Flowers[i]]
if Requester.Character.HumanoidRootPart then --If Requester has a character.
RequesterRootPart = Requester.Character.HumanoidRootPart
end
if (RequesterRootPart.Position - FlowerExists.Position).Magnitude < 18 and FlowerVariables then --If Request is within 18 studs of the Flower in all Axes.
if (FlowerExists.Position.Y - FieldBox.Position.Y) <= -3.25 then --If the Flower Y Position is less than -3.25 then don't lower the Flower's Y Position.
FlowerExists.Transparency = 1
FlowerExists.FlowerDecal.Transparency = 1
return
end
if FlowerVariables.PollenAmount > 0 then
FlowerExists.Position -= Vector3.new(0,PollenToBeGathered * 0.025,0) --Reduces Flower's Position by 0.25 (Sweet Spot)
FlowerVariables.PollenAmount -= PollenToBeGathered
Requester.PlayerStats.PollenAmount.Value += PollenToBeGathered
end
end
else --If Requester does not exist or Flower does not exist then warn.
warn(Requester.Name.." Does not exist or Flower does not exist: "..Flowers[i].Name)
end
end
end
My code that listen for when the Player is collecting Pollen, i don’t know if its structured well or is bad, so ideas would be appreciated.
Your current script is very vulnerable. You are relying on the client for crucial game data (which is very bad), and you are doing no validation of data server side. Never trust the client for anything.
If you are using a tool for this, you can detect an activation of the tool and find the closest field the player is in. You could add attributes, names, etc. to each field of different flower types and raycast downwards from the player when you suspect a field change, or just use a .Touched
event for a BasePart
.
As for the events themselves, I would recommend making your own class similar to RBXScriptSignal
for event changing things. It’d be better than bindables.
local CustomEvent = {}
CustomEvent.__index = CustomEvent
--current events
CustomEvent.Events = {}
--class constructor
function CustomEvent.new(name: string): Event
local self = setmetatable({}, CustomEvent)
--set data about the event
self.Callbacks = {}
self.Fired = false
--add to the module of events
CustomEvent.Events[name] = self
--return the new event
return self
end
--waiting for a signal to exist
function CustomEvent:WaitForEvent(name: string): Event?
local Signal
local attempt = 0
repeat
Signal = self.Events[name]
attempt += 1
task.wait(1)
if attempt == 5 then
warn("Yielded for signal 5+ seconds")
end
until
Signal or attempt == 60
return Signal
end
--replicating RBXScriptSignal's :Connect function
function CustomEvent:Connect(callback: (any?) -> (nil)): nil --the given callback should not return a value
--add an asynchronous version of the given callback to be run
table.insert(self.Callbacks, callback)
end
--to fire your custom event
function CustomEvent:Fire(...:any?): nil -- '...' is data to be sent to the connected callbacks
local extraData = {...} --get extra data into a table
--run each callback
for _, callback: (any?) -> (nil) in next, self.Callbacks, nil do
--an asynchronous version of the callback was added so we can just run it normally
task.spawn(callback, table.unpack(extraData))
end
end
--type for custom event
--I didn't include inherited things that won't be used
export type Event = {
["Connect"]: (self: Event, (any?) -> (nil),
["Fire"]: (self: Event, any?) -> (nil),
["Callbacks"]: {(any?) -> (nil)}
["Fired"]: boolean
}
return CustomEvent
An example use for your game:
local players = game:GetService("Players")
local Event = require(game:GetService("ServerScriptService")["FinishWithYourPathHere"])
--create a new event for when a player enters a new field
local FieldEvent = Event.new("PlayerFieldChanged")
local fields = {}
--now, every time a player changes field, you can store it
--example use in a player's raycast using suggestions I mentioned earlier:
--'fields' holds the current field of each player
local function checkNewField(field:string, hit:BasePart)
local player = players:GetPlayerFromCharacter(hit.Parent)
if player and fields[player.Name] ~= field then
fields[player.Name] = field
FieldEvent:Fire(player, field)
end
end
for _, field in next, fields, nil do
field.Touched:Connect(function(hit)
checkNewField(field.Name, hit)
end)
end
Another script can receive this:
local Event = require(game:GetService("ServerScriptService")["FinishWithYourPathHere"])
local function onFieldChanged(player: Player, newField: string)
print(`@{player.Name} entered field: {newField}`)
end
event.Events.PlayerFieldChanged:Connect(onFieldChanged)
Another example use for listening for a player collecting pollen:
local players = game:GetService("Players")
local Event = require(game:GetService("ServerScriptService")["FinishWithYourPathHere"])
local CollectEvent = Event.new("PlayerCollectPollen")
players.PlayerAdded:Connect(function(player: Player)
local char = player.Character or player.CharacterAdded:Wait()
local backpack = player.Backpack
local tool = backpack:WaitForChild("ToolName", 5) or char:WaitForChild("ToolName", 5)
tool.Activated:Connect(function()
CollectEvent:Fire(player) --custom event saves a lot of scripts holding memory references to the tool
end)
end)
Event.Events.PlayerCollectPollen:Connect(function(player: Player)
print("Player @"..player.Name.." requested to collect pollen.")
end)
This structure could be applied to the things you mentioned you wanted to do.
Wow, i didn’t know i could make my own script signals and events.
Could u provide some in-depth tutorial for this so i can learn more?
Thanks for your reply.
Sure, what parts do you need to know more about? After making this module, you should be able to treat each custom event like an RBXScriptSignal, provided you add the other methods to it.
Basically everything, i feel like especially the passing of arguments/callbacks i feel like im not the most keen on, and these Methods u just mentioned.
Im very new to this sort of thing with making Signals/Events.
But i feel like once i learn this it will be very useful, since i can make my own Events instead of using roblox’s own Events which i believe has some drawbacks if im not mistaken.
This system uses an Object-Oriented structure. This means each signal is called an object, and they all inherit methods from the class. We use a class constructor (indicated here by .new
) to create this object. In each function, self
refers to the signal itself. It’s defined through colon notation. Here’s some more information.
When we use :Connect
with this class, we are passing our callback function which is to be run whenever the event fires. We use coroutine.wrap
to allow each callback to be run asynchronously when the event fires. This means they can be run at the same time as one another.
When we use :Fire
, we are passing information that we want to be sent to each callback function. Each callback will receive this data and do as it will with the data. ...
is used to store this data and is then gathered into a table using local extraData = {...}
. It then iterates over each callback that has been connected to it, setting it to run with the extra data.
I’ve only explained the functions I included, please let me know if you need more detail and on what parts.
Roblox’s events are very helpful and very good for a lot of things - I think your own events and Roblox’s should go hand-in-hand.
Why you can modify variables in functions passed and why the functions themselves actually run
Let me give you a statement that will probably make you go, “what?”
A variable does not store a value.
In fact, a variable stores the memory location of the value. A variable is a pointer to a location in memory that can change whilst the program is running.
When we pass a function to :Connect
, we are in fact passing the memory location of a function. This means we can run it with the correct code.
So, when we reference variables within these functions, we are referencing the same location in memory, hence why if we changed it in one place, it would change in all the others, even if it is under a different variable name.
Hope this helps!
Thank you for the extra info, could we talk via discord if i need any help on this sort of topic?
Another thing how will i fire this, just like a Remote Event or Function?
And does this go Client → Server → Client? or vice versa?
I made this to mimic bindables, I personally think it’s a lot simpler than bindables and it takes up less memory. So, use it on the same side of the client-server boundary. In terms of firing, you could fire it from any script that has the reference to the event.
local Event = require(path_to_module)
--create a new signal
local Signal = Event.new("Test1")
--we can fire it from here...
Signal:Fire()
--or, in a seperate script...
local Signal = Event.Events.Test1
Signal:Fire()
--either way, it will run any connected callbacks.
Interesting, thank you!
Any issues i have ill post them here.
Heres my discord if u wish to talk via there:
peter.3_
Just like to add that I’ve edited my solution to add another method for waiting for a signal to be created.
--Script 1
task.wait(1)
local Signal = Event.new("Test1")
--Script 2
local Signal = Event:WaitForEvent("Test1")
Signal:Connect(function()
print("Signal fired")
end)
It’s helpful, hope it helps you!
Why wait thought? Is it so there’s no clogging up?
It’s an easier way to wait for events; I noticed the event takes a little bit of time to be indexable, because of the time it takes to create the event. It was a very short amount of time but it required about 10 lines of code to fix. I put those 10 -ish lines of code into the class and made a method for it.
hm ok, imma start looking into the code now was working on a project.
So i have some questions:
-- Class Constructor
function CustomEvent.new(name: string): Event
local self = {}
setmetatable(self, CustomEvent)
self.Callbacks = {} -->> Any Functions/Arguments to be executed/passed!
self.Fired = false -->> Tells us if this Event has fired.
--Add to the Events Table.
CustomEvent.Events[name] = self
--Return the self/New Event.
return self
end
In the “.new” Function mentioned above, it says that this function will return an “Event” Type but the values you set for it is only 2 of those values?
for example if i set the script mode (i think thats the name) to --!strict
It shows me this:
Now i think its good(correct me if im wrong) to set these variables and connect everything together.
But why did u not set them here directly in the class constructor?
Another thing is:
Callbacks are Functions/Arguments to be executed/passed right? Example:
Event.Events.PlayerFieldChanged:Connect(onFieldChanged) -->> This is a callback
Or if im not mistaken the above was for Function like the normal :Connect.
And below is the arguments like in FireServer/Client() for example:
FieldEvent:Fire(player, field) -->> Fires Event with given Arguments.
I believe its separate from the two, but please correct me if im wrong.
Another thing that i actually realized was that the Signal here is an Event not boolean:
local Signal = Event:WaitForEvent("PlayerFieldChanged")
Signal:Connect(function(...)
print("I got fired!!!")
print("Arguments: ", ...)
end)
I thought this was a boolean(Also because of the name being Signal not Event) at first because of how this was structured:
But now i understand it by just setting the types of some variables like Signal to Event.
Also makes sure script doesn’t warn when using --!strict
function CustomEvent:WaitForEvent(EventName: string): Event
local Signal: Event
local attempt = 0 --Times waited for a Signal.
repeat
Signal = self.Events[EventName] -->> Signal is equal to the Event given.
attempt += 1
task.wait(1)
if attempt == 5 then -->> If we have waited for a Signal for more than 5 seconds then warn.
warn("Yielded for Event: "..EventName.."5+".."seconds")
end
until
Signal or attempt == 60
return Signal
end
So to add to this post i was thinking of making my own Bindable Functions code for this Script you made, so i could return arguments and things like that
now i wanna try and make it with as minimal help as possible but i still want some guidance/path that i could take to make such Features.
And also maybe in the future implement this into Server-Client if needed! (Maybe not the best idea since Remote Events exist)
But overall i learned a lot here and i wanna try and make my own version and get some explanations back from you!
Thanks for reading.
I will provide a Place copy with some of my comments if u wanna see my thought process(and things i didn’t include in this post) when reading through your code and some things i added to it:
PLACE:
CustomEvents Place.rbxl (56.9 KB)
Another thing, what is next???
for _, field in next, PlayerFields, nil do -->> what is next???? for _, field in next??? so it jumps up an index without getting the current one?
field.Touched:Connect(function(hit) --When Field is touched check Field.
checkNewField(field.Name, hit)
end)
end
I only included items that were relevant to the event class, so using strict mode would indeed create that message.
Do you mean the functions of the class? I can expand on the type if you want me to below.
When using the :Connect
method, you pass a callback to be run when the event is fired. However, like you showed in your snippet, any parameters you include in :Fire
will be passed to the callback functions when executing. You don’t have to use ...
if you know what parameters you are expecting.
Making your own bindable function would be harder, but it’s still possible. I think you would need some sort of middle-ground function.
function Bindable:Invoke(...: any?): any?
--we would only have one callback, just like normal BindableFunctions
local results = table.pack(self.callback(table.unpack({...})))
return table.unpack(results)
end
next
is just another iterator function. You can use it to start iterating from a certain point, but I don’t.
--next is the iterator we are going to use
--yourTable is the table to be passed to next
--nil indicates to next that we want to start iterating at the first index within the table
for index, value in next, yourTable, nil do
end
Please ask if you have any more questions, I’m not quite sure I fully understood your post.
Hi thanks for the reply, so when i said functions of the class i just meant to mention them so in !strict mode the script wouldn’t get mad and just look a bit better and more organized but i feel like i can do those ill learn a bit more too, and what is table.pack and table.unpack and why would it be good for making a BindableFunction?
And when u meant “a callback to be run” and that the parameters i include in :Fire will be sent to the callbacks which are Functions i don’t need to use “…” it’s just so i don’t have to type them out on the explanation.
But so interesting and confusing at first since the name of Callbacks aren’t functions or parameters/arguments.
And another thing before i try to attempt to make my own BindableFunctions i just need to make the receiving end return something back to the function that called :Invoke right?
And some sort of new receiving method instead of just :WaitForEvent etc.
And what do you mean by only one callback on your :Invoke?
i tried fixing the types problem but i couldn’t fix it, tried making some functions or reference to the functions inside the table.
You know how?