Не получается автоматом брать элементы из json файла

Рейтинг: 0Ответов: 1Опубликовано: 17.01.2023

Подскажите, пожалуйста, правильную структуру для проекта. На данный момент подгружается два списка один пустой и в него можно вводить вопросы-ответы руками и все работает но хотелось бы брать рандомные вопросы и ответы из второго или вообще без сериализации сразу из файлавведите сюда описание изображения

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;
        }
    }

Ответы

▲ 2Принят

Суть вопроса абсолютно не понятна, но речь вроде о архитектуре.

Без сериализации сразу из файла это как? А сериализаци сама по себе это не сразу из файла текста в объект данных? Хочешь миновать наличие данных и работать с ... чем-то?

Чем болеет 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) { ... }
}

У каждого класса лишь одна понятная и четкая ответственность.