Unity: Remove The selected object from random.range - c#

I have a Quiz game with multiple choices. I Want to show random question however sometimes the same Questions is being picked more than once. I want to remove the selected questions out of my list so it doesn't get picked anymore..
This is the Game Manager that shows my question's
** Check the ShowQuestion Function there where i do my random. i basically want when a question is picked to not show again
public class GameController : MonoBehaviour {
private dataController DataController;
private roundData currentRoundData;
// QUestions that we will be working with in this round
private questionData[] questionPool;
private List<GameObject> answerButtonGameobjects = new List<GameObject>();
private List<GameObject> QuestionGameobjects = new List<GameObject>();
[Header("OTHER")]
public SimpleObjectPool answerButtonObjectPool;
public Transform answerButtonParent;
[Tooltip("Variables")]
// is the game going?
private bool isRoundActive;
private float timerRemaing;
// What number question we are on
private int questionIndex;
private int questionNumber = 1;
private int totalQuestions;
[Header("UI")]
[Header("TEXT ")]
public Text questionText;
public Text scoreDisplayText;
public Text timeRemainingDisplayText;
public Text highScoreText;
public Text questionIndexText;
void Start () {
DataController = FindObjectOfType<dataController>();
currentRoundData = DataController.getCurrentRoundData();
// stores our questions
questionPool = currentRoundData.questions;
timerRemaing = currentRoundData.timeLimitInSeconds;
questionIndex = 0;
totalQuestions = 10;
ShowQuestion();
}
// Show our 1st question
private void ShowQuestion()
{
RemoveAnsweredButtons();
// Hold current question data from our pool
questionData QuestionData = questionPool[Random.Range(0, totalQuestions)];
questionText.text = QuestionData.questionText;
for (int i = 0; i < QuestionData.answers.Length; i++)
{
GameObject answerButtonGameobject = answerButtonObjectPool.GetObject();
answerButtonGameobject.transform.SetParent(answerButtonParent);
answerButtonGameobjects.Add(answerButtonGameobject);
AnswerButton answerButton = answerButtonGameobject.GetComponent<AnswerButton>();
answerButton.Setup(QuestionData.answers[i]);
}
}
/// <summary>
/// Removes the old answers button before we display new ones.
/// </summary>
private void RemoveAnsweredButtons()
{
while (answerButtonGameobjects.Count > 0)
{
answerButtonObjectPool.ReturnObject(answerButtonGameobjects[0]);
answerButtonGameobjects.RemoveAt(0);
}
}
private void UpdateQuestionIndex()
{
questionIndex++;
questionNumber++;
questionIndexText.text = "Question " + (questionNumber.ToString()) + " out of 10";
}
}
The Questions and Answers are being generated using a Simple pool system that was taken from Unity Site.
Here's the pooling class
using UnityEngine;
using System.Collections.Generic;
// A very simple object pooling class
public class SimpleObjectPool : MonoBehaviour
{
// the prefab that this object pool returns instances of
public GameObject prefab;
// collection of currently inactive instances of the prefab
private Stack<GameObject> inactiveInstances = new Stack<GameObject>();
// Returns an instance of the prefab
public GameObject GetObject()
{
GameObject spawnedGameObject;
// if there is an inactive instance of the prefab ready to return, return that
if (inactiveInstances.Count > 0)
{
// remove the instance from teh collection of inactive instances
spawnedGameObject = inactiveInstances.Pop();
}
// otherwise, create a new instance
else
{
spawnedGameObject = (GameObject)GameObject.Instantiate(prefab);
// add the PooledObject component to the prefab so we know it came from this pool
PooledObject pooledObject = spawnedGameObject.AddComponent<PooledObject>();
pooledObject.pool = this;
}
// put the instance in the root of the scene and enable it
spawnedGameObject.transform.SetParent(null);
spawnedGameObject.SetActive(true);
// return a reference to the instance
return spawnedGameObject;
}
// Return an instance of the prefab to the pool
public void ReturnObject(GameObject toReturn)
{
PooledObject pooledObject = toReturn.GetComponent<PooledObject>();
// if the instance came from this pool, return it to the pool
if(pooledObject != null && pooledObject.pool == this)
{
// make the instance a child of this and disable it
toReturn.transform.SetParent(transform);
toReturn.SetActive(false);
// add the instance to the collection of inactive instances
inactiveInstances.Push(toReturn);
}
// otherwise, just destroy it
else
{
Debug.LogWarning(toReturn.name + " was returned to a pool it wasn't spawned from! Destroying.");
Destroy(toReturn);
}
}
}
// a component that simply identifies the pool that a GameObject came from
public class PooledObject : MonoBehaviour
{
public SimpleObjectPool pool;
}

