My position generation method doesn't work - c#

I have a method that is supposed to generate a certain number of Vector3 at a distance not less than specified.
// Generate random point based on plane area
public List<Vector3> GeneratePositions(int numberOfPositions, float minDistanceBetweenPositions)
{
float entireArea = 0f;
List<AreasWeight> areasWeights = new List<AreasWeight>();
List<Vector3> positions = new List<Vector3>();
foreach (GeneratorPlane plane in GeneratorPlanes.GetCollectionAsList())
{
entireArea += plane.GetArea();
}
foreach (GeneratorPlane plane in GeneratorPlanes.GetCollectionAsList())
{
float weight = plane.GetArea() / entireArea;
int numOfPositionsInArea = Mathf.RoundToInt(numberOfPositions * weight);
areasWeights.Add(new(plane, weight, numOfPositionsInArea));
}
foreach (AreasWeight areaWeight in areasWeights)
{
for (int i = 0; i < areaWeight.NumOfPointsInArea; i++)
{
Vector3 generatedPoint = areaWeight.Plane.GetRandomPointOnPlane();
foreach (Vector3 position in positions)
{
int attempts = 1;
while ((position - generatedPoint).magnitude < minDistanceBetweenPositions)
{
generatedPoint = areaWeight.Plane.GetRandomPointOnPlane();
attempts++;
if (attempts > 2000)
{
Debug.Log("Can't generate all positions.");
break;
}
}
}
positions.Add(generatedPoint);
}
}
return positions;
}
Get random point method:
public Vector3 GetRandomPointOnPlane()
{
float xPosition = Random.Range(Mathf.Min(DownPoint.x, DownPointHelper.x), Mathf.Max(DownPoint.x, DownPointHelper.x));
float zPosition = Random.Range(Mathf.Min(DownPoint.z, UpPointHelper.z), Mathf.Max(DownPoint.z, UpPointHelper.z));
return new(xPosition, DownPoint.y + 0.002f, zPosition);
}
But when i Instantiate objects based on these Vector3. Objects still have a distance less than the specified. What am i doing wrong?

I found a solution. The problem was a bad loop structure. When the algorithm confirmed that the distance was too small and generated a new one, it did not check whether the generated position had a gap from the previous positions on the list. It only confirmed that the gap was preserved and the program continued to execute.
I moved the code that makes sure that the distances are saved to the public List<Vector3> GeneratePositions(int numberOfPositions, float minDistanceBetweenPositions) method in the GeneratorPlane class. I also added a private Vector3 PickRandomPos() method to it, just to return the generated position.
Methods in the public class GeneratorPlane:
public Vector3 GetRandomPointOnPlane(List<Vector3> alreadyGeneratedPoints, float minDistnaceBetweenPositions)
{
if (alreadyGeneratedPoints.Count != 0)
{
int attemps = 1;
bool pointFound = false;
Vector3 posToReturn = new();
while (!pointFound)
{
pointFound = true;
posToReturn = PickRandomPos();
foreach (Vector3 position in alreadyGeneratedPoints)
{
if (Vector3.Distance(position, posToReturn) < minDistnaceBetweenPositions)
{
pointFound = false;
attemps++;
if (attemps > 2000)
{
Debug.LogError("Points cannot be generated. Too little available space");
return Vector3.zero;
}
break;
}
}
}
return posToReturn;
}
else
{
Debug.Log("First point generated");
return PickRandomPos();
}
}
private Vector3 PickRandomPos()
{
float xPosition = Random.Range(Mathf.Min(DownPoint.x, DownPointHelper.x), Mathf.Max(DownPoint.x, DownPointHelper.x));
float zPosition = Random.Range(Mathf.Min(DownPoint.z, UpPointHelper.z), Mathf.Max(DownPoint.z, UpPointHelper.z));
return new(xPosition, DownPoint.y + 0.002f, zPosition);
}
Method to generate and return a certain number of items:
public List<Vector3> GeneratePositions(int numberOfPositions, float minDistanceBetweenPositions)
{
float entireArea = 0f;
List<AreasWeight> areasWeights = new();
List<Vector3> positions = new();
foreach (GeneratorPlane plane in PlanesGenerator.GetCollectionAsList())
{
entireArea += plane.GetArea();
}
foreach (GeneratorPlane plane in PlanesGenerator.GetCollectionAsList())
{
float weight = plane.GetArea() / entireArea;
int numOfPositionsInArea = Mathf.RoundToInt(numberOfPositions * weight);
areasWeights.Add(new(plane, weight, numOfPositionsInArea));
}
foreach (AreasWeight areaWeight in areasWeights)
{
for (int i = 0; i < areaWeight.NumOfPointsInArea; i++)
{
Vector3 generatedPoint = areaWeight.Plane.GetRandomPointOnPlane(positions, minDistanceBetweenPositions);
positions.Add(generatedPoint);
}
}
return positions;
}

