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

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

ただいまの
回答率

87.79%

Unityで別シーンのオブジェクトにアタッチされているスクリプト内のメソッドを使用したい

解決済

回答 1

投稿

  • 評価
  • クリップ 0
  • VIEW 1,054

score 1

Unityで別シーンのオブジェクトにアタッチされているスクリプト内のメソッドを使用したい

ここに質問の内容を詳しく書いてください。
(例)PHP(CakePHP)で●●なシステムを作っています。
■■な機能を実装中に以下のエラーメッセージが発生しました。

Unityでゲームを作成しています
リザルト画面にゲームのスコアを表示したいので、そのためのメソッドを作成しておりましたが、恐らく別シーンのスクリプトの参照がうまくいっていません

詳しい状況の説明ですが
『シーン_ゲーム画面(昼)』というシーンの「GameControler_Hiru」という空のオブジェクトに、〈GameControl_Hiru〉というスクリプトをアタッチ
『シーン_ゲーム画面(昼)』というシーンの「障害物」というオブジェクトに、〈CabeBig〉というスクリプトをアタッチ
『シーン_リザルト画面』というシーンの「GameControler_Result」というオブジェクトに、〈Result〉というスクリプトをアタッチしております。

やりたいこととしては、『シーン_ゲーム画面(昼)』内で障害物に主人公が当たった場合に、〈CabeBig〉スクリプト内の≪OnCollisionStay2D≫メソッドで〈GameControl_Hiru〉スクリプト内の≪SendScore≫メソッドを呼び出し、≪SendScore≫メソッドで、別シーンの〈Result〉スクリプト内の≪get_score≫メソッドを呼び出しスコアを橋渡ししたいと思っています

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

NullReferenceException: Object reference not set to an instance of an object
GameControl_Hiru.SendScore () (at Assets/スクリプト/ゲーム画面(昼)/GameControl_Hiru.cs:127)
CabeBig.OnCollisionStay2D (UnityEngine.Collision2D col) (at Assets/スクリプト/CabeBig.cs:36)

該当のソースコード

CabeBigスクリプト

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement;

public class CabeBig : MonoBehaviour
{
    private GameObject player;

    // Start is called before the first frame update
    void Start()
    {
        player = GameObject.Find("プレハブ_紙飛行機");
    }

    // Update is called once per frame
    void Update()
    {
        Vector3 playerPos = player.transform.position;
        Vector3 thisPos = this.transform.position;

        if (playerPos.y - thisPos.y < -4)
        {
            Destroy(this.gameObject);
        }

    }

    void OnCollisionStay2D(Collision2D col)
    {
        if (col.gameObject.name == "プレハブ_紙飛行機")
        {
            if (SceneManager.GetActiveScene().name == "シーン_ゲーム画面(昼)")
            {
                GameObject game_root = GameObject.Find("GameControler_Hiru");
                game_root.GetComponent<GameControl_Hiru>().SendScore();
            }
            else
            {
                GameObject game_root = GameObject.Find("GameControler");
                game_root.GetComponent<GameControl_Yoru>().SendScore();
            }
        }
    }
}

--------------------------------------------------------
GameControl_Hiruスクリプト

using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using UnityEngine;
using UnityEngine.UI;
using UnityEngine.SceneManagement;

public class GameControl_Hiru : MonoBehaviour
{

    public GameObject prefab_cloude;
    public GameObject prefab_coin;
    public GameObject prefab_cabe;
    public GameObject prefab_cabe2;

    public double OldplayerPos;

    private GameObject player;

    private float timer = 0.0f;
    public int score = 0;
    public Text textTimer;

    public int Coin = 0;
    public Text textCoin;

    // Start is called before the first frame update
    void Start()
    {
        player = GameObject.Find("プレハブ_紙飛行機");
        Vector3 playerPos = player.transform.position;
        OldplayerPos = playerPos.y;
    }

    // Update is called once per frame
    void Update()
    {
        timer += Time.deltaTime;
        score = (int)timer;
        textTimer.text = score.ToString();

        Vector3 playerPos = player.transform.position;

        if (UnityEngine.Random.Range(0, 300) == 0)
        {
            CreateCloude();
        }

        if (UnityEngine.Random.Range(0, 3000) == 0)
        {
            CreateCoin();

        }

        if (OldplayerPos - playerPos.y >= 4)
        {
            CreateCabe();
            OldplayerPos = playerPos.y;
        }

    }

    private void CreateCloude()
    {

        Vector3 playerPos = player.transform.position;

        GameObject cloude = Instantiate(prefab_cloude);

        cloude.transform.position = new Vector3(UnityEngine.Random.Range(-200, 200) / 100.0f, playerPos.y - 8, 1);

    }