It's untested, and vanilla 'psuedo' C# to give you a guideline
private List<int> _questions = null;
private int _current = 0;
void Start()
{
_questions = Enumerable.Range(0, questionPool.Length).ToList();
Shuffle(_questions);
}
void Shuffle(List<int> list)
{
// put fisher-yates algorithm here to shuffle the numbers
}
void ShowQuestion()
{
var idx = _questions[_current++];
var questionData = questionPool[idx];
}

Related

Can I create array of Scenes unity?

I want to create public Scene[] levels; and manually assign my levels to the array, then loop through it and generate level selection buttons, but it won't show up in the inspector.
Is there any workaround?
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement;
public class True : MonoBehaviour
{
// Start is called before the first frame update
public static int money;
[SerializeField]
public SceneManager[] scenes;
void Start()
{
}
// Update is called once per frame
void Update()
{
}
public void nextscencefirst()
{
SceneManager.LoadScene("Level2");
money++;
}
}
SceneManager is a built-in static class to control scenes during runtime. We can go to any scene by calling LoadScene method from that class. Also SceneManager cannot be seen in Inspector since it cannot be serialized class.
A reminder: Your scenes should be listed on build settings for these to work.
There are three ways to do what you want to do:
Method 1. Create a string list that holds names of scenes
public List<string> sceneNameList = new List<string>;
Method 2. Create a list of indices of scenes that added in build settings.
public List<int> sceneBuildIndexList = new List<int>
If the names of scenes are long or somehow difficult or you want to use sceneBuildIndex value it would be good to create a class and reach scenes from it
[System.Serializable]
public class SceneData
{
public int sceneBuildIndex;
public string sceneKey;
//public void LoadScene() //Maybe
}
public List<SceneData> sceneDataList = new List<SceneData>();
public SceneData GetSceneData(string key)
{
for (int i = 0; i < sceneDataList.Count; i++)
{
if (sceneDataList[i].sceneKey == key)
{
return sceneDataList[i];
}
}
return null;
}
Method 3: In Unity most classes are created from UnityEngine.Object, so it may let you assign scene from the Inspector.
[System.Serializable]
public class SceneData
{
public UnityEngine.Object scene;
public string key;
public void LoadScene()
{
if (scene == null)
{
Debug.LogError("Scene is not assigned");
return;
}
string pathToScene = AssetDatabase.GetAssetPath(scene);
SceneManager.LoadScene(pathToScene); //If the scene is not set in BuildSettings it will throw an error.
}
}
public List<SceneData> sceneDataList = new List<SceneData>();
public SceneData GetSceneData(string key)
{
for (int i = 0; i < sceneDataList.Count; i++)
{
if (sceneDataList[i].key == key)
{
return sceneDataList[i];
}
}
return null;
}
private void Awake()
{
SceneData data = GetSceneData("Level1");
if (data != null)
{
data.LoadScene();
}
}

I Can't get my quiz to pop up only once per item

