質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.48%
C#

C#はマルチパラダイムプログラミング言語の1つで、命令形・宣言型・関数型・ジェネリック型・コンポーネント指向・オブジェクティブ指向のプログラミング開発すべてに対応しています。

Unity

Unityは、Unity Technologiesが開発・販売している、IDEを内蔵するゲームエンジンです。主にC#を用いたプログラミングでコンテンツの開発が可能です。

Q&A

解決済

2回答

2484閲覧

C#(Unity)でクイズゲームを作る際の、ランダム出題用の配列について

yamasho69

総合スコア19

C#

C#はマルチパラダイムプログラミング言語の1つで、命令形・宣言型・関数型・ジェネリック型・コンポーネント指向・オブジェクティブ指向のプログラミング開発すべてに対応しています。

Unity

Unityは、Unity Technologiesが開発・販売している、IDEを内蔵するゲームエンジンです。主にC#を用いたプログラミングでコンテンツの開発が可能です。

0グッド

0クリップ

投稿2019/03/24 13:10

前提・実現したいこと

  1. Unityでクイズゲームを作る。

過去にこのWebページを参考にJavaでクイズゲームを作成したので、Unityで作成しなおし、ブラッシュアップしたい。
0. 問題はCSV管理する。
0. 問題はランダムに10問出題する。←ここで詰まっています。

発生している問題・エラーメッセージ

1問目はランダム出題されるのですが、1問目を回答し、2問目の出題を行う際に停止してしまいます。

  1. 空のオブジェクトにQuizMgrスクリプトをアタッチし、1問目を出題します。
  2. 配列Order1を作成し、OからCSVの行数分数字を入れます。
  3. Order2でOrder1をシャッフルします。
  4. Order2の前から2番目の問題を出題します。
  5. 4つの回答ボタンにJudgeスクリプトをアタッチし、

正解の回答ボタンをクリックするとScoreが10点加算されるところまではできています。

しかし、Judgeクラスから、QuizMgrクラスのNextQuizSetメソッドを呼ぶところで、以下のエラーコードが表示されます。

JudgeクラスからNextQuizSetメソッドを呼んだ場合は、Order2の要素が元から入っていないと判断されているのはなんとなくわかるのですが、QuizMgrクラスのStartメソッドが実行された時点で、Order2の要素は固定することはできないのでしょうか。
大変わかりにくい説明となっていしまいましたが、ご教示いただけると幸いです。