On the original code if you generate a point 2000 times you actually keep the last generatedPoint, and as you mentioned you don't actually cross check the whole list of positions, only the remaining positions.
Although you have solved your problem and posted a solution, I took the liberty of doing a simple script with the same end, I will share it here in hopes its useful for you or others.
This solution will not fill any area, its only making sure no two objects are at shorter distance than specified.
In my tests, with 50 nPoints only 10/20 points are instantiated before a point takes over 2000 attempts and consequently conclude the search for points. Although this will depend on the ratio between spawnLimits and nPoints.
[SerializeField]
GameObject trunkPrefab;
List<Vector3> positions;
//input variables
int nPoints = 50;
float minDistance = 2.5f;
int spawnLimits = 20;
void Start()
{
positions = new();
for (int i = 0; i < nPoints; i++)
{
Vector3 position = Vector3.zero;
bool newPosition = true;
int attempts = 0;
do
{
//first generation will be automatically added to the list
position = new(Random.Range(-spawnLimits, spawnLimits), .5f, Random.Range(-spawnLimits, spawnLimits));
if (positions.Count < 1)
{
break;
}
//every position will be compared here,
//if any position is too close from then new position
//"newPosition" is set to false and we try again from the start.
for (int p = 0; p < positions.Count; p++)
{
if (Vector3.Distance(position, positions[p]) < minDistance)
{
newPosition = false;
attempts++;
if (attempts > 2000)
{
Debug.Log("Max attempts reached.");
return;
}
break;
}
}
} while (!newPosition);
//adding a random rotation
Vector3 rotation = new(Random.Range(80, 100), Random.Range(0, 179), 0);
Instantiate(trunkPrefab, position, Quaternion.Euler(rotation));
positions.Add(position);
}
}

Related

Unity, I need to prevent my objects from being affected by the vertical layout and scroll view

I have cars inside a Vertical Layout, they need to reach the finish line but they're moving up and down when I scroll up and down, I want the cars to only move in one direction on the Y-axis, how can I achieve this?
enter image description here
your text
public Transform\[\] startLines;
public Transform\[\] finishLines;
public float speed = 10f;
private int currentRectangle = 0;
public void MoveCar()
{
if (currentRectangle >= startLines.Length)
{
return;
}
StartCoroutine(MoveToEndPosition());
}
private IEnumerator MoveToEndPosition()
{
for (int i = 0; i < startLines.Length; i++)
{
Transform startLine = startLines[currentRectangle];
Transform finishLine = finishLines[currentRectangle];
Vector3 endPosition = finishLine.position;
while (transform.position != endPosition)
{
transform.position = Vector3.MoveTowards(transform.position , endPosition, speed * Time.deltaTime);
yield return null;
}
currentRectangle++;
if (currentRectangle < startLines.Length)
{
transform.position = startLines[currentRectangle].position;
}
}
}

Generating random Path in 2D Grid in C# (Tower Defense)

