Recently, I’ve been trying to find the best way to build a footsteps system in Unity. At first I though it would be simple, after all detecting a Game Object’s surface material is as easy as adding a tag, right? but what if you can’t use tags, what if you’re actually using terrain for some or all of the walkable surfaces in your game?
Well, that’s exactly the problem I ran into while working on my Sun Temple video series.
Terrain objects, like any other object, can only have one tag but they will often use multiple different surface textures that each require different sounds. So, when you’re using terrain in Unity, how can you detect the terrain texture that the player is walking on?
In Unity Terrain textures are stored as Alpha Maps or “Splat Maps”, which are arrays of values that hold the mix of each different texture at every point on the Terrain. It’s possible to detect what mix of textures the player is standing on at any given moment by converting the player’s current transform position to a coordinate on the alpha map image. This can then be used to check the Alpha Map data at that position. This is ideal for determining what footstep sound should play. In this post I’ll show you how to detect what surface material the player is standing on step by step and how you can use that data to trigger footstep sounds.
Below I’ll explain how this works and show you the method I used to actually do this. If, however you just want a working script that you can copy and paste, skip to the bottom of this post for my finished example.
How to detect terrain textures at a specific world position
In this example I’m going to show you how I used terrain alpha maps to check for certain textures when the player is walking on them. I used the first person character controller but you can use this with any type of player or object as it only needs a transform position to work.
1. Create the script and variables
Before I do anything I’m going to need to create a new script on the player object, I’m going to call it CheckTerrainTexture and then add a few variables:
public class CheckTerrainTexture : MonoBehaviour {
public Transform playerTransform;
public Terrain t;
public int posX;
public int posZ;
public float[] textureValues;
}
What I’ve done here is added a transform for the player, a game object variable for the terrain, which I’ve called t to keep my lines a bit shorter, and two integer variables posX and PosZ which I’ll use to hold the alpha map coordinate once I’ve converted it.
I’ve also created a float array call textureValues which I’ll use to hold the texture values that I get (more on that later).
2. Convert the player’s position to an alpha map coordinate
Next, I need to work out where on the alpha map I’m going to check for textures by converting the world position of the player to a coordinate on the alpha map. To do this, I’m going to create a new function called ConvertPosition that takes one parameter, a Vector3, which I’ll use to hold the player’s position.
The way to do this is to first subtract the terrain’s world position from the player’s world position. This removes any offset from the centre of the scene and the start of the terrain. For example, the position at the bottom left corner of the terrain, which is where it starts, would now be 0,0.
Next I’m going to measure how far along the x and z axises the position of the player is by dividing the, now offset, position by the total size of the terrain (in world units).
Essentially this returns a position on the terrain that’s represented by a percentage along the x and z axes. For example, if the Player was standing in the exact centre of the terrain, the value would be 0.5 along the x axis and 0.5 along the z. If they were standing top right, it’d be 1,1 or somewhere in the top left, maybe 0.24,0.89. You get the idea.

Dividing the relative position of the player by height and width of the terrain gives you a percentage that can be used for the Alpha Map.
Finally, I’m going to convert the percentage position value to an alpha map coordinate. To do this, all I need to do is multiply the total width and height of the alpha map image in pixels by the percentage position I just worked out. Then I need to cast it as an integer, which will round it to an exact pixel.
void ConvertPosition(Vector3 playerPosition)
{
Vector3 terrainPosition = playerPosition - t.transform.position;
Vector3 mapPosition = new Vector3
(terrainPosition.x / t.terrainData.size.x, 0,
terrainPosition.z / t.terrainData.size.z);
float xCoord = mapPosition.x * t.terrainData.alphamapWidth;
float zCoord = mapPosition.z * t.terrainData.alphamapHeight;
posX = (int)xCoord;
posZ = (int)zCoord;
}
3. Retrieve the texture mix at that position
Now that I’ve converted the player’s position to a point on the alpha map, I can now check to see what’s there by using the terrain’s alpha maps.
I’m going to do this in another function, called CheckTexture.
Create a multidimensional array to store the data
To store the alpha map data I need to use a 3d float array, which is like a normal array just with 3 dimensions.
In scripting, a multidimensional array looks like this:
private float[] normalArray;
private float[,] 2dArray;
private float[,,] 3dArray;
The 3 dimensions of the alpha map array relate to the x position, z position and the texture that each splat map belongs to.
Each float value in the array is a texture mix, which is a value between 0 and 1, that corresponds to how much of the texture is applied at that position. I can use this to find out what mix of surfaces the player is standing on.
Use Get Alphamaps to get the data
To get the alpha map data I need to use the Get Alphamaps function. This is a function of terrain Data on the terrain class and takes four parameters:
- x offset
- z offset
- sample size width
- sample size height
In this example I’m going to pass in the x and z coordinates that I previously calculated for the offset but I’m going to set the sample size width and height to 1.
It’s important to remember that you’re taking a sample of data, not all of it. When I first tried this I got mixed up trying to pass in my x and z coordinates along with the full width and height of the texture.
So if you, like me, keep seeing the error texture rectangle is out of bounds, then you may have made the same mistake.
Store each texture mix value
Now that I know how to retrieve the data, I need to store it in my textureValues array.
Before I do that though I’m going to set the textureValues array size to four in the inspector, as I already know that the terrain I’m using has four textures.
To check how many textures are being used on the terrain, and what order they’re in, open up the paint tab in the terrain options to view all the textures in use.

