PUN2: No OnPhotonSerializeView call for updating array values? - c#

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();
}
}
}

Related

Logic error adding 2 variables together in Unity Engine

This sounds dumb, but I can't get 2 simple variables to add to each other, I have set points to add to 100 in the inspector. Everything would seem to work fine but when I all AddPoints from another script I Always get a debug log of 5. No matter what I set pointsToAdd to it always comes out as 5. I've tried score++; and that comes out just fine, I also tried to write a Debug.log just to make sure pointsToAdd wasn't set to something weird and it always returned 0! I don't know what in the world is happening. I must have screwed something up somehow.
private float score = 0;
public float pointsToAdd; // Setting in the editor
public void AddPoints()
{
score += pointsToAdd;
Debug.Log(score);
}
And the script that's calling AddPoints contains:
public LevelManager levelManager; // I'm setting in the editor
void OnCollisionEnter2D(Collision2D other)
{
if (other.transform.CompareTag("Wall"))
{
levelManager.AddPoints();
}
Edit: I've been testing for a few days now, with every chance I get completely unpredictable results until I set my score and Text UI to static variables, what about static variables changes the way that works?
Going to need a little more information on this one.
However I don't know how you are doing it in the other script, I am assuming its not this script causing the problem, most likely the other. I would personally do:
OtherScript:
pS.AddPoints(100f);
PointsScript
private float score;
public void AddPoints(float pointsToAdd)
{
score += pointsToAdd;
}

Cannot implicitly convert type *** to ****

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 ;)

Unity3d: Adding gameobject to List from an array

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.

Custom List .Remove

Heyo. First of all, I'm sorry if I'm asking something that's really simple. It's been holding me up for a day or two and I can't find any tutorials that cover what it is I'm trying to do.
I have a set of cubes that have to hold some game objects, along with an int that displays how important that object is and where it will appear in something called the Resolution Order. That bit is all working fine.
The CubeContents class:
public class CubeContents : IComparable<CubeContents>{
public GameObject objectType;
public int resolutionOrder;
public CubeContents (GameObject name, int importance){
objectType = name;
resolutionOrder = importance;
}
public int CompareTo(CubeContents other){
if(other == null)
return 1;
return resolutionOrder - other.resolutionOrder;
}
The method I'm using passes the specific game object to cube and uses this code to add it to the array:
public void newArrival(GameObject incoming){
int importance = discoverImportanceOfObject (incoming);
thisContents.Add (new CubeContents (incoming, importance));
thisContents.Sort ();
}
discoverImportanceOfObject is basically a long list of "else if" statements that returns a number I'm using to order these things. The problem I'm having is when I'm trying to remove this object from the array before I destroy it. This piece of code basically seems totally non responsive, but it compiles and runs just fine. No odd error messages, nothing.
public void leavingObject(GameObject leaving){
int importance = discoverImportanceOfObject (leaving);
thisContents.Remove (new CubeContents (leaving, importance)));
thisContents.TrimExcess ();
}
I'm at a total loss as to why this is. I've tried all sorts of things (IndexOf, then a RemoveAt, nulling out the entire array and then rebuilding it based on colliders...)
This just feels like it'll be a simple fix that I'm completely overlooking, but as I don't have an error message to search, or any other sort of jumping off point, I'm a bit stuck...
This object will not exist...
thisContents.Remove (new CubeContents (leaving, importance)));
Instead, loop through the thisContents collection to find the object matching the 'leaving' and 'importance'. Then remove that object.

How to find inactive objects using GameObject.Find(" ") in Unity3D?

