Unity

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

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

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

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

※2020年4月9日追記:G2UがAssetStoreから公開終了になったようです。G2U4Sを使用する場合はExcelをご利用ください(一応過去バージョンのG2UがあればSpreadsheetも動作可能です)

動作環境

動作確認バージョン:Unity2018.4.23, Unity2019.4.1
動作保証バージョン:Unity2018.4 LTS, Unity2019.4 LTS
G2U 2.1.13+

  • 動作確認バージョンは作者が動作確認を行ったバージョンです
  • 動作保証バージョンはアセット利用可能なバージョンです(どのLTSでも動く想定です)
  • G2UなしでもExcelだけで動作可能です(Spreadsheetの連携でG2Uを使用)

使用方法

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

G2U4S.xlsxで設定を変更

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

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

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

Const

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

GameConst.cs

// 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は拡張メソッド)。

GameEnum.cs

// 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。

TestData.cs

// 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;
    }
}

LocalizeData.cs

// 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。

WorldData.cs

// 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;
    }
}

MapData.cs

// 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」を開く

Googleにログイン

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

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

「Workbooks/Account Workbooks」を開く

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

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

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

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

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

スキル

SpreadsheetとExcelに対応

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

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

柔軟な型と型の追加

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

指定方法
配列通常:値1, 値2
構造体:(値1, 値2), (値3, 値4)
enumIdle enum名
ScriptableObject  空文字(型名で決まるため)
ScriptableObjects1  ScriptableObjectのキー(1列目の値)
sbyte1
byte1
short1
ushort1
int1
uint1
long1
ulong1
chara
float1.0
double1.0
booltrue 1
G2U4S.xlsx/G2U4SConst/BoolTrueValues参考
stringM4u
SerializableDateTime2019-03-22 00:51
Vector21, 1
カンマ指定の数値は後ろを省略すると0で初期化される
Vector31, 1, 1
Vector41, 1, 1, 1
Rect1, 1, 1, 1
Vector2Int1, 1
Vector3Int1, 1, 1
RectInt1, 1, 1, 1
Quaternion1, 1, 1, 1
Color1, 1, 1, 1
Color32255, 255, 255, 255

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

SerializableDateTime

Unityでシリアライズ可能なDateTimeを作ってみた 時間をエディタ上で操作したい、ということはままあると思う。 プログラムではDateTimeで扱いたい ただそのままだ...

上記記事で書きましたが、元々G2U4Sのために作ったクラスでした。
実際、ゲームでは時間を扱う機会は多いと思います。

ScriptableObject in ScriptableObject


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

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

Subenum

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

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を作りました。
以下のように使用します(やり方はいろいろあるかと思うので一例です)。

[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は以下のシーケンスで実行され、それぞれの終了時にコールバックが呼ばれます。

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

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

最後に

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

【エビでもわかる】オセロプログラミング
〜オセロを作りながらゲームのプログラムを学ぼう〜
「Unityで初めてゲームを作ってみたい!」

そんな人のためにこの本を作りました。
オセロを一から作りながら実践形式でプログラムを学べる本です。
すでに完成したプロジェクトを説明するのではなく、実際に作りながら説明していきます。
一緒に手を動かしながら、プログラムを覚えていきましょう🌟