im trying to create a 2D TowerDefense Game and i´m stuck with creating a random Path between a random start- and endpoint. These two points are located at the top edge and bottom edge respectively.
Currently my code is looking for the direction on the x axis where the end point is. If the path is level with the end point, a straight path is generated to that point.
But I want more variety. The path shouldn't just go left or right and then down. For example, I want something like curves, but the path must not collide.
I hope someone can help me with my Code.
Code:
public class MapGenerator : MonoBehaviour
{
public GameObject mapTile;
[SerializeField] private int mapWidth; //set in the unity environment
[SerializeField] private int mapHeight; //set in the unity environment
public static List<GameObject> mapTiles = new List<GameObject>();
public static List<GameObject> pathTiles = new List<GameObject>();
public static GameObject startTile;
public static GameObject endTile;
private bool reachedX = false;
private bool reachedY = false;
private GameObject currentTile;
private int currentIndex;
private int nextIndex;
public Color startTileColor;
public Color endTileColor;
public Color mapColor;
public Color pathColor;
private void Start()
{
generateMap();
}
//selecting all Tiles at the top Edge
private List<GameObject> getTopEdgeTiles()
{
List<GameObject> edgeTiles = new List<GameObject>();
for (int i = mapWidth * (mapHeight - 1); i < mapWidth * mapHeight; i++)
{
edgeTiles.Add(mapTiles[i]);
}
return edgeTiles;
}
//selecting all Tiles at the bottom Edge
private List<GameObject> getBottomEdgeTiles()
{
List<GameObject> edgeTiles = new List<GameObject>();
for (int i = 0; i < mapWidth; i++)
{
edgeTiles.Add(mapTiles[i]);
}
return edgeTiles;
}
//void for moving down
private void moveDown()
{
pathTiles.Add(currentTile); //adding currentTile to PathTile
currentIndex = mapTiles.IndexOf(currentTile); //getting current Index of Tile in mapTiles
nextIndex = currentIndex - mapWidth; //setting next Index
currentTile = mapTiles[nextIndex]; //setting next currentTile
}
//void for moving left
private void moveLeft()
{
pathTiles.Add(currentTile);
currentIndex = mapTiles.IndexOf(currentTile);
nextIndex = currentIndex - 1;
currentTile = mapTiles[nextIndex];
}
//void for moving right
private void moveRight()
{
pathTiles.Add(currentTile);
currentIndex = mapTiles.IndexOf(currentTile);
nextIndex = currentIndex + 1;
currentTile = mapTiles[nextIndex];
}
private void generateMap()
{
//setup 2D Map
for (int y = 0; y < mapHeight; y++)
{
for (int x = 0; x < mapWidth; x++)
{
GameObject newTile = Instantiate(mapTile);
mapTiles.Add(newTile);
newTile.transform.position = new Vector2(x, y);
}
}
List<GameObject> topEdgeTiles = getTopEdgeTiles();
List<GameObject> bottomEdgeTiles = getBottomEdgeTiles();
int rand1 = Random.Range(0, mapWidth);
int rand2 = Random.Range(0, mapWidth);
startTile = topEdgeTiles[rand1]; //random starting point
endTile = bottomEdgeTiles[rand2]; //random end point
currentTile = startTile;
moveDown();
//starting the path algorithm
bool moving = true;
while (moving)
{
if (!reachedX)
{
if (currentTile.transform.position.x > endTile.transform.position.x)
{
moveLeft();
}
else if (currentTile.transform.position.x < endTile.transform.position.x)
{
moveRight();
}
else
{
reachedX = true;
}
}
else if(!reachedY) {
if (currentTile.transform.position.y > endTile.transform.position.y)
{
moveDown();
}
else
{
reachedY = true;
}
}
else if (reachedX && reachedY)
{
moving = false;
}
}
pathTiles.Add(endTile);
//setting colors for each tile
foreach (GameObject obj in mapTiles)
{
obj.GetComponent<SpriteRenderer>().color = mapColor;
}
foreach (GameObject obj in pathTiles)
{
obj.GetComponent<SpriteRenderer>().color = pathColor;
}
startTile.GetComponent<SpriteRenderer>().color = startTileColor;
endTile.GetComponent<SpriteRenderer>().color = endTileColor;
}
}
This generate something like:
current path generation
An I want something like that:
my imagination
Thank you and Best Regards!
I actually tried a bunch of different ways of producing random paths. They all worked, but in all cases the vast majority of the paths that were generated were not pleasing -- not enough wiggles or not using most of the available area.
This is a way that I think would work well, but it would be a fair bit of code:
Generate about 4 or 5 random points. Make sure you have at least one in each quadrant.
Calculate the Voronoi diagram of those points, along with the entrance and exit.
Try to find a Hamiltonian path through the diagram from the entrance to the exit. If you can't find one, then just take the longest simple path that you found.
In each Voronoi cell, connect the path entry point to the exit point with a smooth curve.
The Hamiltonian path, along with the requirement to have a point in each quadrant, ensures that the path uses the available space well. The number of random points determines how tight the curves are.

I am trying to delete Vector2 item in List of Vector2 vectors comparing it with other Vector2

