bluetomato
@bluetomato

Are you sure you want to delete the question?

If your question is resolved, you may close it.

Leaving a resolved question undeleted may help others!

We hope you find it useful!

Unityでチュートリアルを作りたい

解決したいこと

interfaceのITutorialTaskを取得したい

例)
Unityでチュートリアルを作成している際、interfaceで定義されているITutorialTask taskがnullになってしまいます。
UnityもC#も初心者で途方に暮れています。
解決方法を教えてください。

発生している問題・エラー

NullReferenceException: Object reference not set to an instance of an object
TutorialManager+<SetCurrentTask>d__10.MoveNext () (at Assets/TutorialManager.cs:100)
UnityEngine.SetupCoroutine.InvokeMoveNext (System.Collections.IEnumerator enumerator, System.IntPtr returnValueAddress) (at <f712b1dc50b4468388b9c5f95d0d0eaf>:0)

該当するソースコード

TutorialManager.cs

using DG.Tweening;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
using UnityEngine.UI;

/// <summary>
/// ゲーム上のチュートリアルを管理するマネージャクラス
/// </summary>
public class TutorialManager : MonoBehaviour
{
    // チュートリアル用UI
    protected RectTransform tutorialTextArea;
    protected Text TutorialTitle;
    protected Text TutorialText;

    // チュートリアルタスク
    protected ITutorialTask currentTask;
    protected List<ITutorialTask> tutorialTask;

    // チュートリアル表示フラグ
    private bool isEnabled;

    // チュートリアルタスクの条件を満たした際の遷移用フラグ
    private bool task_executed = false;

    // チュートリアル表示時のUI移動距離
    private float fade_pos_x = 350;

    void Start()
    {
        // チュートリアル表示用UIのインスタンス取得
        tutorialTextArea = GameObject.Find("TutorialTextArea").GetComponent<RectTransform>();
        TutorialTitle = tutorialTextArea.Find("Title").GetComponent<Text>();
        TutorialText = tutorialTextArea.Find("Text").GetComponentInChildren<Text>();

        // チュートリアルの一覧
        tutorialTask = new List<ITutorialTask>()
        {
            new MovementTask(),
            new AttackTask(),
            
        };

        // 最初のチュートリアルを設定
        StartCoroutine(SetCurrentTask(tutorialTask.First()));

        isEnabled = true;
    }

    void Update()
    {
        // チュートリアルが存在し実行されていない場合に処理
        if (currentTask != null && !task_executed)
        {
            // 現在のチュートリアルが実行されたか判定
            if (currentTask.CheckTask())
            {
                task_executed = true;

                DOVirtual.DelayedCall(currentTask.GetTransitionTime(), () => {
                    iTween.MoveTo(tutorialTextArea.gameObject, iTween.Hash(
                        "position", tutorialTextArea.transform.position + new Vector3(fade_pos_x, 0, 0),
                        "time", 1f
                    ));

                    tutorialTask.RemoveAt(0);

                    var nextTask = tutorialTask.FirstOrDefault();
                    if (nextTask != null)
                    {
                        StartCoroutine(SetCurrentTask(nextTask, 1f));
                    }
                });
            }
        }

        if (Input.GetButtonDown("Help"))
        {
            SwitchEnabled();
        }
    }

    /// <summary>
    /// 新しいチュートリアルタスクを設定する
    /// </summary>
    /// <param name="task"></param>
    /// <param name="time"></param>
    /// <returns></returns>
    protected IEnumerator SetCurrentTask(ITutorialTask task, float time = 0)
    {
        // timeが指定されている場合は待機
        yield return new WaitForSeconds(time);

        currentTask = task;
        task_executed = false;

        // UIにタイトルと説明文を設定
        TutorialTitle.text = task.GetTitle();
        TutorialText.text = task.GetText();

        // チュートリアルタスク設定時用の関数を実行
        task.OnTaskSetting();

        iTween.MoveTo(tutorialTextArea.gameObject, iTween.Hash(
            "position", tutorialTextArea.transform.position - new Vector3(fade_pos_x, 0, 0),
            "time", 1f
        ));
    }

