Recently I was working on the door system for my multiplayer game, and I had a problem with door ID (that specifies which door to interact with). I wanted to avoid assigning IDs manually, due to my game has a lot of doors, so I have found a solution - instead of unreliable FindObjectsOfType, I have build following scene looper:
void IterateThroughScene(Scene scene)
{
SortedList<string, NetworkedObject> sortedObjects = new SortedList<string, NetworkedObject>();
//List<CrossSceneAudioSyncListener> audioListeners = new List<CrossSceneAudioSyncListener>();
foreach (GameObject sceneRootObject in scene.GetRootGameObjects())
{
foreach (NetworkedObject netObj in sceneRootObject.GetComponentsInChildren<NetworkedObject>())
{
Vector3 position = netObj.transform.position;
string transformHash = position.x.ToString("0.00") + position.y.ToString("0.00") + position.z.ToString("0.00");
transformHash = Hashing.MD5(transformHash);
sortedObjects.Add(transformHash, netObj);
//CrossSceneAudioSyncListener audioListener = netObj.GetComponent<CrossSceneAudioSyncListener>();
//if(audioListener != null) audioListeners.Add(audioListener);
}
}
//CrossSceneAudioSyncInstance.LoadObjects(audioListeners);
Dictionary<ushort, NetworkedObject> preparedObjects = new Dictionary<ushort, NetworkedObject>();
ushort lastId = _ids[scene.name];
foreach (var part in sortedObjects)
{
NetworkedObject preparedObject = part.Value;
preparedObject.GetObjectData().id = lastId;
preparedObjects[lastId] = preparedObject;
lastId++;
}
_ids[scene.name] = lastId;
_registeredObjects[scene.name] = preparedObjects;
}
Here I list step-by-step what this method is doing:
SortedList is instantiated to hold objects
2 foreach's are getting all objects in the scene
here script generates MD5 of position and adds it to SortedList
After that, Dictionary instance is created to hold final data
Next foreach is done, to loop through SortedList and assign NetworkedObject IDs in a reliable way.
This was doing a great job in small/medium scenes, but I started having serious problems with big scenes - I have a good PC & itaration takes 12 seconds, and my game's target are medium PCs.
So, Do you have any idea how can I optimize this algorithm?
As I do not know the internals of FindObjectsOfType it is hard to compare the method to your snippet. You did however mention that order matters, and as FindObjectsOfType is not guaranteed to return objects in any particular order, it would not work for your use case.
To optimize the snippet you would need to prune your search in some way. As you are looking at all root gameObjects, you can mark each root object with the number of doors that it has as children, and keep a counter for each root. That way, once you reach the number needed sent from the children, it can stop searching that root object.
However, as it seems you would just like to assign ordered ids to objects in a scene for referencing, I would advise to use an editor tool to handle the assignment. The code would be near identical but with the advantage of not needing to care about overhead during runtime as it is computed outside of the game. When implementing any editor tool that alters your scene, make sure to mark the scene as dirty to assure Unity will save the changes.
Related
In my app, I wanted to get a list of certain scenes and their build indices at start up of a particular scene. The probability is that none of the scenes has yet been loaded.
To do this I tried SceneManager.GetSceneByName() - I have the names of the relevant scenes. Nothing came back.
Looking at the docs it seemed this should work. However looking at the similar GetSceneByIndex() that does state it only works for loaded scenes, and I am assuming - thought it does not say so - the same applies to GetSceneByName().
So, I have not managed to find a way of listing all the available, but maybe not yet loaded, scenes. So far I have hard coded the relevant build indicies but I do not like that.
is there a way of doing this?
In general it makes sense that you can not get any reference to a Scene that is not loaded yet. Even using SceneManager.GetSceneByBuildIndex states
This method will return a valid Scene if a Scene has been added to the build settings at the given build index AND the Scene is loaded. If it has not been loaded yet the SceneManager cannot return a valid Scene.
What you can get however are at least all scene paths/names by using SceneUtility.GetScenePathByBuildIndex by iterating over SceneManager.sceneCountInBuildSettings
int sceneCount = SceneManager.sceneCountInBuildSettings;
string[] scenes = new string[sceneCount];
for( int i = 0; i < sceneCount; i++ )
{
scenes[i] = SceneUtility.GetScenePathByBuildIndex(i);
}
These would be the full scene paths. If you rather only want the scene name itself (but possible duplicates) you could probably instead use
scenes[i] = Path.GetFileNameWithoutExtension(SceneUtility.GetScenePathByBuildIndex(i));
I am currently working on a small project in Unity to test out some new features, primarly the new Input System and i have some trouble wrapping my head around lists. My goal with this project is to make a script that creates a small cursor for each player to use and move around with using their respective controllers.
Currently, i am creating and storing objects within a list during runtime whenever my computer detects a new Gamepad Controller. What i am having issues with is finding a method that removes excess objects that are not inside a list, i.e. when the list gets reduced in size which in this case is when a player disconnects their Gamepad Controller.
I have had similar issues in past projects whenever i had to add and remove items from a list while trying to destroy said items in the scene.
I have tried searching for such an answer on trying to achieve this effect on multiple websites (including this one) but with no prevail.
This is my current code that i am using at this moment to add players whenever a new device is added.
public GameObject cursorPrefab;
public int amountOfPlayers = 4;
List<GameObject> currentPlayers = new List<GameObject>();
private void DynamicADP(){
if(Gamepad.all.Count > amountOfPlayers) return;
for(int i = 0; i < Gamepad.all.Count; i++){
if(currentPlayers[i] != null) continue;
Cursor obj = Instantiate(cursorPrefab).GetComponent<Cursor>();
obj.PlayerDevice = Gamepad.all[i];
currentPlayer.Add(obj.gameObject);
}
}
Edit: While Kevins solution didnt quite solve the problem, it did however help me broaden my perspective on the matter which finally lead into a solution for what i desire with this piece of code! So i will accept Kevin's answer for it! Thanks Kevin!
Here is what i added, its very simple.
private void DynamicADP(){
//Previous logic from the code snipper above
if(currentPlayers.Count > Gamepad.all.Count){
Destroy(currentPlayers.Count[currentPlayers.Count - 1]);
currentPlayers.RemoveAt(currentPlayers.Count - 1);
//Whenever there is a difference in size on both lists, remove the last
//object from both in-scene and from currentPlayers list.
}
}
}
Reverse the loop to remove items from a list.
private void DynamicADP(){
if(Gamepad.all.Count > amountOfPlayers) return;
for(int i = Gamepad.all.Count-1; i >=0 ; i--){
if (somelogic) Gamepad.all.RemoveAt(i);
}
}
List is not a UnityEngine class, if you seek for List methods on unity, you'll never find. But in Microsft API documentation you can.
I never used list in Unity, but I think this way should work:
currentPlayers.RemoveAt(removed_controller_index);
See more of Lists on Microsft docs:
https://learn.microsoft.com/pt-br/dotnet/api/system.collections.generic.list-1?view=netframework-4.7.2
So I have a simple code like this:
public void OnGUI
{
if (this.myDict.Count > 0)
{
//blah
foreach (GameObject gameObject in new List<GameObject>(this.myDict.Keys))
{
//keep stuff up to date
}
}
}
onGUI is Unity's default method that updates every frame (really does not matter how often in this case when we iterate at the level of every frame or slightly lower, stop moving goalposts). And I NEED to keep data up-to-date as often, it is realtime list, but at the same time I NEED to be able to modify Dictionary (add/remove). Now, some random people told me that this foreach which allocates (?) new List every time is a massive memory leak and bad, but when asked why and how they weren't able to explain. I did some googling but result were inconclusive, I found some examples marked as "efficient" which used similar approach.
I'm greatly confused - is there a better way? Shall I define List as a global variable and use it as a temporal bin instead of defining local one each time? Would it matter though?
I'm currently working on a new game in XNA and I'm just setting basic stuff up like sprites/animations, input, game objects etc.
Meanwhile I'm trying to think of a good way to detect collisions for all the game objects, but I can't really think of a fast algorithm, which limits the game to very few objects.
This is what I did in my last project that was a school assignment
public static void UpdateCollisions()
{
//Empty the list
AllCollisions.Clear();
//Find all intersections between collision rectangles in the game
for (int a = 0; a < AllGameObjectsWithCollision.Count; a++)
{
GameObject obja = AllGameObjectsWithCollision[a];
for (int b = a; b < AllGameObjectsWithCollision.Count; b++)
{
GameObject objb = AllGameObjectsWithCollision[b];
if (obja.Mask != null & objb.Mask!= null && obja != objb && !Exclude(new Collision(obja, objb)))
{
if (obja.Mask.CollisionRectangle.Intersects(objb.Mask.CollisionRectangle))
AllCollisions.Add(new Collision(obja, objb));
}
}
}
}
So it checks for all collisions between all objects, but excludes adding collisions I found unnecessary.
To then inform my objects that they are colliding, I used a virtual method OnCollision that I called like this
//Look for collisions for this entity and if a collision is found, call the OnCollision method in this entity
var entityCol = FindCollision(entity);
if (entityCol != null)
{
if (entityCol.Other == entity)
entityCol = new Collision(entity, entityCol.Obj1);
entity.OnCollision(entityCol);
}
Collision FindCollision(GameObject obj)
{
Collision collision = AllCollisions.Find(delegate (Collision col) { return (GameObject)col.Obj1 == obj || (GameObject)col.Other == obj; });
return collision;
}
This made my game run slower fairly quickly when the amount of gameobjects increased.
The first thing that pop ups in my head is to create new threads, would that be a good idea & how would I do that in a good way?
I have studied algorithms a little bit so I know basic concepts and how ordo works.
I'm fairly new to c# and programming overall, so don't go too advanced without explaining it. I learn quick though.
There are quite a few things you can do:
The nested for loops produce a quadratic running time, which grows rather quickly as the number of objects increase. Instead, you could use some acceleration data structures (e.g. grids, kd-trees, BVHs). These allow you to reduce the intersection checks to only the entities that can potentially intersect.
If you can order your entities, you can reduce the collision checks by half by just checking entity pairs, where the second is "greater" (with respect to the order) than the first. I.e. you do not need to check b-a if you already checked a-b.
This part can be potentially slow:
!Exclude(new Collision(obja, objb)))
If Collision is a class, then this will always allocate new memory on the heap and recycle it eventually. So it might be better to make Collision a struct (if it is not already) or pass obja and objb directly to Exclude(). This also applies to your other uses of Collision. You haven't shown the implementation of Exclude() but if it is a simple linear search, this can be improved (e.g. if you search linearly in a list that contains an entry for every object, you already have a cubic running time for just the loops).
The implementation of FindCollision() is most likely a linear search (depending on what AllCollisions is) and it can get quite slow. Why are you storing the collisions anyway? Couldn't you just call OnCollision() as soon as you found the collision? A different data structure (e.g. a hash map) would be more appropriate if you want to check if a collision is associated with a specific entity efficiently.
Finally, parallelizing things is surely a viable option. However, this is somewhat involved and I would advice to focus on the basics first. If done correctly, you can reduce your cubic running time to n log n or even n.
Ok so I have this coroutine :
IEnumerator ShowCharTraits()
{
while(!hasPlayerChosen)
{
yield return null;
traitPanel.SetActive(true);
}
hasPlayerChosen = false;
traitPanel.SetActive(false);
// Debug.Log("Got called! job done");
}
It's being called like this from the awake method in my GameManager:
players = GameObject.FindGameObjectsWithTag("Player");
foreach (GameObject g in players)
{
ui_Controller.StartShowCharTraits();
g.GetComponent<PlayerToken>().isTurn = false;
}
StartShowCharTraits() is a simple method that does this :
public void StartShowCharTraits()
{
StartCoroutine("ShowCharTraits");
}
Now, I have checked the tags, no null reference exception, actually no errors or warnings are being thrown. If i load the scene in the editor and then play it everything works fine. traitPanel.SetActive(true); get called and my panel shows up. However when I load my scene from another scene using SceneManager.LoadScene(1); the above mentioned line is never reached. Any ideas why this is happening ?
Say you want to have one central place that is "like a singleton" in a Unity project. Example,
SoundEffects
LapTimer
Scores
SocialMediaConnections
All you do is this.
make your script SoundEffects.cs
recall that every Unity project must have a "preload" scene. (it's impossible to not have one)
in the preload scene, have a empty game object called "holder". make sure it is marked "DontDestroyOnLoad"
attach SoundEffects.cs to that holder
you're done.
there's just nothing more to it.
you're finished.
it's "just that simple"
So, any particular script, which happens to be attached to any particular object, in any particular scene, may need to access "SoundEffects"...
To do so, simply do this in Awake:
SoundEffects soundEffects = Object.FindObjectOfType<SoundEffects>();
then just use soundEffects anywhere in that script, for example soundEffects.PlayBoom(13) etc etc.
So in Awake()
Laps laps = Object.FindObjectOfType<Laps>();
Social social = Object.FindObjectOfType<Social>();
AI ai = Object.FindObjectOfType<AI>();
or whatever.
Unity is simple. Incredibly simple. It's just that easy.
write your script, LapTimer.cs or whatever
put it on a holder object in your preload scene (where else could it be?)
there's no "3", that's all there is. It's just that ridiculously simple.
Note that...
In the early days of Unity, someone unfortunately mentioned "singletons" on a QA board somewhere. (You can't have "singletons" in an ECS system, it's meaningless.) At the time, this led to huge discussions about how you can make a "singleton-like thingy" in Unity, which is piling confusion on confusion. Unfortunately from that day to this, you get people learning Unity, who see "singleton" mentioned here and there on the net, and it leads to endless confusion.
Once again, note that the idea of "managers" is just impossibly simple in Unity - explained above. It's trivial. It just couldn't be easier.
Note, above I mention you might want syntactic candy to avoid the incredible chore of typing Laps laps = Object.FindObjectOfType<Laps>();
There are very unfortunately no macros in Unity/c# so you need another way.
All you do is use a "Grid" script http://answers.unity3d.com/answers/1124859/view.html which has been popular for years in Unity.
But honestly, it's so simple to use Scores scores = Object.FindObjectOfType<Scores>(); that I usually just do that nowadays.
Ok how do I explain this. I have a singleton that acts as a data holder. While developing the scene with the game manager I had attached the singleton to the gamemanger object that hold a bunch of scripts. Now when I made the main menu I ofc added my singleton to it so I can carry info to the game scene and because of this my whole game manger object was being deleted. This is the culprit from the DataHolder :
void Awake()
{
if (instance == null)
instance = this;
else if (instance != this)
Destroy(gameObject);//This right here.
DontDestroyOnLoad(gameObject);
}
So I changed that line to Destroy(gameObject.GetComponent(instance.GetType()));