I am trying to create a 3D cave exploration mini game for Android tablets.
Throughout the cave, there will be items that when the player touches them, they will get Destroyed On Collision at which point a window will pop up with a multiple choice quiz mini game.
When the player object picks up the first item and the quiz pops up, it forces me to complete the whole quiz whereas I want the questions to be one or two per item.
Below are my three scripts that control the Quiz.
QnA.cs
[System.Serializable]
public class QnA
{
public string Question;
public string[] Answers;
public int CorrectAnswers;
}
QuizManager.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class QuizManager : MonoBehaviour
{
public List<QnA> QnA;
public GameObject[] options;
public int CurrentQuestion;
public GameObject QuizPanel;
public GameObject GoPanel; //Game Over Panel
public Text QuestionTxt;
public Text ScoreText;
int totalQuestions = 0;
public int score;
private void Start()
{
totalQuestions = QnA.Count; //Sets the total number of questions to the count of inserted questions.
GoPanel.SetActive(false); //Deactivates the Game Over Panel
GenerateQuestions();
}
//What to do if the questions are over.
void GameOver()
{
QuizPanel.SetActive(false);
GoPanel.SetActive(true);
ScoreText.text = score + "/" + totalQuestions;
}
//Method attached to the button in the GameOverPanel to close the popup.
public void CloseWindow()
{
GoPanel.SetActive(false);
}
//What to do if the answer is correct.
public void correct()
{
score += 1;
QnA.RemoveAt(CurrentQuestion);
GenerateQuestions();
}
//What to do if the answer is wrong.
public void wrong()
{
QnA.RemoveAt(CurrentQuestion);
GenerateQuestions();
}
//Set the answer to the current question.
void SetAnswers()
{
for (int i = 0; i < options.Length; i++)
{
options[i].GetComponent<AnswerScript>().isCorrect = false;
options[i].transform.GetChild(0).GetComponent<Text>().text = QnA[CurrentQuestion].Answers[i];
if (QnA[CurrentQuestion].CorrectAnswers == i+1)
{
options[i].GetComponent<AnswerScript>().isCorrect = true;
}
}
}
void GenerateQuestions()
{
if (QnA.Count > 0)
{
CurrentQuestion = Random.Range(0, QnA.Count);
QuestionTxt.text = QnA[CurrentQuestion].Question;
SetAnswers();
}else
{
Debug.Log("Out of Questions");
GameOver();
}
}
}
AnswerScript.cs is the script that I have assigned the the multiple choice buttons in order to display if the answer is correct or wrong.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class AnswerScript : MonoBehaviour
{
public bool isCorrect = false;
public QuizManager quizManager;
public void Answer()
{
if (isCorrect)
{
Debug.Log("Correct!");
quizManager.correct();
}else
{
Debug.Log("Wrong");
quizManager.wrong();
}
}
}
I know that there is something that I have to add or change in the QuizManager to achieve what I want to do, but I have no idea.
I think I would adapt quizmanager to take an overview role, and implement a new class (e.g. quizinstance) that will display the questions and pass answers back to quizmanager. This way you can have quizmanager store all your questions and the players answers, and quizinstance pull x questions from the list when it gets created (when your collectable object gets destroyed).
Now when the player has answered as many questions as you want them to (x), quizinstance can be closed and the questions and answers associated with this quizinstance instance can be preserved in quizmanager.
Here is a rough attempt to explain what I mean. I haven't tested any of this code, but I hope it will give you a rough enough idea that you can fix any of the bugs it produces.
public static class QuizManager
{
public static List<QnA> Questions = new();
private static List<QnA> ServedQuestions = new();
public static int Score;
public static List<QnA> ServeQuestions()
{
return Questions.Where(q =>
!ServedQuestions.Contains(q)).Take(Mathf.Min(2, (Questions.Count -
ServedQuestions.Count))).ToList();
//this takes 2 questions in the event that there are atleast 2 un-asked questions left, the difference between total questions and asked questions otherwise
//IMPORTANT: if more than one QuizInstance is going to exist at once, you'll need to modify this code to add the questions this function returns to
//ServedQuestions now, rather than in StoreResult. If you don't, it's possible that the same question gets picked more than once.
}
public static void StoreResult(QnA answeredQuestion)
{
ServedQuestions.Add(answeredQuestion);
//QuizInstance will call this function after a question has been answered. It stores our QnA
}
}
[System.Serializable]
public class QnA
{
public string Question;
public string[] Answers;
public int CorrectAnswers;
}
public class QuizInstance : MonoBehaviour
{
public List<QnA> QnA;
public GameObject[] options;
public int CurrentQuestion;
public GameObject QuizPanel;
public GameObject GoPanel; //Game Over Panel
public Text QuestionTxt;
public Text ScoreText;
int totalQuestions = 0;
private void Start()
{
QnA.AddRange(QuizManager.ServeQuestions()); //Pull a list of
//questions for this QuizInstance to ask
totalQuestions = QnA.Count; //Sets the total number of questions
//to the count of inserted questions.
//GoPanel.SetActive(false); //Don't deactivate the panel here,
//have the panel deactivated in the editor and activate it when needed.
GenerateQuestions();
}
//What to do if the questions are over.
void GameOver()
{
QuizPanel.SetActive(false);
GoPanel.SetActive(true);
ScoreText.text = QuizManager.Score + "/" + totalQuestions;
}
//Method attached to the button in the GameOverPanel to close the popup.
public void CloseWindow()
{
GoPanel.SetActive(false);
}
//What to do if the answer is correct.
public void correct()
{
QuizManager.Score += 1;
QuizManager.StoreResult(QnA[CurrentQuestion]);
}
//What to do if the answer is wrong.
public void wrong()
{
QuizManager.StoreResult(QnA[CurrentQuestion]);
}
//Set the answer to the current question.
void SetAnswers()
{
for (int i = 0; i < options.Length; i++)
{
options[i].GetComponent<AnswerScript>().isCorrect = false;
options[i].transform.GetChild(0).GetComponent<Text>().text = QnA[CurrentQuestion].Answers[i];
if (QnA[CurrentQuestion].CorrectAnswers == i + 1)
{
options[i].GetComponent<AnswerScript>().isCorrect = true;
}
}
}
void GenerateQuestions()
{
if (QnA.Count > 0)
{
CurrentQuestion = UnityEngine.Random.Range(0, QnA.Count);
QuestionTxt.text = QnA[CurrentQuestion].Question;
SetAnswers();
}
else
{
Debug.Log("Out of Questions");
GameOver();
}
}
}
public class AnswerScript : MonoBehaviour
{
public bool isCorrect = false;
public QuizInstance quizInstance;
public void Answer()
{
if (isCorrect)
{
Debug.Log("Correct!");
quizInstance.correct();
}
else
{
Debug.Log("Wrong");
quizInstance.wrong();
}
}`

How to setup undo/redo system during runtime with Unity3D

I'm trying to set up an undo/redo system during runtime and couldn't get my code to work correctly. I have created an undo button, but when I moved my game object and press the undo button, the game object doesn't go back to its original states.
I just wanted to be able to have the end-user undo /redo his lastest action during runtime and not during Editor mode. I have made several attempts and have tried many different scripts but haven't made any progress. Any suggestions?
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class undoit : MonoBehaviour
{
public class setting
{
public GameObject Obj;
public Vector3 Pos;
public Quaternion Rot;
public bool Deleted;
public void Restore()
{
Obj.transform.position = Pos;
Obj.transform.rotation = Rot;
Obj.SetActive(Deleted);
}
public setting(GameObject g)
{
Obj = g;
Pos = g.transform.position;
Rot = g.transform.rotation;
Deleted = g.activeSelf;
}
}
public List<setting> UndoList;
public void AddUndo(GameObject g)
{
setting s = new setting(g);
UndoList.Add(s);
}
public void Undo()
{
if (UndoList.Count > 0)
{
UndoList[UndoList.Count - 1].Restore();
UndoList.RemoveAt(UndoList.Count - 1);
}
}
void Start()
{
UndoList = new List<setting>();
}
}
First you need something you can store the relevant data to like e.g.
public struct ObjectState
{
// The transform this data belongs to
private Transform transform;
private Vector3 localPosition;
private Quaternion localRotation;
private Vector3 localScale;
private bool active;
public ObjectState (GameObject obj)
{
transform = obj.transform;
localPosition = transform.localPosition;
localRotation = transform.localRotation;
localScale = transform.localScale;
active = obj.activeSelf;
}
public void Apply()
{
transform.localPosition = localPosition;
transform.localRotation = localRotation;
transform.localScale = localScale;
transform.gameObject.SetActive(active);
}
}
Next I would use a wrapper class to store undo able changes like
public struct UndoableChange
{
private ObjectState _before;
private ObjectState _after;
public UndoableChange (ObjectState before, ObjectState after)
{
_before = before;
_after = after;
}
public void Undo()
{
_before.Apply();
}
public void Redo()
{
_after.Apply();
}
}
Or in case you need it for my objects at once you could use lists e.g. something like
public struct UndoableChange
{
private List<ObjectState> _before;
private List<ObjectState> _after;
public UndoableChange (List<ObjectState> before, List<ObjectState> after)
{
_before = before;
_after = after;
}
public void Undo()
{
foreach(var state in _before)
{
state.Apply();
}
}
public void Redo()
{
foreach(var state in _after)
{
state.Apply();
}
}
}
Then for the controller I would use two Stack. A Stack is "last in - first out" so exactly what you need. You add new entries using Push and by using Pop you retrieve the last added element and at the same time remove it from the stack. So it would look like
public static class UndoRedo
{
private static Stack<UndoableChange> undoStack = new Stack<UndoableChange>();
private static Stack<UndoableChange> redoStack = new Stack<UndoableChange>();
public static void Undo()
{
if(undoStack.Count == 0) return;
var lastAction = undoStack.Pop();
lastAction.Undo();
redoStack.Push(lastAction);
}
public static void Redo()
{
if(redoStack.Count == 0) return;
var lastAction = redoStack.Pop();
lastAction.Redo();
undoStack.Push(lastAction);
}
public static void AddAction(UndoableChange action)
{
redoStack.Clear();
undoStack.Push(action);
}
}
I dont know how exactly your user interacts with the objects but you now would always do something like
// at some point store the initial values
var before = new ObjectState (selectedGameObject);
//TODO Apply all your changes to the selected object
// get the new values
var after = new ObjectState (selectedGameObject);
// Log to the history
UndoRedo.AddAction(new UndoableChange(before, after));
or use lists for all selected objects accordingly e.g. simply using Linq
using System.Linq;
...
var before = selectedGameObjects.Select(obj => new ObjectState(obj)).ToList();
//TODO Apply all your changes to all the selected objects
var after = selectedGameObjects.Select(obj => new ObjectState(obj)).ToList();
UndoRedo.AddAction(new UndoableChange(before, after));
Note: Typed on smartphone but I hope the idea gets clear

My first C# game in Unity (introducing playerprefs)

In my game, there is a profit count which is controlled by the addMoney variable in my money script ex. profitcount = addMoney.
When I add the player pref regarding my addMoney Variable it defaults the profitCount to 0 when it should in fact be 1. This is my first game, so it could be be very easily a small thing that I have misunderstood or overlooked.
moneyCount
public class moneyCount : MonoBehaviour
{
float timeTillAdd = 1;
public int addMoney = 1;
public int money;
public Text txt;
// Start is called before the first frame update
void Start()
{
money = PlayerPrefs.GetInt("Money");
addMoney = PlayerPrefs.GetInt("addmoney");
}
// Update is called once per frame
void Update()
{
PlayerPrefs.SetInt("addmoney", addMoney);
if (Time.time >= timeTillAdd)
{
money += addMoney;
timeTillAdd++;
}
txt.text = money.ToString();
PlayerPrefs.SetInt("Money", money);
}
}
profit count
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class profitCount : MonoBehaviour
{
public int profitAmount;
public GameObject moneyManagerObj;
moneyCount mc;
public Text txt;
// Start is called before the first frame update
void Start()
{
mc = moneyManagerObj.GetComponent<moneyCount>();
// profitAmount = PlayerPrefs.GetInt("profitamount");
}
// Update is called once per frame
void Update()
{
profitAmount = mc.addMoney;
txt.text = profitAmount.ToString();
// PlayerPrefs.SetInt("profitamount", profitAmount);
}
}
shopManager
public class ShopManager : MonoBehaviour
{
public int item1_cost = 50;
public int item1_upgrade = 5;
public int item1_tier = 1;
public int item2_cost = 50;
public int item2_upgrade = 5;
public GameObject moneyManagerObj;
moneyCount mc;
public Text txt;
public Text item1;
public Text item1_text;
// Start is called before the first frame update
void Start()
{
mc = moneyManagerObj.GetComponent<moneyCount>();
item1_cost = PlayerPrefs.GetInt("Item1_cost");
//item1_upgrade = PlayerPrefs.GetInt("Item1_upgrade");
item1_tier = PlayerPrefs.GetInt("Item1_tier");
}
// Update is called once per frame
void Update()
{
item1.text = item1_tier.ToString();
PlayerPrefs.SetInt("Item1_cost", item1_cost);
// PlayerPrefs.SetInt("Item1_upgrade", item1_upgrade);
PlayerPrefs.SetInt("Item1_tier", item1_tier);
if (item1_tier > 0)
{
item1_text.text = ("Upgrade");
}
}
public void on_click()
{
{
if (mc.money >= item1_cost)
{
mc.money -= item1_cost;
mc.addMoney += item1_upgrade;
item1_tier += 1;
item1.text = item1_tier.ToString();
item1_cost += 50 * item1_tier;
}
}
}
}
Two things:
Don't use PlayerPrefs to store save data. It isn't meant for that. Its meant for saving player preferences such as volume, full screen mode, or input type. The file used by PlayerPrefs is plain text and can't support complex data types.
If no save data exists, the values read out are zero (at least from PlayerPrefs). You need to account for this and currently you are not. When you move to a different method of saving, you'll get other errors, like null pointers or file not founds. You have to determine if a save exists and only if it does, should you read from it.
Ok so you were assigning default value to addMoney during initialization i.e public int addMoney = 1;
But then in your start you were again assigning it a value that has not been saved yet addMoney = PlayerPrefs.GetInt("addmoney");
If you want to create a record do it like this PlayerPrefs.SetInt("addmoney",addmoney);

Adding a point to variable every second doesn't work (Unity3d)

public static int pscore = 0;
void Start()
{
InvokeRepeating("AddToMoney",1, 1);
}
void AddToMoney ()
{
pscore++;
}
I have this code to add a point to integer pscore every second. But right now it adds 2 points instead of one. Is it because it's static? And if so, how can I fix it? Or how can I transfer one variable from script to another script without using static?
Note: I couldn't get answer in the unity3d forums, that's why I'm asking here.
add a point to integer pscore every second
bool keepIncrementing = false;
public static int pscore = 0;
void Start()
{
StartCoroutine(IncementEachSecond());
}
IEnumerator IncementEachSecond()
{
keepIncrementing = true;
while (keepIncrementing)
{
pscore++;
yield return new WaitForSeconds(1);
}
}
void stopIncrementing()
{
keepIncrementing = false;
}
how can I transfer one variable from script to another script without
using static
public class ScriptA : MonoBehaviour{
public int pscore = 0;
void Start()
{
}
}
Access variable pscore in ScriptA from ScriptB.
public class ScriptB : MonoBehaviour{
ScriptA scriptInstance = null;
void Start()
{
GameObject tempObj = GameObject.Find("NameOfGameObjectScriptAIsAttachedTo");
scriptInstance = tempObj.GetComponent<ScriptA>();
scriptInstance.pscore = 5;
}
}

Categories

Resources