Dynamically instantiating a prefab c# based on list - c#

I am trying to populate a panel with a prefab when the player has an upgrade available however I am getting an error with my current code and in the console i am getting the following error
InvalidOperationException: Collection was modified; enumeration operation may not execute.
The following code block is causing this error
GameObject currentTable = upgradeTable[upgradeTableLevel - 1];
if(GameManager.Instance.AvailableUpgrades != null){
foreach(Upgrade upg in GameManager.Instance.AvailableUpgrades){
GameObject go = Instantiate(upgradePrefab,currentTable.transform);
go.GetComponentInChildren<Text>().text = upg.ToString() + "(level: " + GameManager.Instance.upgrades[upg] + ")";
go.GetComponentInChildren<Button>().GetComponentInChildren<Text>().text = "Buy for " + GameManager.Instance.GetNextCost(GameManager.Instance.upgrades[upg]).ToString() + " units";
GameManager.Instance.DisplayedUpgrades.Add(upg);
GameManager.Instance.AvailableUpgrades.Remove(upg);
}
}
This code is also spawning multiple of the prefab
The goal behavior was for the List "AvailableUpgrades" to be populated with upgrades when they are available which is done with this piece of code
public void UpdateAvailableUpgrades(){
foreach (KeyValuePair<Upgrade,int> cur in upgrades) {
if (units >= GetNextCost(cur.Value)) {
if (!AvailableUpgrades.Contains (cur.Key) && !DisplayedUpgrades.Contains(cur.Key)) {
AvailableUpgrades.Add (cur.Key);
}
}
}
}
Once an upgrade is available this code block is called
#region UpgradeTable
int upgradeTableLevel = GameManager.Instance.FetchUpgradeLevel(Upgrade.UpgradeTable);
upgradeTable[upgradeTableLevel - 1].SetActive(true);
foreach(GameObject go in upgradeTable){
if(go != upgradeTable[upgradeTableLevel - 1]){
go.SetActive(false);
}
}
//TODO
//Add a way to find the currently active table and add new upgrades prefab there
//TEMP SOLUTION
GameObject currentTable = upgradeTable[upgradeTableLevel - 1];
if(GameManager.Instance.AvailableUpgrades != null){
foreach(Upgrade upg in GameManager.Instance.AvailableUpgrades){
GameObject go = Instantiate(upgradePrefab,currentTable.transform);
go.GetComponentInChildren<Text>().text = upg.ToString() + "(level: " + GameManager.Instance.upgrades[upg] + ")";
go.GetComponentInChildren<Button>().GetComponentInChildren<Text>().text = "Buy for " + GameManager.Instance.GetNextCost(GameManager.Instance.upgrades[upg]).ToString() + " units";
GameManager.Instance.DisplayedUpgrades.Add(upg);
GameManager.Instance.AvailableUpgrades.Remove(upg);
}
}
#endregion
Whats happening here is im getting the table level(needed for grabbing the currently active table and other unrelated stuff) once we get down to //temp solution I am grabbing the currently active table. Then checking to see if there are any upgrades in the AvailableUgrades if so I loop through the list to instantiate a new upgrade prefab for each upgrade in the list at the end of this list the upgrade is removed from AvailableUpgrades and added to DisplayedUpgrades I added 2 list for handling this to solve the issue of it constantly spawning new prefabs but it is still spawning them
So how do I fix my script to provide the expected behavior?

The error explains clearly, the collection you are enumerating here;
foreach(Upgrade upg in GameManager.Instance.AvailableUpgrades){
...
Is then modified here, which is still inside the loop (enumeration);
GameManager.Instance.AvailableUpgrades.Remove(upg);
}
You simply need to instead maintain a list of upgrades to remove, and then remove them after the loop is finished.
So make a new list of upgrades, change the .Remove to add it to this new list instead [upgradesToRemove.Add(upg)], and then after the loop is finished you can remove all the upgrades in the new list from the GameManager.Instance.AvailableUpgrades, since it is no longer being enumerated when you are outside the loop. Ez.

