Spawn image at random position in canvas - c#

I have a canvas and inside it a panel. I want to spawn different image UI object at random position inside my panel (on screen). I took x and y min and max limits of the panel in order to generate a random position (withing these limts) but the problem is object are not instantiating at desired position. This is my code.
public class ImageSpawnerScreen : MonoBehaviour {
public float waitTime = 2;
public float cubeSpawnTotal = 10;
public List<GameObject> cubePrefabList;
float xPosMinLimit = -347;
float xPosMaxLimit = 340;
float yPosMinLimit = -458f;
float yPosMaxLimit = 450f;
public GameObject panel;
void Start()
{
StartCoroutine(SpawnCube());
}
IEnumerator SpawnCube()
{
for (int i = 0; i < cubeSpawnTotal; i++)
{
GameObject prefabToSpawn = cubePrefabList[Random.Range(0, cubePrefabList.Count - 1)];
//Vector3 spawnPosition = Camera.main.ScreenToViewportPoint(new Vector3(Random.Range(0,Screen.width),0,Random.Range(0,Screen.height))); //Random.Range(xPosMinLimit, xPosMaxLimit);
float xPos = Random.Range(xPosMinLimit, xPosMaxLimit);
float yPos = Random.Range(yPosMinLimit, yPosMaxLimit);
Vector3 spawnPosition = new Vector3(xPos, yPos, 0f);
GameObject spwanObj = Instantiate(prefabToSpawn, spawnPosition, Quaternion.identity) as GameObject;
spwanObj.transform.parent = panel.transform;
spwanObj.transform.position = spawnPosition;
yield return new WaitForSeconds(waitTime);
}
}
}
I checked instantiated object positions are far away from the given random range position. What I am doing wrong? I think it is Rect Transform so i have to set its position differently.

Indeed, you need to use RectTransform inside a Canvas.
You also don't need the -1 for you List.Count. Check the link in the comment below.
I think the following script will do what you are looking for:
public class ImageSpawnerScreen : MonoBehaviour
{
public float waitTime = 2;
public float cubeSpawnTotal = 10;
public List<GameObject> imagesList;
public RectTransform panel;
void Start()
{
StartCoroutine(SpawnImage());
}
IEnumerator SpawnImage()
{
for (int i = 0; i < cubeSpawnTotal; i++)
{
GameObject imageToSpawn = imagesList[Random.Range(0, imagesList.Count)]; // Remove -1 after count since is exclusive for int (https://docs.unity3d.com/ScriptReference/Random.Range.html)
Vector3 spawnPosition = GetBottomLeftCorner(panel) - new Vector3(Random.Range(0, panel.rect.x), Random.Range(0, panel.rect.y), 0);
print("Spawn image at position: " + spawnPosition);
GameObject spwanObj = Instantiate(imageToSpawn, spawnPosition, Quaternion.identity, panel);
yield return new WaitForSeconds(waitTime);
}
}
Vector3 GetBottomLeftCorner(RectTransform rt)
{
Vector3[] v = new Vector3[4];
rt.GetWorldCorners(v);
return v[0];
}
}
Let me know if you need more explanations.

To manage Canvas object's position you should definitely use RectTransform instead of regular transform. In your case you have to do something like :
m_RectTransform = GetComponent<RectTransform>();
As for setting position - coordinates you see on the object inside Canvas is not .position but RectTransform.anchoredPosition, so you should use it in your code. Here is an official documentation on anchoredPosition.

