spawning object on the ground in terrain - c#

I created a randomly spawn animal script but my animals spawning inside the hill and my terrain has a lot of hill. How do i make the animals spawn always on the ground.
Note: Normally my terrains y axis value is 0.
Here is my code.
IEnumerator spawnenemiesatposition1()
{
yield return new WaitForSeconds(3f);
while (cond1)
{
Vector3 position1 = new Vector3(Random.Range(5, 195), 1, Random.Range(5, 495));
Instantiate(Deer, position1, Quaternion.Euler(-90, 0, 0));
deernum1++;
if (deernum1 > 5)
{
cond1 = false;
}
}
Example of my issue
How could i make the animals spawn always on the ground.

Use Terrain.SampleHeight.
You can find the documentation here.
Basically, given your world position, it returns you the height of the terrain in that position.

Related

OverlapBox returns null almost every time

I've tried to make a script to spawn objects at a random position without them colliding with one another. It doesn't work properly as the OverlapBox returns almost every time null even when it touches a square.
Here is the script:
var quadBoundaries = quad.GetComponent<MeshCollider>().bounds;
var squareRadius = new Vector2(1, 1);
foreach (var square in squaresToSpawn)
{
_isOverlapping = true;
while (_isOverlapping)
{
_spawnPoint = new Vector2(Random.Range(quadBoundaries.min.x + 1.5f, quadBoundaries.max.x - 1.5f),
Random.Range(quadBoundaries.min.y + 1.5f, quadBoundaries.max.y - 1.5f));
_collisionWithSquare = Physics2D.OverlapBox(_spawnPoint, squareRadius,
0, LayerMask.GetMask("Square Layer"));
if (_collisionWithSquare is null)
{
square.transform.position = _spawnPoint;
_isOverlapping = false;
}
}
}
The quadBoundaries are the boundaries of a quad I placed so the squares will randomly spawn in a limited space.
My understanding is that I am generating a random point in the quad boundaries and then I check if on that point a square of scale (1,1) will fit without touching any other thing that has a collider and is on the square layer. if it touches then I generate a new point until the collision is null so I can place the square at the designated position.
But a bunch of things that I don't understand are happening.
First, the squares are touching each other. Second, just a few specific squares are registering a collision but even those are being touched by other squares. Third, when I scale up the square radius (for example 10,10) I get a big split between the squares (shown in the picture bellow).
I must add that all squares have a collider, are all on the square layer and the quad is on a different layer.
Can anybody explain to me what I'm not getting here? Thanks a lot!
Before the answer I'd like to say, that such algorithm of spawning is very dangerous, because you can enteran infinity loop, when there will no be place for new square. Minimum, that you can do to make this code more safe is to add a retries count for finding a place to spawn. But I will leave it on your conscience.
To make this algorithm work, you should understand that all physics in Unity are updated in fixed update. So all operations, that you do with Phisycs or Phisics2D are working with the state of objects, that was performed in the last Pyhsics update. When you change the position of the object, physics core don't capture this changes instantly. As a workaround you can spawn each object in the fixed update separately. Like this:
public class Spawner : MonoBehaviour
{
[SerializeField] private GameObject[] _squaresToSpawn;
[SerializeField] private GameObject _quad;
// Start is called before the first frame update
void Start()
{
StartCoroutine(Spawn());
}
private IEnumerator Spawn()
{
var quadBoundaries = _quad.GetComponent<MeshCollider>().bounds;
var squareRadius = new Vector2(1, 1);
Vector2 spawnPoint;
foreach (var square in _squaresToSpawn)
{
yield return new WaitForFixedUpdate();
var isOverlapping = true;
var retriesCount = 10;
while (isOverlapping && retriesCount > 0)
{
spawnPoint = new Vector2(Random.Range(quadBoundaries.min.x + 1.5f, quadBoundaries.max.x - 1.5f),
Random.Range(quadBoundaries.min.y + 1.5f, quadBoundaries.max.y - 1.5f));
var hit = Physics2D.OverlapBox(spawnPoint, squareRadius,
0, LayerMask.GetMask("Square"));
if (hit is null)
{
square.transform.position = spawnPoint;
isOverlapping = false;
}
else
{
retriesCount--;
if (retriesCount == 0)
{
Debug.LogError("Can't find place to spawn the object!!");
}
}
}
}
}
}
But such code will have effect of continious spawning:
To make objects spawning properly within one frame. You should manualy store all spawned objects bounding boxes inside your code, and manualy check if your new spawn bounding box collide with previously spawned objects.

Unity: Get the position of the last cloned instantiated object

I'm trying to create an infinite ground for Android using Unity. Trying to use object pooling to achieve the ground repeating but is proving a bit tricky.
I can get my ground to Instantiate and create the clones along x axis.
What I am trying to achieve is to get the position of the last cloned object and set that as the new position of and create new object in the new position and instantiate again.
Do I need to work with the transform parent?
Am I going the right way about this?
Code below.
public class InfiniteGround : MonoBehaviour
{
public Transform ground1Obj;
private int count;
private Vector3 lastPosition;
void Start()
{
count = 0;
for (int i = 0; i < 10; i++)
{
Instantiate(ground1Obj, new Vector3(i * 100f, 0, 0), Quaternion.identity);
count++;
if (count == 10)
{
lastPosition = ground1Obj.position;
Debug.Log("Last Position: " + lastPosition);
}
}
}
}
For the Instantiating it should work, but not the way you intend to. If you want to have infinite ground you should add ground depending on the player position
If the player moves forward instantiate new ground before him and
destroy the old ground behind him.
If the player moves backward instantiate new ground behind him and
destroy the old ground before him
If you wanted to change your code. I would:
Change the functionname for example (InstantiateFloor), because you want to call it more than once at the start
Call the function depending on the player position (as described above)
Just instantiate 1 big floor piece (instead of 10 smaller ones) and take the position of that
Why not using the Gameobject returned by the Instantiation?
GameObject newObject = Instantiate(ground1Obj, new Vector3(i * 100f, 0, 0), Quaternion.identity);
count++;
if (count == 10)
{
lastPosition = newObject .position;
Debug.Log("Last Position: " + lastPosition);
}

Unity2D - Randomly instantiated objects need to not spawn close to each other

I'm a beginner to Unity. I'm creating an educational snake game that randomly spawns three potential answers. It's working, however I need to not have them spawn close to each other. I also need not to have any of them spawn near the player/snake. Someone already tried to help me and I asked him for help, but it seems like he's unable to fix the problem and the objects still spawn near each other.
void GeneratePotentialAnswers()
{
allAnswers.Clear();
List<Vector3> AnswerPositions = new List<Vector3>();
AnswerPositions.Add(player.transform.position);
for (int i = 0; i < potentialAnswers.Count; i++)
{
GameObject ans = Instantiate(enemyAnswerPrefab);
Vector3 pos;
int index = 0;
bool tooClose = false;
do
{
float rndWidth = Random.Range(15f, Screen.width - 25f);
float rndHeight = Random.Range(15f, Screen.height - 105f);
pos = Camera.main.ScreenToWorldPoint(new Vector3(rndWidth, rndHeight, 0f));
pos.z = -1f;
foreach (Vector3 p in AnswerPositions)
{
float distance = (pos - p).sqrMagnitude;
if (distance < 3f)
{
tooClose = true;
}
}
index++;
}
while (tooClose == true && index < 500);
Debug.Log(index);
AnswerPositions.Add(pos);
ans.transform.localPosition = pos;
ans.GetComponent<Answer>().potentialAnswer = potentialAnswers[i];
ans.GetComponent<Answer>().addToScore = scorePerCorrectAnswer;
ans.GetComponent<Answer>().SetAnswer();
allAnswers.Add(ans.GetComponent<Answer>());
}
}
Answer
Manually place GameObjects that act as spawnpoints, attach a SpawnPoint script to each of them.
When placing an answer, have the SpawnPoint script of the chosen spawnpoint use a SphereCastAll to check if the nearby area is free, if it is not, another spawnpoint should be selected until a free one is found.
When a free spawnpoint is found, instantiate the answer at its location.
Elaboration
A simple solution would be to have a SpawnPoint script. Which would be a script put on a set of individual spawnpoints you place manually, there could be perhaps 20 of them at various points in the map.
The SpawnPoint script could then use the SphereCastAll method, which gathers a collection of colliders in a zone of a given size, in the shape of a sphere. (Should work in 3d but maybe not 2d, find another cast type than SphereCastAll in that case).
Here is documentation on how to make a SphereCastAll, notice that you can set the radius of the spherecast.
https://docs.unity3d.com/ScriptReference/Physics.SphereCastAll.html
If any objects are struck by the SphereCastAll, they will be stored in the RaycastHit[] it returns. You can search through this, each RaycastHit will have a reference to the collider it hit, along with the GameObject of that collider. Meaning that you have a list of all objects within a zone.
You can then check these objects to see if they are the player or another answer.
Here is documentation for a RaycastHit.
https://docs.unity3d.com/ScriptReference/RaycastHit.html
Note that to gain access to the spawnpoints it would be useful to have a list of them or a manager script of sorts that can select one for you.

How to rotate a parent object while rotating a child object to a certain angle using coroutines

I have a semicircle like game object which I made by putting two arcs in an empty game object (SCircle) and rotating the 15° (for left arc) and -15° (for right arc) as seen below.
SCircle has an Orientation enum with two valuesLeft (rotates SCircle to 45°) and Right (rotates SCircle to -45°) as seen in the image below.
I use the following coroutine to move SCircle between orientations.
IEnumerator RotateLeftOrRight(Vector3 byAngles, float inTime)
{
Quaternion fromAngle = gameObject.transform.rotation ;
Quaternion toAngle = Quaternion.Euler (transform.eulerAngles);
if (circOrientation == Orientation.Left) {
toAngle = Quaternion.Euler (gameObject.transform.eulerAngles - byAngles);
circOrientation = Orientation.Right;
}
else if (circOrientation == Orientation.Right) {
toAngle = Quaternion.Euler (gameObject.transform.eulerAngles + byAngles);
circOrientation = Orientation.Left;
}
for(float t = 0f ; t <= 1f ; t += Time.deltaTime/inTime)
{
gameObject.transform.rotation = Quaternion.Lerp(fromAngle, toAngle, t) ;
yield return null ;
}
gameObject.transform.rotation = Quaternion.Lerp(fromAngle, toAngle, 1);
}
I also used a very similar coroutine to move the individual arcs by 30° (in opposite directions) from say, Orientation Left, as seen below in the coroutine and image:
IEnumerator RotateArc(GameObject arcGO, Vector3 byAngles, float inTime)
{
Quaternion fromAngle = arcGO.transform.rotation ;
Quaternion toAngle = Quaternion.Euler (arcGO.transform.eulerAngles);
if (rightArc.arcOrientation == Arc.Orientation.Down) {
toAngle = Quaternion.Euler (arcGO.transform.eulerAngles + byAngles);
rightArc.arcOrientation = Arc.Orientation.Up;
}
else if (rightArc.arcOrientation == Arc.Orientation.Down) {
toAngle = Quaternion.Euler (arcGO.transform.eulerAngles - byAngles);
rightArc.arcOrientation = Arc.Orientation.Up;
}
for(float t = 0f ; t <= 1f ; t += Time.deltaTime/inTime)
{
arcGO.transform.rotation = Quaternion.Lerp(fromAngle, toAngle, t) ;
yield return null ;
}
arcGO.transform.rotation = Quaternion.Lerp(fromAngle, toAngle, 1);
}
Since SCircle Coroutine is activated by a mouse click, I have the case where the individual arcs coroutine is run and before it is complete the parent SCircle coroutine is also run. In this case the arcs end up moving from Left to A, which is not the behavior I need. I would want the behavior of them ending up at B when moving from the Left. Likewise, from B, when the SCircle coroutine is run while the arcs coroutine is in progress the orientation will return to the Left.
Please note that the blue arrow represents the movement of the left Arc, the red represents the right Arc and the black represents movement of SCircle - the parent object.
I believe the issue you're having is caused by the fact that as you rotate the individual arcs, what they're rotating around is also rotating itself.
In other words, your SCircle's axes are rotating, and your arcs are using those same axes to determine their own rotations. So if you look at your example A, you tell it to rotate in world space by -90, then immediately tell to rotate in world space by 30. To solve this, you want to get the arcs to rotate locally, not in world space.
I think you can solve this by setting up this structure:
Empty GameObject SCircle
------Empty gameObject BlueArcCenter -> position 0,0,0
-----------blue arc -> change position here to make it offset from zero ex:(radius, 0, 0)
------Empty gameObject RedArcCenter -> position (0,0,0)
-----------red arc -> change position here to make it offset from zero ex:(-radius, 0, 0)
Now alter your code so that:
To rotate the blue and red arcs individually you rotate their arcCenters,
To rotate the whole configuration, rotate SCircle. This will in turn rotate both ArcCenters, which will in turn rotate both the arcs irrespective of how they are oriented relative to each other, or whether they are rotating themselves.
Note:
Also, I've read that people are using Quaternions a lot less these days since they don't really make sense when you read the code. You might want to change your code to normal rotations to make it more readable.
If you have the setup listed above, use something like:
to spin circle:
thisSCircle.transform.Rotate(0, -90, 0, Space.Self);
to rotate arcs around their common center:
blueArcCenter.transform.Rotate(0, -30, 0, Space.Self)
redArcCemter.transform.Rotate(0, 30, 0, Space.Self)
(you'd need to add lerps in and stuff but this should get you started).

Error upon spawning multiple boundingBoxes "System.ArgumentOutOfRangeException"

I'm trying to create boundingBoxes (Collision) between an enemy and the bullets that it's struck with. Unfortunately, I'm being presented with the error System.ArgumentOutOfRangeException every time a enemy leaves the screen, or more than 1 bullet is spawned at a time.
Both bounding boxes are created in the Update functions of the bullet & enemy classes as such:
boundingBox = new Rectangle((int)position.X, (int)position.Y, texture.Width, texture.Height);
This is the relevant code being used in my Game1 update function:
for (int i = bullets.Count - 1; i >= 0; i--)
{
bullets[i].Update(gameTime);
ERROR OCCURS HERE -> if (bullets[i].boundingBox.Intersects(enemies[i].boundingBox))
{
score += 1;
bullets.RemoveAt(i);
}
//Bullets being destroyed upon leaving
if (bullets[i].position.Y < 0)
bullets.RemoveAt(i);
}
Any help would be appreciated.
Also, Here is how I implemented the bullet list into the Game1 class if that helps at all?:
List<Bullet> bullets = new List<Bullet>();
public void Shoot (Vector2 pos, Vector2 dir, float speed)
{
Bullet bullet = new Bullet(bulletTexture);
bullet.position = pos;
bullet.direction = dir;
bullet.speed = speed;
if (bullets.Count() < 40) //Maximum amount of bullets
bullets.Add(bullet);
}
If I have to guess, the error comes from the if (bullets[i].boundingBox.Intersects(enemies[i].boundingBox)) statement.
Basically, do you have the same amount of enemies and bullets all the time? If not, then it will fail whenever the numbers are different.
Why is this? Well, you are using two different collections, bullets and enemies, and taking the element at position i.
So if you have 5 bullets but only 1 enemy in the collection, the first iteration of the for will have i = 4, a position that does not exist in the enemies collection (it only has the enemies[0] position).
One way to solve the problem would be:
foreach (enemy in enemies)
foreach (bullet in bullets)
if (bullet.boundingBox.Intersects(enemy.boundingBox))
{
// do your logic here.
}
You go through every enemy, and for each one you check if any bullet is intersecting with it. This is not the best solution performance wise, but I think it's a good start.

Categories

Resources