As mentioned by Milney, foreach loops do not allow you to alter the enumeration being iterated for safety reasons. It is usually a good practice to avoid this sort of operation anyway since it can easily lead to nasty bugs. If you wish to do it anyway (as I have done it myself a few times), you can do it with for loops instead of foreach:
for (int i = 0; i < GameManager.Instance.AvailableUpgrades.Count; i++) {
// Do you thing
if (/*some possible condition*/) {
GameManager.Instance.DisplayedUpgrades.Add(/*some new Object*/);
}
if (/*another possible condition*/) {
GameManager.Instance.AvailableUpgrades.RemoveAt(i);
i--; // Since i is pointing to current object that was just removed
// Otherwise you'd skip one object
}
}
I repeat. This kind of code can easily lead to hard-to-track bugs and is also hard to understand for newcomers in your project so recommend you try to avoid messing with the enumeration during the loop when possible.
Edit: Or... since I noticed your code does not check for conditions to remove each object you could simply call GameManager.Instance.AvailableUpgrades.Clear() after the loop to remove all objects.

Related

Trying to insert/sort a list based on 'Order' value

I am creating a documentation application in which I allow people inside my business to access documentation about certain software. With this, I have a page where any admin can manage categories and users. For this, I want the admin to be able to change the order of a category in which it should be displayed on my NavigationMenu. Now, the part where it should properly order the categories based on Order inside the NavigationMenu works. But when I try to edit existing categories and their order numbers, the orders won't update accordingly, see this example:
This is before editing existing categories, these categories are freshly added/made
This is after editing the categories, "React" should be Order 2, Where API would be Order 0, and Test would be Order 1
As you can see, the order doesn't make sense anymore. Obviously, there shouldn't be allowed more than 1 of any order.
Now, the problem is most likely coming from this code.
CategoryService.cs
public async Task<List<Category>> InsertCategory(Category category)
{
await GetCategories();
for (int i = 0; i < Categories.Where(c => c.Order >= category.Order).Count(); i++)
{
Categories[i].Order++;
if (Categories[i].Order == category.Order)
{
Categories[i].Order--;
break;
}
}
await categoryRepository.InsertAsync(Categories);
EventHelper.NotifyCategoryListChanged(Categories, EventArgs.Empty);
return Categories;
}
The order of the code goes like this:
CategoryDialog.razor
private async void SaveCategory(Category category)
{
if(!string.IsNullOrEmpty(category.Name))
{
await categoryService.SaveCategory(category);
Snackbar.Add("Category " + category.Name + " added", Severity.Success);
MudDialog.Close(DialogResult.Ok(category.Id));
}
else
{
Snackbar.Add("Please enter a category name.", Severity.Warning);
}
}
The above code is called after a button press. This passes along a category with a certain order number, this number gets passed along from a simple dropdown menu.
The SaveCategory function looks like this:
CategorySerice.cs
public async Task<Category> SaveCategory(Category category)
{
await InsertCategory(category);
if (categoryRepository.GetByIdAsync(category.Id) == null)
{
await categoryRepository.AddAsync(category);
}
else
{
await categoryRepository.SaveAsync(category);
}
EventHelper.NotifyCategoryListChanged(Categories, EventArgs.Empty);
return category;
}
This function calls the problematic function before actually saving/adding anything to the database. So it can take place for the newly added or edited category.
After this, an Event gets fired to notify my NavigationMenu that there have been changes made, and it should re-render to show this. This has no problems.
But I can't figure out how I would properly have the Orders be listed when I change them.
Quick reminder, this function doesn't work when editing existing categories. If I'd add a new category for example at order 2. It does properly shift everything with no problem.
Any help is welcome!
So if user tries to make a second with say order 2, it will reduce the order number of the one that was before?
Your loop logic doesn't support that, because your loop is increasing so by the time it happens you have already passed the previous so you'll get a double 1 presumably, after.
I think you'll get where you want if your change your loop to run downwards like your pushing down, though obviously you'll have to support order below zero soon and that is not always supported
TO:
for (int i = Categories.Count() -1; i >-1; i--)
{
if (Categories[i].Order <= category.Order)
{
Categories[i].Order = Categories[i].Order--;
}
}
But for everything to make sense and not running in to this i suggest pushing the order up instead, because positive high numbers do not have the same problems in the corners so to say
Or instead:
for (int i = 0; i < Categories.Count(); i++)
{
if(Categories[i].Order < category.Order) continue;
Categories[i].Order = Categories[i].Order++;
}
Ultimately also unless your implementation is different than i imagine, you will want to actually add the new category and not all of them (excluding thew new) again
--FROM: await categoryRepository.InsertAsync(Categories);
--TO:
await categoryRepository.InsertAsync(category);

PUN 2 Getting Custom Properties