public class ImageSpawnerScreen : MonoBehaviour {
public float waitTime = 2;
public float cubeSpawnTotal = 10;
public List<GameObject> cubePrefabList;
public GameObject panel;
void Start()
{
StartCoroutine(SpawnCube());
}
IEnumerator SpawnCube()
{
for (int i = 0; i < cubeSpawnTotal; i++)
{
GameObject prefabToSpawn = cubePrefabList[Random.Range(0, cubePrefabList.Count - 1)];
//Vector3 spawnPosition = Camera.main.ScreenToViewportPoint(new Vector3(Random.Range(0,Screen.width),0,Random.Range(0,Screen.height))); //Random.Range(xPosMinLimit, xPosMaxLimit);
float xPos = Random.Range(0, Screen.width);
float yPos = Random.Range(0, Screen.height);
Vector3 spawnPosition = new Vector3(xPos, yPos, 0f);
GameObject spwanObj = Instantiate(prefabToSpawn, spawnPosition, Quaternion.identity) as GameObject;
spwanObj.transform.parent = panel.transform;
spwanObj.transform.position = spawnPosition;
yield return new WaitForSeconds(waitTime);
}
}
}
This should help u out.

Related

How to infinitely generate a grid in unity?

So I have this piece of code that generates a 7x50 grid that falls down.
public class FieldController : MonoBehaviour
{
[SerializeField] private GameObject gridPrefab;
[SerializeField] private int rows = 50;
[SerializeField] private int columns = 7;
[SerializeField] private float tileSize = 1;
private GameObject tile;
private GameObject parentObject;
private float gridW;
private float gridH;
private float posX;
private float posY;
private void Start()
{
parentObject = new GameObject("Parent");
GenerateGrid();
}
private void Update()
{
MoveGridDown();
}
void GenerateGrid()
{
for (int i = 0; i < rows; i++)
{
for (int j = 0; j < columns; j++)
{
tile = Instantiate(gridPrefab, parentObject.transform);
posX = j * tileSize;
posY = i * tileSize;
tile.transform.position = new Vector2(posX, posY);
}
}
SetParentToStart(parentObject);
}
void SetParentToStart(GameObject parent)
{
parent.transform.position = new Vector3(-columns / 2 + tileSize, rows / 10, 0);
}
void MoveGridDown()
{
parentObject.transform.position -= new Vector3(0, FallingObjectBehaviour.FallingSpeed * Time.deltaTime);
}
}
This is how it looks:
https://imgur.com/a/nKtOuyV
What I want to do is to be able to generate the new grid every time the previous one reaches the end so it would look like the grid is infinite.
I've tried to use extra GameObject for a parent and switch between them but the results were buggy.
Is there any way to make it possible?
i believe you want to generate the next grid before it reaches the end, and i suggest a system where you have
GameObject gridPrefab;
GameObject grid;
Queue<GameObject> grids;
Transform camera;
Vector3 newPos = Vector3.zero;
Vector3 offset = new Vector3(0,0,50);
Quaternion forward = Quaternion.Euler(Vector3.forward);
int i = 0;
then you instantiate the new grid once camera is beyond "newPos", add it to the queue and destroy the last one.
if(newPos.z > camera.z)
{
newPos += offset;
grid = Instantiate(gridPrefab,newPos,forward);
grid.name = $"grid{++i}";
grids.Enqueue(grid);
Destroy(grids.Dequeue());
}
this is done without testing, let me know if makes sense to you, i can test it later if makes sense but is not working.

Sphere are overlapping even tho I check for collisions

