What is the most optimized way of making abilities

I know that the simplest way to make abilities that affect the server would be to use a remote event, but I know that remote events are very bad to use if u have multiple of them.

So what is the best way to make abilities then?

PS: Didn’t know where to really upload this but oh well also, sorry if I’m unclear.

In my opinion, this is how I would go about it:

  • Client handles ability input
  • Client fires to server when ability is triggered
  • Server recieves client input
  • Server validates client input
  • Server sends to all clients of the triggered ability

It’s okay to be cautious of using remote events, but the memory they consume is only temporary as they are signaling from client → server or server → client and then they cleanup.

If you’re worried about having 100 abilities and 100 remote events, maybe try generalizing the abilites into categories so the events are limited while the abilities are limitless. As a very basic example:

local ReplicatedStorage: ReplicatedStorage = game:GetService('ReplicatedStorage')

local FarDistanceAbilities = {
	['Throw'] = {},
	['Swing'] = {},
	['Glide'] = {},
}

local WithinDistanceAbilities = {
	['Punch'] = {},
	['Kick'] = {},
}

local FarDistanceEvent = ReplicatedStorage:FindFirstChild('FarDistanceEvent')
FarDistanceEvent.OnServerEvent:Connect(function( Player: Player , AbilityType: string )
	local AbilityInfo = FarDistanceAbilities[AbilityType]
	if not AbilityInfo then
		return -- Ability did not match up.
	end
	-- Handle specific ability.
end)

local WithinDistanceEvent = ReplicatedStorage:FindFirstChild('WithinDistanceEvent')
WithinDistanceEvent.OnServerEvent:Connect(function( Player: Player , AbilityType: string )
	-- etc
end)

My suggestion would be to add a rate limits per player and proper validation. Roblox briefly mentions these topics within their documentation as well: https://create.roblox.com/docs/scripting/events/remote

2 Likes

Framework for abilities are generally contained in modules and its typically up to the developer how they like to structure that aspect of abilites. But for actually activating and using abilities I think there are only a few good ways, and this is the one that I use though there are a few requirements:

Tool activation (allows me to go from server - > server without needing a remote)
Effect replication system and audio replication system (ill explain later)

So generally this is the flow of abilites for me:
Tool → Required ability module (up to you how exactly you design these) → Hitbox Class (open sourced and amazing to use) → Effect Replication Module (takes a key and uses unreliable remote events to run either a function or some preset for vfx)

The ability logic itself works like this:
Animation plays on an animation controller or you could just play the animation

Keyframe events dictate when and for how long hitboxes come out (handle this with a “boilerplate” so I don’t repeat myself, the other keyframe events are named in a way that I can use the keyframe name as a key for my effects replication)

While the animation is running I’ve connected a few bindable events to listen for anything that would interrupt the ability (getting hit, parried, etc)

The hitbox lands and runs hit verification using a HitService module (just validate attacks and decides which events to fire etc)

I’m not against using remote events but I’d like to minimize the amount of times I have to use them so I either use things like tools or I generalize my remote events so i don’t have a million of them for each input the client could make. Say the player presses mouse button 1, instead of having 3 different remotes for air attacks, sliding attacks, and crouching attacks, you could just pass these modifiers as strings with the remote. Cuts down ont he amount of remotes you need and keeps everything clean.

I’ll create a base abstract class that all abilities will inherit from. This will help enforce a common interface.

Class Structure

From my experience working with Luau, I’ve developed a standard structure for each class. It looks like this:

Ability (Base Class)

├──Server
 | └── Controller
 |
└──Client
   ├── Controller
   └── Handler

· Controller (both Server and Client side): These are the class constructors that handle the main logic on their respective sides.

We’ll add 2 RemoteEvents to our class folder: ClientSetup and Activation

· Handler: This is a module that can exist on either server or client, depending on where the class instance is created. It handles the event for creating the object.

Logic

· On the server side, in Server/Controller, we’ll declare an Activate() method. This method should be overridden in every child class that implements a specific ability. The server will call this method when the client sends an Activation (RemoteEvent), signaling they want to use the ability.
· On the client side, in Client/Controller, we’ll declare a BuildActivationData() method. It builds the data for sending the activation signal. Child classes should also override this method.

To be clear. Remote events are a pipe to send data across the network. Sure each pipe needs an ID so that they can be sent to the correct thing, but if you use a single pipe for handling data for multiple things you end up reinventing the wheel on that (often worse, like using a string instead of a number and doing this after roblox has already resolved the id to the remote in question). So to be clear remote events are efficient. You just want to minimize the data you intend to send because that allows more systems more bandwidth and it also reduces ping with smaller messages (though I’m pretty sure Roblox already optimizes packet sizes if they can).

So use them wherever you need them. Just make sure you are actually only sending data you need to send to keep the server and client in sync (as much as you need to). Also use unreliable remote events if you don’t need every single packet (like for player replication since if you miss a packet you’d get a newer one before a resend anyways).
And in fact many remote events is very likely better than 1 since Roblox can more efficiently pipe data to the right place that way instead of you needing to manage it.