〝 G2U4S 〟G2Uで読み込んだゲームデータを1クリックでScriptableObjectに変換

f:id:okamura0510:20190505023910g:plain

G2U4Sは、SpreadsheetやExcelで作成したゲームデータを1クリックでScriptやScriptableObjectに変換するアセットです。
Spreadsheetの連携にはG2Uを使用しています(オフラインではExcelで動作可能です)。

・G2U連携
・SpreadsheetとExcelに対応(チーム開発にも個人開発にも最適)
・様々なゲームデータを1クリックで変換(Const, Enum, ScriptableObject)
・ScriptableObjectにScriptableObjectを参照可能(ScriptableObject in ScriptableObject)
・新しいEnumシステム『Subenum』
・ローカライズ対応可能(サンプルあり)
・プラットフォームに依存しないデータ形式(Unity完結)
・ソースコード同梱(カスタマイズ自由)
・日本語と英語のドキュメント(Japanese / English)

G2U4Sは、作者の10年のゲーム開発ノウハウを集約して作られたゲームデザインアセットです!
G2U4Sを使用してゲーム開発を楽しみましょう☆

動作環境

Unity2018.4.2+
G2U 2.1.13+
※G2UなしでもExcelだけで動作可能です(Spreadsheetの連携でG2Uを使用)
※おそらくG2Uのバージョンには依存しません(CSVが出力出来れば良い)

使用方法

SpreadsheetかExcelでゲームデータを入力します(どちらも入力形式に違いはありません)。
Spreadsheetの場合はG2UでCSVを出力します(Excelではこの手順は不要)。
「Tools/G2U4S」でCSVやExcelをScriptやScriptableObjectに変換します。

G2U4S.xlsxで設定を変更

アセット直下にG2U4Sの設定ファイル「G2U4S.xlsx」があります。G2U4SConstが設定値です(G2U4SEnumはプログラムで使用しているenum)。Valueの値を変更することで、アセットの動作を変更出来ます。
余談ですが、本アセット自体もG2U4Sで作成したスクリプト(G2U4SConst、G2U4SEnum)で動作しています(アセットの開発自体が動作検証を兼ねてました)。

f:id:okamura0510:20190502214939j:plain

SpreadsheetかExcelでゲームデータを入力

シート左上セルがデータタイプです。データタイプごとにScriptやScriptableObjectに変換されます(Noneの場合はそのシートは無視されます)。

Const

定数スクリプト(conststatic readonly)。

f:id:okamura0510:20190505020805p:plain

f:id:okamura0510:20190502215016p:plain:w160
// Auto-generated files
using G2U4S;
using GAMEST;
using UnityEngine;
using System;

namespace GAMEST
{
    /// <summary>
    /// ゲーム定数
    /// </summary>
    public static class GameConst
    {
        /// <summary>
        /// リリースフラグ
        /// </summary>
        public const bool IsRelease = true;
        /// <summary>
        /// アプリバージョン
        /// </summary>
        public static readonly string Version = Application.version;
        /// <summary>
        /// ローカライズ言語
        /// </summary>
        public static readonly SystemLanguage Language = SystemLanguage.Japanese;
    }
}
Enum

enumスクリプト(Subenumは拡張メソッド)。

f:id:okamura0510:20190502215054p:plain

f:id:okamura0510:20190502215113p:plain:w160
// Auto-generated files
using G2U4S;
using GAMEST;
using UnityEngine;
using System;

namespace GAMEST
{
    /// <summary>
    /// 向き
    /// </summary>
    public enum Dir : int
    {
        /// <summary>
        /// なし
        /// </summary>
        None = -1,
        /// <summary>
        /// 上
        /// </summary>
        Up = 0,
        /// <summary>
        /// 右
        /// </summary>
        Right = 1,
        /// <summary>
        /// 下
        /// </summary>
        Down = 2,
        /// <summary>
        /// 左
        /// </summary>
        Left = 3,
    }
    /// <summary>
    /// モーション
    /// </summary>
    public enum MotionType : int
    {
        /// <summary>
        /// 待機
        /// </summary>
        Idle,
        /// <summary>
        /// 歩く
        /// </summary>
        Walk,
        /// <summary>
        /// 走る
        /// </summary>
        Run,
        /// <summary>
        /// 防御
        /// </summary>
        Guard,
        /// <summary>
        /// ダメージ
        /// </summary>
        Damage,
        /// <summary>
        /// 通常攻撃
        /// </summary>
        NormalAttack,
        /// <summary>
        /// 必殺技
        /// </summary>
        SpecialAttack,
    }

