I'm setting up a GameObject pool in unity and i have encountered an error.
I am trying to save an object from a Dictionary to a new gameObject.
Please bear in mind i simplified the code for the post.
public class Pool
{
public string tag;
public GameObject prefab;
public int maximumSize;
}
public List<Pool> pools;
public Dictionary<string, Queue<GameObject>> poolDictionary;
private void UpdatePool()
{
var objectPool = new Queue<GameObject>();
for (int i = 0; i < pool.maximumSize; i++)
{
GameObject obj = Instantiate(pool.prefab);
obj.SetActive(false);
objectPool.Enqueue(obj);
}
poolDictionary.Add(pool.tag, objectPool);
}
public void SpawnFromPool(string tag)
{
// the error is present here:
GameObject objectToSpawn = poolDictionary[tag];
}
If i type:
GameObject objectToSpawn = poolDictionary[tag].Dequeue();
It works just fine, i can dequeue and Enqueue but this isn't the method i want to use. I would highly appreciate any help.
Error type: Cannot implicitly convert type 'System.Collections.Generic.Queue>UnityEngine.GameObject> to 'UnityEngine.GameObject'
As already said the problem is that poolDictionary[tag] is of type Queue<GameObject> not GameObject.
If what you want is retrieve the first element in that queue without using Dequeue (which removes the element from the queue) you can simply use Peek instead
GameObject objectToSpawn = poolDictionary[tag].Peek();
This method is similar to the Dequeue method, but Peek does not modify the Queue.
To retrieve them all without removing them from the queue you can use e.g. ToArray() and run through the elements in a for or foreach loop or simply access a specific one e.g. an equal (not in terms of overhead ofcourse) call to Peek might be
GameObject objectToSpawn = poolDictionary[tag].ToArray()[0];
What I don't understand yet is why using a Queue if you don't want to Dequeue it ...
you could probably simply use a List<GameObject> than since the order of a List is also not changed (at least not without doing it actively)
If this is explicitly for object pooling (what it seems to be from the naming and spawning) you maybe should watch the Tutorial on Object Pooling .. spoiler: They use a List ;)
Related
It seems like whatever logic PUN uses to determine if an observed script's properties have changed doesn't look at the values in arrays. Changing the values of all other properties generates automatic OnPhotonSerializeView calls, but changing values in arrays does not. Is there a way to get PUN to check array contents, or am I supposed to create a meaningless bool to flip whenever I want to force an update?
NOTE: I have my PhotonView set to Unreliable On Change. I've tried byte[], which Photon seems to indicate is supported, and I've also tried bool[].
First of all: I'm really no Photon expert so I have to trust my Google power :P
In general: make sure the GameObject with your component on it is actually in the PhotonView's "Observe" list, otherwise it won't get OnPhotonSerializeView called at all.
I would guess: An array is a reference type. Photon probably does not iterate over the entire array all the time in order to track any changes but only checks whether the reference itself changed.
As alternative you can however afaik after a change simply manually send the array like e.g.
public bool[] myBools;
...
photonView.RPC("SetArrayRPC", PhotonTargets.All, (object)myBools);
...
[PunRPC]
private void SetArrayRPC(bool[] array)
{
myBools = array;
}
But afaik this should actually also do it
public class Example : Photon.MonoBehaviour
{
public bool[] myBools = new bool[5];
// Hit "Test" in the context menu in order to invert all values
// Then everywhere you should see in the Inspector the updated array
[ContextMenu(nameof(Test))]
private void Test()
{
for(var i = 0; i < myBools.Length; i++)
{
myBools[i] = !myBools[i];
}
}
public void OnPhotonSerializeView(PhotonStream stream, PhotonMessageInfo info)
{
if (stream.IsWriting)
{
// We own this player: send the others our data
stream.SendNext(myBools);
}
else
{
// Network player, receive data
myBools = (bool[])stream.ReceiveNext();
}
}
}
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 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!
During runtime, i first add some items to an array, then write their values to the log(works just fine) then i instantiate them(still works fine), then later i want to change the values of the array items, but for some reason now the array indexes returns null? Anyone who has an idea whats going wrong?
public class ListEquippedItems : MonoBehaviour
{
private static ListEquippedItems instance = null;
public static ListEquippedItems Instance
{
get
{
if (instance == null) instance = new ListEquippedItems();
return instance;
}
}
[SerializeField]
private GameObject[] currentlyEquippedItems = new GameObject[8];
void Awake()
{
// Putting fake items into the currently equipped to test that items can actually be transfered to it
for (int i = 0; i < currentlyEquippedItems.Length; i++)
{
//////////////////// In 3 below lines the array works just fine and returns proper values.
currentlyEquippedItems[i].GetComponent<ListEquippedEntry>().SetItemType((EffectTypes)i);
currentlyEquippedItems[i].GetComponent<ListEquippedEntry>().SetItemName("I am item: " + i);
currentlyEquippedItems[i].GetComponent<ListEquippedEntry>().SetItemIcon(RewardAssetContainer.Instance.commonExample);
Debug.Log(currentlyEquippedItems[i].GetComponent<ListEquippedEntry>().GetItemIcon().ToString());
Debug.Log(currentlyEquippedItems[i].GetComponent<ListEquippedEntry>().GetItemType().ToString());
Debug.Log(currentlyEquippedItems[i].GetComponent<ListEquippedEntry>().GetItemName());
}
}
public void SetEquippedItem(EffectTypes _itemType, Sprite _itemIcon, string _itemName)
{
for (int i = 0; i < currentlyEquippedItems.Length; i++)
{
Debug.Log(currentlyEquippedItems[i].GetComponent<ListEquippedEntry>().GetItemType().ToString());
Debug.Log(currentlyEquippedItems[i].GetComponent<ListEquippedEntry>().GetItemName());
Debug.Log(currentlyEquippedItems[i].GetComponent<ListEquippedEntry>().GetItemIcon().ToString());
//////////////////// In 3 below lines the array returns null references, but why?
if (currentlyEquippedItems[i].GetComponent<ListEquippedEntry>().GetItemType() == _itemType)
{
currentlyEquippedItems[i].GetComponent<ListEquippedEntry>().SetItemName(_itemName);
currentlyEquippedItems[i].GetComponent<ListEquippedEntry>().SetItemIcon(_itemIcon);
break;
}
}
}
I would ask a clarification question, but I don't have the rep to comment, so I'll just take a stab at it...
You have ListEquippedItems coded similar to a singleton, but it's not technically a singleton. My guess is that the object that SetEquippedItem() is invoked on is not the same object that Awake() is invoked on.
Ways this might happen:
You create a ListEquippedItems game object in the hierarchy. This creates one instance.
You reference ListEquippedItems.Instance. This creates another instance.
You create a ListEquippedItems using the constructor. This would created another instance.
You have 2 (or more) threads reference ListEquippedItems.Instance at the same time. With just the right timing, both threads could see instance as null, and create a new instance.
How to verify if this is what is happening:
Add the following code snippet to Awake() and SetEquippedItem() methods:
if( this != instance )
{
Debug.Log( "<methodName> invoked against non-singleton instance." );
}
How to resolve the issue:
Only create the ListEquippedItems one way. If you want to keep it in the heirarchy, store the instance in the Awake() method instead of when the Instance field is referenced.
If you want a proper singleton, check out this wiki entry from the Unity wiki. It appears there's a stackoverflow discussion on the wiki entry here.
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.