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

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

ただいまの
回答率

87.35%

UnityのC#スクリプトで、別のスクリプトで書いたCoroutineを実行したい/NullReferenceExceptionがでる

解決済

回答 1

投稿 編集

  • 評価
  • クリップ 0
  • VIEW 666

score 15

状況

シーン上には
・Button1
・Button2
・Button3
という3つのオブジェクトがある。

Button1にアタッチしたスクリプトで、String変数”Type"で条件分岐し「指定されたURLへリクエストを送って自身のテクスチャを変更する」「子要素のマテリアルの色を指定の色に変更する」という処理を行なうコルーチンを定義した。
これをUpdate()内で呼んで実行したところ、期待通り動作した。

他のButtonもButton1と同じように色やテクスチャ画像を変更したいが、条件分岐の処理はまとめてどこかに置いておきたいので(でないと分岐が一つ加わると全部変えないといけない)、他のオブジェクトではこのスクリプトに書いた同一のコルーチンを呼び出す形で使いたい。

試したこと

とりあえず、Button2にアタッチするスクリプトでpublic Button1Script button1Scriptとして、UnityのGUI上でオブジェクトを紐付けた上で、Update()内で StartCoroutine(button1Script.myCoroutine()); という形で呼んでみたところ、

NullReferenceException: Object reference not set to an instance of an object


と言われた。

書いたコード抜粋

Button1Script

public IEnumerator myCoroutine()
    {
        bool once = true;
        string url;
        Texture2D tex;
        Color32 color;

        if (once)
        {
            if (type == "twitter")
            {
                url = "hoge";
                color = new Color32(29, 161, 250, 255);
            }
            else if (type == "facebook")
            {
                url = "hogehoge";
                color = new Color32(36, 119, 241, 255);
            }
            else
            {
                url = "";
                color = new Color32(0, 0, 0, 0);
            }


            var request = UnityWebRequestTexture.GetTexture(url);
            yield return request.Send();
            tex = new Texture2D(4, 4, TextureFormat.DXT1, false);

            tex.LoadImage(request.downloadHandler.data);
            GetComponent<Renderer>().material.mainTexture = tex;
            cylinder.GetComponent<Renderer>().material.color = color;
            once = false;
            yield break;
        }
    }

    // Update is called once per frame
    void Update()
    {
        type = "hogehoge";//こちらも別スクリプトから取得してきた変数

        StartCoroutine(myCoroutine());

    }

Button2Script

