Alright, so this might not be how they do it it but here’s a few ideas i can come up with right now:
-
Starting off, the grass itself, You will want to have a folder in workspace for it containing all the grass meshpart assets. This way you organize your workspace and your scripts. You can manually place the grass or you can probably get away with using a modelbrush plugin,
-
Next up: Graphics-level based grass (The quality of grass will be proportional to the quality of graphics)
How we might do this is by using this API: UserGameSettings | Documentation - Roblox Creator Hub
From there we should fire whenever it is changed, We base two values off the graphics level:
1). Our render-distance for grass (How close the grass is so we can see it)
2). Our new mesh-asset id (The new mesh of the grass)
When it is fired, we can use a dictionary, say the graphics quality value of ‘1’ for this example.
This ‘1’ may correspond to a mesh-id in our dictionary, like so
local QualityToMeshId = {
[1] = 'rbxassetid://idhere'
}
To make this cleaner though, we will be using an array with all the ids in order
We will also want to pre-load these IDs to make the transition unnoticeable, So when the function fires, we will get the graphics value and our new ID will be QualityToMeshId[graphicsqualtiylevel]. You will then iterate through the folder with the grass (That’s why we organized it) and you will set the IDs of all the grass instances.
Now, as for render-distance, im not too good at this sort of stuff, but how I would do it is use either the :DistanceFromCharacter() API or the :FindPartsInRegion3WithWhitelist() API, (our whitelist being our grass-folder). We will have our graphics value and that will correspond to the render-distance, again, we can make another table. Once you’ve the render-distance you will make a new Region3 everytime the player Moves a set amount of studs and the size of the region3 will be dependant on your render-distance.
From there you will iterate through what :FindPartsInRegion3WithWhitelist() returns and make all of the transparency 0, while keeping all the other grass-instances at 1.
- Finally, the effect: You can implement math to make the grass move depending on the direction the player is moving. We will use humanoid.MoveDirection for this.
Use the .Touched and .TouchEnded events for this, When the grass instance gets touched, You will tween the grass instance to be pushed down, The effect can be achieved by altering the scale and CFrame mostly.
The maths for the grass’s new orientation may look something like this:
local MoveDir = humanoid.MoveDirection.Unit
local FaceVector = Grass.Position + Vector3.new(MoveDir.X, 1, MoveDir.Z)
local Orientation = CFrame.new(Grass.Position, FaceVector)
You can change the ‘1’ value to make it orient more but you want to be careful with this so that it won’t make the roots of the grass poke out. For the scale, you can squish the grass a bit which is as simple as changing the ‘y’ property of the grass’ size.
Before you tween any of this, you may want to cache the grass’s old CFrame and Size, or, you could do some back-tracking and find the original size and CFrame, it’s up to you.
The .Touched event will tween the grass to this new CFrame and size while the .TouchEnded event will return it to it’s original size and CFrame.
That’s about it, just make sure you don’t forget to add a debounce for the .Touched events and absolutely DO NOT Forget to handle all of this on the client. Best of luck with it all