OverlapBox returns null almost every time - c#

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.

Related

The most efficient way to track allowed object movement in Unity 2D for puzzle making

Okay so I've rolled this around in my brain, and while I would probably know what code to use if I could figure it out, I just can't decide what the best way to implement this is.
Basically, say I have a vector grid. This grid could have any number of blockaded areas that the item I am pushing around can't go to. The pushing of the object is done by an interact button using the "new" inputsystem package. I could have a dynamic rigidbody and let it be pushed around that way, but I want to use the interact button I have, in part because the first puzzle of this type is used in a tutorial to teach the player the game's commands.
Without knowing where the blockades are going to be, I'm not sure how to tell where the object can move at any given time. My current thought is use colliders, and keep them far enough away that there isn't an actual collision since I don't think I can place them perfectly enough to get collisions at the right times, and throw out raycasts at a short range to detect these colliders. (This gives me more "give" when placing the colliders.)
Does this sound right? Is there a more efficient way to do it? Even if it requires more complex code, I am interested in feedback because I want to learn "good coding" and how to do things in the best, most efficient way possible, rather than spaghetti coding my way through it.
The most efficient way? Forget Colliders. Basic old-school game dev skill: Working with arrays. Create a 2D array of boolean values, or integers set to 0 or 1 representing OPEN or CLOSED.
using UnityEngine;
public class TestObstacleGrid : MonoBehaviour
{
const int GRID_WIDTH = 8; //if you change this, you'll need to match the size of the grid, below.
int[,] grid = {
// -> +x
{0,0,0,0,0,0,0,0},
{0,0,0,0,0,0,0,0},
{0,0,0,1,1,1,1,1},
{0,0,0,1,0,1,1,1},
{0,0,0,1,0,1,1,1},
{0,0,0,0,0,0,0,0},
{1,1,0,0,0,0,0,0},
{1,1,0,0,0,0,0,0}
//|
//V
//+z
};
const int OPEN = 0;
const int CLOSED = 1;
public Vector3 playerPos = Vector3.zero; //give player a start position via inspector.
//You need to drag/drop something like a Cube GameObject into here, via the inspector.
public GameObject playerGameObject;
Vector3 playerOffset = new Vector3(0.5f, 0.5f, 0.5f);
Texture2D tex;
void Start()
{
tex = new Texture2D(8, 8, TextureFormat.ARGB32, false);
tex.filterMode = FilterMode.Point; //makes texture crisp, not blurry
//assumes you are using a plane, not a quad.
this.transform.position = new Vector3(GRID_WIDTH / 2, 0, GRID_WIDTH / 2); //shift by half
this.transform.localScale = new Vector3(-GRID_WIDTH / 10f, 1f, -GRID_WIDTH / 10f); //Unity Planes are 10 units wide.
MeshRenderer renderer = this.GetComponent<MeshRenderer>();
renderer.material.mainTexture = tex;
playerGameObject.transform.position = playerPos;
//Only needs to be called once in Start() or Awake(), if grid doesn't change.
RenderGridToTexture();
}
void Update()
{
Vector3 motion = Vector3.zero;
if (Input.GetKeyDown(KeyCode.W))
motion += Vector3.forward;
if (Input.GetKeyDown(KeyCode.S))
motion += Vector3.back;
if (Input.GetKeyDown(KeyCode.A))
motion += Vector3.left;
if (Input.GetKeyDown(KeyCode.D))
motion += Vector3.right;
Vector3 proposedPos = playerPos + motion;
//prevent player leaving the grid.
proposedPos.x = proposedPos.x < 0 ? 0 : proposedPos.x;
proposedPos.x = proposedPos.x > GRID_WIDTH - 1 ? GRID_WIDTH - 1 : proposedPos.x;
proposedPos.z = proposedPos.z < 0 ? 0 : proposedPos.z;
proposedPos.z = proposedPos.z > GRID_WIDTH - 1 ? GRID_WIDTH - 1 : proposedPos.z;
if (grid[(int)proposedPos.z, (int)proposedPos.x] == CLOSED)
{
proposedPos = playerPos; //reset the proposed to the current position (stay there).
Debug.Log("Invalid attempted move from " + playerPos +
" to " + proposedPos);
}
else //OPEN
playerPos = proposedPos;
playerGameObject.transform.position = playerPos + playerOffset; //offset shifts the cube so it looks right.
//Uncomment this if the grid can change during play.
//RenderGridToTexture();
}
void RenderGridToTexture()
{
for (int z = 0; z < GRID_WIDTH; z++)
for (int x = 0; x < GRID_WIDTH; x++)
tex.SetPixel(x, z,
grid[z, x] == 1 ? Color.red : Color.black );
tex.Apply();
}
}
Create an empty GameObject, and attach to this script along with a MeshRenderer and MeshFilter; for the MeshFilter, give it a Plane mesh. Create a Cube GameObject and drag it onto the script's playerGameObject field in the inspector. The script will do the rest.
Using the keys, your player moves one full square at a time, but you'll want smooth movement in the end. That's fine; this is only a basis for your game. Later on, when you want smooth movement, you can figure it out using Mathf.Lerp() with coroutines to move your player smoothly from one space to another (as well as the object your player is pushing).
You'll need to figure out pushing - the principle is the same as already used here to check for collisions: Look at the grid and see if a non-zero value is there. Then look in front of the object to see if there is an obstacle. Use a different number in the grid, like 2, to represent a pushable object.
That's the best I can do in short form, hope it's enough. Adios and good luck.

Having trouble on a Scripting Exercise for Unity and deleting objects (So close!!)

