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.
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.
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