Removing excess objects from a dynamically changing list of objects - c#

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

Related

Optimizing GameObject unique ID assigner Unity

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.

Unity - unable to list all scenes in a project

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

Scene not changing when the game is exported, even though it does in the engine

Using unity for a small video game, changing levels works in the engine completely fine, but when I export it to .exe it just doesn't work. The game itself works fine in .exe, only level changing doesn't work
public KeyCode nextLevelKey;
if(Input.GetKeyDown(nextLevelKey) && gameOver == true)
{
SceneManager.LoadScene(nextLevel.name);
Debug.Log("loaded next level");
}
Any ideas on why it doesn't work when exported?
I have included every scene in the right order when making the build so it's not that.
Ok so i took a look at unity answers and found this:
https://answers.unity.com/questions/139598/setting-variable-as-type-quotscenequot.html
and also this:
https://answers.unity.com/questions/205742/adding-scene-to-build.html
Editor Way:
What people have tried in 2nd link i guess is with custom editor scripts !That you can keep your Object nextLevel field also add string nextLevelName;
And then using OnValidate() or some other Editor event you can set nextLevelName value whenever nextLevel 's value changes and use SceneManager.Load(nextLevelName) instead.
Kinda Easier Way : Try converting that field to string it's a pain to update scene names twice (or maybe more) but fixes things anyways.
To Smooth things a bit you can try sth like this , though I'm not sure what you're trying to achieve with having nextLevel as variable but you can try:
SceneManager.LoadScene(SceneManager.GetActiveScene().buildIndex +1)
Or you can have a Scenes class that provides a way of dealing with your nextLevel problem. Maybe with tokens? Maybe sth else.
public class Scenes
{
public static string[,] scenes = new string[,]{
{"first_level", "scene-1-name"},
{"level_2", "scene-2-name"},
{"level_3", "factory-level"},
{"level_4", "scene-4-name"},
{"level_5", "jungle"},
};
public static string GetSceneNameByToken(string token)
{
for (var i = 0; i < scenes.GetLength(0); i++)
if (scenes[i, 0] == token)
return scenes[i, 1];
return null;
}
}
I Personally like the 2nd approach cos first one is still kinda unreliable imo (specially if you were to change unity version in middle of project or for next projects) maybe that's cos I've not worked with Editor Events much idk.
Ok, so I figured it out. Instead of making the nextLevel variable an Object I made it a string, and then in the object (that the code for changing levels is attached to) I entered the level name.

Why does terminating a for loop cause the NavMeshAgent to start moving faster?

This code worked fine and everything but it seemed to pause once the trees were staring to get into their 1000's of instances. The navmesh agent would just wait at it's current position and do nothing until a new batch of trees would placed in and then just move to one and pause again. But once I had it terminate the for loop it would continuously move from tree to tree as desired. Why is this? I thought it updated the frame once the OnCollision code was resolved. Is it because there were too many things in the list for it to sort?
Code:
void OnCollisionEnter(Collision collision)
{
GameObject data = GameObject.Find("Data");
List<GameObject> treeLists = data.GetComponent<DataStorage>().treeList;
for (removeArea = 0; treeLists.Count > removeArea; removeArea++)
{
if (treeLists[removeArea] != null)
if (collision.gameObject.name == treeLists[removeArea].name)
{
GameObject.Destroy(collision.gameObject);
treeLists[removeArea] = null;
removeArea = treeLists.Count; //Added this
}
}
}
removeArea was supposed to be for something else which I decided to scrap. It isn't used anywhere else in the code.
Is there any way to make it process the for loop before rendering the next frame?
If you have a lot of work to be done that can't all fit into a single update, use a coroutine. But in your case, you may only need to improve your algorithm.
Since you only provided OnCollisionEnter, I can only help improve this method and you will be responsible for modifying the rest of your code to test this.
First change treeList's type from a List to a HashSet in DataStorage. Sets are great when you want fast insert, delete, and contains. More information on sets. With that, you will no longer need to loop through the whole collection to find who collided. Removing the loop also allows you to remove the GameObject right from the set too. This is necessary since you can't have duplicates in a set (where you originally set the gameObject reference to null). Your new OnCollisionEnter is now:
void OnCollisionEnter(Collision collision) {
GameObject data = GameObject.Find("Data");
var treeList = data.GetComponent<DataStorage>().treeList;
// compare references
if (treeList.Contains(collision.gameObject)) {
treeList.Remove(collision.gameObject);
GameObject.Destroy(collision.gameObject);
}
}
I hope this helps and starts you on the right path.

Farseer 3.3 Checking if a non enabled body will collide if it were to be enabled (spawning)

I am using Farseer 3.3 and XNA.
I have a problem that i just cant solve in a nice way.
I have a situation where there is a world with bodys in it all working away doing there thing.
I then have an object thats body is set to not enabled, is there a nice way of checking if the body is currently colliding with anything? i.e. to know if it would be a clean spawn should I make it enabled and set it to dynamic?
Thanks in advance.
For anyone else finding this post while trying to solve the same kind of problem here is how I did it in the end.
In this example I use the word Item to represent the class that contains a body etc, this could be your player / bullet / whatever.
In your Item class subscribe to the bodies on collision and on separation events.
this.Body.OnCollision += Body_OnCollision;
this.Body.OnSeparation += Body_OnSeperation;
then setup a member for the item to hold a count of collisions and separations.
private int _canBePlacedCounter = 0;
On the event handler methods, increase of decrease the member count. In the code below there are extra conditions as for myself, I only want to perform these operations when an item is being placed into the "world".
private bool Body_OnCollision(Fixture fixturea, Fixture fixtureb, Contact contact)
{
if(this.IsBeingPlaced)
{
_canBePlacedCounter++;
}
return true;
}
private void Body_OnSeperation(Fixture fixturea, Fixture fixtureb)
{
if (this.IsBeingPlaced)
{
_canBePlacedCounter--;
}
}
We then can setup a simple public property (this really should be a method if we want to get into coding standards but this is not the time or place)
public bool CanBeDropped
{
get
{
if (_canBePlacedCounter == 0) return true;
else return false;
}
}
The reason for this implementation is, I originally had a bool which I set to true or false whenever I got one of the events. The problem is... If your item collides with item A, then collides with item B then leaves item A you get a reading of not colliding. So... using a counter like this we can count in and count out all of the collisions and separations. Belive me this works like an absolute charm.
Hope it is of use to someone out there.
UPDATE: I have found that this only works well with basic rectangles. If you get into bodies with many polygons such as those automatically generated from images its very unreliable as for whatever reason farseer regularly raises less separation events than it does collision events meaning the counter often does not return to 0.

Categories

Resources