- Home /
Terrain with multiple splat textures - how can I detect which kind is under my character's location?
How can you detect if a character walk on for example grass, gravel or sand? Currently it seems that you can only assign one physics material or tag for one big terrain?
Answer by duck · Nov 17, 2010 at 10:12 AM
This is slightly trickier than it might initially seem - partly because any given location on the terrain can be a mixture of two or more types of terrain, and partly because the data used to store this information is relatively complex.
Briefly, you need to read Alpha Map data from the terrain class using the GetAlphaMaps function. The alphamap data is a grid of values spread over the area of the terrain, and each entry in the grid contains the values used to "mix" each of the different terrain textures, so more specifically you need to calculate which grid cell your player is in, then read the mix values for that grid cell.
I've written about this in more detail here, however I'm tempted to wrap this up quickly into a helper function which can be used without getting knee deep in the terrain API :)
-- EDIT --
Here you go. Add the large script below in as a new C# script in your project. Name the file "TerrainSurface".
You'll then be able to use these helper functions to read either the mix values of textures, or just get the most dominant texture index at a given world position. Eg:
function Update() {
var surfaceIndex = TerrainSurface.GetMainTexture(transform.position);
}
Your "surfaceIndex" will now contain an integer denoting the zero-based-index of the texture which is the most dominant texture at that location. If you have 4 textures added to your terrain, the value will be one of 0,1,2 or 3.
Alternatively you can read the actual mix of textures, like this:
function Update() {
var surfaceMix = TerrainSurface.GetTextureMix(transform.position);
}
Your "surfaceMix" will contain an array of floating point values, denoting the relative mix of each texture at that location. For example, if you have 4 textures, and the 4th texture is "Mud", you could use this to determine how muddy the current location is, like this: (remember, the indices are zero-based, so the 4th texture has an index of 3)
function Update() {
var surfaceMix = TerrainSurface.GetTextureMix(transform.position);
var muddiness = surfaceMix[3];
}
And below is the main helper script. Add this in as a new C# script in your project. Name the file "TerrainSurface".
You can use the functions from C# scripts straight off. If you want to use these function from other Javascript scripts (as in the examples above), you need to put the script in a folder that gives it an earlier compilation pass. To do this, make a folder in your Project called "Plugins" and put the C# TerrainSurface script in there.
Enjoy!
// -- TerrainSurface.cs --
using UnityEngine; using System.Collections;
public class TerrainSurface {
public static float[] GetTextureMix(Vector3 worldPos) {
// returns an array containing the relative mix of textures
// on the main terrain at this world position.
// The number of values in the array will equal the number
// of textures added to the terrain.
Terrain terrain = Terrain.activeTerrain;
TerrainData terrainData = terrain.terrainData;
Vector3 terrainPos = terrain.transform.position;
// calculate which splat map cell the worldPos falls within (ignoring y)
int mapX = (int)(((worldPos.x - terrainPos.x) / terrainData.size.x) * terrainData.alphamapWidth);
int mapZ = (int)(((worldPos.z - terrainPos.z) / terrainData.size.z) * terrainData.alphamapHeight);
// get the splat data for this cell as a 1x1xN 3d array (where N = number of textures)
float[,,] splatmapData = terrainData.GetAlphamaps(mapX,mapZ,1,1);
// extract the 3D array data to a 1D array:
float[] cellMix = new float[splatmapData.GetUpperBound(2)+1];
for (int n=0; n<cellMix.Length; ++n)
{
cellMix[n] = splatmapData[0,0,n];
}
return cellMix;
}
public static int GetMainTexture(Vector3 worldPos) {
// returns the zero-based index of the most dominant texture
// on the main terrain at this world position.
float[] mix = GetTextureMix(worldPos);
float maxMix = 0;
int maxIndex = 0;
// loop through each mix value and find the maximum
for (int n=0; n<mix.Length; ++n)
{
if (mix[n] > maxMix)
{
maxIndex = n;
maxMix = mix[n];
}
}
return maxIndex;
}
}
This is how I would do it too. Tough one thing you ll have to note is GetAlpha$$anonymous$$aps and all its class of functions aren't so-called official scripts so if changes come about in further versions of unity they aren't guaranteed to work. Sometimes, I have left over materials in my terrain on editor mode that I created at run-time dynamically so be sure to check after use.
1 more thing. To easily see what functions you have available in the API could I suggest that you view your scripts via Visual Studios. That way, these scripts can also be displayed when you press "." so you wont have a hard time figuring out whats available and stuff...
Oh 1 more thing to add. IF you re going for large scale and real terrain data you might want to look at this concept we call shapefiles: http://en.wikipedia.org/wiki/Shapefile
@denewbie: Alpha map reading is now officially exposed in Unity 3.0
Answer by Martian-Games · Apr 15, 2012 at 08:08 AM
Here is my JavaScript translation of the same cs code: Thanks Ben! (works splendidly) !
// -- TerrainSurface.js -- // // Ben Pitt // // JS translation by MartianGames :) //
public class TerrainSurface {
public static function GetTextureMix(worldPos:Vector3) {
// returns an array containing the relative mix of textures
// on the main terrain at this world position.
// The number of values in the array will equal the number
// of textures added to the terrain.
var terrain:Terrain = Terrain.activeTerrain;
var terrainData:TerrainData = terrain.terrainData;
var terrainPos:Vector3 = terrain.transform.position;
// calculate which splat map cell the worldPos falls within (ignoring y)
var mapX:int = (((worldPos.x - terrainPos.x) / terrainData.size.x) * terrainData.alphamapWidth);
var mapZ:int = (((worldPos.z - terrainPos.z) / terrainData.size.z) * terrainData.alphamapHeight);
// get the splat data for this cell as a 1x1xN 3d array (where N = number of textures)
var splatmapData = terrainData.GetAlphamaps(mapX,mapZ,1,1);
// extract the 3D array data to a 1D array:
var cellMix = new float[splatmapData.GetUpperBound(2)+1];
for (var n=0; n<cellMix.Length; ++n)
{
cellMix[n] = splatmapData[0,0,n];
}
return cellMix;
}
public static function GetMainTexture(worldPos:Vector3) {
// returns the zero-based index of the most dominant texture
// on the main terrain at this world position.
var mix = GetTextureMix(worldPos);
var maxMix = 0f;
var maxIndex = 0;
// loop through each mix value and find the maximum
for (var n=0; n<mix.Length; ++n)
{
if (mix[n] > maxMix)
{
maxIndex = n;
maxMix = mix[n];
}
}
return maxIndex;
}
}
Hi, it is very usefull script. I would like to use it for my scene where I have 4 terrains.
This script is working perfectly on $$anonymous$$AIN TERRAIN.(terrain = Terrain.activeTerrain;)
Can You help me to udpate this script to work with 4 terrain?
Thanks.
$$anonymous$$ilos.
Answer by oliver-jones · Nov 17, 2010 at 10:04 AM
I would suggest that you place a collider or something like that over, for example grass. Then give that collider a tag of Grass, or a name of Grass.
Then in your script you have something like:
function OnCollisionEnter(collisionInfo : Collision) {
print("Now Touching: " + collisionInfo.transform.name);
PlayGrass = true;
}
And place that within your Player
EDIT---
With that in place you can do something like this:
var PlayGrass = false;
function Update() {
if(PlayGrass == true) { audio.Play(Grass Audio File); } }
Rough Coding there, but you get the picture
Answer by Yanger_xy · Mar 03, 2011 at 03:56 PM
// calculate which splat map cell the worldPos falls within (ignoring y)
int mapX = (int)(((worldPos.x - terrainPos.x) / terrainData.size.x) * terrainData.alphamapWidth);
int mapZ = (int)(((worldPos.z - terrainPos.z) / terrainData.size.z) * terrainData.alphamapHeight);
I can not fully understand it, could you explain it for me? Thanks a lot.
if you think about terrain splat maps as a 2d plane it becomes a little easier.
"worldPos.x - terrainPos.x" : getting the position in world space relative to the terrain (think local space)
"/ terrainData.size.x" : divide by the max width (think percentages. ex: 5 / 10 = 0.5 OR 50%, 5 being the local space position, 10 being the max width)
" terrainData.alphamapWidth" : get the same point but on the alpha map. taking the above example, if the alpha map width for some strange reason is 200, 0.5 200 = 100 OR 50% of 200 is 100.
basically youre translating world position to the terrains local position, getting that point, and updating the alpha map based off that point
Answer by Erra666 · Dec 08, 2015 at 11:59 AM
You cant use if statements to return an int. Cannot convert int to bool..
Your answer
Follow this Question
Related Questions
Combine Colormap + Splat 0 Answers
Unity Terrain Texture Resolutions 1 Answer
Terrain Splatting 1 Answer
Modifying Terrain Splat Texture at Runtime 2 Answers
How can I perform some action based on the terrain texture currently under my player? 4 Answers