How to optimize a game without using complex mechanics

Hello Developers,
Many of you know that there is a concept or framework called FPS (Frames per second).
If you know how FPS and fps drop-down works skip to the optimization process, if not, here’s a clear explanation of how to do it!

Each time your device renders a frame (think frame as whatever you see on screen, including UI), it counts as 1 frame. For example, each time you see a change on your screen, it’s a different frame.

In other words:
Think frame as an image, when you flip 10 images that are quite similar to each other in a very short time, it will look like a video. The same thing is fps, your laptop renders 1 image on your screen in a very short period, and the speed of changing images is so high that you don’t find it changing images or slide shows but you see it as a video or a gameplay.

Now your devices renders until you leave game, it can be 1000, it can be 1034943034934, or even 200. So why is FPS not 200 or 1000, but some number around 60.

Basically FPS is not the count of frames your device rendered overall, it is the count of frames per second. So if your device renders 30 frames in 1 second, then it is 30 FPS, but not 30 + old frames count.

So each time your device renders, it has to consider physical and user interface objects. So it means every new item you add to a player, it adds up load on the device of the player. So the more the number of objects in the camera viewport and user interface objects, the more laggy the player becomes.

So is the solution for this to remove all objects? No ofcourse not, you won’t have anything if you have 0 objects. You try to reduce the time taken per frame by reducing objects without affecting the game concept or game.

Optimizing the game

This is a pretty hard task and can never be perfect, but helps overall, so if your game is not fully optimized you should be happy it’s at least better than 0 optimization.

Now 3D objects have more impact on the load on the device than 2D objects, additionally, too many user interfaces and scripts on the client can affect the performance, so whatever you do should not break any script and the game should work according to the concept you made.

Now you need to reduce the number of items that are complex and increase the load per rendered frame.

These are a few things that can possibly be reduced:
1. Unnecessary elements: When a player is not focusing on a UI or has too many objects in their PlayerGui folder that are simply lying without any use, turn off their Visible or Enabledproperties of those, if your sure, that you don’t need them very soon, then destroy them. And add them back when they should show up, ofc it might take 0.1-3 seconds to load the UI again, but it is better to improve performance and waiting 0.1-3 seconds isn’t something bad and wouldn’t normally affect a player’s experience.

2.Too many loops: If you use too many loops, then each loop should be run in every render of the device, and it will add up a lot of load on their device and their FPS will drop in a very significant amount over time. End and break loops and try to avoid the memory leaks the best you can!

3. Hiding far objects/Streaming Enabled: A player won’t focus on objects which are far from them, most of the games, in those cases, you can enable StreamingEnabled property in workspace which will not load the 3D objects which are outside the radius you keep.

4. Memory clean-up: Whenever you perform large tasks, make sure you clear the data after it is done because storing a lot of data will increase the CMU, the client memory usage, and the memory consumed by the client, when it increases, the device will have more things active and speed of device itself will reduce not just the game, and reducing this is the best option. And deleting unnecessary data will surely increase the performance of the device, and therefore increasing the FPS.

5. 3D world optimization: Try to use the least amount of triangles possible on a mesh without affecting the shape of the mesh, each triangle counts, so whenever you making a mesh, try to reduce the triangles by merging them or reducing the number of vertices where you don’t have anything to do specifically.

If your using blender, you can use decimate and change the ratio to reduce triangles, but make sure you don’t compromise on your game if you want your game to be the best. But don’t forget the user experience too.

However, optimization of a game differs from game to game, for example pvp games require players to see long distances to shoot them such as arsenal or bedwars. And obby games don’t really need that much of a sight compared to pvp games and ultimately these are the few points which are based on universal validity and more things can be done based on your concept to reduce load on server and client inclusively.

Thanks for reading this long, I hope you can understand it, good luck on your future and current experiences.


Awesome post but what do you mean by this? How would you clear it, if you could provide examples of a large task and how to clear the data or code samples that would be great. Thanks!

1 Like

Hmm, it’s hard to explain it without going into depth about memory allocation, garbage collection and all that kind of stuff.

Here’s my attempt at explaining it: the Roblox Client (the program that runs on a given player’s machine) needs to allocate memory for itself. When a program does this, it prevents other programs in the system from using this memory. This is why we must deallocate it, otherwise this program would take all of the system’s memory for itself.

Lua(u) solves this issue with something called a garbage-collector. It basically does the process of deallocating resources automatically for us. And that’s what we have to worry about: what is being freed-up / deallocated by the garbage collector.

The implementation uses a system of weak-references and strong-references. For any resource that takes up memory (such as an Instance or a table), the language runtime keeps track of the different kinds of references that there exist to this object. A reference is simply a place where this object is being actively used, which means that when this object serves no purpose anymore, the garbage-collector frees up this memory.

Here’s an example:

     local myTable = {1, 2, 3, 4, 5}
     <do something with `myTable`>

-- `myTable` went out of scope. We can't access / use it anymore,
-- so it *will* be deallocated.

It’s a rather simple example, but it will do.

Ok. The garbage-collector is cool and all, but where do the problems rise? When we keep a strong-reference to an unused object.

You know, there used to be this function in the Roblox API called Instance:Remove. It is analogous to the Instance:Destroy method, but it has key differences. All Remove did was parent the instance to nil, so here’s the thing: whenever we create a connection (BasePart.Touched, for one.), we create a strong-reference to this instance. So, whenever Remove was called and there were connections to some event in this instance, it would never be garbage-collected, which in turn generates a memory leak. This is when a process (a program) keeps the allocated memory to itself, never releasing it. This, stacked (many leaks at a time), will make our program use tons of memory, which is terrible. So, we decided to fix this by deprecating Instance:Remove and using Instance:Destroy, which disconnects any connections related to this instance.

So, although Instance:Remove has been deprecated, doing Instance.Parent = nil can still result in a memory leak. This is why there are people complaining and asking Roblox for a getnilinstances function (or similar) (see here).

What OP is asking us to do is to be mindful of the resources we allocate, and make sure they are properly deallocated so that they do not leak memory.

Thank you for coming to my TED talk.

P.S. incredibly oversimplified, kinda forgot to mention that any Instance which is a descendant of the DataModel (i.e., not nil) will not be automatically deallocated, because the engine itself holds a strong-reference to it. So, for instances that are meant to be discarded, we must use Destroy on them.