I needed to find inactive objects in Unity3D using C#.
I have 64 objects, and whenever I click a button then it activates / inactivates objects for the corresponding button at runtime. How can I find inactive objects at this time?
Since Unity 2020
In the years since this question was asked, Unity put in the exact thing you need. At least, the exact thing I needed. Posting here for future peoples.
To find an object of a certain type whether it's on an active or inactive GameObject, you can use FindObjectsOfType<T>(true)
Objects attached to inactive GameObjects are only included if inactiveObjects is set to true.
Therefore, just use it like you regularly would, but also pass in true.
The following code requires System.Linq:
SpriteRenderer[] onlyActive = GameObject.FindObjectsOfType<SpriteRenderer>();
SpriteRenderer[] activeAndInactive = GameObject.FindObjectsOfType<SpriteRenderer>(true);
// requires "using System.Linq;"
SpriteRenderer[] onlyInactive = GameObject.FindObjectsOfType<SpriteRenderer>(true).Where(sr => !sr.gameObject.activeInHierarchy).ToArray();
The first array includes only SpriteRenderers on active GameObjects, the second includes both those on active and inactive GameObjects, and the third uses System.Linq to only include those on inactive GameObjects.
See this answers for Unity 2020 and higher.
Before Unity 2020
Well, using GameObject.Find(...) will never return any inactive objects. As the documentation states:
This function only returns active gameobjects.
Even if you could, you'd want to keep these costly calls to a minimum.
There are "tricks" to finding inactive GameObjects, such as using a Resources.FindObjectsOfTypeAll(Type type) call (though that should be used with extreme caution).
But your best bet is writing your own management code. This can be a simple class holding a list of objects that you might want to find and use at some point. You can put your object into it on first load. Or perhaps add/remove them on becoming active or inactive. Whatever your particular scenario needs.
If you have parent object (just empty object that plays role of a folder) you can find active and inactive objects like this:
this.playButton = MainMenuItems.transform.Find("PlayButton").gameObject;
MainMenuItems - is your parent object.
Please note that Find() is slow method, so consider using references to objects or organize Dictionary collections with gameobjects you need access very often
Good luck!
For newer Unity versions this answer provides probably a better solution!
First of all
In general any usage of Find or it's variants should be avoided.
Actually they are never really required but only a "hot-fix" used to cover an implementation "laziness".
Usually from the beginning storing and passing on required references is always the better approach.
Especially in your case you seem to have a fix amount of objects so you could probably already reference them all in a certain "manager" component and store them in a list or array (them you can get a reference by index) or even a Dictionary<string, GameObject> (then you can also get the according reference by name - you can find an example below).
Workarounds
There are alternative solutions (FindObjectsWithTag, FindObjectsOfType) but it will always be quite expensive (though most of the Find variants are expensive anyway).
You could e.g. also "manually" iterate through all objects in the scene using Scene.GetRootGameObjects
Returns all the root game objects in the Scene.
And then search through them until you find your object. This way you get also inactive GameObject.
public static GameObject Find(string search)
{
var scene = SceneManager.GetActiveScene();
var sceneRoots = scene.GetRootGameObjects();
GameObject result = null;
foreach(var root in sceneRoots)
{
if(root.name.Equals(search)) return root;
result = FindRecursive(root, search);
if(result) break;
}
return result;
}
private static GameObject FindRecursive(GameObject obj, string search)
{
GameObject result = null;
foreach(Transform child in obj.transform)
{
if(child.name.Equals(search)) return child.gameObject;
result = FindRecursive (child.gameObject, search);
if(result) break;
}
return result;
}
But ofcourse this should be strongly avoided and the usage of such deep searches reduced to a minimum!
What I would do
Another way - in my eyes the best approach here - could be to have a certain component attached to all your objects and actually store all the references once as said before in a dictionary like e.g.
public class FindAble : MonoBehaviour
{
private static readonly Dictionary<string, GameObject> _findAbles = new Dictionary<string, GameObject>();
public static GameObject Find(string search)
{
if(!_findAbles.ContainsKey(search)) return null;
return _findAbles[search];
}
private IEnumerator Start()
{
// Wait one frame
// This makes it possible to spawn this object and
// assign it a different name before it registers
// itself in the dictionary
yield return null;
if(_findAbles.ContainsKey(name))
{
Debug.LogError($"Another object with name /"{name}/" is already registered!", this);
yield break;
}
_findAbles.Add(name, gameObject);
}
private void OnDestroy ()
{
if(_findAbles.ContainsKey(name))
{
_findAbles.Remove(name);
}
// Optionally clean up and remove entries that are invalid
_findAbles = _findAbles.Where(kvp => kvp.Value).ToDictionary(kvp => kvp.Key, kvp => kvp.Value);
}
}
and then use it like
var obj = FindAble.Find("SomeName");
if(obj)
{
// ...
}
Also for this the component would need to be enabled at least once so Start is called.
Again an alternative would be to have instead a
public void Initialize(string newName)
{
if(_findAbles.ContainsKey(name))
{
Debug.LogError($"Another object with name /"{name}/" is already registered!", this);
return;
}
name = newName;
_findAbles.Add(name, gameObject);
}
which you could call also after e.g. spawning an inactive object.
You can use Predicates.
Just get the gameObjects and check them whith a Predicate as below:
public List<GameObject> FindInactiveGameObjects()
{
GameObject[] all = GameObject.FindObjectsOfType<GameObject> ();//Get all of them in the scene
List<GameObject> objs = new List<GameObject> ();
foreach(GameObject obj in all) //Create a list
{
objs.Add(obj);
}
Predicate inactiveFinder = new Predicate((GameObject go) => {return !go.activeInHierarchy;});//Create the Finder
List<GameObject> results = objs.FindAll (inactiveFinder);//And find inactive ones
return results;
}
and don't forget using System; using System.Collections.Generic;
You can do this at runtime by having your inactive gameobject under an active parent object as previously mentioned; slightly different from what was mentioned, this is an approach I've used for activating/deactivating menus that should be inactive by default:
canvas = GameObject.FindGameObjectWithTag("GameMenu").GetComponentInChildren<Canvas>().gameObject;
Now you can change its activeSelf to toggle it in a method/event listener of your choice:
canvas.SetActive(!canvas.activeSelf);
Even while it is inactive, you can still use the tag property of it and use it for a filter, if getting multiple components of the same type. I haven't tested this using GetComponentsInChildren, but you could probably use a 'Single' linq query, and get the object by tag name which would require creating a tag for every gameobject you want to do this to.
Although its not the correct answer, but this is what I did in my case.
1) Attach a script to (inactive) game objects and instead of setting then inactive keep it active.
2) Position them out of the scene somewhere.
3) Set a flag in the script which says inactive.
4) In Update() check for this inactive flag and skip function calls if false.
5) When needed the object, position it at the proper place and set the flag active.
It will be a bit of a performance issue but that's the only workaround I could think of so far.

Categories