EDIT: I have updated the code, but it still doesn't quite work properly.
I am almost done with this project but am stuck on the last part.
Basically, the parameters for what I'm working on is as follows:
Instantiate one cube prefab every frame into the scene.
The cubes need to be named as “Cube1”, “Cube2”… based on the order they are generated.
The cubes need to be generated at a random locations within 1 unit from the origin [0,0,0].
Each cube should have a random color.
The cube size (localScale) shrink 10% in each frame.
When the cube’s scale is less than 10% of its original scale, the cube is destroyed.
I am stuck on six. The cubes are created, colored and named, and shrink over time, but will not always destroy themselves after they shrink enough.
I've gotten them to delete SOME of the cubes, but others will now just stop shrinking and stay in the scene indefinitely.
if this helps, I noticed that after it deletes a certain element (cube0 for example)it will reuse that name again and then the list of objects gets all messed up looking and things stop deleting.
My code so far:
using JetBrains.Annotations;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class CubeCreator : MonoBehaviour
{
Vector3 origin;
private List<GameObject> cubeList;
public float targetScale = 0.1f;
public float shrinkSpeed = 0.1f;
GameObject newCube;
// Start is called before the first frame update
private void Awake()
{
cubeList = new List<GameObject>();
}
void Start()
{
origin = new Vector3(0, 0, 0);
}
// Update is called once per frame
void Update()
{
AddNewCube();
for (int i = 0; i < cubeList.Count; i++)
{
cubeList[i].name = "cube" + i;
cubeList[i].transform.localScale = Vector3.Lerp(cubeList[i].transform.localScale, new Vector3(targetScale, targetScale, targetScale), Time.deltaTime * shrinkSpeed);
if (cubeList[i].transform.localScale.x <= targetScale + 0.5f)
{
cubeList.RemoveAt(i);
RemoveCube(newCube);
print("cube " + i + " removed");
}
}
}
private void SetRandCubeState(GameObject cube)
{
float randScale = Random.Range(0.8f, 1.0f);
Vector3 randomScaler = new Vector3(randScale, randScale, randScale);
cube.transform.localScale = randomScaler;
Vector3 randomPosition = new Vector3(Random.Range(origin.x - 1.0f, origin.x + 1.0f), Random.Range(origin.y - 1.0f, origin.y + 1.0f), Random.Range(-1.0f,1.0f));
cube.transform.localPosition = randomPosition;
Renderer render = cube.GetComponent<Renderer>();
render.material.SetColor("_Color", Random.ColorHSV());
}
private void AddNewCube()
{
GameObject newCube = GameObject.CreatePrimitive(PrimitiveType.Cube);
cubeList.Add(newCube);
SetRandCubeState(newCube);
}
private void RemoveCube(GameObject oldCube)
{
Destroy(oldCube);
}
}
Note that the tasks says nothing about a random initial scale. It further says the cubes should destroy after reaching < 10% of the original scale. So if you use random initial scale then you would also need to store the original scale somewhere.
It also says the cube should shrink 10% each frame not based on seconds. So how I understand it the first frame it goes 1 -> 0.9, the second frame it goes 0.9 -> 0.81 etc so either the task is bad formulated or you are doing something else ^^
Lerp takes a factor between 0 and 1 and linear interpolates the start and end value accordingly. You are using it wrong ^^
Given an e.g. 60 frames per second you are all the time interpolating between the current scale and 0.1 with a (more or less constant) factor of about 0.0017 .. your cubes will barely scale down and get slower and slower the closer you get to the target. Definitely not scaling down 10 % per frame ;) You are also lerping against the value 0.1 instead of 0. So your threshold might never be reached at all or only very very slow.
Also the cubes should be named accordingly to the order they are created in -> you shouldn't iterate through the list and rename them.
// Store existing cube references together with their minimum scale
// before getting destroyed
private Dictionary<Transform, float> cubes = new Dictionary<Transform, float>();
// Count created objects for correct naming
private int cubeSpawnCounter;
void Update()
{
AddNewCube();
foreach(var kvp in cubes)
{
// Scale down 10% per frame
kvp.Key.localScale *= 0.9f;
// Check if reached below threshold of 10% of original scale
if (kvp.Key.localScale.x <= kvp.Value)
{
RemoveCube(kvp.Value);
}
}
}
private void SetRandCubeState(GameObject cube)
{
// Multiply the vector that saves you a lot of typing ;)
cube.transform.localScale = Vector3.one * Random.Range(0.8f, 1.0f);
// storing the origin is redundant
cube.localPosition = new Vector3(Random.Range(-1.0f, 1.0f), Random.Range(-1.0f, 1.0f), Random.Range(-1.0f,1.0f));
// Only go via the Shader properties if really necessary
var render = cube.GetComponent<Renderer>();
render.material.color = Random.ColorHSV());
}
private void AddNewCube()
{
var newCube = GameObject.CreatePrimitive(PrimitiveType.Cube);
SetRandCubeState(newCube);
// Count the created cubes and set the name ONCE
cubeSpawnCounter++;
newCube.name = $"Cube{cubeSpawnCounter}";
// store the destroy threshold for each cube (10% of original scale)
cubes[newCube.transform] = randomScale * 0.1f;
}
private void RemoveCube(Transform oldCube)
{
// Remove from the Dictionary
cubes.RemoveKey(oldCube);
print($"{oldCube.name} removed");
Destroy(oldCube.gameObject);
}
It appears that the value never reaches .1 due to the lerp. You could try adding a float to the conditional check (I used a large number, but using .01f could work depending on your use case)
if(cubeList[i].transform.localScale.x <= targetScale + .3f)
{
cubeList.RemoveAt(i);
RemoveCube(newCube);
print("cube " + i + " removed");
}

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.

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