    public static partial class G2U4Subenum
    {
        /// <summary>
        /// 待機
        /// </summary>
        public static bool IsIdle(this MotionType value) { return value == MotionType.Idle; }
        /// <summary>
        /// 歩く
        /// </summary>
        public static bool IsWalk(this MotionType value) { return value == MotionType.Walk; }
        /// <summary>
        /// 走る
        /// </summary>
        public static bool IsRun(this MotionType value) { return value == MotionType.Run; }
        /// <summary>
        /// 防御
        /// </summary>
        public static bool IsGuard(this MotionType value) { return value == MotionType.Guard; }
        /// <summary>
        /// ダメージ
        /// </summary>
        public static bool IsDamage(this MotionType value) { return value == MotionType.Damage; }
        /// <summary>
        /// 通常攻撃
        /// </summary>
        public static bool IsNormalAttack(this MotionType value) { return value == MotionType.NormalAttack; }
        /// <summary>
        /// 必殺技
        /// </summary>
        public static bool IsSpecialAttack(this MotionType value) { return value == MotionType.SpecialAttack; }
        /// <summary>
        /// 移動か
        /// </summary>
        public static bool IsMove(this MotionType value) { return value == MotionType.Walk || value == MotionType.Run; }
        /// <summary>
        /// 攻撃か
        /// </summary>
        public static bool IsAttack(this MotionType value) { return value == MotionType.NormalAttack || value == MotionType.SpecialAttack; }
        /// <summary>
        /// 攻撃可能か
        /// </summary>
        public static bool CanAttack(this MotionType value) { return value == MotionType.Idle || value == MotionType.Walk || value == MotionType.Run || value == MotionType.Guard; }
    }
}
ScriptableObject

単体ScriptableObject。

f:id:okamura0510:20190503031254p:plain

f:id:okamura0510:20190502215211p:plain
// Auto-generated files
using G2U4S;
using GAMEST;
using UnityEngine;
using System;

namespace GAMEST
{
    /// <summary>
    /// テストデータ
    /// </summary>
    public class TestData : ScriptableObject
    {
        /// <summary>
        /// テスト中か
        /// </summary>
        public bool IsTest;
        /// <summary>
        /// ワールドタイム
        /// </summary>
        public SerializableDateTime WorldTime;
        /// <summary>
        /// モーション
        /// </summary>
        public MotionType Motion;
        /// <summary>
        /// 武器IDリスト
        /// </summary>
        public int[] WeaponIds;
        /// <summary>
        /// 当たり時間範囲
        /// </summary>
        public Vector2 CollidableTime;
    }
}
// Auto-generated files
using G2U4S;
using GAMEST;
using UnityEngine;
using System;

namespace GAMEST
{
    /// <summary>
    /// ローカライズデータ
    /// </summary>
    public class LocalizeData : ScriptableObject
    {
        /// <summary>
        /// キー
        /// </summary>
        public string[] Keys;
        /// <summary>
        /// 日本語
        /// </summary>
        public string[] Japanese;
        /// <summary>
        /// 英語
        /// </summary>
        public string[] English;
    }
}
ScriptableObjects

複数ScriptableObject。

f:id:okamura0510:20190502215244p:plain

f:id:okamura0510:20190502215305p:plain
// Auto-generated files
using G2U4S;
using GAMEST;
using UnityEngine;
using System;

