Не получается автоматом брать элементы из json файла
Подскажите, пожалуйста, правильную структуру для проекта. На данный момент подгружается два списка один пустой и в него можно вводить вопросы-ответы руками и все работает но хотелось бы брать рандомные вопросы и ответы из второго или вообще без сериализации сразу из файла
using System;
using System.Collections.Generic;
using TMPro;
using UnityEngine;
using UnityEngine.UI;
public class Quiz : MonoBehaviour
{
[Header("Questions")]
[SerializeField] TextMeshProUGUI questionText;
[SerializeField] List<JSONReader> questions = new List<JSONReader>();
JSONReader currentQuestion;
[Header("Answers")]
[SerializeField] GameObject[] answerButtons;
int correctAnswerIndex;
bool hasAnsweredEarly = true;
[Header("Button Colors")]
[SerializeField] Sprite defaultAnswerSprite;
[SerializeField] Sprite correctAnswerSprite;
[Header("Timer")]
[SerializeField] Image timerImage;
Timer timer;
[Header("Scoring")]
[SerializeField] TextMeshProUGUI scoreText;
ScoreKeeper scoreKeeper;
[Header("BestScoring")]
[SerializeField] TextMeshProUGUI bestScoreText;
[Header("ProgressBar")]
[SerializeField] Slider progressBar;
public bool isComplete;
void Awake()
{
timer = FindObjectOfType<Timer>();
scoreKeeper = FindObjectOfType<ScoreKeeper>();
progressBar.maxValue = questions.Count;
progressBar.value = 0;
UpdateScoreText();
GetBestScoreText();
Console.WriteLine(questions);
}
void Update()
{
timerImage.fillAmount = timer.fillFraction;
if (timer.loadNextQuestion)
{
if (progressBar.value == progressBar.maxValue)
{
isComplete = true;
return;
}
hasAnsweredEarly = false;
GetNextQuestion();
timer.loadNextQuestion = false;
}
else if (!hasAnsweredEarly && !timer.isAnsweringQuestion)
{
DisplayAnswer(-1);
SetButtonState(false);
UpdateScoreText();
GetBestScoreText();
}
}
public void OnAnswerSelected(int index)
{
hasAnsweredEarly = true;
DisplayAnswer(index);
SetButtonState(false);
timer.CancelTimer();
UpdateScoreText();
GetBestScoreText();
}
void UpdateScoreText()
{
scoreText.text = "Score: " + scoreKeeper.CalculateScore();
}
void GetBestScoreText()
{
bestScoreText.text = "Best score: " + scoreKeeper.MaxCorrectAnswers();
}
void DisplayAnswer(int index)
{
Image buttonImage;
if (index == currentQuestion.GetCorrectAnswerIndex())
{
questionText.text = "Correct!";
buttonImage = answerButtons[index].GetComponent<Image>();
buttonImage.sprite = correctAnswerSprite;
scoreKeeper.IncrementCorrectAnswers();
}
else
{
correctAnswerIndex = currentQuestion.GetCorrectAnswerIndex();
string correctAnswer = currentQuestion.GetAnswer(correctAnswerIndex);
questionText.text = "Sorry, the correct answer was:\n" + correctAnswer;
buttonImage = answerButtons[correctAnswerIndex].GetComponent<Image>();
buttonImage.sprite = correctAnswerSprite;
}
}
void GetNextQuestion()
{
if (questions.Count > 0)
{
SetButtonState(true);
SetDefaultButtonSprites();
GetRandomQuestion();
DisplayQuestion();
progressBar.value++;
scoreKeeper.IncrementQuestionsSeen();
}
}
void GetRandomQuestion()
{
int index = UnityEngine.Random.Range(0, questions.Count);
currentQuestion = questions[index];
if (questions.Contains(currentQuestion))
{
questions.Remove(currentQuestion);
}
}
void DisplayQuestion()
{
questionText.text = currentQuestion.GetQuestion();
for (int i = 0; i < answerButtons.Length; i++)
{
TextMeshProUGUI buttonText = answerButtons[i].GetComponentInChildren<TextMeshProUGUI>();
buttonText.text = currentQuestion.GetAnswer(i);
}
}
void SetButtonState(bool state)
{
for (int i = 0; i < answerButtons.Length; i++)
{
Button button = answerButtons[i].GetComponent<Button>();
button.interactable = state;
}
}
void SetDefaultButtonSprites()
{
for (int i = 0; i < answerButtons.Length; i++)
{
Image buttonImage = answerButtons[i].GetComponent<Image>();
buttonImage.sprite = defaultAnswerSprite;
}
}
}
using UnityEngine;
public class JSONReader : MonoBehaviour
{
public TextAsset textJSON;
public Question questionJSON = new Question();
[SerializeField] int correctAnswerIndex = 0;
[System.Serializable]
public class Question
{
public string question;
public string answer;
public string answer1;
public string answer2;
public string answer3;
}
[System.Serializable]
public class QuestionList
{
public Question[] questions;
}
public QuestionList myQuestionList = new QuestionList();
void Start()
{
myQuestionList = JsonUtility.FromJson<QuestionList>(textJSON.text);
}
public string GetQuestion()
{
return questionJSON.question;
}
public string GetAnswer(int index)
{
return questionJSON.answer;
}
public int GetCorrectAnswerIndex()
{
return correctAnswerIndex;
}
}
Ответы (1 шт):
Суть вопроса абсолютно не понятна, но речь вроде о архитектуре.
Без сериализации сразу из файла это как? А сериализаци сама по себе это не сразу из файла текста в объект данных? Хочешь миновать наличие данных и работать с ... чем-то?
Чем болеет JSONReader вообще не понятно. Там есть questionJSON и myQuestionList сериализуемый из textJSON и это ВООБЩЕ НИКАК не связано друг с другом.
Видимо ты хочешь иметь вопросы из двух источников, текстового файла и из объекта сцены прописанные в инспекторе.
Нужно разделять суп от мух и следовать принципу единой ответственности Single Responsibility Principle.
Нужен контейнер который просто содержит вопросы не важно откуда. Можно только добавлять, нельзя удалять или редактировать.
// хранилище всех вопросов
public class QuestionCollection : MonoBehaviour
{
public IList<Question> Questions;
private List<Question> _questions = new List<Question>();
private void Awake ()
{
Questions = _questions.AsReadOnly();
}
public void AddQuestion (Question question) => _questions.Add(question);
public void AddQuestions (IEnumerable<Question> questions) => _questions.AddRange(questions);
}
И два источника хранящих вопросы, тестовый файл и объект, который можно редактировать в инспекторе, ScriptableObject прекрасно подойдет.
[CreateAssetMenu(fileName = "QuestionHolder", menuName = "Question/Holder")]
public class QuestionHolder : ScriptableObject
{
[SerializeField] private Question[] _elements;
public IEnumerable<Question> Questions => _elements;
}
И те кто будут наполнять коллекцию из разных источников, каждый новый источник и способ, новый класс и нет нужды переписывать код для добавления.
// наполняет хранилище вопросами, написанными инспекторе
[RequireComponent(typeof(QuestionCollection))]
public class QuestionSorceHolder : MonoBehaviour
{
// но можно и без ScriptableObject, полем Question[],
// хотя разделять функции хранения и заполнение правильнее.
[SerializeField] private QuestionHolder[] _sorces;
private void Awake ()
{
QuestionCollection collection = GetComponent<QuestionCollection>();
foreach (var sorce in _sorces)
if (sorce != null)
collection.AddQuestions(sorce.Questions);
Destroy(this);
}
}
// наполняет хранилище вопросами, написанными в текстовом файле
[RequireComponent(typeof(QuestionCollection))]
public class QuestionSorceText : MonoBehaviour
{
[SerializeField] private TextAsset[] _sorces;
private void Awake ()
{
QuestionCollection collection = GetComponent<QuestionCollection>();
foreach (var sorce in _sorces)
if (sorce != null)
collection.AddQuestions(JsonUtility.FromJson<QuestionArray>(sorce.text).elements);
Destroy(this);
}
[Serializable]
public struct QuestionArray
{
public Question[] elements;
}
}
Работа не с одним ScriptableObject/TextAsset позволяет не создавать одного гигантского списка, а удобного множества объектов по темам с понятными названиями типа Принцессы из сказок, Марки автомобилей, Культовые порноактрисы 90ых...
Если есть что-то с именами ...1, ...2, ...3, ...4, то это массив, а не куча полей.
[Serializable]
public struct Question
{
public string question;
public string[] answers; // first answer is correct
}
Чем занимается класс Quiz не понятно, судя по тому что так называется сама игра... АБСОЛЮТНО ВСЕМ. Это называется God class, антипаттерн.
// выдавать новый вопрос с перетасованными ответами
public class QuestionGenerator : MonoBehaviour
{
[SerializeField] private QuestionCollection _collection;
public int QuestionIndex { get; private set; }
public Question Question { get; private set; }
public int CorrectAnswerIndex { get; private set; }
public void Next ()
{
QuestionIndex = GetIndex();
Question = _collection.Questions[QuestionIndex];
CorrectAnswerIndex = 0;
RollAnswers();
}
private void RollAnswers ()
{
int lenght = Question.answers.Length;
string correct = Question.answers[0];
for (int i = 0; i < lenght; i++)
{
int roll = UnityEngine.Random.Range(0, lenght);
if (roll == i)
continue;
string temp = Question.answers[i];
Question.answers[i] = Question.answers[roll];
Question.answers[roll] = temp;
}
// перетасовывать массив все-же удобнее, чем кучу полей
for (int i = 0; i < lenght; i++)
if (Question.answers[i] == correct)
{
RigthAnserIndex = i;
break;
}
}
private int GetIndex () => UnityEngine.Random.Range(0, _collection.Questions.Count);
}
// буквально говорящее название, отвечающее за ход игры, дать вопрос и принять на него ответ
public class Session : MonoBehaviour
{
[SerializeField] private QuestionGenerator _generator;
[SerializeField] private QuestionView _view;
public void NewQuestion ()
{
_generator.Next();
_view.SetQuestion(_generator.Question);
}
public void GiveAnswer (int index)
{
if (index == _generator.CorrectAnswerIndex)
OnSuccessAnswer(index);
else
OnFailAnswer(index);
}
private void OnSuccessAnswer (int index)
{
_view.GaveSuccessAnswer(index);
...
}
private void OnFailAnswer (int index)
{
_view.GaveFailAnswer(index);
...
}
}
// отвечает за визуализацию вопроса (UI)
public class QuestionView : MonoBehaviour
{
...
public void SetQuestion (Question question) { ... }
public void GaveSuccessAnswer (int index) { ... }
public void GaveFailAnswer (int index) { ... }
}
У каждого класса лишь одна понятная и четкая ответственность.