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);
}
Related
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.
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.
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.
This may seem like a stupid question but I'm stuck with it. I have GameObjects in a list (List<GameObject>) and I want to add them on the scene runtime, prefarbly on predefined places (like placeholders or something). What would be a good way to do it? I've been searching the net but can't really find anything that would solve this. This is my code so far:
public static List<GameObject> imglist = new List<GameObject>();
private Vector3 newposition;
public static GameObject firstGO;
public GameObject frame1;//added line
void Start (){
newposition = transform.position;
firstGO = GameObject.Find ("pic1");
frame1 = GameObject.Find ("Placeholder1");//added line
//this happens when a button is pressed
imglist.Add(firstGO);
foreach(GameObject gos in imglist ){
if(gos != null){
print("List: " + gos.name);
try{
//Vector3 temp = new Vector3 (0f, 0f, -5f);
Vector3 temp = new Vector3( frame1.transform.position.x, frame1.transform.position.y, -1f);//added line
newposition = temp;
gos.transform.position += newposition;
print ("position: " + gos.transform.position);
}catch(System.NullReferenceException e){}
}
}
}
How can I place the pics (5) on the predefined spots?
//----------------
EDIT: Now I can place 1 image to a placeholder (transparent png). For some reason z-value goes all over the place so it needs to be forced to -1f but that's OK. I add the images to the list from other scenes and there can be 1-5 of them. Do I need to put the placeholders in another list or array? I'm a bit lost here.
If you've already created 5 new objects you can just do like they do here:
http://unity3d.com/learn/tutorials/modules/beginner/scripting/invoke under the InvokeScript
foreach(GameObject gos in imglist)
{
Instantiate(gos, new Vector3(0, 2, 0), Quaternion.identity);
}
I don't really understand what you're trying to do, but if I'm correct and you have a list of objects, and you know where you want to move them at runtime, just make two lists,
one containing the objects and
one containing transforms of empty game-objects in the scene placed at those predefined positions, and match them at runtime.
Populate both lists from the inspector.
public List<GameObject> imglist = new List<GameObject>();
public List<Transform> imgPositions = new List<Transform>();
void Start()
{
for(var i = 0 i < imglist.Count; ++i)
{
imglist[i].transform.position = imgPositions[i].position
}
}
The general best way is to create prefabs for your objects, passing them as a parameter and instantiate when needed (Start in your case). That's the common case, but maybe yours is slightly different.
This is an example of passing a prefabs array and to instantiate one object for each one in the array:
public GameObject prefabs[];
List<GameObject> objects = new List<GameObject>();
void Start() {
for(GameObject prefab in prefabs) {
GameObject go = Instantiate(prefab, Vector3.zero, Quaternion.identity) as GameObject; // Replace Vector3.zero by actual position
objects.Add(go); // Store objects to access them later: total enemies count, restart game, etc.
}
}
In case you need several instances for the same prefab (multiple enemies or items, for instance) just adapt code above.
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.