I want to spawn a player at a random location on a Terrain. I’ve done that successfully using Terrain.SampleHeight(). However now I am finding the player is spawning at unwanted locations, like in a cravas ,and things like that. I want to spawn a player at a random location OVER a certain height of the terrain, but I’m not sure how to achieve this.
I’ve thought about making a Raycast against the terrain from all 4 sides and picking a location which is at a random height (between a min and max value), but I’d like to do something less hacky.
Can anyone think of a solid approach to this kind of thing?
This is my random position generator right now. It’s pretty simple. I want it to have a ‘Find Random Terrain Area Between 50m-60m high’! So I ask for a height, and it returns positions of that height. You get me?
You want your function only to return points on the terrain the are between 50m-60m in height? So you want to be able to only spawn on mountains, or only spawn in valleys, that kind of thing?
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
public class TerrainTesting : MonoBehaviour
{
Terrain myTerrain;
TerrainData myTerrainData;
public List<Vector2> myGoodPositions;
public float lowerThreshold;
public float upperThreshold;
void Start ()
{
myTerrain = GetComponent<Terrain>();
myTerrainData = myTerrain.terrainData;
myGoodPositions = new List<Vector2>();
// get height data
float[,] myHeights = myTerrainData.GetHeights(0, 0, myTerrainData.alphamapWidth, myTerrainData.alphamapHeight);
for(int x=0; x < myHeights.GetLength(0); x++)
{
for (int y=0; y < myHeights.GetLength(1); y++)
{
// check world height at the position
if(myHeights[x,y] * myTerrainData.size.z >= lowerThreshold
&& myHeights[x, y] * myTerrainData.size.z < upperThreshold)
{
// within thresholds store the position
myGoodPositions.Add(new Vector2(x, y));
}
}
}
/// everything from here is about switching the textures around
float[, ,] alphas = myTerrainData.GetAlphamaps(0, 0, myTerrainData.alphamapWidth, myTerrainData.alphamapHeight);
// reset, terrain assets changes are kept after ending runtime apparently
for(int i=0; i<alphas.GetLength(0); i++)
{
for (int j=0;j<alphas.GetLength(1); j++)
{
alphas[i, j, 0] = 1f;
alphas[i, j, 1] = 0f;
}
}
for(int i = 0; i<myGoodPositions.Count; i++)
{
alphas[(int)myGoodPositions[i].x, (int)myGoodPositions[i].y, 0] = 0f;
alphas[(int)myGoodPositions[i].x, (int)myGoodPositions[i].y, 1] = 1f;
}
myTerrainData.SetAlphamaps(0, 0, alphas);
}
}
setup a terrain and add two textures to it, this should apply the second texture to the area between the two threshold values
seems to work… obviously you’ll want to do something else instead of switching the textures around like picking a random position from the “myGoodPositions”. If you’re doing that you probably want a List and to store the calculated height so you don’t need to look it up again later.
*disclaimer: I “muddled” through to work out the above script, was fun but I can’t guarantee it’s correct let me know if I made a mess of something
That was brilliant LeftyRighty! I’ve modified it for myself… Only problem, it’s only placing the player in one or two positions in a very small surface area, even though the Debug.Log(positionList.Count) returns 84! I think it’s not translating it to world locations? I have a 6000x4000 terrain and it’s placed at position Vector3(-3000,0,-2000), so it’s center point is at the center of the world. you get me? But the player is only loading around about between Vector3(Random.Range(0,500), therightheight, Random.Range(0,500))(ish) :S any ideas? Thanks so much for the code btw!
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
public class TerrainHandler : MonoBehaviour {
Terrain terrain;
TerrainData terrainData;
public List<Vector3> positionList;
private int checkIntervals = 50;
private float lowerThreshold;
private float upperThreshold;
void SetupThreshold()
{
if(terrainData)
{
upperThreshold = terrainData.size.y;
lowerThreshold = (terrainData.size.y * 0.1f);
}
else
{
Debug.Log("TerrainHandler::SetupThreshold - Reference to TerrainData not created!");
}
}
void Awake()
{
terrain = GetComponent<Terrain>();
terrainData = terrain.terrainData;
SetupThreshold();
positionList = new List<Vector3>();
float[,] myHeights = terrainData.GetHeights(0, 0, terrainData.alphamapWidth, terrainData.alphamapHeight);
for (int x = 0; x < myHeights.GetLength(0); x += checkIntervals)
{
for (int y = 0; y < myHeights.GetLength(1); y += checkIntervals)
{
if (myHeights[x, y] * terrainData.size.z >= lowerThreshold
&& myHeights[x, y] * terrainData.size.z < upperThreshold)
{
float height = terrain.SampleHeight(new Vector3(x, 0, y));
//Vector3 position = transform.TransformPoint(new Vector3(x, height, y));
positionList.Add(new Vector3(x, height, y));
}
}
}
Debug.Log(positionList.Count);
}
public Vector3 GetSuitableLocation()
{
if(positionList.Count < 1)
{
Debug.Log("TerrainHandler::GetSuitableLocation - No suitable positions listed!");
return Vector3.zero;
}
else
{
int rand = Random.Range(1, positionList.Count);
Vector3 suitablePos;
suitablePos = positionList[rand];
return suitablePos;
}
}
}
You can see I tried using TransformPoint() to see if I needed to translate it’s position in the terrain into world coordinates, but it just did the same 0-500 range thing but in the bottom left corner!
for (int i = 0; i < positionList.Count; i++)
{
Debug.Log(positionList[i]);
}
It says it’s only doing it’s checks between 0-500… I checked with breakpoints and it says that terrainData.alphamapWidth and terrainData.alphamapHeight are only 512 each… so it’s only checking up to those heights… However those ARE the heights of the terrain which it’s checking against… So I think that bit’s actually right, however I’m not finding the correct locations of these height points, I think? This is all a little new to me lol
The default height map resolution is 513. The default terrain size is 2000.
You’ll need to scale your position to get a world position. It will be hieghtmap position divided by hight map resolution then multiplied by terrain size.
I’ve redone the code, however it’s not quite putting me in the right position, or the right height!
for (int x = checkFrameSize; x < (terrainHeights.GetLength(0) - checkFrameSize); x += checkIntervals)
{
for (int y = checkFrameSize; y < (terrainHeights.GetLength(1) - checkFrameSize); y += checkIntervals)
{
if (terrainHeights[x, y] * terrainData.size.z >= lowerThreshold
&& terrainHeights[x, y] * terrainData.size.z < upperThreshold)
{
Vector3 position = new Vector3(x, 0, y);
position /= terrainData.heightmapResolution;
position = Vector3.Scale(position, terrainData.size);
position.y = terrain.SampleHeight(position);
position += terrain.transform.position;
positionList.Add(position);
}
}
}