    private void CreateCoin()
    {

        Vector3 playerPos = player.transform.position;

        GameObject coin = Instantiate(prefab_coin);

        coin.transform.position = new Vector3(UnityEngine.Random.Range(-200, 200) / 100.0f, playerPos.y - 8, 1);

    }

    private void CreateCabe()
    {

        Vector3 playerPos = player.transform.position;

        int randomValue = UnityEngine.Random.Range(0, 3);

        UnityEngine.Debug.Log(randomValue);

        if (randomValue == 0)
        {
            GameObject cabe = Instantiate(prefab_cabe);
            GameObject cabe2 = Instantiate(prefab_cabe);

            cabe.transform.position = new Vector3(1.59f, playerPos.y - 8 - 0.625f, 0);
            cabe2.transform.position = new Vector3(-1.59f, playerPos.y - 8 - 0.625f, 0);
        }
        else if (randomValue == 1)
        {
            GameObject cabe = Instantiate(prefab_cabe2);

            cabe.transform.position = new Vector3(-0.86f, playerPos.y - 8 - 0.625f, 0);
        }
        else
        {
            GameObject cabe = Instantiate(prefab_cabe2);

            cabe.transform.position = new Vector3(0.86f, playerPos.y - 8 - 0.625f, 0);
        }
    }

    public void PlusCoin()
    {
        Coin++;
        textCoin.text = Coin.ToString();

    }

    public void SendScore()
    {
        UnityEngine.Debug.Log(6);
        GameObject.Find("GameControler_Result").GetComponent<Result>().get_score(score, Coin);
        UnityEngine.Debug.Log(7);
        SceneManager.LoadScene("シーン_リザルト画面");
    }
}

--------------------------------------------------------
Resultスクリプト

using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using UnityEngine;
using UnityEngine.UI;
using UnityEngine.SceneManagement;

public class Result : MonoBehaviour
{

    public int score = 0;
    private int coin = 0;
    private int total_score = 0;
    static private int [] high_score = { 0, 0, 0 };

    public Text Now_Score;
    public Text Now_Coin;
    public Text Now_Total_Score;
    public Text High_Score1;
    public Text High_Score2;
    public Text High_Score3;

    void Start()
    {

    }

    void Update()
    {
        if (Input.GetMouseButtonDown(0) || Input.GetKeyDown("left") || Input.GetKeyDown("a") || Input.GetMouseButtonDown(1) || Input.GetKeyDown("right") || Input.GetKeyDown("d"))
        {
            SceneManager.LoadScene("シーン_ゲーム開始画面");
        }
    }

    public void get_score(int a, int b)
    {
        score = a;
        coin = b;
        score_record();
    }

    public void score_record()
    {
        total_score = score * (1 + coin);
        if (total_score > high_score[0])
        {
            high_score[2] = high_score[1];
            high_score[1] = high_score[0];
            high_score[0] = total_score;
        }
        else if (total_score > high_score[1])
        {
            high_score[2] = high_score[1];
            high_score[1] = total_score;
        }
        else if (total_score > high_score[2])
        {
            high_score[2] = total_score;
        }
    }
}

試したこと

障害物はいくつか種類があるのですが(スクリプトの内容はおおむね同じ)、どの障害物にあたってもうまくいきません

GameControl_Hiruスクリプト内のSendScoreスクリプトを以下のようにしてどこまでちゃんと動いているか確認したところ、
public void SendScore()
{
UnityEngine.Debug.Log(6);
GameObject.Find("GameControler_Result").GetComponent<Result>().get_score(score, Coin);
UnityEngine.Debug.Log(7);
SceneManager.LoadScene("シーン_リザルト画面");
}
デバッグログには6しか表示されませんし、当然シーンも変更されませんでした

public void SendScore()
{
UnityEngine.Debug.Log(6);
//GameObject.Find("GameControler_Result").GetComponent<Result>().get_score(score, Coin);
UnityEngine.Debug.Log(7);
SceneManager.LoadScene("シーン_リザルト画面");
}
としてゲームオブジェクトに触る部分をコメントアウトした場合、6,7ともに表示されシーンも変更されます
よって良くない部分がこの部分であると判断しました

public void SendScore()
{
UnityEngine.Debug.Log(7);
GameObject Go = GameObject.Find("GameControler_Result");
UnityEngine.Debug.Log(8);
Go.GetComponent<Result>().get_score(score, Coin);
UnityEngine.Debug.Log(9);
SceneManager.LoadScene("シーン_リザルト画面");
}
一行で横着したのが良くないと思い以上のようにオブジェクトに触る部分を変更した場合、7,8まで表示されます

‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐
恐らくシーンをまたぐのが良くないと思い同じシーン内にTryというスクリプトを新たに作り、それをGameControler_Hiruオブジェクトにアタッチしてみました
Tryスクリプトの内容は以下です
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Try : MonoBehaviour
{
// Start is called before the first frame update
void Start()
{

}

// Update is called once per frame
void Update()
{

}

public void sample1(){
Debug.Log(10);
}
}

そのうえでGameControl_Hiruスクリプトを以下のように変更しました
public void SendScore()
{
GameObject.Find("GameControler_Hiru").GetComponent<Try>().sample1();
SceneManager.LoadScene("シーン_リザルト画面");
}
この場合、デバッグログにちゃんと10と出てくれますし、シーン変更もされます

上記から恐らく別シーンのスクリプトに触れるのに問題が発生していると考えます
scoreをstaticにして橋渡ししても、結局別シーンのスクリプトに触れない問題は解決されないので、あくまでメソッドを使ってスコアを橋渡ししたいと思っています

とても初歩的な内容だとは思いますが、やり方をご教授願えればと思います

補足情報(FW/ツールのバージョンなど)

UnityはUnity 2019.4.11f1 (64-bit)というものを使用しています

https://drive.google.com/file/d/15xqjHUvS_VX4qWAgGutcG4wTh6vxsds_/view?usp=sharing
ファイルは上記リンクからダウンロードできます
どうぞよろしくお願いします

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

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

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

    クリップを取り消します

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

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

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

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

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

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

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

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

    質問の評価を下げる

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

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

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

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

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

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

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

    詳細な説明はこちら

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

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

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

回答 1

checkベストアンサー

+1

GameObject.Findメソッドですが現在ロードしているシーンの中からオブジェクトをサーチするみたいですね。複数シーンをロードしている場合はすべてのシーンが対象になりますが、ロードしてないシーンのオブジェクトはサーチできないようです。

https://docs.unity3d.com/ja/current/ScriptReference/GameObject.Find.html
Note: If the game is running with multiple scenes then Find will search in all of them.

シーンの加算、アンロードするなども参考になるかと思います。

  SceneManager.LoadScene("OtherSceneName", LoadSceneMode.Additive);


https://docs.unity3d.com/ja/2019.4/ScriptReference/SceneManagement.SceneManager.UnloadSceneAsync.html

ちなみにシーンを完全に読み込むのに少し時間がかかるのでOnLoadイベントに登録しておいて次のシーンを呼び出せば登録したメソッドがロード完了したあとに実行されます。
以下参考のコードです。

public class SendTest : MonoBehaviour
{
    void OnSceneLoaded(Scene scene, LoadSceneMode mode)
    {
        if(scene.name == "Receive"){
            Send();
        }
    }

    [ContextMenu("send")]
    public void Send(){
        GameObject.Find("Receive").GetComponent<ReceiveTest>().Receive();
        //GameObject.Find("GameControler_Result").GetComponent<Result>().get_score(score, Coin);
    }

    [ContextMenu("NextScene")]
    public void NextScene(){
        SceneManager.sceneLoaded += OnSceneLoaded;
        SceneManager.LoadScene("Receive");
    }
}


ただ、シーンロード先のメソッドを呼び出すよりも他の方法を検討してみてはどうでしょうか?
質問者さんは今のシーンのスコアデータを次のシーンで使いたい感じなので

案1,シングルトンを使う
次のシーンに移動してもスコアデータのオブジェクトは残す
以下が参考になります。
https://unity.moon-bear.com/unitychan-coin/singleton-gamemanager/

案2,PlayerPrefsを使う
スコアを保存しておいて、次のシーンで保存したスコアを呼び出す
https://www.sejuku.net/blog/69991

案3,ロード後シーン先のメソッドを呼び出す
今回のOnSceneLoadedに登録するしてロード後に呼び出す方法

別案、スコア表示UIを表示非表示させる
スコアを表示させるだけであればシーンを移動しなくてもUIで表示非表示してもいい
GameControl_HiruでUIを作成してスコアを表示させる

どの方法を使うかは好みにも状況にもよります。
別案で問題なければシーンをまたぐ必要がありません
どうしてもシーンをまたぐ必要があるなら案1を使う人が多いようです。
以上参考まで

投稿

編集

  • 回答の評価を上げる

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

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

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

  • 回答の評価を下げる

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

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

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

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

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

関連した質問

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