I am using this code to generate a number of sphere around a center point, with different size and position.
There is a conditional check to prevent sphere from overlapping.
The problem I face is, in the scene some sphere are always overlapping.
=>
[Header("Galaxy")]
public int numberOfStars = 300;
public int maximumRadius = 100;
public float minDistBetweenStars = 2f;
[Header("Star")]
public float minimumSize = 1f;
public float maximumSize = 2f;
void Start()
{
for(int i = 0; i < numberOfStars; i++)
{
float distance = Random.Range(0, maximumRadius);
float angle = Random.Range(0, 2 * Mathf.PI);
float starSize = Random.Range(minimumSize, maximumSize);
Vector3 starPosition = new Vector3(distance * Mathf.Cos(angle), 0, distance * Mathf.Sin(angle));
Collider[] starCollider = Physics.OverlapSphere(starPosition, (starSize * 0.5f) + minDistBetweenStars);
if (starCollider.Length == 0)
{
GameObject star = GameObject.CreatePrimitive(PrimitiveType.Sphere);
star.transform.position = starPosition;
star.transform.localScale = new Vector3(starSize, starSize, starSize);
}
else
{
i--;
}
}
}
I do pass in the remove, so some sphere are being removed,m but I still see a lot of overlapping.
So the problem was we were not disabling the collider on the sphere we was checking :P
public class StarGenSO : MonoBehaviour
{
[Header("Galaxy")]
public int numberOfStars = 300;
public int maximumRadius = 100;
public float minDistBetweenStars = 2f;
[Header("Star")]
public float minimumSize = 1f;
public float maximumSize = 2f;
void Awake()
{
for (int i = 0; i < numberOfStars; i++)
{
GameObject star = GameObject.CreatePrimitive(PrimitiveType.Sphere);
float distance = Random.Range(0, maximumRadius);
float angle = Random.Range(0, 2 * Mathf.PI);
float starSize = Random.Range(minimumSize, maximumSize);
Vector3 starPosition = new Vector3(distance * Mathf.Cos(angle), 0, distance * Mathf.Sin(angle));
var currentCol = star.GetComponent<Collider>();
currentCol.enabled = false;
Collider[] starCollider = Physics.OverlapSphere(starPosition, (starSize * 0.5f) + minDistBetweenStars);
if (starCollider.Length == 0)
{
star.transform.position = starPosition;
star.transform.localScale = new Vector3(starSize, starSize, starSize);
currentCol.enabled = true;
}
else
{
Debug.Log("remove");
Destroy(star);
i--;
}
}
}
}
A preferred solution
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class StarGenSo : MonoBehaviour
{
[Header("Galaxy")]
public int numberOfStars = 300;
public int maximumRadius = 100;
public float minDistBetweenStars = 2f;
[Header("Star")]
public float minimumSize = 1f;
public float maximumSize = 2f;
[Header("Star Object")]
public GameObject obj;
IEnumerator Start()
{
for (int i = 0; i < numberOfStars; i++)
{
float distance = Random.Range(0, maximumRadius);
float angle = Random.Range(0, 2 * Mathf.PI);
float starSize = Random.Range(minimumSize, maximumSize);
Vector3 starPosition = new Vector3(distance * Mathf.Cos(angle), 0, distance * Mathf.Sin(angle));
Collider[] starCollider = Physics.OverlapSphere(starPosition, (starSize * 0.5f) + minDistBetweenStars);
if (starCollider.Length == 0)
{
GameObject star = Instantiate(obj);
star.transform.position = starPosition;
star.transform.localScale = new Vector3(starSize, starSize, starSize);
star.AddComponent<SphereCollider>();
}
else
{
i--;
}
}
yield return null;
}
}
Result:
The problem here is your starSize use both for local scale (multiple factor) and sphere collider (flat factor). Eg: your star have default size of 2 and your starSize equal 2, then your actual radius of your sphere is 4, but your collider check for 2*0.5 + 2 = 3.

Spawning and destroying random platforms in unity

I am trying to make a doodle jump game in unity for fun and I have a question.
How could I change my script to spawn other platforms on top and destroy any platform that is under main camera.I'll let down my code for spawning random platforms
public class GradinariuZAHAR : MonoBehaviour
{
public Transform Player;
public GameObject platformPrefab;
private float minimY = 0.2f;
private float maximY = 1.6f;
public int numberOfPlatforms = 200;
public float levelWidth = 3f;
// Use this for initialization
void Start()
{
GenerateChunk(.2f, 1.5f);
}
void GenerateChunk(float minY, float maxY)
{
Vector3 spawnPosition = new Vector3(0f, 0f, 0f);
for (int i = 0; i < numberOfPlatforms; i++)
{
spawnPosition.y += Random.Range(minY, maxY);
spawnPosition.x = Random.Range(-levelWidth, levelWidth);
Instantiate(platformPrefab, spawnPosition, Quaternion.identity);
}
}
void Update()
{
if (Player.position.y > 160) {
Debug.Log("acum");
minimY = maximY;
maximY = maximY * 2;
GenerateChunk(minimY, maximY);
}
}
}
Just attach the following script to your platform. It'll destroy the platform once its entire sprite is completely below the camera, exactly how you want it.
using UnityEngine;
public class Platform : MonoBehaviour
{
SpriteRenderer sprite;
float bottomOfScreen;
private void Start()
{
sprite = GetComponent<SpriteRenderer>();
var cam = Camera.main;
var screen = new Vector2(Screen.width, Screen.height);
var camWorldPos = cam.ScreenToWorldPoint(screen);
bottomOfScreen = camWorldPos.y - cam.orthographicSize * 2;
}
private void Update()
{
var height = sprite.bounds.size.y;
var topOfPlatform = transform.position.y + height / 2;
if (topOfPlatform < bottomOfScreen)
Destroy(gameObject);
}
}

