I have a SceneController that's supposed to initialize a set of empty GameObject spawners, each working together at the same rhythm. The RockSpawners receive an array of time delays and wait the X seconds between spawning another rock.
I set the _nextSpawn = float.maxValue when the spawners start and plan to overwrite this after "Initializing" them (my own method), however even though my debug logs say I've overwritten my _nextSpawn value while initializing, the update loop is still reporting float.maxValue and nothing ends up spawning because _timeSinceLastSpawn hasn't exceeded float.maxValue seconds.
Is there something I'm missing with the scope of my _nextSpawn variables? It doesn't seem to be a "this" vs "local" issue, at least at first glance.
Debug output: 0 0 3 3. 0's stay the same, 3's will vary based on rng.
SceneController.cs:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class SceneController : MonoBehaviour {
[SerializeField] private GameObject _rockSpawnerPrefab;
public int numRocks = 6;
public int minSpawnDelaySec = 1;
public int maxSpawnDelaySec = 3;
private bool spawnersInitialized = false;
void Start () {
InitializeSpawners();
}
void Update () {
}
void InitializeSpawners() {
float[] pattern = new float[numRocks];
for (int i = 0; i < numRocks; i++) {
// Generate delays at half second increments within bounds
float delay = Mathf.Floor(Random.value * ((float)(maxSpawnDelaySec + 0.5f - minSpawnDelaySec) / 0.5f));
delay = delay * 0.5f + minSpawnDelaySec;
pattern[i] = delay;
}
GameObject spawner = Instantiate(_rockSpawnerPrefab) as GameObject;
spawner.transform.position = new Vector3(0, 4, 0);
RockSpawner rockSpawnerScript = spawner.GetComponent<RockSpawner>();
rockSpawnerScript.Initialize(pattern);
}
}
RockSpawner.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class RockSpawner : MonoBehaviour {
[SerializeField] private GameObject _rockPrefab;
public float minSpawnDelay = 3f;
public float maxSpawnDelay = 6f;
private float[] _pattern;
private int _currentPattern;
private float _timeSinceLastSpawn;
private float _nextSpawn;
void Start () {
_currentPattern = -1;
_nextSpawn = float.MaxValue;
}
void Update () {
if (_pattern == null) return;
_timeSinceLastSpawn += Time.deltaTime;
if (_timeSinceLastSpawn > _nextSpawn) {
GameObject rock = Instantiate(_rockPrefab) as GameObject;
rock.transform.position = transform.position;
NextTimer();
}
}
public void Initialize(float[] pattern) {
_pattern = pattern;
NextTimer();
}
private void NextTimer() {
_timeSinceLastSpawn = 0;
_currentPattern += 1;
Debug.Log(_nextSpawn);
Debug.Log(this._nextSpawn);
this._nextSpawn = _pattern[_currentPattern];
Debug.Log(_nextSpawn);
Debug.Log(this._nextSpawn);
}
}
It's not about scoping, it's about call order. When you create a GameObject its Start method is called on the frame it's enabled, not when the object is created. So your code will call Initialize first, then Start which overwrites the values.
Remove the code in Start and handle everything in Initialize and it should work as you want.
Related
here is my code and i have no idea how to work it up. im a beginner in c# and this is for our project.
the project is about a balloon popper and i am having trouble to set an amount of balloons to spawn.
I am planning to set the amount of balloons to spawn at 5, 10 even 20 and after that, the spawning will stop.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class SpawnScript: MonoBehaviour
{
public Transform[] spawnPoints;
public GameObject[] balloons;
public float spawnTime = 0f;
float spawnTimeLeft = 0f;
// Start is called before the first frame update
void Update()
{
if (spawnTimeLeft >= spawnTime)
{
int randBalloon = Random.Range(0, balloons.Length);
int randSpawnPoint = Random.Range(0, spawnPoints.Length);
Instantiate(balloons[randBalloon], spawnPoints[randSpawnPoint].position, transform.rotation);
spawnTimeLeft = 0f;
}
else
{
spawnTimeLeft = spawnTimeLeft + Time.deltaTime;
}
}
}
Add a variable to the class for the maximum amount. Have done this as public so you can set it in the inspector to the amount you wish.
Also add a counter variable and set it to 0. This is private as nothing but this class needs access to it. Do serialize this field.
public int spawnAmountMax = 5;
[SerializeField]
private int spawned = 0;
Start the Update() with the next check, return from the method when it evaluates to true.
if (spawned >= spawnAmountMax)
return;
Than after instantiating the GameObject, increase the variable:
spawned++;
The adjusted code
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using static System.Windows.Forms.VisualStyles.VisualStyleElement.TaskbarClock;
public class SpawnScript : MonoBehaviour
{
public int spawnAmountMax = 5;
private int spawned = 0;
public Transform[] spawnPoints;
public GameObject[] balloons;
public float spawnTime = 0f;
float spawnTimeLeft = 0f;
// Start is called before the first frame update
void Update()
{
if (spawned >= spawnAmountMax)
return;
if (spawnTimeLeft >= spawnTime)
{
int randBalloon = Random.Range(0, balloons.Length);
int randSpawnPoint = Random.Range(0, spawnPoints.Length);
Instantiate(balloons[randBalloon], spawnPoints[randSpawnPoint].position, transform.rotation);
spawnTimeLeft = 0f;
spawned++;
} else
{
spawnTimeLeft = spawnTimeLeft + Time.deltaTime;
}
}
}
Try this code:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class SpawnScript: MonoBehaviour
{
public Transform[] spawnPoints;
public GameObject[] balloons;
public float spawnTime = 5.0f;
public float maxSpawnTime = 20.0f;
private void Start()
{
Invoke(nameof(Spawn), spawnTime); //This will start the spawning process after the initially set spawnTime, so first this will wait for 5 seconds.
}
private void Spawn()
{
int randBalloon = Random.Range(0, balloons.Length);
int randSpawnPoint = Random.Range(0, spawnPoints.Length);
Instantiate(balloons[randBalloon], spawnPoints[randSpawnPoint].position, transform.rotation);
spawnTime *= 2; //doubles the spawnTime, according to your request, so the second time this will be 10, and the third time, this will be 20 seconds
if (spawnTime <= maxSpawnTime) //check if we reached or not the maximum spawn time
{
Invoke(nameof(Spawn), spawnTime); //if we not yet reached the maximum spawn time, it will start to wait and spawn one more balloon again.
}
}
}
since you're doing this in you update, you can add a counter after your Instantiate call.
Instantiate(balloons[randBalloon], spawnPoints[randSpawnPoint].position, transform.rotation);
counter++;
And then, nest your if statement inside another if checking for the counter.
if(counter < amountToSpawn)
{
//your code here
}
For me personally, I would make a Coroutine method with delay that will have a for loop inside, rather than doing this in update.
Sample:
private IEnumerator SpawnObjects(int spawnCount)
{
for(int i = 0; i < spawnCount; i++)
{
//Instantiate here
yield return new WaitForSeconds(5);
}
}
To call the method:
StartCoroutine(SpawnObjects(10));
I am working on a flocking system in Unity and am new to c#. I am working with 2 scripts - 1 that manages the overall flock (FlockTest) and the other that manages particle behaviour (FlockParticleBehaviour). I have followed a tutorial which has public boolean values that control seeking behaviour in FlockParticleBehaviour through FlockTest. In play mode, I can toggle these booleans to change the goal seeking behaviour. However, I want to automate this toggling based on time (To add it to an AR session). I have added an if statement to void Update() in the FlockTest and when I hit play, the seekGoal and obedient boolean boxes switch on and off but nothing happens to the particles. I have tried using an invoke method which didn't work(no errors but boxes dont switch on and off) and thought about trying a coRoutine but I am not sure this will work since I don't want to stop and start my script. I am at a loss as to how to get the particles obeying the boolean in update. Am I meant to be referencing in my particle behaviour script's flock function? Very new so would love some help if anyone knows a better way forward!
FlockTest script (contains if statement)
using System.Collections.Generic;
using UnityEngine;
public class FlockTest : MonoBehaviour
{
public GameObject[] particles;
public GameObject particlePrefab;
public int particleCount = 10;
public Vector3 range = new Vector3(5,5,5);
public Vector3 innerLimit = new Vector3(1,1,1);
public bool seekGoal = true;
public bool obedient = true;
public bool willful = false;
[Range(0, 200)]
public int neighbourDistance =50;
[Range(0,2)]
public float maxForce = 0.5f;
[Range(0,5)]
public float maxvelocity = 2.0f;
// Start is called before the first frame update
void Start()
{
int time = (int)Time.time;
particles = new GameObject[particleCount];
for(int i = 0; i < particleCount; i++)
{
Vector3 particlePos = new Vector3(Random.Range(-range.x, range.x), Random.Range(-range.y, range.y), Random.Range(-range.z, range.z));
particles[i] = Instantiate(particlePrefab, this.transform.position + particlePos, Quaternion.identity) as GameObject;
particles[i].GetComponent<FlockParticleBehaviour>().manager = this.gameObject;
}
}
void Update()
// the toggles in the inspector are changing but nothing is happening with the particles.
{
int time = (int)Time.time;
if(time == 3f) {
seekGoal = false;
obedient = false;
willful = true;
}
if(time == 6f)
{
seekGoal = true;
obedient = true;
willful = false;
}
}
}
FlockParticleBehaviour script
using System.Collections.Generic;
using UnityEngine;
public class FlockParticleBehaviour : MonoBehaviour
{
public GameObject manager;
public Vector3 location = Vector3.zero;
public Vector3 velocity;
Vector3 goalPos = Vector3.zero;
Vector3 currentForce; //this is a current force position. pushes particle around by adding all the other forces
// Start is called before the first frame update
void Start()
{
velocity = new Vector3(Random.Range(0.01f, 0.1f), Random.Range(0.01f, 0.1f), Random.Range(0.01f, 0.1f));
location = new Vector3(this.gameObject.transform.position.x, this.gameObject.transform.position.y, this.gameObject.transform.position.z);
}
Vector3 seek(Vector3 target)
{
return(target - location);
}
void applyForce(Vector3 f)
{
Vector3 force = new Vector3(f.x, f.y, f.z);
if(force.magnitude > manager.GetComponent<FlockTest>().maxForce)
{
force = force.normalized;
force *= manager.GetComponent<FlockTest>().maxForce;
}
this.GetComponent<Rigidbody>().AddForce(force);
if(this.GetComponent<Rigidbody>().velocity.magnitude > manager.GetComponent<FlockTest>().maxvelocity)
{
this.GetComponent<Rigidbody>().velocity = this.GetComponent<Rigidbody>().velocity.normalized;
this.GetComponent<Rigidbody>().velocity *= manager.GetComponent<FlockTest>().maxvelocity;
}
Debug.DrawRay(this.transform.position, force, Color.white);
}
Vector3 align()
{
float neighbourdist = manager.GetComponent<FlockTest>().neighbourDistance;
Vector3 sum = Vector3.zero;
int count = 0;
foreach (GameObject other in manager.GetComponent<FlockTest>().particles)
{
if(other == this.gameObject) continue;
float d = Vector3.Distance(location, other.GetComponent<FlockParticleBehaviour>().location);
if (d < neighbourdist) {
sum += other.GetComponent<FlockParticleBehaviour>().velocity;
count++;
}
}
if (count >0)
{
sum /= count;
Vector3 steer = sum - velocity;
return steer;
}
return Vector3.zero;
}
Vector3 cohesion()
{
float neighbourdist = manager.GetComponent<FlockTest>().neighbourDistance;
Vector3 sum = Vector3.zero;
int count = 0;
foreach (GameObject other in manager.GetComponent<FlockTest>().particles)
{
if(other == this.gameObject) continue;
float d = Vector3.Distance(location, other.GetComponent<FlockParticleBehaviour>().location);
if(d < neighbourdist)
{
sum += other.GetComponent<FlockParticleBehaviour>().location;
count++;
}
}
if (count > 0)
{
sum /= count;
return seek(sum);
}
return Vector3.zero;
}
void flock()
{
location = this.transform.position;
velocity = this.GetComponent<Rigidbody>().velocity;
if(manager.GetComponent<FlockTest>().obedient && Random.Range(0,50) <=1)
{
Vector3 ali = align();
Vector3 coh = cohesion();
Vector3 gl;
if(manager.GetComponent<FlockTest>().seekGoal)
{
gl = seek(goalPos);
currentForce = gl + ali +coh;
}
else
currentForce = ali + coh;
currentForce = currentForce.normalized;
}
if(manager.GetComponent<FlockTest>().willful && Random.Range(0,50)<=1)
{
if(Random.Range(0,50)<1) //change direction
currentForce = new Vector3(Random.Range(0.01f, 0.1f), Random.Range(0.01f, 0.1f),Random.Range(0.01f, 0.1f));
}
applyForce(currentForce);
}
// Update is called once per frame
void Update()
{
flock();
goalPos = manager.transform.position;
}
}
Several points:
it is much easier and cleaner to set your flock manager directly as FlockTest, not GameObject to avoid GetComponent calls.
I cannot understand what you want to achieve by calling (int)Time.time and comparing it later with 3 and 6. Time.time returns the number of seconds that passed from the start of the application. So your code in Update method of FlockTest script will not have any chance to be called after the seventh second of your game passed. So obedient will always be true and willful will always be false after the seventh second.
Your Random.Range(0, 50) <= 1 is quite a low chance. It will return an int value from 0 to 49, so it is only a 2% chance that your changes in FlockTest will apply to FlockParticleBehaviour instance. Is it what you wanted to get? You can try to remove this random from the if statement to make this chance 100% and check if this is an issue.
Right now it seems like the chance of changing something is too low to see it in several seconds of the game. As I've said above, after the seventh second your bool values will never change.
I have created a script who will spawn objects and give tag and color for one element of prefab. But script not working with clones. This 2 scripts, GameManager( he must spawn objects) and
RandomTagAndColor( he give to element of prefab tag and name). And in scene of game, where objects is spawning, script give tag and colour only to first prefab. In game those prefabs 10. Well, I'm sorry if question is stupid, this first thing, what i doing without books,guides.
GameManager script:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System;
public class GameManager : MonoBehaviour
{
// Start is called before the first frame update
public GameObject firstBarrier;
public GameObject secondBarrier;
public int numOfBarriers = 0;
System.Random rnd = new System.Random();
System.Random rndY = new System.Random();
Vector3 vector = new Vector3(5, 1/3 , 1);
void Start()
{
for (numOfBarriers = 0; numOfBarriers < 10; numOfBarriers++)
{
int ewq = rnd.Next(1, 20);
int rY = rndY.Next(1, 4);
if(ewq <= 10)
{
Instantiate(firstBarrier, vector, Quaternion.identity);
}
else
{
Instantiate(secondBarrier, vector, Quaternion.identity);
}
vector.x -= 7;
vector.y = rY;
}
}
void Update()
{
}}
RandomColorAndTag script:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System;
public class RandomColorAndTag : MonoBehaviour
{
GameObject Bar;
System.Random randomElement = new System.Random();
int el = 0 ;
void Start()
{
el = randomElement.Next(2, 6);
GameObject.Find($"Cube ({el})").GetComponent<Renderer>().material.color = Color.green;
if (GameObject.Find($"Cube ({el})").GetComponent<Renderer>().material.color == Color.green)
{
GameObject.Find($"Cube ({el})").transform.tag = "Green";
}
}
void Update()
{
}
}
As mentioned there are lot of unclear issues in your codes.
I will assume the RandomColorAndTag is attached to the objects you spawn. In that case don't use Find but rather simply assign the random color and tag to yourself
what is a random value between 1 and 19 good for, of all you ever do with it is check whether it is bigger or smaller than 10? => Simply use only a random 0 or 1 and check whether it is 0 ;)
I'm unsure exactly what items should be green now .. I assume among the 10 spawned items you want to pick one and make it special..
finally I wouldn't even let the objects assign their own tag and color but let the GameManager trigger it
So I would do something like
public class GameManager : MonoBehaviour
{
public TagAndColorController fortBarrier;
public TagAndColorController secondBarrier;
public int numOfBarriers = 10;
public Vector3 vector = new Vector3(5, 1f/3f , 1);
public Color specialColor = Color.Green;
public string specialTag = "Green";
private void Start()
{
var specialIndex = Random.Range(0, numberOfBarriers);
for (var i = 0; i < numOfBarriers; i++)
{
// upper bound is exclusive -> this returns 0 or 1
var rndBarrier = Random.Range(0, 2);
// not sure if intended but again: upper bound is exclusive -> this return 1, 2, or 3
var rY = Random.Range(1, 4);
var tagAndColor = Instantiate(rndBarrier == 0 ? firstBarrier : secondBarrier, vector, Quaternion.identity);
if(i == numberOfBarriers)
{
tagAndColor.SetColorAndTag(specialColor, specialTag);
}
vector.x -= 7;
vector.y = rY;
}
}
}
And the other script has no logic whatsoever but rather only is a quick access to the objects renderer and bundles the behavior
public class TagAndColorController : MonoBehaviour
{
[SerializeField] private Renderer _renderer;
public void SetColorAndTag(Color newColor, string newTag)
{
gameObject.tag = newTag;
if(!_renderer) _renderer = GetComponent<Renderer>();
_renderer.material.color = newColor;
}
}
I am working through the unity tutorials and I was wondering how to stop a timer when you hit 12 of count.
I currently have two scripts I am using.
PlayerController
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.InputSystem;
using TMPro;
public class PlayerController : MonoBehaviour
{
public float speed;
public TextMeshProUGUI countText;
public GameObject winTextObject;
private float movementX;
private float movementY;
private Rigidbody rb;
private int count;
// At the start of the game..
void Start()
{
rb = GetComponent<Rigidbody>();
count = 0;
SetCountText();
winTextObject.SetActive(false);
}
void FixedUpdate()
{
// Create a Vector3 variable, and assign X and Z to feature the horizontal and vertical float variables above
Vector3 movement = new Vector3(movementX, 0.0f, movementY);
rb.AddForce(movement * speed);
}
void OnTriggerEnter(Collider other)
{
// ..and if the GameObject you intersect has the tag 'Pick Up' assigned to it..
if (other.gameObject.CompareTag("Pickup"))
{
other.gameObject.SetActive(false);
// Add one to the score variable 'count'
count = count + 1;
// Run the 'SetCountText()' function (see below)
SetCountText();
}
if (other.gameObject.CompareTag("SpeedPickup"))
{
other.gameObject.SetActive(false);
speed = speed + 25;
count = count + 1;
}
}
void OnMove(InputValue value)
{
Vector2 v = value.Get<Vector2>();
movementX = v.x;
movementY = v.y;
}
void SetCountText()
{
countText.text = "Count: " + count.ToString();
if (count >= 12)
{
winTextObject.SetActive(true);
}
}
}
And a second script Timer.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using TMPro;
public class Timer : MonoBehaviour
{
[SerializeField]
private TextMeshProUGUI timerUILabel; //drag UI Text object here via Inspector
private float t_offset = 0f; // set to nothing, can be set to an offset if needed.
private int t_minutes;
private int t_seconds;
private int t_milliseconds;
private bool finished = false;
private void Update()
{
float t = Time.time - t_offset;
//Debug.Log("currentTime in seconds = " + t);
t_minutes = ((int)t / 60); // t(seconds) / 60 = total minutes
t_seconds = ((int)t % 60); // t(seconds) % 60 = remaining seconds
t_milliseconds = ((int)(t * 100)) % 100; // (total seconds * 1000) % 1000 = remaining milliseconds
//display the text in a 00:00:00 format
timerUILabel.text = string.Format("{0:00}:{1:00}:{2:00}", t_minutes, t_seconds, t_milliseconds);
}
}
My questions are
Do I need to import the Timer.cs script into the PlayerController to get this functionality to work? If so how? (is it as simple as using a using statement?)
I am thinking I need to put in an additional conditional to change the win condition from false to true. Is that the right path?
Thanks for all your help!
Why not use a coroutine?
private int _time;
private Coroutine _cr;
void Start ()
{
//Call this to start it and save coroutine to a variable
_cr = StartCoroutine(Timer());
//Then to stop it call:
//StopCoroutine(_cr);
//And read the value of _time to get seconds since start.
}
private IEnumerator Timer ()
{
yield return new WaitForSeconds(1);
_time++;
_cr = StartCoroutine(Timer());
}
I have the SpawnScript (Below)
I want to create a function within the SPAWN, so I can put the minimum and maximum delay time that an object can be created by the inspector.
using System.Collections;
using System.Collections.Generic;
public class SpawnController : MonoBehaviour
{
public float maxWidth;
public float minWidth;
public float minTime;
public float maxTime;
public float rateSpawn;
private float currentRateSpawn;
public GameObject tubePrefab;
public int maxSpawnTubes;
public List<GameObject> tubes;
// Use this for initialization
void Start ()
{
for (int i = 0; i < maxSpawnTubes; i++) {
GameObject tempTube = Instantiate (tubePrefab) as GameObject;
tubes.Add (tempTube);
tempTube.SetActive (false);
}
currentRateSpawn = rateSpawn;
}
// Update is called once per frame
void Update ()
{
currentRateSpawn += Time.deltaTime;
if (currentRateSpawn > rateSpawn) {
currentRateSpawn = 0;
Spawn ();
}
}
private void Spawn ()
{
float randWitdh = Random.Range (minWidth, maxWidth);
GameObject tempTube = null;
for (int i = 0; i < maxSpawnTubes; i++) {
if (tubes [i].activeSelf == false) {
tempTube = tubes [i];
break;
}
}
if (tempTube != null)
tempTube.transform.position = new Vector3 (randWitdh, transform.position.y, transform.position.z);
tempTube.SetActive (true);
}
}
you could use Time.realtimeSinceStartup for timestamps - this kinda would fit the way you do it atm.
or you use coroutines which is very unity and one better learns to use them early than late.
or you use invoke which probably is the shortest way of doing it.
http://docs.unity3d.com/ScriptReference/Time-realtimeSinceStartup.html http://docs.unity3d.com/Manual/Coroutines.html
http://docs.unity3d.com/ScriptReference/MonoBehaviour.Invoke.html
edit:
well actually you also could just rateSpawn = Random.Range(minTime, maxTime); inside the if statement in update, this would fit your current approach most.
InvokeRepeating Method is the way to deal with code repetation. You can call your Spawn method with invoke repeating inside of Start event and specify time according to your choice as invoke repeating define:
public void InvokeRepeating(string methodName, float time, float repeatRate);
Invokes the method methodName in time seconds, then repeatedly every
repeatRate seconds.
something like this edit require in your script:
void Start(){
InvokeRepeating("Spawn",2, 0.3F);
}