I've recently taken on the task of custom properties in Photon. I have been able to figure out how to set the custom properties, but not get the custom properties. My hashtable is in my player controller script, while the place where I set (and where I want to get) properties is in a round loop script.
From RoundSystem:
private IEnumerator TeamBalance()
{
angelCount = Mathf.Floor(PhotonNetwork.PlayerList.Length * angelPercent);
currentAngels = angelCount;
currentPlayers = PhotonNetwork.PlayerList.Length;
foreach (var item in PhotonNetwork.PlayerList)
{
var itemPhotonView = (PhotonView)item.TagObject;
itemPhotonView.RPC("SetPlayerTeam", item, citiString);
}
for (int i = 0; i < angelCount;)
{
var item = PhotonNetwork.PlayerList[Random.Range(0, PhotonNetwork.PlayerList.Length)];
var itemPhotonView = (PhotonView)item.TagObject;
if (/* random player selected's, AKA, item's team == citiString */)
{
itemPhotonView.RPC("SetPlayerTeam", item, angelString);
i++;
}
}
yield return null;
//the reason this is in an IEnumerator with 'yield return null'
//is because I plan to add a waiting period once I figure this out
//it's for the game loop
}
From PlayerController:
[PunRPC]
public void SetPlayerTeam(string teamString)
{
//in the class: private ExitGames.Client.Photon.Hashtable playerProperties;
if (!playerProperties.ContainsKey("team"))
{
playerProperties.Add("team", teamString);
}
playerProperties["team"] = teamString;
PhotonNetwork.LocalPlayer.SetCustomProperties(playerProperties);
}
At the beginning of the round, a percentage (in this case 1/3) of players are chosen to be an "angel". The check here is needed because in cases of multiple angels, you don't want an already existing angel to count as a new change. (Also, it's probably important to known generally how to get custom properties if I'm going to be using them.) If I don't include the check in RoundSystem, the outcome is 2 citizens and 1 angel (in a test with 3 players). Also, if you see any spaghetti code that could be improved on, please don't hesitate to tell me. :)
Use Player.CustomProperties dictionary to access player's custom properties.
foreach (var item in PhotonNetwork.PlayerList)
{
if (item.CustomProperties.ContainsKey("team"))
{
Debug.Log(item.CustomProperties["team"]);
}
}
Also, the RoundSystem can implement IInRoomCallbacks interface and listen to OnPlayerPropertiesUpdate to catch the exact moment when the team gets updated. https://doc-api.photonengine.com/en/pun/v2/interface_photon_1_1_realtime_1_1_i_in_room_callbacks.html

Question about PhotonNetwork.CurrentRoom.CustomProperties

When the game starts in multiplayer, the master client sends a PunRPC to have all clients run a function. This function tries to get the room properties to see if the game is active, if so it does something. For some reason a client gets a null reference error, but the master client does not. The strange thing is, a debug of the hash table for room properties is visible but I cannot get a specific item in it.
Tried Debugging the hash table to make sure that the key was set when the code was run. It was. "(System.String)ag=(System.Boolean)True ag=activeGame" This shows in Debug.Log(hash); But (bool)hash[rpk.activeGame] gets a null reference error. But only on the client side not the master client. So the key also works.
// Call all the clients to set up the room settings in the sub menu.
[PunRPC]
private void GameRoomSetup (string pOne, string pTwo, int pOneColor, int pTwoColor)
{
GameObject gameMenu = GameObject.Find ("GameMenu");
gameMenu.GetComponent<SubMenu> ().UpdatePlayers (pOne, pTwo, pOneColor, pTwoColor);
gameMenu.GetComponent<SubMenu> ().StartGameSetup ();
// If you are a player, change the active buttons that are visible.
if (PhotonNetwork.NickName == pOne || PhotonNetwork.NickName == pTwo)
{
gameMenu.GetComponent<GameButtonManager> ().GameStart ();
}
hash = PhotonNetwork.CurrentRoom.CustomProperties;
Debug.Log(hash);
if ((bool)hash[rpk.activeGame]) // Error on this line on client but not on master client. Says null reference.
{
GameObject.Find ("SoundManager").GetComponent<SoundManagerScript> ().PlayBackgroundTwo ();
GameObject.Find ("GameMenu").GetComponent<SubMenu> ().ChangeSubMenuActive (false);
}
}
I'm trying to run my if statement as a client but I get an error.
Thank you for choosing Photon!
To get a custom property, I recommend you use TryGetValue method as follows:
hash = PhotonNetwork.CurrentRoom.CustomProperties;
object temp;
string key = rpk.activeGame;
if (hash.TryGetValue(key, out temp))
{
if (temp is bool)
{
bool activeGame = (bool)temp;
}
else
{
// unexpected custom property value type
}
}
else
{
// custom property not found
}
If the custom property is not available yet, wait for the callback IInRoomCallbacks.OnRoomPropertiesUpdate(Hashtable propertiesThatChanged) (reference API).
Other notes and recommendations:
private void GameRoomSetup (string pOne, string pTwo, int pOneColor, int pTwoColor)
Not sure if it's supported or if it's a good idea to pass multiple parameters to a PUN RPC method.
To debug log a Dictionary or a Hashtable you could make use of SupportClass.DictionaryToString() method.
so instead of
Debug.Log(hash);
use
Debug.Log(SupportClass.DictionaryToString(hash));
Avoid calling expensive methods like GameObject.Find:
GameObject gameMenu = GameObject.Find ("GameMenu");
Also here you have duplicate calls to gameMenu.GetComponent<SubMenu>(), at least call it once and cache the component result found if any.
gameMenu.GetComponent<SubMenu> ().UpdatePlayers (pOne, pTwo, pOneColor, pTwoColor);
gameMenu.GetComponent<SubMenu> ().StartGameSetup ();
Comparing strings should not be done using == operator. At least use Equal method and proper StringComparison type. Read "How to compare strings in C#".
// If you are a player, change the active buttons that are visible.
if (PhotonNetwork.NickName == pOne || PhotonNetwork.NickName == pTwo)
{
gameMenu.GetComponent<GameButtonManager> ().GameStart ();
}
Besides, why do you use the Nickname to check if it's player one or two? maybe use ActorNumber or a custom player index. Or use the player count if the room is for 2 players only.

