- Home /
How can I perform some action based on the terrain texture currently under my player?
I want trigger with texture on terrian, some area make player hurts, some area player can not pass throught, how this can be handled in Unity. Thanks.
Answer by duck · Apr 13, 2010 at 10:30 AM
(see new edit below for link to example script)
You can read values from the "Mix Map" (a.k.a. Splat Map, Alpha Map, Control Texture - these are often used to refer to the same thing) using some undocumented terrain functions. The Mix Map controls the blending of each of your textures across the terrain, and is a 2D grid of values. The size of this grid is determine by the "Control Texture Resolution" setting in the terrain dialog box.
So, you'll need to convert the player's world coordinates to coordinates which represent the grid cell of the "control texture" which the player is currently within. A similar technique is described in this question, but in this case it converts it to the 2d heightmap grid:
How to translate WORLD coordinates to TERRAIN coordinates?
However instead of the heightmap size, you'll need to use the Mix Map size which can be read using the undocumented command, terrain.alphamapResolution.
So, to convert, you need to subtrack the terrain's position, divide by the terrain size, and multiply by that Mix Map Size:
var terrainData = terrain.terrainData;
var terrainPos = terrain.transform.position;
var mapX = ((player.x - terrainPos.x) / terrainData.size.x) * terrainData.alphamapWidth;
var mapZ = ((player.z - terrainPos.z) / terrainData.size.z) * terrainData.alphamapHeight;
Next you need to use those coordinates to sample the values which control the mix of textures at that position, using the undocumented function GetAlphamaps, like this:
var splatmapData = terrain.GetAlphamaps(x, y, width, height);
Because the alphamap data is a 2d grid, the x & y values here are integers, as are the width & height values (which let you sample an area of the grid of any size). Because you're only interested in sampling the cell under the player, the width and height should be 1.
Now you're left with a variable (splatmapData) which contains a 3d array. To read the mix levels of each texture layer, you need something like this:
var texture1Level = splatmapData[0,0,0]; // texture layer 1
var texture2Level = splatmapData[0,0,1]; // texture layer 2
var texture3Level = splatmapData[0,0,2]; // texture layer 3
var texture4Level = splatmapData[0,0,3]; // texture layer 4
(the reason you have 0,0 at the start is because "splatmapData" could have contained a sampled area larger than 1x1 in size, so this specifies the location within the sampled area. Because our width & height was 1,1 - the sampled area is only one cell in size).
The number of lines above should match the number of texture layers you're using in your terrain. If an area is 100% covered in one texture, you should find that texture level has a value of 1.0, and the rest have a value of 0.0. If there is a blend of textures at that location, you'll find that more than one layer has a value larger than 0.0, and that they all add up to a total of 1.0.
For more detail about maniupulating the terrain textures via scripting, see these answers:
How can I automatically place grass and other details on my terrain to correspond with the splatmap?
How to automatically apply different textures on terrain based on height?
Note:
*
Some of these functions are undocumented.
These have now been officially added to the Unity API! Check the Terrain and TerrainData documentation.
In addition, I have written a helper script to easily detect the type of texture on a terrain at a given location, in answer to another question.
this doesn't solve the problem of not being able to pass through certain areas though...!
I think that using textures for collision doesn't really make sense . But for sound and changes in movement is ace ! You can do loads !!
Yes I think for collisions, unless it has to be procedurally deter$$anonymous$$ed, your best option would be to manually build invisible colliders in the correct place - either using collider primitives, or by importing a mesh built in a 3D app which contains the "walls" in the correct positions.
By "texture layer" do you mean all the different terrain textures that can be painted, or something else? How can we tell how many texture layers we have?
Answer by KvanteTore · Apr 13, 2010 at 09:51 AM
Having a texture on the terrain in an area can be handled independently of taking an action when the player enters that area.
The texture can be applied as normal using the terrain editor.
To detect when the player enters these areas, you could create objects without renderers, but with colliders that you scale and place around the landscape. To make the areas impassable you simply add the colliders, which will turn the invisible. To make them passable but taking some when the player enters you can mark the colliders as Trigger
and use the OnTriggerEnter
, OnTriggerLeave
and OnTriggerStay
to take the appropriate action.
Yes but is there any way to raycast on the terrain and get the current texture you are sitting on ? You could do some nice staff if you could find the name of each splat map. Like changing the footstep sound or the jump height etc. And it would be seriously quicker than adding colliders.
I mean yes you should add colliders and triggers for some functions but there are some nice things you could do with knowing the texture.
Answer by alexnode · Apr 13, 2010 at 10:34 AM
There is a discussion here about that topic. It is interesting http://forum.unity3d.com/viewtopic.php?t=6429&highlight=ray+terrain+texture
Answer by TheKusabi · Nov 07, 2012 at 08:06 PM
After looking into it for a few hours.,I am pretty happy with this function I made.
public Texture getTerrainTextureAt( Vector3 position )
{
// Set up:
Texture retval = new Texture();
Vector3 TS; // terrain size
Vector2 AS; // control texture size
TS = Terrain.activeTerrain.terrainData.size;
AS.x = Terrain.activeTerrain.terrainData.alphamapWidth;
AS.y = Terrain.activeTerrain.terrainData.alphamapHeight;
// Lookup texture we are standing on:
int AX = (int)( ( position.x/TS.x )*AS.x + 0.5f );
int AY = (int)( ( position.z/TS.z )*AS.y + 0.5f );
float[,,] TerrCntrl = Terrain.activeTerrain.terrainData.GetAlphamaps(AX, AY,1 ,1);
TerrainData TD = Terrain.activeTerrain.terrainData;
for( int i = 0; i < TD.splatPrototypes.Length; i++ )
{
if( TerrCntrl[0,0,i] > .5f )
{
retval = TD.splatPrototypes[i].texture;
}
}
return retval;
}
Just use it like this.
Texture floorTexture = getTerrainTextureAt( transform.position );
Feel free to use this.