エラーコード NullReferenceException: Object reference not set to an instance of an object QuizMgr.QuizSet () (at Assets/QuizMgr.cs:58) QuizMgr.NextQuizSet () (at Assets/QuizMgr.cs:77) Judge.CapText () (at Assets/Judge.cs:26) UnityEngine.Events.InvokableCall.Invoke () (at C:/buildslave/unity/build/Runtime/Export/UnityEvent.cs:166) UnityEngine.Events.UnityEvent.Invoke () (at C:/buildslave/unity/build/Runtime/Export/UnityEvent_0.cs:58) UnityEngine.UI.Button.Press () (at C:/buildslave/unity/build/Extensions/guisystem/UnityEngine.UI/UI/Core/Button.cs:36) UnityEngine.UI.Button.OnPointerClick (UnityEngine.EventSystems.PointerEventData eventData) (at C:/buildslave/unity/build/Extensions/guisystem/UnityEngine.UI/UI/Core/Button.cs:45) UnityEngine.EventSystems.ExecuteEvents.Execute (IPointerClickHandler handler, UnityEngine.EventSystems.BaseEventData eventData) (at C:/buildslave/unity/build/Extensions/guisystem/UnityEngine.UI/EventSystem/ExecuteEvents.cs:50) UnityEngine.EventSystems.ExecuteEvents.Execute[IPointerClickHandler] (UnityEngine.GameObject target, UnityEngine.EventSystems.BaseEventData eventData, UnityEngine.EventSystems.EventFunction`1 functor) (at C:/buildslave/unity/build/Extensions/guisystem/UnityEngine.UI/EventSystem/ExecuteEvents.cs:261) UnityEngine.EventSystems.EventSystem:Update()

C#

1using UnityEngine; 2using System.Collections; 3using System.Collections.Generic;// 追記 4using UnityEngine.UI;//UI利用時必須 5using System.Linq;//配列に必要 6using System;//これがないとGuidクラスは使えない 7 8public class QuizMgr: MonoBehaviour { 9 public TextAsset csvFile;      // GUIでcsvファイルを割当 10    List<string[]> csvDatas = new List<string[]>(); //ここは参考ブログとは違う 11 public static string AnswerStr;//問題の答え 12 int[] Order1 = null; //出題数を管理するメンバ変数 13 public int[] Order2; //出題をランダムにするメンバ変数 14 public static int Count = 1; //今何問目か 15 public int Score = 0; //得点 16 17    //スタート時、CSVファイルを読み込む 18    public void Start() { 19        // 格納 20        string[] lines = csvFile.text.Replace("\r\n", "\n").Split("\n"[0]); 21 foreach (var line in lines) { 22 if (line == "") { continue; } 23 csvDatas.Add(line.Split(','));    // string[]を追加している 24        } 25 26 Order1 = new int[csvDatas.Count]; //配列の要素数をCSVの行数分にする 27 //配列に順番に数字を入れる 28 for(int i = 0; i < csvDatas.Count-1; i++) { 29 Order1[i] = i; 30 } 31 //配列の数字をランダムに入れ替えた配列を作成 32 Order2 = Order1.OrderBy(i => Guid.NewGuid()).ToArray(); 33 34 //問題をセットするメソッドを呼び出す 35 QuizSet(); 36    } 37 //問題をセットするメソッド 38 void QuizSet() { 39 40 //1から4の配列(ary1)を作成 41 int[] ary1 = new int[] {1, 2, 3, 4}; 42 //ary1をランダムに並び替えたary2を作成 43 int[] ary2 = ary1.OrderBy(i => Guid.NewGuid()).ToArray(); 44 45 //for (int i =0; i < csvDatas.Count; i++) { 46 47 //答えをセット 48 AnswerStr = csvDatas[Order2[Count]][1]; 49 //特定の名前のオブジェクトを検索してアクセス 50 Text question = GameObject.Find("Quiz/Question").GetComponentInChildren<Text>(); 51 Text button1 = GameObject.Find("Quiz/Button1").GetComponentInChildren<Text>(); 52 Text button2 = GameObject.Find("Quiz/Button2").GetComponentInChildren<Text>(); 53 Text button3 = GameObject.Find("Quiz/Button3").GetComponentInChildren<Text>(); 54 Text button4 = GameObject.Find("Quiz/Button4").GetComponentInChildren<Text>(); 55 //データをセットすることで、既存情報を上書きできる 56 question.text = csvDatas[Order2[Count]][0];//問題文セット 57 button1.text = csvDatas[Order2[Count]][ary2[0]];//ary2の1番目に入っている数字の項目をボタン1にセット 58 button2.text = csvDatas[Order2[Count]][ary2[1]];//ary2の2番目に入っている数字の項目をボタン2にセット 59 button3.text = csvDatas[Order2[Count]][ary2[2]];//ary2の3番目に入っている数字の項目をボタン3にセット 60 button4.text = csvDatas[Order2[Count]][ary2[3]];//ary2の4番目に入っている数字の項目をボタン4にセット 61 //} 62 } 63 public void NextQuizSet() { 64 Count++; 65 Debug.Log(Count); 66 if(Count == 11) { 67 } else {QuizSet();} 68 } 69}

C#

1using UnityEngine; 2using UnityEngine.UI; 3 4public class Judge : MonoBehaviour { 5 6 public static Text selectedBtn; 7 8 //選択したボタンのテキストラベルと正解のテキストを比較して正誤を判定 9 public void CapText() { 10 11 QuizMgr quizMgr = new QuizMgr();//他のクラスのメソッドを使用するためにはオブジェクト作成が必要 12 13 //選択したボタンのテキストラベルを取得する 14 Text selectedBtn = this.GetComponentInChildren<Text>(); 15 16 //選択したボタンのテキストラベルと問題の答えを比較 17 if (selectedBtn.text == QuizMgr.AnswerStr) { 18 quizMgr.Score += 10; 19 } 20 quizMgr.NextQuizSet();//上記で作成したオブジェクトを使用する 21 } 22}

試したこと

Judgeクラスに配列Order3を作成し、Order2の要素を渡す方法等も考えましたが、うまくいきませんでした。

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

回答2

0

ベストアンサー

58行目が以下だとすれば、
button2.text = csvDatas[Order2[Count]][ary2[1]];
「button2」「csvDatas」「Order2」「ary2」のどれかnullになっている可能性が高いです。
Debug.Logを使用して、具体的に何がnullなのかをまず確認してみてください。

ありえそうなのは、別の部分で「button2」のTextをオフにしている結果GameObject.Find("Quiz/Button2").GetComponentInChildren<Text>()に失敗しているとかでしょうか。

また、Startメソッドで設定した値は、シーンを変更しない限りは維持されます。
なので本来はcsvDatas、Order2もnullにならないはずなのですが、別スクリプトが影響していないかも確認してみてください。


QuizMgr quizMgr = new QuizMgr();を見落としてたので追記)

そもそも現状は「MonoBehaviourに'new'キーワードを使うことは許されていません」という意味合いの警告が出ていると思います。

QuizMgrはオブジェクトに付いていると思うので、newではなく
QuizMgr quizMgr = GetComponent<QuizMgr>();
のように「オブジェクトに付いたクラスを取得」する必要があります。(※他オブジェクトに付いている場合は書き方がちょっと変わるので調べてください)

他のクラスのメソッドを使用するためにはオブジェクト作成が必要というのはちょっと語弊があります。

■最初からQuizMgrをGameObjectに付けてある場合
→シーン開始時にQuizMgrのStartメソッドが呼ばれます。
→既にオブジェクトが存在している為、新たにオブジェクトを作る必要はありません(GetComponentなどで「既存のGameObjectのコンポーネントを取得」する必要があります)。

■新たにQuizMgrをGameObjectに付ける場合
QuizMgr quizMgr = gameObject.AddComponent<QuizMgr>();とします。(なおここを何度も呼ぶとその数だけ新たなQuizMgrがくっ付くので、一度だけにしたい場合はちゃんと条件分岐する必要があります)
→「新たにQuizMgrを生成してGameObjectに付与(=新たにQuizMgrインスタンスを生成)」という意味になります。
→QuizMgrのStartメソッドは呼ばれますが、quizMgr.NextQuizSet();とどっちが早いかは保証されません(多分Startメソッドより前にNextQuizSetメソッドが呼ばれる可能性が高い)。

new QuizMgr()する場合(QuizMgrがMonoBehaviourを継承していない場合)
→「新たにQuizMgrを生成」という意味になります。(GameObjectには付与しない、というかMonoBehaviourを継承していないと付与出来ない)
→QuizMgrのStartメソッドは呼ばれません。コンストラクタを使用するか初期設定用メソッドを作って自分で呼ぶ必要があります。

後者の2つだと毎回Order2を生成しているようなものなので、
今回はGetComponentを使用した形式が適していると思います。
(「unity インスタンス」とか「unity 他オブジェクトのスクリプト」とかで検索すると詳しい情報が出ます)

投稿2019/03/25 02:14

編集2019/03/25 05:23
sakura_hana

総合スコア11427

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

yamasho69

2019/03/25 03:35

58行目はAnswerStr = csvDatas[Order2[Count]][1];です。 NextQuizSetメソッドでOrder2をDebug.Logした際にエラーが発生したので、おそらくnullになっていると思います。csvDatasは調べていないので、後ほど調べますが、どちらにせよStartメソッドで設定した値が保持されていないことになります。 本アプリは今のところ、QuizMgrスクリプトとJudgeスクリプトしかなく、シーンも一つしかありません。
yamasho69

2019/03/25 13:10

「unity 他オブジェクトのスクリプト」で検索し、出てきたページ (https://qiita.com/tsukasa_wear_parker/items/09d4bcc5af3556b9bb3a)を参考にし、 Judgeクラスに下記のコードを入れて、解決しました。お答えいただいたsakura_hana様、y_waiwai様、ありがとうございました。大変助かりました。 GameObject quizmanager; QuizMgr quizMgr; //選択したボタンのテキストラベルと正解のテキストを比較して正誤を判定 public void CapText() { quizmanager = GameObject.Find("QuizManager"); //QuizManagerをオブジェクトの名前から取得して変数に格納する quizMgr = quizmanager.GetComponent<QuizMgr>();//Mgrはスクリプト、managerはオブジェクト
guest

0

Startメソッドが実行されてるところは見当たらないですが。

投稿2019/03/24 22:01

y_waiwai

総合スコア87749

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

yamasho69

2019/03/24 22:33

シーンが実行された際に、QuizMgrクラスが実行され、その歳に public void Startメソッドが実行されます。 そこでOrder2の要素を決めているのですが、その時点で要素を固定できないのでしょうか。 JudgeクラスからQuizMgrクラスを呼び出した時に、再度Order2の要素を決めることになると、Order2の順序が変わってしまい、出題がかぶってしまう可能性があると思うのですが、間違っているでしょうか…。 【例】 StartメソッドでOrder2をランダムで{3、1、2}とします。 ファイル実行中はこの要素の順序を使い続けたいのです。 しかしJudgeクラスからQuizMgrクラスのNextQuizSetメソッドを 呼び出すとOrder2の要素がありません。 再度Order2の要素を決めると{2、1、3} のように変わってしまうのではないでしょうか…。
y_waiwai

2019/03/24 23:13

必要な初期化はコンストラクタで実行しときましょう
sakura_hana

2019/03/25 02:16

@y_waiwaiさん Unityの場合はStartがコンストラクタ的な動きをします。 (厳密には違うと思うのですが、MonoBehaviourを継承したクラスの場合はシーン上でこのインスタンスが稼働開始する時に勝手にStartメソッドが呼ばれる為) なのでStartメソッドに関してはこのままでも問題無いかと思います。
y_waiwai

2019/03/25 04:40

提示のコードのように、newして即何かをしようとする場合は、Startは呼ばれようがないと思うんですが。 まあ、そのstartメソッドが実行されるまで待つ、というテもあると思いますが
sakura_hana

2019/03/25 05:04

すみません、「QuizMgr quizMgr = new QuizMgr();」の行見落としてました。 この場合だと確かにStartは(いつまで待とうが)呼ばれないですね。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.48%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問