I am taking Vector2 as touch postion,and i want to search for same Vector2 in list where i already added all positions rounded by 1 decimal f.e 0.5.But now they never seems to be same,I guess the vector taken from transform position has more decimals than 1.So how can i fix it.
Using debug i see that both list contains same values,but comparing them and trying deleting duplicates,doesnt work.
public class skriptaPozicija : MonoBehaviour
{
List<Vector2> listaV1;
List<Sprite> slike1t;
public List<Vector2> daseNeponovi;
int randomBroj;
public GameObject instantacija;
GameObject oznacenaSl;
Vector3 skala;
public GameObject povecalo;
// Start is called before the first frame update
void Start()
{
StartCoroutine(postavkeSLova());
List<Vector2> daseNeponovi=new List<Vector2>();
}
// Update is called once per frame
void Update()
{
// RemoveDuplicates();
}
public void klikNaPoziciju()
{
randomBroj = Random.Range(0,slike1t.Count);
// Debug.Log("broj itema u slici"+HintoviMale.slikeT.Count);
oznacenaSl = Instantiate(instantacija,listaV1[randomBroj] , Quaternion.identity) as GameObject;
oznacenaSl.transform.localScale = new Vector3(WordsGrid.skalaPrefaba.x,
WordsGrid.skalaPrefaba.y, 0);
oznacenaSl.GetComponent<SpriteRenderer>().sprite = slike1t[randomBroj];
Destroy(oznacenaSl, 3f);
slike1t.RemoveAt(randomBroj);
listaV1.RemoveAt(randomBroj);
if (slike1t.Count <= 0)
{
povecalo.SetActive(false);
}
for(int i = 0; i < daseNeponovi.Count; i++)
{
Debug.Log(daseNeponovi[i]);
}
for (int i = 0; i < listaV1.Count; i++)
{
Debug.Log(listaV1[i]);
}
listaV1.Add(new Vector2(2, 2));
daseNeponovi.Add(new Vector2(2, 2));
}
IEnumerator postavkeSLova()
{
yield return new WaitForSeconds(0.1f);
slike1t = new List<Sprite>();
for (int i = 0; i < HintoviMale.slike.Count; i++)
{
slike1t.Add(HintoviMale.slike[i]);
}
listaV1 = new List<Vector2>();
for (int i = 0; i < HintoviMale.pozicijaV.Count; i++)
{
listaV1.Add(HintoviMale.pozicijaV[i]);
}
}
public void RemoveDuplicates()
{
foreach (Vector2 item in daseNeponovi)
{
if (listaV1.Contains(item))
{
listaV1.Remove(item);
}
}
}
public void refreshList()
{
slike1t = new List<Sprite>();
listaV1 = new List<Vector2>();
for (int i = 0; i < HintoviMale.slike.Count; i++)
{
slike1t.Add(HintoviMale.slike[i]);
}
listaV1 = new List<Vector2>();
for (int i = 0; i < HintoviMale.pozicijaV.Count; i++)
{
listaV1.Add(HintoviMale.pozicijaV[i]);
}
}
}
I am taking Vector2 as touch postion,and i want to search for same Vector2 in list where i already added all positions rounded by 1 decimal f.e 0.5.But now they never seems to be same,I guess the vector taken from transform position has more decimals than 1.So how can i fix it.

Unity what's wrong with my instantiating algorithm?

