Search Unity

  1. Welcome to the Unity Forums! Please take the time to read our Code of Conduct to familiarize yourself with the forum rules and how to post constructively.
  2. We have updated the language to the Editor Terms based on feedback from our employees and community. Learn more.
    Dismiss Notice
  3. Join us on November 16th, 2023, between 1 pm and 9 pm CET for Ask the Experts Online on Discord and on Unity Discussions.
    Dismiss Notice

If list element contains an item already, change index to assign new position

Discussion in 'Scripting' started by GladGoblinGames, Apr 15, 2015.

  1. GladGoblinGames

    GladGoblinGames

    Joined:
    Dec 14, 2011
    Posts:
    118
    Hi all,

    I'm currently working on a Quiz game, I've got answers and questions and wrong answers all coming up correctly. The problem is, the wrong answers and correct answers. I need them to appear at random pre-set rect positions.

    So basically, question is, is there a way of defining a list of pre-made rect positions and then in OnGUI() get each button to place itself at one of these rect positions without any button overlapping?

    Hope this makes sense, if not, I will try to explain it better :D
     
  2. Antistone

    Antistone

    Joined:
    Feb 22, 2014
    Posts:
    2,833
    I don't use OnGUI, but presumably the OnGUI function for creating a button has some kind of parameter saying where the button should go. So can't you just create some variables that represent the positions you want, and pass those variables to the button-creation-functions?
     
  3. ThomasCreate

    ThomasCreate

    Joined:
    Feb 23, 2015
    Posts:
    81
    There sure is. You almost got to the answer yourself: you need a set of pre-defined coordinates, so you need a list of Vector2's, and initiate that with the coordinates of your choosing.

    Then, every button needs to be positioned randomly at one of those locations. So, using Random.Range(0, list.Length) inside a loop that goes over every possible answer, you pick an index in that list of coordinates. However, like you mentioned, you can't have two buttons pick the same location, so you need a way of knowing what coordinates you already picked. This can be done by removing the chosen Vector2 from the list using list.RemoveAt() - with the coordinate removed, it can never be picked twice. You need to re-initiate the list if you run the script a second time, of course. You could keep a copy of the positions.

    Alternatively to removing the coordinates, you could keep a separate list of indices that you're allowed to pick from and pick and remove from that.
     
    Last edited: Apr 15, 2015
  4. KelsoMRK

    KelsoMRK

    Joined:
    Jul 18, 2010
    Posts:
    5,539
    Actually - you'd want a list of Rects, not a list of Vector2s.

    I'd go the other way and define 4 Rects. Then I'd randomly pick an answer to put at that Rect.
     
    Kiwasi likes this.
  5. ThomasCreate

    ThomasCreate

    Joined:
    Feb 23, 2015
    Posts:
    81
    I reckon all the buttons are the same size, or the GUI will look wonky. If you only store the top left position, you just create a rect from that coordinate. If you later decide to change the width and height, you only have to change those once, instead of for every button coordinate.
     
  6. GladGoblinGames

    GladGoblinGames

    Joined:
    Dec 14, 2011
    Posts:
    118
    Hi, thanks for the suggestions. I'm still using the rect positions and trying to do list.RemoveAt() to remove the index that was last picked, although this causes "Array argument out of range" I'm still playing about with it as maybe I'm going about it wrong the whole way. Trying to do a for loop inside OnGUI() but don't think I should be doing this. So, I'll end up putting it in Start() and if the question is correct, just do the for loop again. However, for the time being, this index issue is still giving me problems. Also, when I used list.RemoveAt() it was also removing the buttons from being drawn xD
     
  7. ThomasCreate

    ThomasCreate

    Joined:
    Feb 23, 2015
    Posts:
    81
    Yes, you're not supposed to do it in OnGUI(), because then you're constantly trying to reposition the buttons and are constantly trying to remove items from the list, even though they've already been removed in the first pass. You need to position the buttons only once for each question.
     
  8. GladGoblinGames

    GladGoblinGames

    Joined:
    Dec 14, 2011
    Posts:
    118
    Yeah, still working on it. It wouldn't be so bad if I stopped getting argument out of range errors every time I try to remove the last known index >.<
     
  9. GladGoblinGames

    GladGoblinGames

    Joined:
    Dec 14, 2011
    Posts:
    118
    Well, after lots of attempts I can't seem to get it working. No matter what I try to do, it always gives me the same index number or errors with argument out of range. Currently, I'm trying to just make it so a random answer is put in the buttons. Instead of moving the rect positions.

    This is the code for the buttons...

    Code (CSharp):
    1.         if(GUI.Button(positions[1], data[index].answers[0]))
    2.         {
    3.          
    4.             Debug.Log("Clicked");
    5.          
    6.         }
    7.  
    8.  
    9.  
    10.         if(GUI.Button(new Rect (native_width / 2 - native_width / 2 + 650, native_height / 2 - native_height / 2 + 750, 200, 100), data[index].answers[1]));
    11.         {
    12.          
    13.          
    14.          
    15.         }
    16.  
    17.  
    18.  
    19.         if(GUI.Button(new Rect(native_width / 2 - native_width / 2 + 1100, native_height / 2 - native_height / 2  + 750, 200, 100), data[index].answers[2]))
    20.         {
    21.          
    22.          
    23.          
    24.         }
    25.  
    26.  
    27.  
    28.         if(GUI.Button(new Rect(native_width / 2 - native_width / 2 + 1100, native_height / 2 - native_height / 2  + 900, 200, 100), data[index].answers[3]))
    29.         {
    30.          
    31.          
    32.          
    33.         }
    As you can see, I just put in there own index numbers to stop it from causing errors (But obviously, this isn't the way I want it, I need a random index number that can not pick the same number twice)

    Edit: The way I've set it up, which I should mention, element 0 in the answers list, is always the correct answer
     
  10. RabenFutter

    RabenFutter

    Joined:
    Dec 30, 2012
    Posts:
    38
    I dident add random index numbers but this should go through your answers without an index error

    Code (CSharp):
    1. int maxLine = 1;
    2. int line = 0;
    3. int col = 0;
    4. for(int i = 0; i <  data[index].answers.Length; i++){
    5.     if(line > maxLine){
    6.         line = 0;
    7.         col++;      
    8.     }
    9.     else
    10.         line++;
    11.     if(GUI.Button( new Rect (650+line*450, 750+col*250, 200, 100), data[index].answers[i]))
    12.     {
    13.        
    14.         Debug.Log(data[index].answers[i]+"was Clicked");
    15.        
    16.     }
    17. }
     
  11. blizzy

    blizzy

    Joined:
    Apr 27, 2014
    Posts:
    775
    I think you are approaching this from the wrong side. First, instead of using Rect positions, you should use a layout (such as GUILayout.BeginVertical().) In this layout, you display all the answers.

    Second, to randomize the answers, you don't shuffle the buttons around, but you shuffle the answers before you create any buttons. Meaning, the buttons always stay in the same order, but what they actually mean is random.
     
    Kiwasi likes this.
  12. GladGoblinGames

    GladGoblinGames

    Joined:
    Dec 14, 2011
    Posts:
    118
    Thanks for the help, I tried RabenFutter's idea first, this caused my gui to move all over the screen and wouldn't stand still haha, but it did solve my augment out of range error.

    I'm just about to try Blizzy's idea with GUILayout. I'll let you know how I get on :D
     
  13. GladGoblinGames

    GladGoblinGames

    Joined:
    Dec 14, 2011
    Posts:
    118
    Ughhhhhhh. I just can't solve it! I bet its something really simple as well, I'm just over-thinking it. I even tried a "while" loop using .Contains to stop it from picking the same number. Even that didn't stop it. Oh, if only the people who develop massive multiplayers and console games could see me now, they'd all be like "Lol, can't even get random index numbers in each List" Oh the shameeeeeeeee >.< haha
     
  14. KelsoMRK

    KelsoMRK

    Joined:
    Jul 18, 2010
    Posts:
    5,539
    Break it down into digestible parts. First - get a reference to your list of answers. Debug.Log the length to make sure you got it. Second, shuffle the list so the answers are in a random order. Debug.Log each item to ensure the shuffle worked. Then get a GUILayout working with buttons that don't do anything and just have hardcoded labels (1, 2, 3, 4, 5, etc would work fine). Next swap out the labels for the answer text. Last hook the button up to actually do something.
     
  15. GladGoblinGames

    GladGoblinGames

    Joined:
    Dec 14, 2011
    Posts:
    118
    I'm getting close...I'm trying the shuffling method...which I didn't know was actually a think o_O

    However, sometimes, it still uses the same value...

    This is the code I'm using to shuffle xD

    Code (CSharp):
    1.         for (int i = 0; i < data[index].answers.Count; i++) {
    2.             tmp = data [index].answers [i];
    3.             int r = Random.Range (i, data [index].answers.Count);
    4.             data [index].answers [i] = data [index].answers [r];
    5.             data [index].answers [r] = tmp;
    6.             Debug.Log (tmp);
    7.         }




     
  16. KelsoMRK

    KelsoMRK

    Joined:
    Jul 18, 2010
    Posts:
    5,539
    Where' your code that's drawing the UI?
     
  17. GladGoblinGames

    GladGoblinGames

    Joined:
    Dec 14, 2011
    Posts:
    118
    Oh my lord, I'm such a freaking fool!

    In the GUILayout.Button, I was using the variable 'tmp' and wondering why it kept coming up as the same value!

    Then I did data[index].answers[0] etc for each button realising, "OH HEY THERE YOU GO!, HOW DID YOU NOT SEE WHAT YOU WAS DOING?!"

    I...feel...like...I...I...don't even know. Never the less, it is fixed! Thanks guys! I will write down a huge comment saying "REMEMBER HOW TO SHUFFLE" :D
     
  18. GladGoblinGames

    GladGoblinGames

    Joined:
    Dec 14, 2011
    Posts:
    118
    Aye, after doing so well, I got me another problem. Sorry to be a pain! I'm learning more as I go along :D

    Basically, I can't seem to get rid of this annoying "Argument out of range error" I know exactly what the error means, and, I thought I knew how to solve it. Turns out I was wrong...This is the code that I have that works perfectly so far :D

    Code (CSharp):
    1.     void OnGUI()
    2.     {
    3.  
    4.         float rx = Screen.width / native_width;
    5.         float ry = Screen.height / native_height;
    6.         GUI.matrix = Matrix4x4.TRS(new Vector3(0, 0, 0), Quaternion.identity, new Vector3(rx, ry, 1));
    7.  
    8.         GUI.skin = mainSkin;
    9.  
    10.         GUI.Box (new Rect (native_width / 2 - native_width / 2 + 100, native_height / 2 - native_height / 2 + 250, native_width - 200, native_height / 2 - 80), "Question:");
    11.         GUI.Label (new Rect (native_width / 2 - native_width / 2 + 100, native_height / 2 - native_height / 2 + 190, native_width - 200, native_height / 2 - 80), data[index].question);//QuestionsAndAnswers.allQuestions[QuestionsAndAnswers.index].question);
    12.         GUI.Label (new Rect (native_width / 2 - native_width / 2 + 100, native_height / 2 - native_height / 2 + 230, native_width - 200, native_height / 2 - 80), "Questions Remain: " + questionsAnswered + " / " + questionsLeft);
    13.  
    14.         GUILayout.BeginVertical ();
    15.         if (GUILayout.Button (data [index].answers [0])) {
    16.             CheckAnswer(data[index].answers[0]);
    17.         }
    18.         GUILayout.Space (40);
    19.         if (GUILayout.Button (data [index].answers [1])) {
    20.             CheckAnswer(data[index].answers[1]);
    21.         }
    22.         GUILayout.Space (40);
    23.         if (GUILayout.Button (data [index].answers [2])) {
    24.             CheckAnswer(data[index].answers[2]);
    25.         }
    26.         GUILayout.Space (40);
    27.         if (GUILayout.Button (data [index].answers [3])) {
    28.             CheckAnswer(data[index].answers[3]);
    29.         }
    30.         GUILayout.Space (40);
    31.         GUILayout.EndVertical ();
    32.  
    33.     }
    34.  
    35.     void CheckAnswer(string newString)
    36.     {
    37.         // The passed "newString" must be EQUAL to answer (element 0)
    38.         if (newString == answer) {
    39.  
    40.             // Yes, we have the correct answer
    41.             Debug.Log("Right Answer");
    42.  
    43.  
    44.             // If the data size is NOT 0...
    45.             if (data.Count != 0) {
    46.  
    47.  
    48.                 // Add the score...
    49.                 score = score + 10;
    50.  
    51.                 // Increase the amount of questions answered by the player
    52.                 questionsAnswered++;
    53.  
    54.                 // Debug...
    55.                 Debug.Log(score);
    56.  
    57.                 // Remove the previous data index...
    58.                 data.RemoveAt (index);
    59.  
    60.                 // Debug...
    61.                 Debug.Log("There are: " + data.Count + " data elements left");
    62.  
    63.  
    64.                 // Grab a new index...
    65.                 index = Random.Range (0, data.Count);
    66.  
    67.  
    68.                 // What question do we have now?
    69.                 Debug.Log("Question is...  " + data[index].question);
    70.  
    71.  
    72.                 // Answer string becomes the new data index's element 0....
    73.                 answer = data [index].answers [0];
    74.  
    75.  
    76.                 // Shuffle the answers to get random answers inside Buttons
    77.                 Sort ();
    78.  
    79.  
    80.                 // IF the data count is 0 (Which, currently, it can't determine, no idea why)...clear the list
    81.                 if(data.Count <= 0)
    82.                 {
    83.                     data.Clear();
    84.                     Debug.Log("No more values are stored in the list");
    85.                 }
    86.             }
    87.         } // End Answer If
    88.     } // End check answer
    When the last answer is clicked, its meant to clear the list. I thought I could use data.Clear() but the problem still persists...

    Errors lines are: 90 and 81. 81 keeps incrementing where as 90 just happens the once (both are argument out of range errors)

    Error on line 90... (This error line changes, depending on where the last answer element is in the list...
    Code (CSharp):
    1. CheckAnswer(data[index].answers[1]);
    Error on line 81...
    Code (CSharp):
    1. GUI.Label (new Rect (native_width / 2 - native_width / 2 + 100, native_height / 2 - native_height / 2 + 190, native_width - 200, native_height / 2 - 80), data[index].question);

    This code may help figure out whats going on...

    This is how my list for data is setup...
    Code (CSharp):
    1. public List<QuestionsAndAnswers.QuestionData> data = new List<QuestionsAndAnswers.QuestionData>();
    and this is the data class...

    Code (CSharp):
    1.     [System.Serializable]
    2.     public class QuestionData
    3.     {
    4.         public string question;
    5.         public List<string> answers;
    6.  
    7.     }
    Hope this helps! :D
     
  19. GladGoblinGames

    GladGoblinGames

    Joined:
    Dec 14, 2011
    Posts:
    118
    UPDATE: Okay, I've fixed the error on line 81 with...

    Code (CSharp):
    1.         if(data.Count <= 1)
    2.         {
    3.             data[index].question.Remove(index);
    4.             data[index].answers.Clear();
    5.             data.Clear();
    6.             Debug.Log("Less than or equal to one, we need to clear data");
    7.         }
    Yet, the error that happens on line 90, still can't get rid of ¬_¬

    New Update: My fix for the error on line 81, didn't work. It just came back suddenly ¬_¬
     
    Last edited: Apr 17, 2015
  20. KelsoMRK

    KelsoMRK

    Joined:
    Jul 18, 2010
    Posts:
    5,539
    Just use a for loop to draw the buttons

    Code (csharp):
    1.  
    2. GUILayout.BeginVertical();
    3. for (int i = 0; i < data[index].answers.Count; i++)
    4. {
    5.     if (GUILayout.Button(data[index].answers[i]))
    6. }
    7. GUILayout.EndVertical();
    8.  
     
    GladGoblinGames likes this.
  21. GladGoblinGames

    GladGoblinGames

    Joined:
    Dec 14, 2011
    Posts:
    118
    Hi Kelso,

    Thanks for the reply. I've now started using a for loop for the buttons, however, I still get the Argument out of range error :'(
     
  22. KelsoMRK

    KelsoMRK

    Joined:
    Jul 18, 2010
    Posts:
    5,539
    Probably means index is out of range then.

    Unless you're modifying your answers list while the UI is drawing.
     
  23. GladGoblinGames

    GladGoblinGames

    Joined:
    Dec 14, 2011
    Posts:
    118
    Aye, I think it might be that you know, my check answers function is after the OnGUI() Hmmm... I'll try moving it about and see what happens
     
  24. GladGoblinGames

    GladGoblinGames

    Joined:
    Dec 14, 2011
    Posts:
    118
    Argh, after so much messing about with the code, this 'Argument out of Range' error is not wanting to leave me alone ¬_¬

    I've modified the code to this:

    Code (CSharp):
    1.     void OnGUI()
    2.     {
    3.  
    4.         float rx = Screen.width / native_width;
    5.         float ry = Screen.height / native_height;
    6.         GUI.matrix = Matrix4x4.TRS(new Vector3(0, 0, 0), Quaternion.identity, new Vector3(rx, ry, 1));
    7.  
    8.         GUI.skin = mainSkin;
    9.  
    10.         GUI.Box (new Rect (native_width / 2 - native_width / 2 + 100, native_height / 2 - native_height / 2 + 250, native_width - 200, native_height / 2 - 80), "Question:");
    11.         GUI.Label (new Rect (native_width / 2 - native_width / 2 + 100, native_height / 2 - native_height / 2 + 190, native_width - 200, native_height / 2 - 80), data[index].question);//QuestionsAndAnswers.allQuestions[QuestionsAndAnswers.index].question);
    12.         GUI.Label (new Rect (native_width / 2 - native_width / 2 + 100, native_height / 2 - native_height / 2 + 230, native_width - 200, native_height / 2 - 80), "Questions Remain: " + questionsAnswered + " / " + questionsLeft);
    13.  
    14.         GUILayout.BeginVertical();
    15.         for(int i = 0; i < data[index].answers.Count; i++)
    16.         {
    17.             if(GUILayout.Button(data[index].answers[i]))
    18.             {
    19.                 // Make a string for comparison to correct answer and answer clicked..
    20.                 string tmp = data[index].answers[i];
    21.  
    22.                 // If the data count is greater than 0, and the answer equals element 0
    23.                 if(data.Count > 1 && answer == tmp)
    24.                 {
    25.  
    26.  
    27.                     // Add the score...
    28.                     score = score + 10;
    29.                    
    30.                     // Increase the amount of questions answered by the player
    31.                     questionsAnswered++;
    32.                    
    33.                     // Debug...
    34.                     //Debug.Log(score);
    35.                    
    36.                     // Remove the previous data index...
    37.                     data.RemoveAt (index);
    38.  
    39.                     // Grab a new index...
    40.                     index = Random.Range (0, data.Count);
    41.  
    42.                     // Once new index is found, make the answer element 0
    43.                     answer = data [index].answers [0];
    44.  
    45.                     // What question do we have now?
    46.                     //Debug.Log("Question is...  " + data[index].question)
    47.                    
    48.                    
    49.                     // Shuffle the answers to get random answers inside Buttons
    50.                     Sort ();
    51.  
    52.                
    53.  
    54.                 }
    55.  
    56.                 if(data.Count == 1 && answer == tmp)
    57.                 {
    58.                     Debug.Log("Less than or equal to one, we need to clear data");
    59.                     data[index].question.Remove(index);
    60.                     data[index].answers.Clear();
    61.                     data.Clear();
    62.                    
    63.                 }
    64.  
    65.  
    66.             }
    67.         }
    68.         GUILayout.EndVertical ();
    69.  
    70.     }
    71.  
    72.     void Sort()
    73.     {
    74.        
    75.         for(int i = 0; i < data[index].answers.Count; i++)
    76.         {
    77.             string tmp = data[index].answers[i];
    78.             int randomIndex = Random.Range(i, data[index].answers.Count);
    79.             data[index].answers[i] = data[index].answers[randomIndex];
    80.             data[index].answers[randomIndex] = tmp;
    81.         }
    82.        
    83.        
    84.        
    85.     }
    Error lines are:
    Code (CSharp):
    1. for(int i = 0; i < data[index].answers.Count; i++)
    and

    Code (CSharp):
    1. GUI.Label (new Rect (native_width / 2 - native_width / 2 + 100, native_height / 2 - native_height / 2 + 190, native_width - 200, native_height / 2 - 80), data[index].question);
    Have I done something totally stupid? Probably xD
     
  25. KelsoMRK

    KelsoMRK

    Joined:
    Jul 18, 2010
    Posts:
    5,539
    You're removing items while you're looping over the collection.

    What is data and why are you removing things from it in the first place?

    The other option - if you absolutely have to remove items - is to copy data to another array each OnGUI call and use the copy to iterate.
     
  26. GladGoblinGames

    GladGoblinGames

    Joined:
    Dec 14, 2011
    Posts:
    118
    Code (CSharp):
    1. using UnityEngine;
    2. using System.Collections;
    3. using System.Collections.Generic;
    4.  
    5. public class QuestionsAndAnswers : MonoBehaviour {
    6.  
    7.     [System.Serializable]
    8.     public class QuestionData
    9.     {
    10.         public string question;
    11.         public List<string> answers;
    12.  
    13.     }
    14. }
    15.  
    Code (CSharp):
    1. public List<QuestionsAndAnswers.QuestionData> data = new List<QuestionsAndAnswers.QuestionData>();
    This is what data is, I can't remember why I did it like this exactly...but this is how it was setup xD would it be easier to set it up a different way?
     
  27. KelsoMRK

    KelsoMRK

    Joined:
    Jul 18, 2010
    Posts:
    5,539
    No I think that makes sense - outside of the class being nested.

    In either case - you're removing items from a collection while you're looping over it. That's what's causing your issue; so don't do that :)
     
  28. GladGoblinGames

    GladGoblinGames

    Joined:
    Dec 14, 2011
    Posts:
    118
    Omggggggggg. How could I have forgot about my for loop! I thought it was a problem with removing from the list the whole time! Strange thing is, I'm getting some odd values when I Debug.Log the for loop.
    Code (CSharp):
    1. for(int i = 0; i < data[index].answers.Count; i++)
    The value of 'i' is totally random when I do
    Code (CSharp):
    1. Debug.Log("Length: " + data[index].answers[i].Length);
     
  29. KelsoMRK

    KelsoMRK

    Joined:
    Jul 18, 2010
    Posts:
    5,539
    That's the length of the string located at i index in the answers List. So yes, I would think it would vary.
     
  30. GladGoblinGames

    GladGoblinGames

    Joined:
    Dec 14, 2011
    Posts:
    118
    Oops xD that explains it.

    Right, so all's good so far, I just created a boolean to stop the for loop from running if data.Count is less than or equal to one and the last answer is correct. Problem is, I still get the argument error happening at least once. Which is a lot better than what it was doing before, which was constantly repeating the same error. So the boolean is working. I just can't get the for loop to turn off before the next OnGUI frame is called :/ is there a way round this?
     
  31. KelsoMRK

    KelsoMRK

    Joined:
    Jul 18, 2010
    Posts:
    5,539
    I'll ask again why you're removing items from the collection in the first place. And then mention again the way around it is to copy the collection each OnGUI call and enumerate the copy instead of the "real" one :)
     
  32. GladGoblinGames

    GladGoblinGames

    Joined:
    Dec 14, 2011
    Posts:
    118
    Well, its basically to move onto the next randomly selected question really. If I use random range to select a new data index number, I could get the same question twice if I don't remove it. So I was using data.RemoveAt(index) to stop the same question from coming up twice. Hope that makes sense. I can post the code I have so far with full comments if it would make it easier to understand XD
     
  33. KelsoMRK

    KelsoMRK

    Joined:
    Jul 18, 2010
    Posts:
    5,539
    Then copy the list and use the copy to draw the UI.

    The other option would be to keep a list of integers representing index values in your question list that haven't been used yet. Then Random.Range that list and remove from it instead of your question list.