Click on the paint tab in the Terrain options to find out what order the textures are in.
The textures are numbered, starting from 0, from left to right, you’ll need these indexes to tell each texture apart in the alpha map.
For each value, simply store whatever texture mix is at the array position.
Here’s what it looks like all together:
void CheckTexture()
{
float[,,] aMap = t.terrainData.GetAlphamaps (posX, posZ, 1, 1);
textureValues[0] = aMap[0,0,0];
textureValues[1] = aMap[0,0,1];
textureValues[2] = aMap[0,0,2];
textureValues[3] = aMap[0,0,3];
}
Because I only took a 1-pixel sample, I don’t need to pass in x and z coordinates for the offset (because there’s only one value present for each texture). The only value I need to change is the third value, which changes which texture I’m checking. This is the value that matches up to the texture index in the terrain options.
4. Call the functions when you need terrain footsteps
Lastly, with everything in place, I’m going to add a public function to call CheckTexture and ConvertPosition whenever I need it.
void Update()
{
// For better performance, move this out of update
// and only call it when you need a footstep.
GetTerrainTexture();
}
public void GetTerrainTexture()
{
ConvertPosition(playerTransform.position);
CheckTexture();
}
I’m using a public function because a more efficient way to use this method is by only calling it when you need it. By making it public I can have another script call GetTerrainTexture whenever a footstep sound is needed. For now though, I’m just calling it in Update.
Finished Script Example
Here’s the finished working script that I used for you to copy and paste.
Please note that you’ll need to adjust for the number of textures in the textureValues array to match what’s on your terrain.
My example uses four textures, so I set the textureValues array size to four and updated textureValues four times in the CheckTexture function.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class CheckTerrainTexture : MonoBehaviour {
public Transform playerTransform;
public Terrain t;
public int posX;
public int posZ;
public float[] textureValues;
void Start ()
{
t = Terrain.activeTerrain;
playerTransform = gameObject.transform;
}
void Update()
{
// For better performance, move this out of update
// and only call it when you need a footstep.
GetTerrainTexture();
}
public void GetTerrainTexture()
{
ConvertPosition(playerTransform.position);
CheckTexture();
}
void ConvertPosition(Vector3 playerPosition)
{
Vector3 terrainPosition = playerPosition - t.transform.position;
Vector3 mapPosition = new Vector3
(terrainPosition.x / t.terrainData.size.x, 0,
terrainPosition.z / t.terrainData.size.z);
float xCoord = mapPosition.x * t.terrainData.alphamapWidth;
float zCoord = mapPosition.z * t.terrainData.alphamapHeight;
posX = (int)xCoord;
posZ = (int)zCoord;
}
void CheckTexture()
{
float[,,] aMap = t.terrainData.GetAlphamaps (posX, posZ, 1, 1);
textureValues[0] = aMap[0,0,0];
textureValues[1] = aMap[0,0,1];
textureValues[2] = aMap[0,0,2];
textureValues[3] = aMap[0,0,3];
}
}
How to use terrain texture data to trigger different footstep sounds
What’s great about this method is that, unlike a tag, it doesn’t just return a true or false value. It’s a mix value, between 0 and 1 that can be used for more natural sounding footstep sounds.
To create natural landscapes, textures on terrain objects are often blended together. There’s often no hard division between one surface and another. You might be standing on 80% stone and 20% sand, or 100% grass, or 50/50 mud and rocks.
It’s possible to blend footstep sound effects in the same way for a really effective footsteps system.