Linked list Advice

I've been given a task as follows:
I'd like to to build an implementation of a Linked List. Specifically I'd like it to be a doubly linked list.
My task:
Your program should use the linked list to model a train route using the linked list.
First the user will enter as many stops as they'd like the train to have, and the name of each stop.
The program should then print a map of the route.
Once finished they then enter the name of the stop they want to start at.
From there they can enter commands to move the train either forward to the next stop or backward to the previous one.
I've been told I'm not doing this task right but I don't really understand how not, I'd appreciate it if someone could explain what I'm not doing that I should be doing.
My Route class (it isn't finished but it would've been nearly finished if it was done correctly):
namespace TrainRoute
{
class Route
{
Stops root;
public LinkedList<Stops> linkedList = new LinkedList<Stops>();
public Stops MakeNewStop(string stopName)
{
Stops stopWithStopName = new Stops(stopName);
return stopWithStopName;
}
public void AddStops(Stops stopIWantToAdd)
{
if (linkedList.Count == 0)
{
linkedList.AddFirst(stopIWantToAdd);
}
else
{
//stopIWantToAdd.prevStop = linkedList.Last();
linkedList.AddLast(stopIWantToAdd);
}
}
public void StopRelationships()
{
for (int i = 0; i < linkedList.Count; i++)
{
if (linkedList.ElementAt<Stops>(i).nextStop == null && linkedList.ElementAt<Stops>((i + 1)) != null)
{
linkedList.ElementAt<Stops>(i).nextStop = linkedList.ElementAt<Stops>((i + 1));
}
if (linkedList.ElementAt<Stops>((i - 1)) != null)
{
linkedList.ElementAt<Stops>(i).prevStop = linkedList.ElementAt<Stops>(i - 1);
}
}
}
public void Print()
{
if (linkedList != null)
{
foreach (var item in linkedList)
{
Console.WriteLine("Stop name: " + item.stopName);
}
}
}
public int StopPosition(string usersInput)
{
int position = 0;
for (int i = 0; i < linkedList.Count; i++)
{
if (linkedList.ElementAt<Stops>(i).stopName == usersInput)
{
position = i;
break;
}
}
return position;
}
public int MoveForward(int indexPosition)
{
Console.WriteLine("The train is now at " +linkedList.ElementAt<Stops>(indexPosition).nextStop.stopName);
return (indexPosition + 1);
}
public int MoveBackwords(int indexPosition)
{
Console.WriteLine("The train is now at " + linkedList.ElementAt<Stops>(indexPosition).prevStop.stopName);
return (indexPosition - 1);
}
public bool VerifyRoute(int indexPosition, string prevOrForward)
{
if (prevOrForward.Contains("forward"))
{
if (linkedList.ElementAt<Stops>((indexPosition+1)) != null)
{
return true;
}
}
else
{
if (linkedList.ElementAt<Stops>((indexPosition-1)) != null)
{
return true;
}
}
return false;
}
}
}
I'm also not allowed to use the Linked list class but I'm to use a linked list (I'm not 100% sure what that means).
Any and all advice/help provided will be appreciated!
Let's piece together the breadcrumbs here:
I'd like to to build an implementation of a Linked List.
and this:
I'm also not allowed to use the Linked list class
Obviously the task here is for you to implement your own linked list (class), and not to use the existing one provided by .NET.
I'm assuming here the task is not to build the program handling the trains, but instead to learn how a linked list works, and how you would go about implementing one.
As such, your shortcut to simply grab the existing class is the wrong tool for the job. It would be perfect (probably) if your task was to build that program, but in this case the program is orthogonal to your task, it's there to create a context for what you're really asked to do:
Implement your own version of LinkedList<T> (though you probably don't need to make it generic).
Wikipedia has a very good article on linked lists if you're stumped on how such a data structure really works. There's undoubtedly other very good resources out on the net as well, and probably in your text book or other resources.
Additionally, I would urge you to find a classmate to peer with, from experience I can say that most of the really hard problems I've had in my programming career has (usually) been solved by having a sparring partner to work with.
Implementing a linked list isn't that difficult. I assume you have a textbook and it discusses linked list, read it, carefully. Also you want to clarify with your tutor exactly how much functionality your linked list needs to implement.
Basically, you'll start with a node class, if you don't need it to be generic, then you can create a StopNode class. The basics of your node class will be a reference to the next node in the list and, since this is a doubly linked list, a reference to the previous node:
public class StopNode
{
public StopNode Next { get; set; }
public StopNode Previous { get; set; }
// whatever other properties your stop class needs - such as name
}
Now your LinkedList class will manage the collection of stop nodes. It will need to keep a reference to the first or "head" node and probably the last node too (or "tail").
public class StopLinkedList
{
private StopNode Head { get; }
private StopNode Tail { get; }
}
And it will need to implement methods to add and remove nodes (at a minimum) and probably also insert.
Add is pretty easy - check if Head is null. If it is, just set Head and Tail both equal to your new node. If it's not, you will instead set the Next property of your Tail to your new node, then set the Previous of your new node to the Tail and then finally update your Tail to reference your new node.
To remove a node, if given the node to remove, you will need to check it's Previous and Next properties and (assuming one or both isn't null - you'll need to add logic for that), you set your nodes Previous.Next to your nodes Next and your nodes Next.Previous to your nodes Previous. This will cause your node to fall out of the list (you can set your nodes Next and Previous to null if required, but it's not strictly necessary unless your removed node is going to hang around).
Hopefully that gets you started. Clarify with your tutor, check your textbook (probably better to try and match their terminology if it differs from mine) and also search for "linked list" and "doubly linked list" on the internet. You should find plenty of resources.

Children.Add(item) value does not fall within the expected range

I'm developing a Silverlight 3 app and getting this really weird error when I try to add an object to a Canvas. My code is as follows:
for (int i = 0; i < person.Children.Count; i++)
{
//Add children in same position as parent
Person child = person.Children[i];
child.x_PositionTransform.X = person.x_PositionTransform.X;
child.x_PositionTransform.Y = person.x_PositionTransform.Y;
child.Click += new RoutedEventHandler(person_Click);
x_LayoutRoot.Children.Add(child);
}
The first time I use this, it works as expected. However, when I hit x_LayoutRoot.Children.Add(child) after clicking a Person object that was created using this code, I get an ArgumentException telling me that "Value does not fall within the expected range."
However, when I add the following code before adding child to x_LayoutRoot.Children, the problem disappears.
child.SetValue(Canvas.NameProperty, "child" + objCount++);
Why is this happening? Is this a Silverlight bug, or (more likely) am I just missing something?
I think I've figured out the cause of this: I was adding multiple Person objects with the same name. So, if anyone has this issue in the future, make sure all of your objects have unique names!
Person child = person.Children[i];
child.x_PositionTransform.X = person.x_PositionTransform.X;
child.x_PositionTransform.Y = person.x_PositionTransform.Y;
child.Click += new RoutedEventHandler(person_Click);
child.SetValue(Canvas.NameProperty, "child" + objCount++); //!
x_LayoutRoot.Children.Add(child);
you can add different NameProperty before add a child

Categories