namespace GAMEST
{
    /// <summary>
    /// ワールドデータ
    /// </summary>
    public class WorldData : ScriptableObject
    {
        /// <summary>
        /// ID
        /// </summary>
        public int Id;
        /// <summary>
        /// 名前キー
        /// </summary>
        public string NameKey;
        /// <summary>
        /// マップデータリスト
        /// </summary>
        public MapData[] Maps;
    }
}
// Auto-generated files
using G2U4S;
using GAMEST;
using UnityEngine;
using System;

namespace GAMEST
{
    /// <summary>
    /// マップデータ
    /// </summary>
    public class MapData : ScriptableObject
    {
        /// <summary>
        /// ID
        /// </summary>
        public int Id;
        /// <summary>
        /// 名前キー
        /// </summary>
        public string NameKey;
    }
}
Spreadsheetの場合はG2UでCSVを出力

G2U GoogleスプレッドシートのデータをUnityに取り込むエディタ ゼロから始める解説付き
G2Uを使用するに当たり、上記記事を参考にさせていただきました☆

G2Uをインストールして「Window/Google2u」を開く
G2U f:id:okamura0510:20190502215457p:plain:w500
Googleにログイン

「Sign in with Google」をクリックしてGoogleへログイン。

f:id:okamura0510:20190502215532p:plain

ログイン後、コードをコピーして、「OAuth Token」に貼り付けて「Log In」。

f:id:okamura0510:20190502215553p:plain:w600

「Workbooks/Account Workbooks」を開く

f:id:okamura0510:20190502215638p:plain:w300

ゲームデータ以外のシートの瞳を閉じる

f:id:okamura0510:20190502215708p:plain:w300

「Do Not Export」を「CSV」に変更

f:id:okamura0510:20190502215733p:plain:w300

右下のフロッピーボタンでCSVを出力

Google2uGenフォルダにCSVが出力される。
Spreadsheet更新時は、左下のリロードボタン → 右下のフロッピーボタンで更新。

f:id:okamura0510:20190502215755p:plain:w180

「Tools/G2U4S」でCSVやExcelをScriptやScriptableObjectに変換

f:id:okamura0510:20190503011543p:plain:w250

スキル

SpreadsheetとExcelに対応
  • 個人開発やローカル環境ではExcel
  • チーム開発ではSpreadsheet

というような住み分けが可能です。
Excelの標準機能(計算式等)を問題なく使えます。
Spreadsheetの連携にはG2Uを使用しているので安心です。

柔軟な型と型の追加

ユーザ定義型は同じ名前空間内であれば何でも使えます(名前空間はG2U4S.xlsx/G2U4SConst/YourNamespaceで変更可能です)。
その他の型は以下の通りです。

指定方法
配列 値1, 値2
enum Idle enum名
ScriptableObject  空文字(型名で決まるため)
ScriptableObjects 1  ScriptableObjectのキー(1列目の値)
sbyte 1
byte 1
short 1
ushort 1
int 1
uint 1
long 1
ulong 1
char a
float 1.0
double 1.0
bool true 1
G2U4S.xlsx/G2U4SConst/BoolTrueValues参考
string M4u
SerializableDateTime 2019-03-22 00:51
Vector2 1, 1
カンマ指定の数値は後ろを省略すると0で初期化される
Vector3 1, 1, 1
Vector4 1, 1, 1, 1
Rect 1, 1, 1, 1
Vector2Int 1, 1
Vector3Int 1, 1, 1
RectInt 1, 1, 1, 1
Quaternion 1, 1, 1, 1
Color 1, 1, 1, 1
Color32 255, 255, 255, 255

また、型の追加はG2U4SUtil.Parse(Type type, string str, bool isArray, string assetDir = "")で行えます(分岐を追加してください)。

SerializableDateTime

f:id:okamura0510:20180722170345g:plain:w300
Unityでシリアライズ可能なDateTimeを作ってみた
上記記事で書きましたが、元々G2U4Sのために作ったクラスでした。
実際、ゲームでは時間を扱う機会は多いと思います。

ScriptableObject in ScriptableObject

