I am working on a AR project, where the virtual objects will be shown/hide in the scene based on information found in a text file. The text files will be updated from an external service. So I need to read the file on a frequent interval and update the scene. As a result I only have the Camera object and I am rendering the scene in OnPreCull() method.
The text files contain many objects but not all the objects are within the scene at any instance of time. I was looking for a way to render only those objects that are within the scene.
Will creating and placing the gameobjects in the OnPreCull() method crate any performance issue?
Will creating and placing the gameobjects in the OnPreCull() method crate any performance issue?
Yes absolutely ... so would it if you do it in Update or any other repeatedly called method.
Instead you should rather Instantiate objects in Awake and only activate or deactivate them.
Let's say you have 3 objects A, B and C than I would make a kind of controller class that looks like
public class ObjectsController : MonoBehaviour
{
// Define in which intervals the file should be read/ the scene should be updated
public float updateInterval;
// Prefabs or simply objects that are already in the Scene
public GameObject A;
public GameObject B;
public GameObject C;
/* Etc ... */
// Here you map the names from your textile to according object in the scene
private Dictionary<string, GameObject> gameObjects = new Dictionary<string, gameObjects>();
private void Awake ()
{
// if you use Prefabs than instantiate your objects here; otherwise you can skip this step
var a = Instantiate(A);
/* Etc... */
// Fill the dictionary
gameObjects.Add(nameOfAInFile, a);
// OR if you use already instantiated references instead
gameObjects.Add(nameOfAInFile, A);
}
}
private void Start()
{
// Start the file reader
StartCoroutine (ReadFileRepeatedly());
}
// Read file in intervals
private IEnumerator ReadFileRepeatedly ()
{
while(true)
{
//ToDo Here read the file
//Maybe even asynchronous?
// while(!xy.done) yield return null;
// Now it depends how your textile works but you can run through
// the dictionary and decide for each object if you want to show or hide it
foreach(var kvp in gameObjects)
{
bool active = someConditionDependingOnTheFile;
kvp.value.SetActive(active);
// And e.g. position it only if active
if (active)
{
kvp.value.transform.position = positionFromFile;
}
}
// Wait for updateInterval and repeat
yield return new WaitForSeconds (updateInterval);
}
}
If you have multiple instances of the same prefab you also should have a look at Object Pooling
I'd recommend adding each of the game objects to a registry and the switching them on or off (dis/enable SetActive) via the registry class's Update() cycle.
One Update() process to retrieve and handle the server file, another Update() process to dis/enable objects. Might sound oversimplified however it's the fastest way I think of getting the result.
Good Luck!
Related
I'm trying to both align my object with the surrounding objects and have the object try and get the average position of it's surrounding objects,I am trying to do this by creating a list of all the objects that enter a sphere and using there transform to do the calculations however unity only accepts lists of colliders. I would really appreciate some help or advice, further info is that this script is on 200 clones of the same gameobject and the script below gives the error
Cannot implicitly convert type 'UnityEngine.Collider[]' to 'System.Collections.Generic.List'
IEnumerator Flock()
{
Collider[] NearbyBoids = Physics.OverlapSphere(BoidVec, VisionRange, BoidMask, QueryTriggerInteraction.Collide);
foreach (Collider Boid in NearbyBoids)
{
List<Transform> context = NearbyBoids;
}
yield return null;
}
To reduce the number of iterations your Boids have to make, it might make sense to just have them keep a context list of other Boids that enter or leave a trigger volume you set up in the inspector as an "area of sight". Then you can have each Boid evaluate it's own up to date context in Update.
Something along the lines of:
using System.Collections.Generic;
using UnityEngine;
public class Boid : MonoBehaviour
{
private List<Transform> context = new List<Transform>();
private void OnTriggerEnter(Collider other)
{
// good if you want to call the other Boid component
Boid boid = other.gameObject.GetComponent<Boid>();
if (boid != null)
{
context.Add(other.transform);
}
}
private void OnTriggerExit(Collider other)
{
// Pretty efficient, requires tagging of boid objects
if (other.CompareTag("boidTag"))
{
context.Remove(other.transform);
}
}
private void Update()
{
foreach(Transform otherBoid in context)
{
// doing some stuff here based on boids within context
}
}
}
Two ways to go about this.
Quick way is to use LINQ. To do this you need the using namespace declaration
using System.Linq;
Collider[] arrayOfNearbyTransforms = Physics.OverlapSphere(BoidVec, VisionRange, BoidMask, QueryTriggerInteraction.Collide);
List<Transform> listOfAllNearbyTransforms = arrayOfNearbyTransforms.Select(x => x.transform).ToList();
The issue with the code you've posted is that you're creating a list within your loop. Variables declared within a loop only exist within that loop's execution, so you're essentially creating as many lists as there are colliders, while using none of them.
Instead, you should create the list outside your loop, and add transform components from within the loop
void Flock()
{
// This is the array of colliders you've gathered from nearby objects
Collider[] NearbyBoids = Physics.OverlapSphere(BoidVec, VisionRange, BoidMask, QueryTriggerInteraction.Collide);
// This is a brand new list. It's empty at the moment
List<Transform> listOfAllNearbyTransforms = new List<Transform>();
// We're looping through every collider in the array.
foreach (Collider currentColliderReference in NearbyBoids)
{
// Everything that happens in here happens once for every collider.
// The variable currentColliderReference refers to the collider we're looking at during this part of the loop. So throughout the loops execution, it will change to refer to every collider in your array, one at a time.
// We get a reference to the current collider's transform component
Transform transformOfCurrentCollider = currentColliderReference.transform;
// We add that to the list of transform component
listOfAllNearbyTransforms.Add(transformOfCurrentCollider);
}
// At this point listOfAllNearbyTransforms will be a list of all transforms within the area specified in your OverlapSphere() call
}
As you can tell, I've also changed the return type to void. There's no reason for this function to be a coroutine
While I wouldnt do this as a CoRoutine at this stage, Ive kept your code as similar as possible. Unless you really imagine a lot of units 1000's the longest part of the code to run is the overlapsphwere.
List<Transform> context = new List<Transform>();
IEnumerator Flock()
{
context.Clear(); // if there are 1000s this could be costly
Collider[] NearbyBoids = Physics.OverlapSphere(BoidVec, VisionRange, BoidMask, QueryTriggerInteraction.Collide);
foreach (Collider Boid in NearbyBoids)
{
context.Add(NearbyBoids.transform);
}
yield return null;
}
this gives you a class level list of context, which you can access from elsewhere.
Then your context list will have current transforms, but I wouldnt want to run this too often, personally Id use a list and triggers... So, add to list onenter, and remove from list onleave..
I have a prefab object called Beam, which contains several things but one is an object that when an instance of it is collided with and triggered, should destroy itself.
Currently, I have the script that generates all of the instances on a variable called Beams. Shown here:
When that runs, it creates clones within it. Seen here:
You will also see in the last image, the Beam prefab that contains the Cookie in it. That cookie is where I have a script that says, if I hit it, destroy. That code looks like this:
...
public class Collectibles : MonoBehaviour
{
GameManager game;
// Start is called before the first frame update
void Start()
{
game = FindObjectOfType<GameManager> ();
}
...
void OnTriggerEnter2D(Collider2D other) {
if(other.tag == "Player"){
string coinType = "Cookie";
game.AddCollectible(coinType);
Destroy(gameObject);
}
}
}
Currently, when I run into a cookie, it runs Destroy(gameObject) and destroys ALL instances of the cookie (one per each Clone).
This code lives on Cookie, not on Beams. Is that correct? Should I have the code somewhere else? I also tried Destroy(this) but that doesn't do what I thought it would do (just the instance).
Is it possible that from where I was calling Destroy, the script doesn't have access to the instances, or am I missing something? Thank you in advance!
If I understand your question, you want that when the "player" collides with an instance of "beam" only destroy the instance of Cookie (or the gameobject that contains the script), in this case it would do so with the tag:
public GameObject[] arrayofcookie;
public int destroyedinstances=1;
//this int will tell how many instances you want to be destroyed (from the last instantiated to the first)
//for this example the last instance will be deleted
public void destroyCookie()
{
arrayofcookie= GameObject.FindGameObjectsWithTag("Cookie");
for (int i = 0; i < destroyedinstances; i++)
{
Destroy(arrayofcookie[i].gameObject);
}
}
You call this method in the cookie script, in the collider or if you prefer with an invoke method after N seconds.
I do not think I have understood your question very well, but in these problems I prefer to use the label and it also depends on the nature of your game
Using Unity, I'm working on a game where all Gameobjects with a certain tag vanish/reappear fairly regularly (every 10 seconds on average). I use GameObject.FindGameObjectsWithTag() to create a Gameobject[] through which I enumerate every time that the objects need to be made visible/invisible. I cannot call it once, on Start, as new Gameobjects are created while playing. I thought that it would be worse to access and change the Gameobject[] every time something got created/destroyed. Is there a better way to handle this. I know how bad of an impact on performance the GameObject.Find methods make...
Yes, there is a better way to do this. Have a script with List variable that can store GameObjects. Making it a singleton is better but not necessary.
This singleton script should be attached to an empty GameObject:
public class GameObjectManager : MonoBehaviour
{
//Holds instance of GameObjectManager
public static GameObjectManager instance = null;
public List<GameObject> allObjects = new List<GameObject>();
void Awake()
{
//If this script does not exit already, use this current instance
if (instance == null)
instance = this;
//If this script already exit, DESTROY this current instance
else if (instance != this)
Destroy(gameObject);
}
}
Now, write a script that registers itself to the List in the GameObjectManager script. It should register itself in the Awake function and un-register itself in the OnDestroy function.
This script must be attached to each prefab you want to add to the List. Simply do that from the Editor:
public class GameObjectAutoAdd : MonoBehaviour
{
void Awake()
{
//Add this GameObject to List when it is created
GameObjectManager.instance.allObjects.Add(gameObject);
}
void OnDestroy()
{
//Remove this GameObject from the List when it is about to be destroyed
GameObjectManager.instance.allObjects.Remove(gameObject);
}
}
If the GameObjects are not prefabs but GameObjects created through code, simply attach the GameObjectAutoAdd script to them once they are created:
GameObject obj = new GameObject("Player");
obj.AddComponent<GameObjectAutoAdd>();
You can now access your GameObjects in the List with GameObjectManager.instance.allObjects[n]; where n is the index number and you don't have to use any of the Find functions to find the GameObject anymore.
It's true that calling to GameObject.Find consumes a lot of recourses.
The point should be to save the result and use it always but I understand that you can't do that.
Other option is to have all this gameobjects as a child of one gameobject let's call it handlerGameobject. It will have a script that doesn't use GameObeject.Find it could getChild or transform.Find that uses less resources.
Hope it helps!
Thanks for help in advance. Here is a short snippet of the code that I am having an issue with.
GameObject[] allMotor_array;
public List<GameObject> BrokenMotor_list = new List<GameObject>();
void Start()
{
allMotor_array = GameObject.FindGameObjectsWithTag ("Motors");
}
void Update()
{
foreach (GameObject motor in allMotor_array)
{
if(motor.GetComponent<Pump_event>().damaged)
{
BrokenMotor_list.Add(motor);
}
}
}
I have an array of Gameobjects that is created on Start, each of the gameobjects in the array have a script called Pump_event. What I want to do is add the gameobject with a true boolean (damaged) to the list so that I can create a GUI list of all the motors that are damaged (and then take further action on those motors).
With the current code it instantiates the array fine, but when One of the motors boolean changes to true the list tends to continuously add the motor gameobject to the list on each update cycle. So what I want is to figure out a way of adding the gameobject to the list ONCE.
Having it in the update() is probably not the best method but I really am stuck on how to approach this.
G
The Solution to my problem
Thanks for your answers, you all had well thought out responses. I appreciate it. I didn't go with 1 persons method but instead adapted logical approaches found here to work with my script/s.
Here is what I did.
In my pump_event script the events are sorted in a Case and switch as damage increased on the pump the event would escalate. So I added in a section to that script to include "reporting" the damage.
public class Pump_event : MonoBehaviour
//The damage has taken place and event_category=0\\
switch (event_category)
{
case 0:
Master_script.GetComponent<Control_room>().AddtoList (gameObject);
event_category = 1;
break;
I took advice not to insert these types of programing and placed it into its separate class which works out well.
public class Master_script: MonoBehaviour
public void AddtoList(GameObject motor_tobadded)
{
BrokenMotor_list.Add(motor_tobadded);
}
This also eliminated the need on having an array holding all of the pump event controllers as well.
Now the script all works fine. It may not be most efficient but it is doing its job.
Thank you again to all that helped.
In your Pump_event Script you can have a event Action which you register in this snippet and whenever damaged is set true you need to fire the event.
Example:
// in Pump_event Class
public static event Action<GameObject> OnDamagedValueChanged;
private bool _damaged;
public bool Damaged
{
get { return _damaged;}
set
{
_damaged = value;
if(_damaged)
{
if(OnDamagedValueChanged != null)
OnDamagedValueChanged(gameObject);
}
}
}
In your Current Class where you have array of GameObjects:
void OnEnable()
{
Pump_event.OnDamagedValueChanged += HandleOnDamagedValueChanged;
}
void OnDisable()
{
Pump_event.OnDamagedValueChanged -= HandleOnDamagedValueChanged;
}
void HandleOnDamagedValueChanged(GameObject obj)
{
if (!BrokenMotor_list.Contains (obj))
{
BrokenMotor_list.Add (obj);
}
}
Using Actions is a better approach than doing it in Update Method. It is not good for performance to keep checking for a bool in iteration in update method. and try to avoid GetComponent and Find/FindObjectWithTag Methods in Update. It is not good practice. I hope this is helpful.
According to the code you have posted, the problem lies within the fact that the damaged property is never reset. One solution would be to reset this property once you add it to the list, like so:
if(motor.GetComponent<Pump_event>().damaged)
{
motor.GetComponent<Pump_event>().damaged = false;
BrokenMotor_list.Add(motor);
}
However, multiple copies of the same object could still be added to your list if the motor is damaged again.
To go around this, you could use a HashSet. The hash set will allow only one copy of an object to exist within it, thus, if an object is already present is will not be added again.
The catch is that you will need to override the GetHashCode and Equals methods for your GameObject class since these will be used internally by the hash set to place items within itself and identify duplicates.
check if list already contains motor.
if(motor.GetComponent<Pump_event>().damaged)
{
if(BrokenMotor_list.Contains(motor))
{
BrokenMotor_list.Add(motor);
}
}
although on msdn describes how to implement IEquatable in case if you want compare different objects(with different references) https://msdn.microsoft.com/ru-ru/library/vstudio/bhkz42b3%28v=vs.100%29.aspx
if (!BrokenMotor_list.Contains (motor)) {
BrokenMotor_list.Add (motor);
}
You'd better do this after damage event occur by add a delegate.
I have setup a simple shop where when the player clicks to 'BUY' a character, the item gets unlocked and stays saved as unlocked. This is in one scene (Shop scene).
I have a second scene where the character gets unlocked based on this purchase for the player to be able to select (character scene).
I am placing the scripts on empty gameObjects on each scene. It works fine when it is just buying one character. But how do I replicate this for multiple characters.
I could place the scripts on individual buttons and place corresponding gameobjects under the inspector but this would not be practical if I have like a 100 characters.
Please advice how I could make this work across multiple instances. Thought of tagging and that too doesn't seem feasible. I am open for suggestions if there is a better way of doing this. Thanks.
//Class controlling Shop Scene via an empty Gameobject
public class ShopManager : MonoBehaviour
{
private bool unlocked;
public GameObject greyImg;
void Start()
{
unlocked = PlayerPrefs.GetInt("unlocked") == 1;
greyImg.SetActive(unlocked);
}
public void Buy()
{
unlocked = true;
PlayerPrefs.SetInt("unlocked", 1);
PlayerPrefs.Save();
greyImg.SetActive(true);
}
}
This is how the unity setup looks for shop scene. If the item is already bought, the grey image is set to active thus not allowing the user to click the green buy button any more.
When character is unlocked/bought
//Class controlling Character select scene via an empty Gameobject
public class CharacterManager : MonoBehaviour
{
private bool unlocked;
public GameObject greySelect;
void Start()
{
unlocked = PlayerPrefs.GetInt("unlocked") == 1;
}
void Update()
{
if (unlocked)
{
greySelect.SetActive(false);
}
}
}
This is how Unity setup looks for Character select scene. If the character is already unlocked, the grey select image is set to inactive and the orange select button will be visible thus allowing the character to be selected.
when character unlocked
There are probably many ways in which to tackle this problem. Here's one;
You're going to need a separate player prefs entry for each character. So, you'll need a nice way to keep track of the characters you have and their unlock state. Instead of saving 'unlocked' in player prefs, why not create a class that contains unlocked information?
class UnlockedCharacters
{
bool characterAUnlocked = false;
bool characterBUnlocked = false;
bool characterCUnlocked = true;
}
You can then serialize this whole class and save the whole class inside player prefs. Then, when you load your game you can load this class from player prefs to populate your character information. This way, data is managed and is consistent across saved states.
You could go one step further and keep everything relating to your characters inside of a Dictionary whereby your int is an enum referring to a character and the bool is its unlock state. You can then save/load this dictionary again using player prefs.
Inside the class you could have helper methods with your generic gameobject scripts call to keep things nice and encapsulated.
This way, your individual GameObjects which handle specific characters can hold a reference to this enum and you can use your general script to set/modify your data contents based on your enum field that you can then set via your inspector, or in initialisation code for your object.
EDIT FOR COMMENT BELOW - AN EXAMPLE:
Your generic character controller would go on the individual objects, and by changing your CHAR_TYPE the same script will work to unlock multiple characters.
class CharacterManager
{
public enum CHAR_TYPE = { characterA, characterB, characterC }
private Dictionary<CHAR_TYPE, bool> characterUnlockState;
void Start()
{
// Seeding with some data for example purposes
characterUnlockState = new Dictionary<CHAR_TYPE, bool>();
characterUnlockState.Add(CHAR_TYPE.characterA, false);
characterUnlockState.Add(CHAR_TYPE.characterB, false);
characterUnlockState.Add(CHAR_TYPE.characterC, true);
}
public bool IsCharacterUnlocked(CHAR_TYPE character)
{
if (characterUnlockState.Contains(character)) return characterUnlockState[character];
return false;
}
public void UnlockCharacter(CHAR_TYPE character)
{
if (characterUnlockState.Contains(character)) characterUnlockState[character] = true;
}
}
class GenericCharacterController
{
public CHAR_TYPE character;
public CharacterManager manager;
public void UnlockButtonPressed()
{
manager.UnlockCharacter(character);
}
}
There are a number of ways to approach this. You are going to need an ID of some kind for each item in your store. So I would make a StoreItem script that you place on each thing for sale. The StoreItem script would need an ItemID property at the minimum. Then your StoreManager could check if it's unlocked like this:
PlayerPrefs.GetInt(selectedStoreItem.ItemID + "_unlocked");
Also, while this approach would work, it's not recommended. It's possible for playerprefs to be manipulated by the user outside of your game. So they could potentially give themselves free items. I don't know how important that is to you. The better approach would be store this info on a server somewhere and have the game sync up with that to determine what items the player owns.