Terrain textures are often smoothly blended together. Footstep sounds can be too!
Here’s how to do it…
- Get the terrain alpha map values by using the method above.
- If any texture mix value is more than 0, play a footstep sound from an array of clips for that material.
- Trigger it with Play One Shot, not Play (which takes a volume scale parameter).
- Pass in the texture mix value for the Volume Scale parameter of Play One Shot to blend the sound with any others that are playing.
This will smoothly blend the footstep clips together depending on what the player’s standing on. Because it uses a volume scale, you will still be able to adjust the total volume of all the footsteps on the Audio Source if you need to.
Here’s an example of how it looks in scripting:
public AudioSource source;
public AudioClip[] stoneClips;
public AudioClip[] dirtClips;
public AudioClip[] sandClips;
public AudioClip[] grassClips;
AudioClip previousClip;
public void PlayFootstep()
{
GetTerrainTexture();
if (textureValues[0] > 0)
{
source.PlayOneShot(GetClip(stoneClips), textureValues[0]);
}
if (textureValues[1] > 0)
{
source.PlayOneShot(GetClip(dirtClips), textureValues[1]);
}
if (textureValues[2] > 0)
{
source.PlayOneShot(GetClip(dirtClips), textureValues[2]);
}
if (textureValues[3] > 0)
{
source.PlayOneShot(GetClip(dirtClips), textureValues[3]);
}
}
AudioClip GetClip(AudioClip[] clipArray)
{
int attempts = 3;
AudioClip selectedClip =
clipArray[Random.Range(0, clipArray.Length - 1)];
while (selectedClip == previousClip && attempts > 0)
{
selectedClip =
clipArray[Random.Range(0, clipArray.Length - 1)];
attempts--;
}
previousClip = selectedClip;
return selectedClip;
}
Now I want to know how you’re using terrain in your game. Do you have your own unique challenges when it comes to detecting surface types? Does this system work for your game? or maybe you’re using alpha maps in some other weird and wonderful way.
Whatever you’re up to, let me know by leaving a comment below.
34 Comments on “Terrain Footsteps in Unity (how to detect different textures)”
I was looking for a way to detect textures within a terrain for my footstep system. I’m happy that I found your blog post. Thank you for taking the time to explain your solution and share your code.
Cheers!
You’re welcome Justin!
Please Create Tutorial By Using This Script and Must Send Me Link Thank you
You mean like this? https://www.youtube.com/watch?v=Tb3NGKgq1t8
This was incredibly useful, thanks for taking the time to post this – it has helped me build a footstep system to recognise both tagged objects and terrain textures using FMOD.
Thanks Chris, really glad it helped.
Ok so GetAlphaMaps is working for the 1st 4 textures on my terrain but I have 15 total textures. Any idea why it isnt returning records for the other 11 textures?
I believe that this is due to how Alpha Maps are stored. While you can use more than 4 textures on the Terrain, the alpha map only stores four. I’m not aware of a method for detecting more than 4 textures but if I find one I’ll update the article.
They are only seeing 4 because you are only looking for 4 in the code… you could use a for loop for how many textures are being used… below looks at 69… replace that with how many textures are used, or, uncomment the list building part of this code and they will get a list of textures they actually used out of those 15, which may be all…
void CheckTexture()
{
float[,,] splatMap = terrain.terrainData.GetAlphamaps(posX, posZ, 1, 1);
for (int i = 0; i 0 && !textureReference.Contains(i))
//{
// textureReference.Add(i);
//}
}
}
if that code didn’t format correctly, it did on the youtube video
Thanks for the tip!
Love the tutorial 😀
i also subscribed to your youtube 😀
Thank you! Also, I got your email. I’ll reply as soon as I can.
Hey John,
Thank you very much for your tutorials,
I enjoyed watching your videos but I can’t seem to get the terrain texture detecting script to work.
Unity keeps telling me
“Texture rectangle is out of bounds (824 + 1 > 512)
UnityEngine.TerrainData:GetAlphamaps(Int32, Int32, Int32, Int32)”
the first number (824) is my pos Z and the last one (512) is one of my map dimensions.
it happens on this line:
float[,,] aMap = t.terrainData.GetAlphamaps (posX, posZ, 1, 1);
Can you please help me fix this problem?
From what I can tell, you’re trying to retrieve data at the position 824 but the map is only 512 on the Z-axis (I’m guessing). Double-check the code that calculates the position relative to the width and height of the map. Is the map longer than it is wide? Is it possible that you’ve mixed up x and z? If you’re still stuck, and you don’t mind sharing more details, send me the full script and I’ll double-check it for you ([email protected]).
I have the same issue and I’ve just noticed that whenever I press play there’s a different terrain in the Inspector under the script from which I’m standing on right now. When I switch to the right one, it works. But each time I press play it pulls up the incorrect one. Don’t know how to change that but I’m working on it. Any advice will be appreciated.
Hey John, the tutorial is extremely helpful, nobody else did it better than you, but im getting one error tho,
ArgumentException: Invalid argument for GetAlphaMaps
UnityEngine.TerrainData.GetAlphaMaps (System.Int32 x, System.Int32 y, System.Int32 width, System.Int32 height) (at :O)
My code looks like this : https://pastebin.com/zKRPXWqc
Did i do something wrong?
Thanks in advance!
Hi Robert, Nothing looks wrong in your code. I copied the exact code into my project and it worked fine. I can’t see a reason why you’re getting that error.
This looks super solid! However I’m getting this error:
IndexOutOfRangeException: Index was outside the bounds of the array.
CheckTerrainTexture.CheckTexture () (at Assets/Scripts/ROTMK Scripts/Helpers/CheckTerrainTexture.cs:61)
CheckTerrainTexture.GetTerrainTexture () (at Assets/Scripts/ROTMK Scripts/Helpers/CheckTerrainTexture.cs:40)
CheckTerrainTexture.Update () (at Assets/Scripts/ROTMK Scripts/Helpers/CheckTerrainTexture.cs:34)
Could you help me out with what this could be?
It could be a couple of things, but my first thing to check would be that you’ve set the
float[] TextureValues;
array size to be at least as many textures as you want to retrieve. If you’re still having trouble, try my video guide that goes through all of steps: https://www.youtube.com/watch?v=Tb3NGKgq1t8&tI have an “army” of 6 characters, each with their own slightly different looping footsteps clip. I’m also adjusting the pitch and volume every loop to give further variations.
I am thinking of stacking all of that 4 times (for each terrain texture) and ramping volume up and down same as you have it. This would result in 24 clips loaded and possibly all 24 being played at once. I have max 8 other sounds loaded in the scene as well.
Is this reasonable or is this a foolish way of doing it?
note: this is for mobile, so limited resources but no spatial audio.
There are a lot of ifs to this, but to give it some perspective I’d say that having four layers of audio for blended footsteps, as in this article, is a ‘nice to have’ on a single player where the realism of the footsteps and their ability to blend is important to the game. However, for 6 characters, on mobile, I think it could be wasteful, not necessarily for resources, but for voices. If it’s an important feature though, maybe a middle ground would work for you, where you take the top two values and trigger them together as all four together may sound very distinct anyway.
Hi! I tried to improve the code by detecting the textures dynamically.
In the Start function I added this :
_textureValues = new float[_terrain.terrainData.alphamapLayers];
and I changed the CheckTexture() function
void CheckTexture()
{
float[,,] aMap = _terrain.terrainData.GetAlphamaps(_posX, _posZ, 1, 1);
for (int i = 0; i < _terrain.terrainData.alphamapLayers; i++)
{
_textureValues[i] = aMap[0, 0, i];
}
}
Thanks Rob!
(Apologies in advanced if this is a duplicate message — I didn’t see it go through)
Hi John! This tutorial was super helpful!
However, I’m running into an error whenever I try to run it.
NullReferenceException: Object reference not set to an instance of an object
CheckTerrainTexture.ConvertPosition (UnityEngine.Vector3 playerPosition) (at Assets/Scripts/CheckTerrainTexture.cs:35)
CheckTerrainTexture.GetTerrainTexture () (at Assets/Scripts/CheckTerrainTexture.cs:29)
CheckTerrainTexture.Update () (at Assets/Scripts/CheckTerrainTexture.cs:24)
I’m pretty new to Unity, so maybe this is something obvious that I’m missing, but any help from you would be greatly appreciated 🙂
Thanks Jacob, if you’re using multiple scripts (as I believe I did in this example) have you double checked that you’ve set the reference to the CheckTerrainTexture script?
Hi! Thanks for the great tutorial! Can you tell me how can I call audio events from Wwise but not from Unity?
Hi thanks a lot for this tutorial! Its working perfectly!
For performance improvements I would cache (store in a variable) the alpha map you get from GetAlphamaps (Use GetAlphamaps(0, 0, terrainData.alphamapWidth, terrainData.alphamapHeight)), its height and width as well as the overall terrain size at the start of the game. These are almost never something you change at runtime, so its just unnecessary calculations, and even then if you change them you can just re-cache them.
Still, a great tutorial thank you very very much!
Thanks for the tip!
Can’t seem to get the audio to play, need help
Are you getting any errors when footsteps are supposed to play?
Hello, thank you. this was very usefull and interesting.
But i can’t find the download link for the footsteps sounds.
Can you please send the link
Sorry about that, the YouTube link wasn’t working properly, it’s on this page: https://johnleonardfrench.com/unity-audio-tutorial-series-adding-sounds-to-the-sun-temple/#pt3
No problem, thank you very much for everything. You helped a lot!