I'm making a survival game, and I want the player to both walk slower and regain less heat on snow, but I don't know how to check what material they're walking on. I've tried using Humanoid.FloorMaterial, but that value is always nil for me. If anyone knows a better method, that'd be amazing.
Is there anyway to check what terrain the player is walking on?
2 answers
That's actually a good question and it's quite simple too, all you need for that is Raycasting. It will fire ray from your Character and return the result. Here is how you set it up (LocalScript):
So first you need RunService, i will be using it's event called RenderStepped in this case:
local RunService = game:GetService("RunService")
-- Function which will run when render is stepped (Every frame)
-- If you have 60 FPS, it will run 60 times per second
local function OnRenderStepped()
end)
RunService.RenderStepped:Connect(OnRenderStepped) -- Connects the event to the function
Now you need to create Ray, rays are created with 3 arguments, Origin, Direction and RaycastParams. The parameters should be created right at the start of script, they have 4 arguments, FilterDescendantsInstances (Which descendants of given instances will only be accepted OR be ignored based off of given FilterType), IgnoreWater (Whether the ray will ignore water), CollisionGroup (Ray's collisiong group) and FilterType (Blacklist or Whitelist). So let's create the parameters:
local RaycastParameters = RaycastParams.new()
RaycastParameters.IgnoreWater = true or false -- Choose what you need
RaycastParameters.FilterDescendantsInstances = {Character} -- Put your character in here so rays won't be detecting the character
RaycastParameters.FilterType = Enum.RaycastFilterType.Blacklist -- If it would be Whitelist then ONLY Character would be detected, but with blaclist the character WON'T be detectable
Now let's create the ray!
local RunService = game:GetService("RunService")
local HumanoidRootPart = "Your Humanoid Root Part"
local RaycastDistance = 5 -- How far will the ray cast, 5 studs is perfect (It won't detect the terrain when you jump which is good)
local function OnRenderStepped()
local Origin = HumanoidRootPart.Position -- From where will the ray cast (Vector3)
-- Now for direction, Since you can't do CFrame.DownVector, you will need to use CFrame.UpVector which returns upper look vector of given CFrame and turn it into negative number which will turn it into DownVector.
local Direction = - (HumanoidRootPart.CFrame.UpVector * RaycastDistance) - Multiply by raycast distance here to make the ray longer
-- Now this creates the ray itself which as i said earlier, accepts 3 arguments and is called using workspace
local Hit = workspace:Raycast(Origin, Direction, RaycastParameters) -- This returns hit which means what did the ray hit
-- Now check if the hit exists
if Hit then
-- Hit returns: Instance, Position, Material and 1 more thing sorry i don't remember but it's said somewhere on the devforum, in this case we need Material
local TerrainName = Hit.Material.Name -- Gets the name of material
print(TerrainName) -- if i would be standing on grass material, it would say "Grass"
end
end)
RunService.RenderStepped:Connect(OnRenderStepped)
Now this should be working but let's turn this into a lot more comfortable thing using BindableEvents and it's event called BindableEvent.Event!
local TerrainChangedSignal = Instance.new("BindableEvent") -- Creates the event
local function OnTerrainChanged(NewTerrain) -- Whenever the event is fired, this function will run
print(NewTerrain)
end
TerrainChangedSignal.Event:Connec(OnTerrainChanged) -- Connects the event
And now it's done, then you need to connect the event and so the whole script looks like this:
local RunService = game:GetService("RunService")
local TerrainChangedSignal = Instance.new("BindableEvent")
local Character = "Your Character"
local HumanoidRootPart = "Your Humanoid Root Part"
local RaycastParameters = RaycastParams.new()
RaycastParameters.IgnoreWater = false
RaycastParameters.FilterDescendantsInstances = {Character}
RaycastParameters.FilterType = Enum.RaycastFilterType.Blacklist
local RaycastDistance = 5
local function OnTerrainChanged(NewTerrain)
print(NewTerrain)
end
local function OnRenderStepped()
local Origin = HumanoidRootPart.Position
local Direction = - (HumanoidRootPart.CFrame.UpVector * RaycastDistance)
local Hit = workspace:Raycast(Origin, Direction, RaycastParameters)
if Hit then
local TerrainName = Hit.Material.Name
TerrainChangedSignal:Fire(TerrainName) -- Fires the event that floor was changed!
end
end)
TerrainChangedSignal.Event:Connec(OnTerrainChanged)
RunService.RenderStepped:Connect(OnRenderStepped)
The OnTerrainChanged function will fire every render because i did not include checking if last floor terrain was not the new one but i guess you can make it work, you have 80 reputation after all.
2 comments
Help appreciated. I'll be sure to check it out soon.
Great answer, every part is nicely described. OP must accept this answer.
I would make the terrain a part. Then the grassy terrain would be normal terrain.
So:
local humanoid = game.Players.LocalPlayer:WaitForChild("Humanoid")
local terrain = game.Workspace.Terrain --the default terrain things in Roblox Studio
local snowMat = game.Workspace.SnowGround --the part here
local lavaMat = game.Workspace.LavaGround --the part here
if humanoid.FloorMaterial = snowMat then
print("On snow.")
end
if humanoid.FloorMaterial = terrain then
print("On terrain base.")
end
if humanoid.FloorMaterial = lavaMat then
print("Burning cuz of lava.")
end
require(script)
require(lavaMat)
require(snowMat)
require(Terrain)
require(humanoid)
Ensure that you know how to use:
local humanoid humanoid.FloorMaterial
This discussion is preserved as it was. New answers, comments, and votes are closed.

3 comments
i don't think that there is a way to do that yet
use raycasting
The issue with terrain is that it is all one big part, and you can't script it, delete it, move it, edit it, check it etc... it would be more efficient to find a realistic grass part/mesh that you can script with. That's also what .FloorMaterial is for the material of the part not the terrian. Let me know if this helps