Scale GameObject over time

I made a test game in unity that makes it so when I click on a button, it spawns a cylinder created from a factory class. I'm trying to make it so when I create the cylinder, its height shrinks over the next 20 seconds. Some methods I found are difficult to translate into what I'm doing. If you could lead me to the right direction, I'd very much appreciate it.
Here's my code for the cylinder class
public class Cylinder : Shape
{
public Cylinder()
{
GameObject cylinder = GameObject.CreatePrimitive(PrimitiveType.Cylinder);
cylinder.transform.position = new Vector3(3, 0, 0);
cylinder.transform.localScale = new Vector3(1.0f, Random.Range(1, 2)-1*Time.deltaTime, 1.0f);
cylinder.GetComponent<MeshRenderer>().material.color = Random.ColorHSV();
Destroy(cylinder, 30.0f);
}
}
This can be done with the Time.deltaTime and Vector3.Lerp in a coroutine function. Similar to Rotate GameObject over time and Move GameObject over time questions. Modified it a little bit to do just this.
bool isScaling = false;
IEnumerator scaleOverTime(Transform objectToScale, Vector3 toScale, float duration)
{
//Make sure there is only one instance of this function running
if (isScaling)
{
yield break; ///exit if this is still running
}
isScaling = true;
float counter = 0;
//Get the current scale of the object to be moved
Vector3 startScaleSize = objectToScale.localScale;
while (counter < duration)
{
counter += Time.deltaTime;
objectToScale.localScale = Vector3.Lerp(startScaleSize, toScale, counter / duration);
yield return null;
}
isScaling = false;
}
USAGE:
Will scale GameObject within 20 seconds:
StartCoroutine(scaleOverTime(cylinder.transform, new Vector3(0, 0, 90), 20f));
Check out Lerp. A general example of how to use it would be something like this:
float t = 0;
Update()
{
t += Time.deltaTime;
cylinder.localScale = new Vector3(1, Mathf.Lerp(2f, 1f, t/3f), 1); // shrink from 2 to 1 over 3 seconds;
}
You will create a new monobehaviour script and add it to your primitive. Then you wil use "Update" method of monobehaviour (or use coroutine) for change object over time.
Monobehaviour must be look like this:
public class ShrinkBehaviour : MonoBehaviour
{
bool isNeedToShrink;
Config currentConfig;
float startTime;
float totalDistance;
public void StartShrink(Config config)
{
startTime = Time.time;
currentConfig = config;
totalDistance = Vector3.Distance(currentConfig.startSize, currentConfig.destinationSize);
isNeedToShrink = true;
transform.localScale = config.startSize;
}
private void Update()
{
if (isNeedToShrink)
{
var nextSize = GetNextSize(currentConfig);
if (Vector3.Distance(nextSize, currentConfig.destinationSize) <= 0.05f)
{
isNeedToShrink = false;
return;
}
transform.localScale = nextSize;
}
}
Vector3 GetNextSize(Config config)
{
float timeCovered = (Time.time - startTime) / config.duration;
var result = Vector3.Lerp(config.startSize, config.destinationSize, timeCovered);
return result;
}
public struct Config
{
public float duration;
public Vector3 startSize;
public Vector3 destinationSize;
}
}
For using this, you must do next:
public Cylinder()
{
GameObject cylinder = GameObject.CreatePrimitive(PrimitiveType.Cylinder);
var shrink = cylinder.AddComponent<ShrinkBehaviour>();
shrink.StartShrink(new ShrinkBehaviour.Config() { startSize = Vector3.one * 10, destinationSize = Vector3.one * 1, duration = 20f });
cylinder.transform.position = new Vector3(3, 0, 0);
cylinder.GetComponent<MeshRenderer>().material.color = Random.ColorHSV();
Destroy(cylinder, 30.0f);
}
You must remember, monobehaviour-script must be in separate file, and must have name similar to monobehaviour-class name. For example, ShrinkBehaviour.cs;