I dont know if I can call this algorithm. But I am working on a game in which player will move in a circular path.
As you can see in the picture player is suppose to orbit the circle. And obstacle shall be instantiated in the circle.I am trying to first create the obstacle in first half(left to the long cube) and then in the second half. But things are getting created in the next half too when code is not supposed to do that. Also, it is showing argument exception error. Please have a look at my code and tell me whether my method is wrong or my formulas are wrong or anything else.
public class ObjectInstantiater : MonoBehaviour {
DataHolder dataholder;
GameObject Obstacle;
LevelData leveldata;
private int currentlevel=0; // default level starts from 0
private List<GameObject> Inactivegameobject = new List<GameObject>(); // this object can be used
private List<GameObject> Activegameobject = new List<GameObject>();
private int totalgameobjects;
private int firsthalfgameobjects, secondhalfgameobjects;
public float outerradius;
public float innerradius;
private bool shallspawnouterradiues = true;
// Use this for initialization
void Awake () {
dataholder = (Object)GameObject.FindObjectOfType<DataHolder>() as DataHolder;
Obstacle = GameObject.CreatePrimitive(PrimitiveType.Cylinder);
leveldata = dataholder.Leveldata[0];
}
void Start()
{
Updateleveldata();
FirstHalf();
}
public int Currentlevel
{
get { return currentlevel; }
set { currentlevel = value;
leveldata = dataholder.Leveldata[currentlevel];//sets the level data
}
}
private void Updateleveldata() // this function gets called after a round
{
totalgameobjects = Random.Range(leveldata.MinimumObstacle, leveldata.MaximumObstacle);
firsthalfgameobjects = Mathf.RoundToInt(totalgameobjects / 2);
secondhalfgameobjects = totalgameobjects - firsthalfgameobjects;
}
private void FirstHalf()
{
Debug.Log(firsthalfgameobjects);
Vector3 pos;
if (Inactivegameobject.Count < firsthalfgameobjects)
{
for (int x = 0; x <= (firsthalfgameobjects - Inactivegameobject.Count); x++)
{
GameObject obs = Instantiate(Obstacle) as GameObject;
obs.SetActive(false);
Inactivegameobject.Add(obs);
}
}
float spawnangledivision = 180 / firsthalfgameobjects;
float spawnangle = 180f;
for(int x = 0; x < firsthalfgameobjects; x++)
{
float proceduralRandomangle = spawnangle;
proceduralRandomangle = Random.Range(proceduralRandomangle , proceduralRandomangle + 2f);
if (shallspawnouterradiues)
{
pos = new Vector3(outerradius * Mathf.Cos(spawnangle), outerradius * Mathf.Sin(spawnangle), 0f);
shallspawnouterradiues = false;
}else
{
pos = new Vector3(innerradius * Mathf.Cos(spawnangle), innerradius * Mathf.Sin(spawnangle), 0f);
shallspawnouterradiues = true;
}
spawnangle += spawnangledivision;
Inactivegameobject[0].SetActive(true); // set it to 0
Inactivegameobject[0].transform.position = pos;
Activegameobject.Add(Inactivegameobject[0]);
Inactivegameobject.RemoveAt(0);
}
}
private void SecondHalf()// No need to check this
{
if (Inactivegameobject.Count < firsthalfgameobjects)
{
GameObject obs = Instantiate(Obstacle) as GameObject;
obs.SetActive(false);
Inactivegameobject.Add(obs);
}
}
}

Instantiated object does not have desired scale

I am trying to get two blocks to respawn vertically with a gap in-between. I have done this however, when I click play, one of the blocks width is too short. this results in having two gaps. how can I stop this from happening. I would only like the one gap which is between the two blocks. the code I am currently using:
public Transform block;
public Transform player;
private float objectSpawnedTo = 5.0f;
public static float distanceBetweenObjects = 9.5f;
private float nextCheck = 0.0f;
private ArrayList objects = new ArrayList();
void Start () {
maintenance(0.0f);
}
void Update () {
float playerX = player.position.y;
if(playerX > nextCheck)
{
maintenance(playerX);
}
}
private void maintenance(float playerX)
{
nextCheck = playerX + 30;
for (int i = objects.Count-1; i >= 0; i--)
{
Transform blck = (Transform)objects[i];
if(blck.position.y < (transform.position.y - 30))
{
Destroy(blck.gameObject);
objects.RemoveAt(i);
}
}
spawnObjects(5);
}
private void spawnObjects(int howMany)
{
float spawnX = objectSpawnedTo;
for(int i = 0; i<howMany; i++)
{
Vector3 pos = new Vector3(6.0f, spawnX, 0);
float firstRandom = Random.Range(1,8.6f);
Transform blck = (Transform)Instantiate(block, pos, Quaternion.identity);
blck.localScale+=new Vector3(firstRandom*2,0,0);
objects.Add(blck);
pos = new Vector3(-6.0f, spawnX, 0);
blck = (Transform)Instantiate(block, pos, Quaternion.identity);
blck.localScale +=new Vector3((8.6f-firstRandom)*2,0,0);
objects.Add(blck);
spawnX = spawnX + distanceBetweenObjects;
}
objectSpawnedTo = spawnX;
}
i have attached an image to show what i am trying to achieve. the red outline is the mobile screen. also i would like to be able to move the blocks either left or right using touch. how would i go about doing this.

Categories