I'm trying to do a Candy Crush-like map for my game in Unity, I'm trying to gather all the buttons in an array and then set its onclick property so when I click on them I get to the level I want. I'm using a Foreach loop to achieve it.
The problem is that on every single button, I get to load the same scene. It does list my levels from 1 to N but the onclick does not seem to be working fine! I've tried doing a for loop instead foreach but it doesn't work either.
int i = 0;
foreach(GameObject level in levels)
{
level.GetComponent<Button>().onClick.AddListener(() => SceneManager.LoadScene(i + 1));
i++;
}
You've created a closure on a loop variable. A common pitfall, for sure. A suitable explanation and remedy for this kind of problem already exists on StackOverflow.
When you use the symbol i, it is essentially a reference to the one and only i that existed outside of your loop. Its value, by the time any of the listeners get invoked, will be the value it had at the end of the loop, which, in your case, would be the number of levels you have.
To avoid this, you need to use the value of i, not a reference to the variable. You can do this by creating a new variable that will have scope unique to one iteration of the loop and passing in that new variable's value. Basically a copy of i is what you need.
The essence of the necessary change would be:
int i = 0;
foreach(GameObject level in levels)
{
int copy_of_i = i;
level.GetComponent<Button>().onClick.AddListener(() => SceneManager.LoadScene(copy_of_i + 1));
i++;
}
I think the problem is the lambda expression:
() => SceneManager.LoadScene(i + 1)
you it is not the value of i, but instead the variable i, which will get incremented again
over the other iterations so when you click on it
SceneManager.LoadScene(i + 1); gets called, but i is now 25 or whatever your level number is.
Create a temporary variable directly before it so each lambda expression gets their own variable
int tmp = i;
level.GetComponent().onClick.AddListener(() =>SceneManager.LoadScene(tmp + 1));
Related
<MudTimeline TimelineOrientation="TimelineOrientation.Horizontal" TimelinePosition="TimelinePosition.Alternate" >
#for (int i = 0; i < allBookings.Count - 3; i += 3)
{
//bookString.Add(allBookings[i + 1]);
<MudTimelineItem Color="Color.Error" Variant="Variant.Filled">
<ItemContent>
<MudAlert Severity="Severity.Error">#allBookings[i+1]</MudAlert>
</ItemContent>
<ItemOpposite>
<MudText Color="Color.Error">#allBookings[i]</MudText>
</ItemOpposite>
</MudTimelineItem>
}
</MudTimeline>
I want to add a new timeline item when a new booking is added. But the loop will always make all the previous bookings to the same value as the last one. I tried to move the loop functions around, but either it doesn't work the way I wanted or it simply doesn't allow it.
I also tried to make a new list to store all the existing customers and compare it, but that didn't work out either.
I know this is more of a logic problem than a programmatic one, but please send help.
This is the classic problem of using for loop. The problem is that you're passing i (declared inside the for) which is just one variable that will be incremented in each iteration.
The HTML content will be rendered when the for loop is executed, but the event handlers is called later, which means that the i will not have the value you're expecting.
This problem is not even related with Blazor, it's more related with C# in general.
To solve you problem you must save the i variable in a "local" variable first.
<MudTimeline TimelineOrientation="TimelineOrientation.Horizontal" TimelinePosition="TimelinePosition.Alternate" >
#for (int i = 0; i < allBookings.Count - 3; i += 3)
{
var index = i;
<MudTimelineItem Color="Color.Error" Variant="Variant.Filled">
<ItemContent>
<MudAlert Severity="Severity.Error">#allBookings[index+1]</MudAlert>
</ItemContent>
<ItemOpposite>
<MudText Color="Color.Error">#allBookings[index]</MudText>
</ItemOpposite>
</MudTimelineItem>
}
</MudTimeline>
Here I provide a small example of what I just said!
https://try.mudblazor.com/snippet/GawGYhljyIczxjrY
// EDIT:
This is not a duplicate to: "When should I use a List vs a LinkedList". Check the answer I've provided below.
LinkedList might be useful though if someone wants to insert some positions at a specific place after we have a properly ordered List (in that case - LinkedList) already - how to make one, check my answer below. //
How would you iterate backwards (with pauses for player input) through a set of randomly generated numbers?
I'm trying to build a turn-based game. The order of actions is determined by a result of something like that:
int randomPosition = Random.Range(1,31) + someModifier;
// please note that someModifier can be a negative number!
// There is no foreseeable min or max someModifier.
// Let's assume we can't set limits.
I have a List of KeyValue pairs already containing names of objects and their corresponding randomPosition. // Update: Values of it are already sorted by a custom Sort() function, from highest to lowest.
// A list of KeyValue pairs containing names of objects and their corresponding randomPosition.
public List<KeyValuePair<string, int>> turnOrder = new List<KeyValuePair<string, int>> ();
// GameObject names are taken from a list of player and AI GameObjects.
List <GameObject> listOfCombatants = new List<GameObjects>();
// Each GameObject name is taken from listOfCombatants list.
listOfCombatants[i].name;
I thought, maybe let's add player and AI GameObjects to a list on Index positions equal to each of their randomPosition. Unfortunately, a Generic List can't have "gaps" in it. So we can't create it, let alone iterate it backwards.
Also, I'm not sure how we'd stop a for loop to wait for player input. I have a button, pressing which will perfom an action - switch state and run some functions.
Button combat_action_button;
combat_action_button.onClick.AddListener (AttackButton);
// When player takes his turn, so in TurnState.PLAYER_ACTION:
public void AttackButton() {
switch(actionState) {
case PlayerAction.ATTACK:
Debug.Log (actionState);
// Do something here - run function, etc. Then...
currentState = TurnState.ENEMY_ACTION;
break;
}
To make things worse, I've read that "pausing" a while loop isn't good for performance. That it's better to take player input out of loops.
So maybe I should create more for loops, iterate a new loop from position to position until the last GameObject acted, and use some delegates/events, as some things players/AI can do are in different scripts (classes).
This is not a real-time project, so we can't base anything on time (other than potential max turn time).
Another thing is, we don't know how many GameObjects will take turns.
But maybe there's some collection type that can store GameObjects with gaps between their index positions and iterate a loop from highest to lowest with no problem?...
I want to make it as simple as possible.
For the issue of simultaneously having user input and looping, I recommend looking into background workers or threading/tasks. This should facilitate your problem with simultaneously doing two things at once.
For your list problem I personally prefer the lists as well which is why I would designate each "gap" with a specific character like - 1, and when referencing the data ignore the - 1. To ignore the gaps I recommend LINQ queries but if you don't want to use LINQ that should not be a problem.
EDIT*
I know very little about Unity but from what people have been saying it sounds like running multiple threads is or can be an issue. I looked into this issue and it sounds like you just cannot call the unity api from a background thread. So basically, as long as you do not reference the unity api from the background thread, you should be ok. With that said, you may possibly need/want to make a call to the api inside of the background worker. To do this you need to either invoke the call before or after the background worker thread. I am pretty sure there is also a simultaneous invocation from the main thread by setting the workers step property to true.
I've decided to share my own solutions as I think I've developped some accurate answers to my questions.
Solution #1. First, declare an array, 2 variables and 1 function:
int[] arrayOrderedCombatants;
int min = 100000;
int max;
public void SomePlayerAction() {
Debug.Log ("This is some player or AI action.");
}
public void IterateThroughTurnOrderPositions() {
for (int i=max; i >= min; i--) {
if (arrayOrderedCombatants [i] >= 0 && arrayOrderedCombatants [i] >= min) {
Debug.Log ("This is an existing position in turn order: " + arrayOrderedCombatants [i]);
foreach (var position in turnOrder) {
if (position.Value == arrayOrderedCombatants [i]) {
max = (arrayOrderedCombatants [i] - 1);
goto ExitLoop;
}
}
}
}
ExitLoop:
SomePlayerAction ();
}
Then, for our testing purposes let's use an Update() method triggered by Input.GetKeyDown:
if (Input.GetKeyDown (KeyCode.O)) {
arrayOrderedCombatants = new int[turnOrder[0].Value + 1];
Debug.Log ("This is arrayOrderedCombatants Length: " + arrayOrderedCombatants.Length);
foreach (var number in turnOrder) {
if (number.Value < min)
min = number.Value;
}
Debug.Log ("This is min result in random combat order: " + min);
for (int i=0; i < arrayOrderedCombatants.Length; i++)
arrayOrderedCombatants[i] = min -1;
foreach (var combatant in turnOrder) {
if (combatant.Value >= 0) {
arrayOrderedCombatants [combatant.Value] = combatant.Value;
}
}
max = turnOrder [0].Value;
while (max >= min)
IterateThroughTurnOrderPositions ();
}
The above code answers my question. Unfortunately, problems with this solution may be two. First - you can't have a negative index position. So if someModifier makes randomPosition go below 0, it won't work. Second problem - if you have more than 1 occurance of any value from randomPosition, it will be added to the arrayOrderedCombatants only once. So it will be iterated once too.
But that's obvious - you can't have more than one value occupying an int type Arrays' index position.
So I will provide you a better solution. It's a different approach, but works like it should.
Solution #2. First, declare a list of GameObjects:
List<GameObject> orderedCombatants = new List<GameObject> ();
Then, in Update() method:
if (Input.GetKeyDown (KeyCode.I)) {
orderedCombatants.Clear ();
foreach (var combatant in initiative) {
Debug.Log (combatant);
for (int i=0; i < listOfCombatants.Count; i++) {
if (listOfCombatants[i].name.Contains(combatant.Key)) {
orderedCombatants.Add(listOfCombatants[i]);
}
}
}
foreach (var combatant in orderedCombatants) {
Debug.Log (combatant.name);
}
}
The above creates a new list of GameObjects already set in the right order. Now you can iterate through it, easily access each GameObject and perform any actions you need.
Im making a game in c# and it have a scoreboard with the top 5 players.
I made it to work, at least I thought I did...
The script enter the value of player's name and his score in a array, but there is a problem. It just delete the last one, so if you make the best score you become #1, but the old #1 is deleted and #2 is always #2 unless someone make a result for that place. My question is how to move the array by one from some place (it depends on player's result) and delete the last string of it?
Edit: Can't use list, because im doing so much stuff with that array.
Like this:
string topScores = sbName[i].Substring(4);
int topScoreInt = Convert.ToInt32(topScores);
If you need to use an array instead of a List (which has a handy Insert method), you could work your way from the last value forward, replacing each with it's predecessor's value, until you get to the one you want to update:
int place = 2; // Meaning 3rd place, since arrays are zero-based
// Start at end of array, and replace each value with the one before it
for (int i = array.Length - 1; i > place; i--)
{
array[i] = array[i - 1];
}
// Update the current place with the new score
array[place] = score;
Since you have just 5 items and scoring is normally rare some basic LINQ code would be easier and likely more readable:
class Score { public string Name; public int Result;}
Score[] scores = new Score[5];
var newScore = new Score {Name = "Foof", Result=9001};
scores = scores
.Where(s => s != null) // ignore empty
.Concat(Enumerable.Repeat(newScore,1)) // add new one
.OrderByDescending(s => s.Result) // re-sort
.Concat(Enumerable.Repeat((Score)null,4)) // pad with empty if needed
.Take(5) // take top X
.ToArray(); // back to array
Side note: You really will be better off using List<T> or SortedList<K,V>.
I've stored a list of colors in my program. I am after an object in my scene to one of the colors in the list. So far, I have done the followings:
if(Input.GetKeyDown(KeyCode.O))
{
for(int i = 0; i < claddingColor.Count; i++)
{
claddingMaterial.color = claddingColor[i];
}
}
This isn't working due to a reason I know (and you can probably spot) but I lack to the verbal fortitude to write it down.
As opposed to have a multiple lines of the following:
claddingMaterial.color = claddingColor[0];
Each tied to different buttons, I like a way I can emulate the above but tie it to a single button press. Thus, if I hit the 0 button 5 times, it will loop through each color stored in the list. If I hit it for a sixth time, it will go back to the first color in the list.
Could someone please help me implement this? Or point me to something that I may learn how to do it for myself?
Define LastColor property as class member:
int LastColor;
In your function use modulo
if(Input.GetKeyDown(KeyCode.O))
{
claddingMaterial.color = claddingColor[(LastColor++) % claddingColor.Count];
}
Note: Depending on the type of claddingColor use Count for a List or Length for Array.
You won't need a for loop
int lastStep = 0;
if(Input.GetKeyDown(KeyCode.O))
{
claddingMaterial.color = claddingColor[lastStep++];
if (lastStep == claddingColor.Count)
lastStep = 0;
}
So I have and method like this.
var someColletion = _someService.GetSomeCollection(someParam);
var taskCollection = new Task<double>[someCollection.Count];
for (int i = 0; i < taskCollection.Length; i++)
{
// do some stuff on the i-th element of someCollection and taskCollection
// and start the i-th task
}
Task.WaitAll(taskCollection);
double total = 0;
for (int i = 0; i < taskCollection.Length; i++)
{
// get the result of each task and sum it in total variable
}
return total;
the case is when it comes into first for loop and the number of elements in both collections are suppose 1 the ArgumentOutOfRangeException is being thrown and then AggregateException is being thrown on Task.WaitAll() because the i becomes 1 (I don't know why but it does) and when it tries to access the i-th (second) element in array that contains just one element, this happens. But there is more to this. If i set a break point before first loop and go step by step then this thing does not happen. when i becomes one the cycle ends. and everything's okay. now the method I provided above is called by an ASP.NET MVC Controller's Action which itself is called Asynchronously (by ajax call) suppose 3 times. and out of this three just one executes correctly other two do the thing I said above (if not breakpointed). I think that this problem is caused by ajax call most probably because when I breakpoint it stops other calls from executing. Can anyone suggest anything ?
I suspect you're using i within the first loop, capturing it with a lambda expression or anonymous method, like this:
for (int i = 0; i < taskCollection.Length; i++)
{
taskCollection[i] = Task.Run(() => Console.WriteLine(i));
}
If that's the case, it's the variable i which is being captured - not the value of the variable for that iteration of the loop. So by the time the task actually executes, it's likely that the value of i has changed. The solution is to take a copy of the iteration variable within the loop, in a separate "new" variable, and capture that in the anonymous function instead:
for (int i = 0; i < taskCollection.Length; i++)
{
int copy = i;
taskCollection[i] = Task.Run(() => Console.WriteLine(copy));
}
That way, each task captures a separate variable, whose value never changes.