How to spawn enemy at random intervals?

I am using EnemyScript to move the enemy towards the player and killing the player, but I'm unable to spawn it randomly in code. I am currently spawning it directly through screen by placing the prefab on the scene.
Here is my EnemyScript
using UnityEngine;
using System.Collections;
public class EnemyScript : MonoBehaviour {
public Transform target;
public float speed = 2f;
void Update ()
{
transform.position = Vector2.MoveTowards(transform.position, target.position, speed * Time.deltaTime);
}
void OnTriggerEnter2D(Collider2D otherCollider)
{
PlayerControl shot = otherCollider.gameObject.GetComponent<PlayerControl>();
if (shot != null)
{
Destroy(shot.gameObject);
}
}
}
you could use something similar to this:
public GameObject myObj;
void Start ()
{
enemy = GameObject.Find("enemy");
InvokeRepeating("SpawnEnemy", 1.6F, 1F);
}
public void SpawnEnemy()
{
Vector3 position = new Vector3(Random.Range(35.0F, 40.0F), Random.Range(-4F, 2F), 0);
Instantiate(myObj, position, Quaternion.identity);
}
in the InvokeRepeating call you could possibly add the random range there also instead of a timed instantiate. This example is just a snippet of some prototype code i did a while ago, it may not suit your needs directly but hopefully will give you a general idea on what to do.
EDIT: to make sense, put this into a blank object somewhere in your scene, dont attach this to the actual enemy.
i programmed it like this.
public GameObject enemyPrefab;
public float numEnemies;
public float xMin = 20F;
public float xMax = 85F;
public float yMin = 3.5F;
public float yMax = -4.5F;
void Start () {
for (int i=0; i< numEnemies; i++) {
Vector3 newPos = new Vector3(Random.Range(xMin,xMax),Random.Range(yMin,yMax),0);
GameObject enemy = Instantiate(enemyPrefab,newPos,Quaternion.identity) as GameObject;
}
}
This will spawn a random number of enemies, at random locations, after random periods of time, all adjustable in the Inspector.
public float minTime = 1;
public float maxTime = 3;
public int minSpawn = 1;
public int maxSpawn = 4;
public Bounds spawnArea; //set these bounds in the inspector
public GameObject enemyPrefab;
private spawnTimer = 0;
Vector3 randomWithinBounds(Bounds r) {
return new Vector3(
Random.Range(r.min.x, r.max.x),
Random.Range(r.min.y, r.max.y),
Random.Range(r.min.z, r.max.z));
}
void Update() {
spawnTimer -= Time.deltaTime;
if(spawnTimer <= 0) {
spawnTimer += Random.Range(minTime, maxTime);
int randomSpawnCount = Random.Range(minSpawn, maxSpawn);
for(int i = 0; i < randomSpawnCount; i++) {
Instantiate(transform.transformPoint(enemyPrefab), randomWithinBounds(spawnArea), Quaternion.identity);
}
}
}
//bonus: this will show you the spawn area in the editor
void OnDrawGizmos() {
Gizmos.matrix = transform.localToWorldMatrix;
Gizmos.color = Color.yellow;
Gizmos.drawWireCube(spawnArea.center, spawnArea.size);
}
People on here seem to like using coroutines for time delays, but I personally prefer to track my own timers in Update() to maximize control and predictability.

Categories

Resources