How to make Interactive Grass?

Hello!
I have tried to make Interactive Grass like in The Wild West.

I want to make Interactive Grass like in this post from @tyridge77. I mean when someone or something goes through it, it goes away by 35 degress.

I tried some things but i did not work.

I‘ve tried coding and constraints with Attachmets. I don‘t have any code examples.

I deleted every file out of desperation

You don‘t have to send a full code. Only how you would don it.

Thanks! :smiley:

9 Likes

Just go to terrain in the explorer and then click properties then turn on decoration thats what that is.

1 Like

This might help you → How to make 3D grass

Please tell me if it helps. :slight_smile:

@QuackingDuckQuack and @BaggyChris. He is talking about grass that can be interacted with. Not how to put basic Roblox grass in the game.

3 Likes

Well then I think the guy he’'s trying to get reference from is using a plugin.

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

18 Likes

Thanks for the reply :smiley:
I will try it out!

1 Like

No problem, please show me how it ends up looking, im really curious to see the end-product
Sorry for the late reply

1 Like

It will take some time until it is how i want it but i will post a video or something of it here :smiley: :+1: I will try to make that when animals walk through the grass will move too :ox: :deer:

2 Likes

Hey, thanks again for the hint how to do it! But I have a problem:

local grassfolder = workspace.Grass
local ts = game:GetService("TweenService")

while wait(.1) do
	for i, grass in pairs(grassfolder:GetChildren()) do
		
		grass.HitBox.Touched:Connect(function(hit)
			if hit.Parent:FindFirstChild("Humanoid") then
				
				local hum = hit.Parent:FindFirstChild("Humanoid")
				local movedir = hum.MoveDirection.Unit
				print(movedir.X, movedir.Z)
				local facevector = grass.PrimaryPart.CFrame * CFrame.Angles(math.rad(movedir.X), math.rad(0), math.rad(movedir.Z))

				local tween = ts:Create(grass.PrimaryPart, TweenInfo(.5), {CFrame = grass.PrimaryPart.CFrame * CFrame.Angles(math.rad(movedir.X), math.rad(0), math.rad(movedir.Y))})
				
				tween:Play()
				
			end
		end)
	end
end

ERROR:

16:03:04.042 - ServerScriptService.Script:15: attempt to call a table value
16:03:04.042 - Stack Begin
16:03:04.042 - Script 'ServerScriptService.Script', Line 15
16:03:04.042 - Stack End

I hope you can help me!

1 Like

try doing ‘TweenInfo.new(0.5)’ instead

Oh gosh, i did not see that!!! :stuck_out_tongue_closed_eyes: Sry, but yea thx :smiley:

1 Like

It’s alright, btw don’t loop through them all, just have it like this instead

local grassfolder = workspace.Grass
local Players = game:GetService('Players')
local ts = game:GetService("TweenService")

for i, grass in pairs(grassfolder:GetChildren()) do
		local CharModel = hit:FindFirstAncestorWhichIsA('Accesory')

		grass.HitBox.Touched:Connect(function(hit)
			if Players:GetPlayerFromCharacter(CharModel) then
				
				local hum = CharModel:FindFirstChild("Humanoid")
				local movedir = hum.MoveDirection.Unit
				local facevector = grass.PrimaryPart.CFrame * CFrame.Angles(math.rad(movedir.X), math.rad(0), math.rad(movedir.Z))

				local tween = ts:Create(grass.PrimaryPart, TweenInfo(.5), {CFrame = grass.PrimaryPart.CFrame * CFrame.Angles(math.rad(movedir.X), math.rad(0), math.rad(movedir.Y))})
				
				tween:Play()
				
			end
		end)
	end

Get rid of the while loop.

1 Like

Thanks, but i wont use GetPlayerFromCharacter cause I want to
be the grass interactible with animals.

sry for bad english

1 Like

Hi @AdaptabiI!
How can I make this code work better?

local grassfolder = workspace.Grass
local ts = game:GetService("TweenService")
local cooldown = false
local tweenMove
local tweenNormal

-----||      Settings      ||-----
local multiplier = 28
local tweentime = .8
local cooldown_devision = 8
local tweenInfoNormal = TweenInfo.new(tweentime * 2, Enum.EasingStyle.Quad, Enum.EasingDirection.InOut)
local tweenInfoTouched = TweenInfo.new(tweentime)