    /// <summary>
    /// チュートリアルの有効・無効の切り替え
    /// </summary>
    protected void SwitchEnabled()
    {
        isEnabled = !isEnabled;

        // UIの表示切り替え
        float alpha = isEnabled ? 1f : 0;
        tutorialTextArea.GetComponent<CanvasGroup>().alpha = alpha;
    }
}

ITutorialTas.cs

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
 
public interface ITutorialTask
{
    /// <summary>
    /// チュートリアルのタイトルを取得する
    /// </summary>
    /// <returns></returns>
    string GetTitle();
 
    /// <summary>
    /// 説明文を取得する
    /// </summary>
    /// <returns></returns>
    string GetText();
 
    /// <summary>
    /// チュートリアルタスクが設定された際に実行される
    /// </summary>
    void OnTaskSetting();
 
    /// <summary>
    /// チュートリアルが達成されたか判定する
    /// </summary>
    /// <returns></returns>
    bool CheckTask();
 
    /// <summary>
    /// 達成後に次のタスクへ遷移するまでの時間(秒)
    /// </summary>
    /// <returns></returns>
    float GetTransitionTime();
}

自分で試したこと

https://gomafrontier.com/unity/2183
上記のWebサイトをそのまま実現したいです。
質問なのですが、interfaceのC#スクリプトはGamaObjectにアタッチする必要がありますか?Assetsのところにあればいいのですか?
空のUIオブジェクトを作成するには、Create EmptyをCanvasに設置すればいいのですか?

0

2Answer

Comments

  1. @bluetomato

    Questioner

    回答ありがとうございます!!
    確認したのですが、ITutorialTask.csで統一されていました…

  2. 失礼しました。

    リンク先のTutorialManagerクラスのStartメソッドのソースの一部を並べて見てみると、、、

       void Start()
        {
            // チュートリアル表示用UIのインスタンス取得
            tutorialTextArea = GameObject.Find("TutorialTextArea").GetComponent<RectTransform>();
            TutorialTitle = tutorialTextArea.Find("Title").GetComponent<Text>();
            TutorialText = tutorialTextArea.Find("Text").GetComponentInChildren<Text>();
     
            // チュートリアルの一覧
            tutorialTask = new List<ITutorialTask>()
            {
                new MovementTask(),
                new AttackTask1(),
                new AttackTask2(),
            };
    
        void Start()
        {
            // チュートリアル表示用UIのインスタンス取得
            tutorialTextArea = GameObject.Find("TutorialTextArea").GetComponent<RectTransform>();
            TutorialTitle = tutorialTextArea.Find("Title").GetComponent<Text>();
            TutorialText = tutorialTextArea.Find("Text").GetComponentInChildren<Text>();
    
            // チュートリアルの一覧
            tutorialTask = new List<ITutorialTask>()
            {
                new MovementTask(),
                new AttackTask(),
                
            };
    

    となっていますがAttackTaskクラスは別途作成されていますか?

  3. @bluetomato

    Questioner

    はい、AttackTask()を別途作りました。内容は、AttackTask1(), AttackTask2()と変わらず、数を減らした状態です。

コメント失礼します。

表示されているエラーの内容から、
SetCurrentTask内のGetTitleでNullが出ているのかと思います。
恐らくStart内のSetCurrentTaskメソッドかと思います。

投稿主様はStart内にてTaskインタフェースのインスタンスを生成し、currentTaskに保存。
そしてそのcurrentTaskからタイトルや説明を取得しようとしています。

しかしインタフェースは機能の実装を保証するのみで詳細な値などを持ちません。
GetTitleやGetTextというメソッドは存在しているが、現時点では処理の内容は実装されていません。
(return this.title; みたいな)

ですので、例えばPlayerTutorialというクラスを作成し、そのクラスにITutorialTaskを継承します。
そうするとITutorialTask内にあるメソッドを全てオーバーライドしないとエラーが出るかと思います。
そうすれば後は各場面で必要な文字列や値などを定義し、返してあげればうまくいくのではないかと思います。
(最近Unityをあまり触っていないため間違っていたら申し訳ないです、、)

0Like

Your answer might help someone💌