public Button1Script button1Script;//GUIでできたエリアにhierarchyからButton1を突っ込む
void Update(){
type = "hogehogehoge";
StartCoroutine(button1Script.myCoroutine());//エラーメッセージ発生箇所

教えていただきたいこと

Button2Scriptで、これに近い形でコルーチンを呼び出す方法はありますでしょうか?
もし難しそうであれば、「これならいける」という方法をご教授いただけますでしょうか。

散々調べたのですがどうも適切そうな文献が見当たらず。

まだ未熟で稚拙な質問となってしまったかもしれませんが、何卒よろしくお願いいたします!
もし追加で必要な情報等ありましたら迅速に対応させていただきますのでお申し付けくださいませ。

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

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

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

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 過去に投稿した質問と同じ内容の質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

質問への追記・修正、ベストアンサー選択の依頼

  • vo3

    2019/10/20 01:14

    ところで、Button1にはRendererコンポーネントが割り付いていて、cylinderの参照もあり、かつcylinderにはRendererコンポーネントが割り付いているでいいんですよね?

    キャンセル

  • george6493

    2019/10/20 02:33

    vo3さま、間違いありません。ボタンのUI部分をQuadのテクスチャを変えることで表示して、Quadの下にCylinderを重ねることでボタンを雑に表現しています。そして、Button1にアタッチしたButton1Scriptでは正常に動作しております。

    キャンセル

  • vo3

    2019/10/20 09:07

    で、00:03のコメントから判断すると、コルーチン引数にtypeを渡した場合は意図した動きではないけどNullExceptionはでなくなったということでよろしいですか?

    キャンセル

回答 1

checkベストアンサー

0

NullPointerException がどこで発生しているかはこの情報ではちょっとわかりません。
他の方々が言われているように button1Script がnullになっているが一番考えられます。
ただ、 type を引数渡しにした場合に正常動作したのであれば、ここに記載されているコード以外が影響している可能性があります。

というのも、george6493さんは恐らく 【Button2でbutton1Script.myCoroutineを実行すれば、Button2のパラメータで動作する】と考えられているのだと思いますが、myCoroutineは Button1のインスタンスメソッド なのでButton1上のパラメータで動作します。
なので、例えばcilinderはButton1のどこで取得したかによっておかしくなる可能性はあります。

type を引数に渡して正常動作したのは、

type = "hogehogehoge";
StartCoroutine(button1Script.myCoroutine());


でtypeを設定しても button1Script.myCoroutine 内のtypeには一切関係ないので分岐で必ずelseになっていたのが、ようやく判断できるようになったからです。
button1Script はあくまでも参照先の保存ですので、みんながButton1を見に行くようになるわけです。

ちなみにですが、コルーチン内で宣言した変数はそのコルーチン内でしか効果がありません。
その為、コルーチン内で

bool once = true;
if (once)
{
  yield return 数フレームの処理
  once = false;
}


を実行しても呼び出しの度に実行されます。
なので、Button1Script/Button2ScriptともにUpdateで実行されているのはテストだと思いたいのですが、この二つが同一プロジェクトで有効な場合は URLから取得が時間がかかるものだとしても毎フレームButton1/Button2のどちらからもリクエストが実行される ようになっています。
今現在だと myCoroutine を以下のように変更すると

Debug.Log(gameObject.name + "S");
bool once = true;
if (once)
{
  yield return new WaitForSeconds(1);  // 一秒まつ
  once = false;
  Debug.Log(gameObject.name + "E");
  yield break;
}
Debug.Log(gameObject.name + "once lock E");
Button1S   //(Button1のUpdate)
Button1S   //(Button2のUpdate)
Button1S
Button1S
...
Button1S
Button1S
Button1S
Button1S
Button1E   // 最初のButton1からのリクエストから1秒経過
Button1E   // 最初のButton2からのリクエストから1秒経過
Button1S
Button1S
Button1E
Button1E
Button1S
Button1S
...

のようにButton1上でしか動作せず、「once lock E」というログも出ることなく、リクエストしまくるようになっています。
本仕様なのであればこちらの呼び出しは変えないとものすごい数のリクエストが実行されるので変更すべきです。

意図したものにしたいのであれば、少なくとも

  1. once フラグは Button1Script のメンバ変数にする(この場合はプロジェクト上で一つのみ同時リクエストが可)
  2. 外から使えるように必要とするもの(type/GameObject)は引数で渡す

の二つは変更が必要です。
または

  1. once フラグは各自が持って抑制する(呼び出し元が責任持たないと同じようになる)
  2. myCoroutine はインスタンスメソッドではなくStaticメソッドにする
  3. 外から使えるように必要とするもの(type/GameObject)は引数で渡す

です。
あとは、(むしろこちらが推奨)このテクスチャリクエストの処理を Button1 から切り離した別のスクリプトにし、必要とするGameObjectにそれぞれ割り付けるがいいです。
もちろん、onceフラグはコルーチンの中ではなく外で宣言してください。

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2019/10/21 17:34

    vo3様、本当にありがとうございます!!おかげさまで、
    ・Unityでのデバッグの手法
    ・変数のスコープ
    ・コルーチンって結局なんなのか
    といった、自分でググっていた時にモヤモヤしていた部分がかなり整理できました(実はUnity触って一ヶ月ほどでC#も都度拾ったハウツーと雰囲気で書いてました)。

    結局、推奨いただきました通り、ButtonManagerという別オブジェクトにアタッチしたスクリプトでButton1-3の情報を受け取って処理した後に渡す。onceはメンバ関数にしておいて、見せていただいたデバッグ手法でボタン3つ分のリクエストがそれぞれ1回ずつ行われていることを確認することができました。

    稚拙すぎて恥ずかしい質問でしたが、vo3さまのおかげで理解に繋げることができました。ありがとうございました。

    キャンセル

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

  • ただいまの回答率 87.35%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる

関連した質問

同じタグがついた質問を見る

  • トップ
  • C#に関する質問
  • UnityのC#スクリプトで、別のスクリプトで書いたCoroutineを実行したい/NullReferenceExceptionがでる