レベルに応じてゲームの難易度を変える実装


UNITYでゲーム作るときに難易度調整するためのデータ作りについて

ScriptableObjectを使います。
このような数値調整をUNITY上でやりたい場合のやり方です。

上記の画像にあるデータは、いくつのモノを壊したら、次のレベルに進めるか、そのレベルの難易度はどのぐらいかといったデータを表しています。

どんなゲームを作った際に使ったものか

まず、Garbagersというゲームを作ったのですが、その際の難易度などに関するデータ作りに関して作ったものです。
https://mosq.xyz/Garbagers/

まず基本となるデータ

スリーマッチパズルゲームで、多くのゴミを壊すと次のレベルに進み少しづつ難しくなるゲームでした。テトリスのように。
なので、いくつ壊したら次のレベルに進めるか(int)、ゴミが出現するインターバル時間(float)を設定できるようにしました。(GVLevelInfo)
さらに、ゴミがつっかえて溜まってくるとゴミが速く出るようにしました。
横、縦への速度、回転速度、インターバルを指定できるようにしました。(GVPressureInfo)

LevelClasses.cs
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

[Serializable]
public class GVLevelInfo
{
    public int NextBlockCount;
    public float SpawnInterval = 0f;
}

[Serializable]
public class GVPressureInfo
{
    public float HorizontalSpeed;
    public float VerticalSpeed;
    public float AngularSpeed;
    public float Interval;
}

[Serializable]アトリビュートをつけておく必要があります。
これらをレベルごとに用意するためリストにします。
それとScriptableObjectにして、UNITY上に置けるデータにします。
(全部publicで書いちゃってますが、UNITYでは[SerializeField]アトリビュートをつけても、シリアライズされます)

GVLevelInfoのリスト化したScriptableObjectを作る

class宣言以降、三行だけが重要であとはおまけの関数です。

GVLevelInfoList.cs
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;


public class GVLevelInfoList : ScriptableObject
{
    public List<GVLevelInfo> List = new List<GVLevelInfo>();

    public int GetLevel(int breakCount)
    {
        int level = 0;
        foreach(var v in List)
        {
            if (v.NextBlockCount <= breakCount)
            {
                ++level;
                continue;
            }
            break;
        }
        return level;
    }

    public float ToNextRatio(int level,int breakCount)
    {
        int prevCount = 0;      
        if (0 <= level-1)
        {
            prevCount = List[level-1].NextBlockCount;
        }
        if (level < List.Count)
        {
            var v = List[level];
            return (breakCount - prevCount) / (float)(v.NextBlockCount-prevCount);
        }
        return 1f;
    }
}

GVPressureInfoのリストも同様に作ります。

GVPressureInfoList.cs
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class GVPressureInfoList : ScriptableObject
{
    public List<GVPressureInfo> List = new List<GVPressureInfo>();

    public GVPressureInfo GetPressure(int pressure)
    {
        return List[Mathf.Clamp(pressure,0,List.Count-1)];
    }
}

ということで、データ構造としては用意できました。
あとは、このデータをUNITYプロジェクトの中に追加する方法です。

Editorフォルダを作ります

UNITYにおいて、Editorフォルダは特別な名前です。
ここに置かれたスクリプトファイルは、UNITYエディタ上でのみ利用され、ビルドして出力したアプリなどには含まれません。
エディタ上で、データを作ったりする際に使います。

LevelInfoMenu.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEditor;

public static class LevelInfoMenu
{
    [MenuItem ("Create/CreateGVLevelInfoList")]
    static void CreateGVLevelInfoList ()
    {
        var asset = ScriptableObject.CreateInstance<GVLevelInfoList> ();

        AssetDatabase.CreateAsset (asset, "Assets/LevelInfoList.asset");
        AssetDatabase.Refresh ();
    }

    [MenuItem ("Create/CreateGVPressureInfoList")]
    static void CreatePressureInfoList ()
    {
        var asset = ScriptableObject.CreateInstance<GVPressureInfoList> ();

        AssetDatabase.CreateAsset (asset, "Assets/PressureInfoList.asset");
        AssetDatabase.Refresh ();
    }
}

このスクリプトを書くと、UNITYのトップメニューにCreateが追加されています。
実行すると、指定されたパスの場所にデータが追加されます。
インスペクター上で、データを編集することが可能です。

保存場所を選べるようにしたい場合は、下記のような関数を使います。
https://docs.unity3d.com/ja/current/ScriptReference/EditorUtility.SaveFilePanel.html
→プロジェクト内に保存する場合は、こっちでした。
https://docs.unity3d.com/ScriptReference/EditorUtility.SaveFilePanelInProject.html

これをゲームで使っていきます。

実際に使う

普通に、MonoBehaviourにインスペクタから設定することができます。

GarbagersSystem.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class GarbagersSystem : MonoBehaviour {
    [SerializeField]
    LevelInfoUI _levelInfo;
    [SerializeField]
    GVPressureInfoList _pressureInfo;
...

インスペクタに先ほどのメニューから作ったデータをドラッグ&ドロップすればデータが入るので、使いましょう。