I'm a very newbie to using unity and C#. I have a question about spawning an object on the lowest Y coordinate on the mesh.
How to get the lowest Y vertices with other coordinates to spawn an object at that point.
Thank you in advance everyone :)
public Mesh terrain;
public GameObject agents;
void Start()
Mesh terrain = GetComponent<MeshFilter>().mesh;
Vector3[] meshVertices = terrain.vertices;
float minY = float.MinValue;
int count = terrain.vertexCount;
List<Vector3> vertices = new List<Vector3>();
terrain.GetVertices(vertices);
for (int i = 0; i < vertices.Count; i++)
{
Vector3 pos = vertices[i];
minY = Mathf.Min(pos.y,-pos.y);
Vector3 position = transform.TransformPoint(vertices[i]);
if (position.y == minY)
{
Instantiate(agents, position, Quaternion.identity);
}
}
terrain.RecalculateBounds();
If you're looking for a minimum value, then you need to start with something that's so big that anything will be less than that. Something like float.MaxValue. Then you need to go through all the points and compare your current minimum to the running minimum and cache the current point if it's less. Once you're done with all the points then you can use your cached point as the instantiation location. Consider the following:
public Mesh terrain;
public GameObject agents;
void Start()
{
Mesh terrain = GetComponent<MeshFilter>().mesh;
Vector3[] meshVertices = terrain.vertices;
float minY = float.MaxValue; // <--- Change
Vector3 minimumVertex; // <--- Change
int count = terrain.vertexCount;
List<Vector3> vertices = new List<Vector3>();
terrain.GetVertices(vertices);
for (int i = 0; i < vertices.Count; i++)
{
// Vector3 pos = vertices[i]; // <--- Change
// minY = Mathf.Min(pos.y,-pos.y); // <--- Change
Vector3 position = transform.TransformPoint(vertices[i]);
// if (position.y == minY) // <--- Change
if(position.y < minY) // <--- Change
{
minY = position.y; // <--- Change
minimumVertex = position; // <--- Change
//Instantiate(agents, position, Quaternion.identity); // <--- Change
}
}
Instantiate(agents, minimumVertex, Quaternion.identity); // <--- Change
terrain.RecalculateBounds();
}
I was looking for an answer for you that summarizes a large part of the code, all this is possible thanks to system.linq, the following code sorts vertices by y coordinate and puts them in a position list, just enough Set list[0] as position. The other vertices are also arranged in order, which is an advantage.
using System.Linq;
public void Start()
{
var meshFilter = GetComponent<MeshFilter>();
var list = meshFilter.mesh.vertices.Select(transform.TransformPoint).OrderBy(v => v.y).ToList();
Debug.Log(list[0]); // lowest position
Debug.Log(list.Last()); // highest position
}
Related
I'm very new at coding (like two weeks), so forgive me for the silly question. But I'm trying to code a block sliding 3d game in unity, where the obstacles randomly generate, forever, the random generation is fine, but for my life, i cannot figure out how to get the obstacles to spawn at random Z positions. i"m currently stuck with a CS0117 error, so i cant test my latest attempt to fix this.
here's my current code:
using UnityEngine;
public class BlockSpawner : MonoBehaviour
{
public Transform[] SpawnPoints;
public GameObject blockPrefab;
public float timeBetweenWaves = 1f;
private float timeToSpawn = 2f;
public class Random { }
void Update()
{
if (Time.time >= timeToSpawn)
{
SpawnBlocks();
timeToSpawn = Time.time + timeBetweenWaves;
}
}
void SpawnBlocks()
{
//Spawning blocks in
int randomIndex = Random.Range(0, SpawnPoints.Length);
{
for (int i = 0; i < SpawnPoints.Length; i++)
if (randomIndex != i)
{
// generate random position
var viewRange = this.Size - SpawnPoints.Size;
Random random = new Random();
var left = random.Next(0, viewRange.Width);
var top = random.Next(0, viewRange.Height);
// set the random position
SpawnPoints.Location = new Point(left, top);
}
}
}
And Heres is my last working Code (note this has no random location code attempts):
using UnityEngine;
public class BlockSpawner : MonoBehaviour
{
public Transform[] SpawnPoints;
public GameObject blockPrefab;
public float timeBetweenWaves = 1f;
private float timeToSpawn = 2f;
void Update()
{
if (Time.time >= timeToSpawn)
{
SpawnBlocks();
timeToSpawn = Time.time + timeBetweenWaves;
}
}
void SpawnBlocks()
{
//Spawning blocks in
int randomIndex = Random.Range(0, SpawnPoints.Length);
{
for (int i = 0; i < SpawnPoints.Length; i++)
if (randomIndex != i)
{
Instantiate(blockPrefab, SpawnPoints[i].position, Quaternion.identity);
}
}
}
}
Please help!
I will guess at the error and say this is wrong
var viewRange = this.Size - SpawnPoints.Size;
var left = random.Next(0, viewRange.Width);
var top = random.Next(0, viewRange.Height);
// set the random position
SpawnPoints.Location = new Point(left, top);
Random random = new Random();
you are calling methods on 'random' before you have set it up. YOu need
var viewRange = this.Size - SpawnPoints.Size;
Random random = new Random();
var left = random.Next(0, viewRange.Width);
var top = random.Next(0, viewRange.Height);
// set the random position
SpawnPoints.Location = new Point(left, top);
But I will point out 2 things
unity has given you its random number generator, and you already used it (Random.Range) but for some reason you decide to use the c# standard one
creating a new Random each time round that loop will keep generating the same numbers over and over again if called in rapid succession better is to create the Random once at the start of the function (or use the Unity one)
To Generate Obstacles At Random Z position.
Am I keeping it much Simple!.
To Do this we have to keep these things in mind:
1: The X and Y Coordinates are not going to change.
2: The only Z coordinates are going to change.
Solution:
Generate a new Vector3 that will contains same X and Y coordinates but random Z coordinates.
Code:
// the game object (mean obstacle to generate)
public GameObject ObstaclePrefab;
// this will be the coordinates that will not going to change (X and Y coordinates as they will be same for all obstacles but only the Z coordinates will change.)
public Vector2 ConstantCords = new Vector2(0, 0);
// the max and minimum range for obstacles to be generated (X represents the negative [minimum] while y will represent the positive [maximum] position (Cordinates) )
public Vector2 MaxZCords = new Vector2(-50f, 50f);
// this method will generate the obstacle at random position
public void GenerateRandomObstacle()
{
// getting a random Z coordinates
// Random.Range will give random values
float Z = Random.Range(MaxZCords.x, MaxZCords.Y);
// getting position to generate the obstacle
// putting the constant and the random Z value
Vector3 pos = new Vector3(ConstantCords.x, ConstantCords.y, Z);
// generating the obstacle at the generated random position
// we are setting the rotation to rotation of the prefab
Instantiate(ObstaclePerfab, pos, ObstaclePrefab.transfrom.rotation);
}
In my code, I create 2 matrices that store information about two different meshes, using the following code:
Mesh mesh;
MeshFilter filter;
public SphereMesh SphereMesh;
public CubeMesh CubeMesh;
public Material pointMaterial;
public Mesh pointMesh;
public List<Matrix4x4> matrices1 = new List<Matrix4x4>();
public List<Matrix4x4> matrices2 = new List<Matrix4x4>();
[Space]
public float normalOffset = 0f;
public float globalScale = 0.1f;
public Vector3 scale = Vector3.one;
public int matricesNumber = 1; //determines which matrices to store info in
public void StorePoints()
{
Vector3[] vertices = mesh.vertices;
Vector3[] normals = mesh.normals;
Vector3 scaleVector = scale * globalScale;
// Initialize chunk indexes.
int startIndex = 0;
int endIndex = Mathf.Min(1023, mesh.vertexCount);
int pointCount = 0;
while (pointCount < mesh.vertexCount)
{
// Create points for the current chunk.
for (int i = startIndex; i < endIndex; i++)
{
var position = transform.position + transform.rotation * vertices[i] + transform.rotation * (normals[i].normalized * normalOffset);
var rotation = Quaternion.identity;
pointCount++;
if (matricesNumber == 1)
{
matrices1.Add(Matrix4x4.TRS(position, rotation, scaleVector));
}
if (matricesNumber == 2)
{
matrices2.Add(Matrix4x4.TRS(position, rotation, scaleVector));
}
rotation = transform.rotation * Quaternion.LookRotation(normals[i]);
}
// Modify start and end index to the range of the next chunk.
startIndex = endIndex;
endIndex = Mathf.Min(startIndex + 1023, mesh.vertexCount);
}
}
The GameObject starts as a Cube mesh, and this code stores the info about the mesh in matrices1. Elsewhere in code not shown, I have it so that the Mesh changes to a Sphere and then changes matricesnumber to 2, then triggers the code above to store the info of the new Sphere mesh in matrices2.
This seems to be working, as I'm able to use code like Graphics.DrawMesh(pointMesh, matrices1[i], pointMaterial, 0);
to draw 1 mesh per vertex of the Cube mesh. And I can use that same line (but with matrices2[i]) to draw 1 mesh per vertex of the Sphere mesh.
The Question: How would I draw a mesh for each vertex of the Cube mesh (info stored in matrices1) on the screen and then make those vertex meshes Lerp into the positions of the Sphere mesh's (info stored in matrices2) vertices?
I'm trying to hack at it with things like
float t = Mathf.Clamp((Time.time % 2f) / 2f, 0f, 1f);
matrices1.position.Lerp(matrices1.position, matrices2.position, t);
but obviously this is invalid code. What might be the solution? Thanks.
Matrix4x4 is usually only used in special cases for directly calculating transformation matrices.
You rarely use matrices in scripts; most often using Vector3s, Quaternions and functionality of Transform class is more straightforward. Plain matrices are used in special cases like setting up nonstandard camera projection.
In your case it seems to me you actually only need to somehow store and access position, rotation and scale.
I would suggest to not use Matrix4x4 at all but rather something simple like e.g.
// The Serializable makes them actually appear in the Inspector
// which might be very helpful in you case
[Serializable]
public class TransformData
{
public Vector3 position;
public Quaternion rotation;
public Vector3 scale;
}
Then with
public List<TransformData> matrices1 = new List<TransformData>();
public List<TransformData> matrices2 = new List<TransformData>();
you could simply lerp over
var lerpedPosition = Vector3.Lerp(matrices1[index].position, matrices2[index].position, lerpFactor);
And if you then need it as a Matrix4x4 you can still create it ad-hoc like e.g.
var lerpedMatrix = Matrix4x4.Translation(lerpedPosition);
or however you want to use the values.
Unity has a function Terrain.sampleHeight(point) which is great, it instantly gives you the height of the Terrain underfoot rather than having to cast.
However, any non-trivial project has more than one Terrain. (Indeed any physically large scene inevitably features terrain stitching, one way or another.)
Unity has a function Terrain.activeTerrain which - I'm not making this up - gives you: the "first one loaded"
Obviously that is completely useless.
Is fact, is there a fast way to get the Terrain "under you"? You can then use the fast function .sampleHeight ?
{Please note, of course, you could ... cast to find a Terrain under you! But you would then have your altitude so there's no need to worry about .sampleHeight !}
In short is there a matching function to use with sampleHeight which lets that function know which Terrain to use for a given xyz?
(Or indeed, is sampleHeight just a fairly useless demo function, usable only in demos with one Terrain?)
Is there in fact a fast way to get the Terrain "under you" - so as to
then use the fast function .sampleHeight ?
Yes, it can be done.
(Or indeed, is sampleHeight just a fairly useless demo function,
usable only in demos with one Terrain?)
No
There is Terrain.activeTerrain which returns the main terrain in the scene. There is also Terrain.activeTerrains (notice the "s" at the end) which returns active terrains in the scene.
Obtain the terrains with Terrain.activeTerrains which returns Terrain array then use Terrain.GetPosition function to obtain its position. Get the current terrain by finding the closest terrain from the player's position. You can do this by sorting the terrain position, using Vector3.Distance or Vector3.sqrMagnitude (faster).
Terrain GetClosestCurrentTerrain(Vector3 playerPos)
{
//Get all terrain
Terrain[] terrains = Terrain.activeTerrains;
//Make sure that terrains length is ok
if (terrains.Length == 0)
return null;
//If just one, return that one terrain
if (terrains.Length == 1)
return terrains[0];
//Get the closest one to the player
float lowDist = (terrains[0].GetPosition() - playerPos).sqrMagnitude;
var terrainIndex = 0;
for (int i = 1; i < terrains.Length; i++)
{
Terrain terrain = terrains[i];
Vector3 terrainPos = terrain.GetPosition();
//Find the distance and check if it is lower than the last one then store it
var dist = (terrainPos - playerPos).sqrMagnitude;
if (dist < lowDist)
{
lowDist = dist;
terrainIndex = i;
}
}
return terrains[terrainIndex];
}
USAGE:
Assuming that the player's position is transform.position:
//Get the current terrain
Terrain terrain = GetClosestCurrentTerrain(transform.position);
Vector3 point = new Vector3(0, 0, 0);
//Can now use SampleHeight
float yHeight = terrain.SampleHeight(point);
While it's possible to do it with Terrain.SampleHeight, this can be simplified with a simple raycast from the player's position down to the Terrain.
Vector3 SampleHeightWithRaycast(Vector3 playerPos)
{
float groundDistOffset = 2f;
RaycastHit hit;
//Raycast down to terrain
if (Physics.Raycast(playerPos, -Vector3.up, out hit))
{
//Get y position
playerPos.y = (hit.point + Vector3.up * groundDistOffset).y;
}
return playerPos;
}
Terrain.GetPosition() = Terrain.transform.position = position in world
working method:
Terrain[] _terrains = Terrain.activeTerrains;
int GetClosestCurrentTerrain(Vector3 playerPos)
{
//Get the closest one to the player
var center = new Vector3(_terrains[0].transform.position.x + _terrains[0].terrainData.size.x / 2, playerPos.y, _terrains[0].transform.position.z + _terrains[0].terrainData.size.z / 2);
float lowDist = (center - playerPos).sqrMagnitude;
var terrainIndex = 0;
for (int i = 0; i < _terrains.Length; i++)
{
center = new Vector3(_terrains[i].transform.position.x + _terrains[i].terrainData.size.x / 2, playerPos.y, _terrains[i].transform.position.z + _terrains[i].terrainData.size.z / 2);
//Find the distance and check if it is lower than the last one then store it
var dist = (center - playerPos).sqrMagnitude;
if (dist < lowDist)
{
lowDist = dist;
terrainIndex = i;
}
}
return terrainIndex;
}
It turns out the answer is simply NO, Unity does not provide such a function.
You can use this function to get the Closest Terrain to your current Position:
int GetClosestTerrain(Vector3 CheckPos)
{
int terrainIndex = 0;
float lowDist = float.MaxValue;
for (int i = 0; i < _terrains.Length; i++)
{
var center = new Vector3(_terrains[i].transform.position.x + _terrains[i].terrainData.size.x / 2, CheckPos.y, _terrains[i].transform.position.z + _terrains[i].terrainData.size.z / 2);
float dist = Vector3.Distance(center, CheckPos);
if (dist < lowDist)
{
lowDist = dist;
terrainIndex = i;
}
}
return terrainIndex;
}
and then you can use the function like this:
private Terrain[] _terrains;
void Start()
{
_terrains = Terrain.activeTerrains;
Vector3 start_pos = Vector3.zero;
start_pos.y = _terrains[GetClosestTerrain(start_pos)].SampleHeight(start_pos);
}
public static Terrain GetClosestTerrain(Vector3 position)
{
return Terrain.activeTerrains.OrderBy(x =>
{
var terrainPosition = x.transform.position;
var terrainSize = x.terrainData.size * 0.5f;
var terrainCenter = new Vector3(terrainPosition.x + terrainSize.x, position.y, terrainPosition.z + terrainSize.z);
return Vector3.Distance(terrainCenter, position);
}).First();
}
Raycast solution: (this was not asked, but for those looking for Solution using Raycast)
Raycast down from Player, ignore everything that has not Layer of "Terrain" (Layer can be easily set in inspector).
Code:
void Update() {
// Put this on Player! Raycast's down (raylength=10f), if we hit something, check if the Layers name is "Terrain", if yes, return its instanceID
RaycastHit hit;
if (Physics.Raycast (transform.localPosition, transform.TransformDirection (Vector3.down), out hit, 10f, 1 << LayerMask.NameToLayer("Terrain"))) {
Debug.Log(hit.transform.gameObject.GetInstanceID());
}
}
At this point already, you have a reference to the Terrain by "hit.transform.gameObject".
For my case, i wanted to reference this terrain by its instanceID:
// any other script
public static UnityEngine.Object FindObjectFromInstanceID(int goID) {
return (UnityEngine.Object)typeof(UnityEngine.Object)
.GetMethod("FindObjectFromInstanceID", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Static)
.Invoke(null, new object[] { goID });
}
But as written above, if you want the Terrain itself (as Terrain object) and not the instanceID, then "hit.transform.gameObject" will give you the reference already.
Input and code snippets taken from these links:
https://answers.unity.com/questions/1164722/raycast-ignore-layers-except.html
https://answers.unity.com/questions/34929/how-to-find-object-using-instance-id-taken-from-ge.html
I created a series of sphere clones in my game. After that I adapted the scale so that they appear smaller. However, now there is a gap between these spheres ... and I would have to change the position of this instatiate game objects. I changed my code already exactly at this position but nothing happens. So please I need your help! How can I do this? I would have very small spheres which are located near together.
Here the code:
using UnityEngine;
using System.Collections;
public class SineWave : MonoBehaviour {
private GameObject plotPointObject;
private int numberOfPoints= 100;
private float animSpeed =1.0f;
private float scaleInputRange = 8*Mathf.PI; // scale number from [0 to 99] to [0 to 2Pi] //Zahl vor Mathf, Anzahl Bön
private float scaleResult = 2.5f; // Y Achse Range
public bool animate = true;
GameObject[] plotPoints;
// Use this for initialization
void Start () {
if (plotPointObject == null) //if user did not fill in a game object to use for the plot points
plotPointObject = GameObject.CreatePrimitive(PrimitiveType.Sphere); //create a sphere
//add Material to the spheres , load material in the folder Resources/Materials
Material myMaterial = Resources.Load("Materials/green", typeof(Material)) as Material;
plotPointObject.GetComponent<MeshRenderer> ().material = myMaterial;
//change the scale of the spheres
//plotPointObject.transform.localScale = Vector3.one * 0.5f ;
plotPointObject.transform.localScale -= new Vector3(0.5f,0.5f,0.5f);
plotPoints = new GameObject[numberOfPoints]; //creat an array of 100 points.
//plotPointObject.GetComponent<MeshRenderer> ().material =Material.Load("blue") as Material
//plotPointObject.transform.localScale -= new Vector3 (0.5F, 0.5F, 0.5F); //neu: change the scale of the spheres
for (int i = 0; i < numberOfPoints; i++)
{
plotPoints[i] = (GameObject)GameObject.Instantiate(plotPointObject, new Vector3(i -
(numberOfPoints/2), 0, 0), Quaternion.identity); //this specifies
what object to create, where to place it and how to orient it
}
//we now have an array of 100 points- your should see them in the hierarchy when you hit play
plotPointObject.SetActive(false); //hide the original
}
Thank you already in advance!
Edit:
As I said in the comment I achieved now to place my spheres without a gap in between. However, as soon as I animate my spheres (with a sine wave) there is still that gap between the spheres. How can I adapt this? Should I copy the code of the Start function in the Update function?
I would be very happy to get some help. Thank you very much!
enter code here void Update()
{
for (int i = 0; i < numberOfPoints; i++)
{
float functionXvalue = i * scaleInputRange / numberOfPoints; // scale number from [0 to 99] to [0 to 2Pi]
if (animate)
{
functionXvalue += Time.time * animSpeed;
}
plotPoints[i].transform.position = new Vector3(i - (numberOfPoints/2), ComputeFunction(functionXvalue) * scaleResult, 0);
//print (plotPointObject.GetComponent<MeshRenderer> ().bounds.size.x);
// put the position information of sphere clone 50 in a vector3 named posSphere
posSphere = plotPoints [50].transform.position;
}
//print position of sphere 50 in console
//print (posSphere);
}
float ComputeFunction(float x)
{
return Mathf.Sin(x);
}
}
I think you could make the Barış solution.
For each new object that you are instantiating, you will set his position to the lasted instantiated position adding the size of the object itself, or whatever distance that you want they have from each other.
var initialPosition = 0;
var distanceFromEachOther = 20;
for (int i = 0; i < numberOfPoints; i++) {
var newPos = new Vector3(initialPosition + (i * distanceFromEachOther), 0, 0);
plotPoints[i] = (GameObject)GameObject.Instantiate(plotPointObject, newPos, Quaternion.identity);
}
That will make a gap between the spheres at X pivot, depending on their size. Change the distanceFromEachOther var, adjusting for your needs.
You could also get the object distance with plotPointObject.GetComponent<MeshRenderer>().bounds.size, so distanceFromEachOther could be, for example distanceFromEachOther = plotPointObject.GetComponent<MeshRenderer>().bounds.size.x + 5. So then you will have the objects with a perfectly distance of 5 from each other.
give this a try:
Transform objectToSpawn;
for (int i = 0; i < numberOfPoints; i++)
{
float someX = 200;
float someY = 200;
Transform t = Instantiate(objectToSpawn, new Vector3(i -(numberOfPoints/2), 0, 0), Quaternion.identity) as Transform;
plotPoints[i] = t.gameObject;
t.position = new Vector(someX, someY);
}
I'm trying to write a script that makes a cube turn blue and display the vertices of the cube on it when selected during runtime. So basically when clicked, it will show all the vertices and then allow a user to select different vertices of the cube.
This is what i have so far, essentially only turning the cube blue. How can I display vertices of the cube as spheres? I want to be able to select these vertices later.
using UnityEngine;
using System.Collections;
public class Cube : MonoBehaviour {
void OnMouseDown() {
Renderer rend = GetComponent<Renderer>();
rend.material.color = Color.blue;
//insert method to display vertices
}
}
Since both the cube and spheres are primitives:
you find the vertices like this (assuming cube is a GameObject):
Vector3[] vertices = cube.GetComponent<MeshFilter>().mesh.vertices;
And then to create your spheres (need using System.Linq):
GameObject[] spheres = vertices.Select(vert =>
{
GameObject sphere = GameObject.CreatePrimitive(PrimitiveType.Sphere)
sphere.transform.position = vert;
return sphere;
})
.ToArray();
UPDATE: Okay i see your problem with too many vertices now, and this is how i would do it, and i would personally make threshold be the radius of your spheres, so that once the spheres begin overlapping they just become one:
float threshold = 0.1f;
public void CreateSpheres()
{
List<GameObject> spheres = new List<GameObject>();
foreach (Vector3 vert in vertices)
{
if (spheres.Any(sph => (sph.transform.position - vert) < threshold)) continue;
GameObject sphere = GameObject.CreatePrimitive(PrimitiveType.Sphere)
sphere.transform.position = vert;
spheres.Add(sphere);
}
}
Couldn't get the Select Function to work as show in one of the previous example. The coordinates were found using GetComponent<MeshFilter>.mesh.vertices; as described in the other answer.
void OnMouseDown() {
Renderer rend = GetComponent<Renderer>();
rend.material.color = Color.blue;
Vector3[] vertices = GetComponent<MeshFilter>().mesh.vertices;
Vector3[] verts = removeDuplicates(vertices);
drawSpheres(verts);
}
The vertices array contains 24 elements for the reason shown here. So the function removeDuplicates was written to get rid of the duplicate vertices. This function is shown below:
Vector3[] removeDuplicates(Vector3[] dupArray) {
Vector3[] newArray = new Vector3[8]; //change 8 to a variable dependent on shape
bool isDup = false;
int newArrayIndex = 0;
for (int i = 0; i < dupArray.Length; i++) {
for (int j = 0; j < newArray.Length; j++) {
if (dupArray[i] == newArray[j]) {
isDup = true;
}
}
if (!isDup) {
newArray[newArrayIndex] = dupArray[i];
newArrayIndex++;
isDup = false;
}
}
return newArray;
}
Lastly the Spheres were drawn using the new verts Array using the drawSpheres function shown here:
void drawSpheres(Vector3[] verts) {
GameObject[] Spheres = new GameObject[verts.Length];
for (int i = 0; i < verts.Length; i++) {
Spheres[i] = GameObject.CreatePrimitive(PrimitiveType.Sphere);
Spheres[i].transform.position = verts[i];
Spheres[i].transform.localScale -= new Vector3(0.8F, 0.8F, 0.8F);
}
}