f:id:okamura0510:20190502215940p:plain G2U4Sの最大の強みはScriptableObjectの中にScriptableObjectを参照できるところです。
こうすることで、データ間の参照が可能になるので「データ」と「プログラム」を完全に分離出来ます(ConstやEnumもそれを補助しています)。
プランナー・プログラマーで作業分担がしやすくなります。

1点注意点があるとしたら、AssetBundleにした時は重複してメモリに乗ってしまう可能性があるので、その辺はパフォーマンスを気にした設計を行ってください。

Subenum

Subenumとはenumを拡張メソッド化した機能です。
例えば以下のようなenumがあった場合、

f:id:okamura0510:20190502220003p:plain:w300

IsMoveは以下のように展開され、

/// <summary>
/// 移動か
/// </summary>
public static bool IsMove(this MotionType value)
{
    return value == MotionType.Walk || value == MotionType.Run;
}

以下のように使えるようになります。

var motion = MotionType.Walk;

if(motion == MotionType.Walk || motion == MotionType.Run)
{
    // 移動中の処理
}

// ↑ が ↓ こう書ける

if(motion.IsMove())
{
    // 移動中の処理
}

C#でenumをクラスのように扱う
元々、上記記事を作ってから、ちょいちょいenumの拡張メソッド化を試していたのですが、

  • 定義が面倒
  • 修正箇所が増える
  • C#7のusing staticを使えば拡張メソッド化せずとも短く記述できる

等の理由から、抵抗を感じていました。しかし、G2U4Sの開発中に、
「enumを自動生成するなら拡張メソッドも自動生成すればいんじゃね?」
と思いつき、
「どうせ自動生成するなら列挙子も複数まとめてみたらどうだろう?」
と行き着き、この機能が完成しました。

名付けて Subenum!

enumの中に小さなenumがあるイメージ。

メリット

  • コード量の削減
  • 可読性の向上

デメリット

  • メソッド化による呼び出しコスト
  • アプリサイズ増加

個人的には気に入ってるのですが、ここらへんのトレードオフかなぁ。

ローカライズ対応

G2U4Sはローカライズ対応も行えます。
実際、個人開発のゲームをローカライズするためにG2U4Sを作りました。
以下のように使用します(やり方はいろいろあるかと思うので一例です)。

f:id:okamura0510:20190503032339p:plain
[SerializeField] SystemLanguage language = SystemLanguage.Japanese;
[SerializeField] LocalizeData localizeData; 
[SerializeField] WorldData worldData;
Dictionary<string, string> japanese = new Dictionary<string, string>();
Dictionary<string, string> english  = new Dictionary<string, string>();

void Start()
{ 
    var keys = localizeData.Keys;
    for(var i = 0; i < keys.Length; i++)
    {
        japanese.Add(keys[i], localizeData.Japanese[i]);
        english.Add(keys[i],  localizeData.English[i]);
    }
    
    var key       = worldData.NameKey;
    var worldName = (language == SystemLanguage.Japanese) ? japanese[key] : english[key];
    Debug.Log($"worldName = {worldName}");
}
イベント(OnInit, OnScriptCreated, OnAssetCreated, OnAssetUpdated)

G2U4Sは以下のシーケンスで実行され、それぞれの終了時にコールバックが呼ばれます。

シーケンス 内容
Init G2U4S.xlsxの設定を初期化(G2U4SConst、G2U4SEnumの作成)。
G2U4SUtil.OnInit()が呼ばれる。
CreateScript Const, Enum, ScriptableObject, ScriptableObjectsのスクリプトを作成。
G2U4SUtil.OnScriptCreated()が呼ばれる。
CreateAsset ScriptableObject, ScriptableObjectsのアセットを作成。
G2U4SUtil.OnAssetCreated()が呼ばれる。
UpdateAsset ScriptableObject, ScriptableObjectsのアセットの値を更新。
G2U4SUtil.OnAssetUpdated()が呼ばれる。

実行後に何らかの処理を加えたい場合は、これらのコールバックをご使用ください。

最後に

正直、威力は高くないけど、使い勝手のいい能力だと思う。