for i, grass in pairs(grassfolder:GetChildren()) do

	grass.PrimaryPart.Transparency = 1
	grass.RotateMain.Transparency = 1
	
	grass.HitBox.Touched:Connect(function(hit)
		if hit.Parent:FindFirstChild("Humanoid") then
			cooldown = true
			local hum = hit.Parent:FindFirstChild("Humanoid")
			local movedir = hum.MoveDirection.Unit
			
			if movedir.X > 0 and movedir.Z > 0 then
				
				local facevector = grass.RotateMain.CFrame * CFrame.Angles(math.rad(movedir.X * multiplier), math.rad(0), math.rad(movedir.Z * multiplier))
				
				tweenMove = ts:Create(grass.PrimaryPart, tweenInfoTouched, {CFrame = facevector})
				
				tweenMove:Play()
				
				wait(tweentime / cooldown_devision)
				cooldown = false
			else
				cooldown = false
			end
		end
	end)

	grass.HitBox.TouchEnded:Connect(function(hit)
		if hit.Parent:FindFirstChild("Humanoid") then
			local hum = hit.Parent:FindFirstChild("Humanoid")
			local movedir = hum.MoveDirection.Unit
			local facevector = grass.RotateMain.CFrame * CFrame.Angles(math.rad(0), math.rad(0), math.rad(0))

			tweenNormal = ts:Create(grass.PrimaryPart, tweenInfoNormal, {CFrame = facevector})
			
			tweenNormal:Play()

		end
	end)
	
end

Actually this works, but not everytime I touch the bush/grass…
Maybe you can help me :smile:

Thanks :sweat_smile:

2 Likes

I’ve just made the code a bit neater, You’re not using your “cooldown” debounce correctly, it’s not being used at all infact, you should have it as a dictionaryvalue where CoolDown[grass] = false/true

local GrassFolder = workspace.Grass
local TS = game:GetService("TweenService")

-----||      Settings      ||-----
local multiplier = 28
local tweentime = .8
local cooldown_devision = 8
local tweenInfoNormal = TweenInfo.new(tweentime * 2, Enum.EasingStyle.Quad, Enum.EasingDirection.InOut)
local tweenInfoTouched = TweenInfo.new(tweentime)

-----| Debounce Dictionary |-----

local CoolDown = {}

for i, grass in ipairs(GrassFolder:GetChildren()) do
	
	grass.PrimaryPart.Transparency = 1
	grass.RotateMain.Transparency = 1
	
	grass.HitBox.Touched:Connect(function(hit)
		if hit.Parent:FindFirstChild("Humanoid") then
			CoolDown[grass] = true
			local hum = hit.Parent:FindFirstChild("Humanoid")
			local movedir = hum.MoveDirection.Unit
			
			if movedir.Magnitude > 0 then
				
				local facevector = grass.RotateMain.CFrame * CFrame.Angles(math.rad(movedir.X * multiplier), math.rad(0), math.rad(movedir.Z * multiplier))				
				local TweenMove = TS:Create(grass.PrimaryPart, tweenInfoTouched, {CFrame = facevector})
				
				TweenMove:Play()
				
				wait(tweentime / cooldown_devision)
				CoolDown[grass] = false
			return end
			
			CoolDown[grass] = false
		end
	end)
	
	grass.HitBox.TouchEnded:Connect(function(hit)
		if hit.Parent:FindFirstChild("Humanoid") then
			local hum = hit.Parent:FindFirstChild("Humanoid")
			local movedir = hum.MoveDirection.Unit
			local facevector = grass.RotateMain.CFrame * CFrame.Angles(math.rad(0), math.rad(0), math.rad(0))
			
			local TweenNormal = TS:Create(grass.PrimaryPart, tweenInfoNormal, {CFrame = facevector})
			
			TweenNormal:Play()
		end
	end)
end

This is the updated code, .Touched is sometimes unreliable (i may be wrong about this because i remember there being an article showing the proper way to use .touched) So that’s why the .touched might not always fire, try use what i’ve supplied now though

3 Likes

Thanks for fast reply! :smiley:

1 Like

Tried it! It works perfectly!!! Now I will add wind simulation… but yea thx :smiley:

3 Likes

No problem, again DM me when it works i’d like to see it :slight_smile:

Hi again!
I hope that this is the last Question I ask you…
so, i made wind Simulation… but… when I put the Wind strengh to 0.9999998999999999 you can´t really see the wind Animation, but when put the strengh to 1.0 it is too strong and it spines around way too much!

--||     Wind     ||--
while wait(tweentime * (tweenbackMultiplier/2)) do
	if wind then
		for i, grass in ipairs(GrassFolder:GetChildren()) do
			local windtween = TS:Create(grass.PrimaryPart, tweenInfoWind, {CFrame = grass.PrimaryPart.CFrame * CFrame.Angles(math.random(0 - windStrengh, 0 + windStrengh), math.rad(0), math.random(0 - windStrengh, 0 + windStrengh))})
			windtween:Play()
			if not touching then
				wait(tweentime / 1.2)
				local normaltween2 = TS:Create(grass.PrimaryPart, tweenInfoWind, {CFrame = grass.RotateMain.CFrame * CFrame.Angles(math.rad(math.random(-1, 1)), math.rad(0), math.rad(math.random(-1, 1)))})
				normaltween2:Play()
			end
		end
	end
end

windStrengh is a NumberValue in the script.

Hope you can help me! again… :smiley:

2 Likes