〝 G2U4S 〟Convert game data loaded with G2U to ScriptableObject with one click

f:id:okamura0510:20190505023910g:plain

G2U4S is an asset that converts game data created in Spreadsheet or Excel to Script or ScriptableObject with one click.
Use G2U if you work with Spreadsheet, and Excel if offline.

・Cooperation with G2U
・Support Spreadsheet and Excel, suitable for both team and personal development
・Convert various game data to Const, Enum and ScriptableObject with one click
・ScriptableObject can reference ScriptableObject, call it “ScriptableObject in ScriptableObject”
・Subenum: New Enum system
・Localization available, see samples
・Platform-independent data format, complete only within Unity
・Source code is included and customize available
・Documents in Japanese and English available, see Japanese or English

G2U4S is created for game design, and full of know-how that author has developed games for 10 years.
Let's enjoy developing games using G2U4S too!

Operating environment

Unity 2018.4.9+
G2U 2.1.13+
Note: Possible to operate only in Excel without G2U. Use G2U in case of Spreadsheet only.
Note: Probably compatible with any version of G2U. It only needs to be able to output CSV.

How to use

Enter game data in Spreadsheet or Excel, in the same input format whichever you choose.
In case of Spreadsheet, output CSV with G2U. You do not have to do this if in Excel.
Convert CSV or Excel to Script or ScriptableObject with “Tools / G2U4S”.

Change settings in G2U4S.xlsx

The G2U4S configuration file “G2U4S.xlsx” is located directly below the asset. G2U4SConst is a class manages setting value, and G2U4SEnum is an enum used in the program. You can change the behavior of the asset by changing the “Value”.
For your information, this asset itself works with scripts, G2U4SConst and G2U4SEnum, created with G2U4S. At the same time I developed the asset, I also verified it.

f:id:okamura0510:20190622234158p:plain

Enter game data in Spreadsheet or Excel

Define data type in the top left cell of the sheet. Each data type is converted to Script or ScriptableObject. If you select None, the sheet is ignored.

Const

Constant script, such as “const” and “static readonly”.

f:id:okamura0510:20190628012720p:plain

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

namespace GAMEST
{
    /// <summary>
    /// Game constant
    /// </summary>
    public static class GameConst
    {
        /// <summary>
        /// Release flag
        /// </summary>
        public const bool IsRelease = true;
        /// <summary>
        /// Application version
        /// </summary>
        public static readonly string Version = Application.version;
        /// <summary>
        /// Localized language
        /// </summary>
        public static readonly SystemLanguage Language = SystemLanguage.English;
    }
}
Enum

Enum script, and “Subenum” is an extension method.

f:id:okamura0510:20190628013133p:plain

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

namespace GAMEST
{
    /// <summary>
    /// Direction
    /// </summary>
    public enum Dir : int
    {
        /// <summary>
        /// None
        /// </summary>
        None = -1,
        /// <summary>
        /// Up
        /// </summary>
        Up = 0,
        /// <summary>
        /// Right
        /// </summary>
        Right = 1,
        /// <summary>
        /// Down
        /// </summary>
        Down = 2,
        /// <summary>
        /// Left
        /// </summary>
        Left = 3,
    }
    /// <summary>
    /// Motion
    /// </summary>
    public enum MotionType : int
    {
        /// <summary>
        /// Idle
        /// </summary>
        Idle,
        /// <summary>
        /// Walk
        /// </summary>
        Walk,
        /// <summary>
        /// Run
        /// </summary>
        Run,
        /// <summary>
        /// Guard
        /// </summary>
        Guard,
        /// <summary>
        /// Damage
        /// </summary>
        Damage,
        /// <summary>
        /// Normal attack
        /// </summary>
        NormalAttack,
        /// <summary>
        /// Special attack
        /// </summary>
        SpecialAttack,
    }

    public static partial class G2U4Subenum
    {
        /// <summary>
        /// Idle
        /// </summary>
        public static bool IsIdle(this MotionType value) { return value == MotionType.Idle; }
        /// <summary>
        /// Walk
        /// </summary>
        public static bool IsWalk(this MotionType value) { return value == MotionType.Walk; }
        /// <summary>
        /// Run
        /// </summary>
        public static bool IsRun(this MotionType value) { return value == MotionType.Run; }
        /// <summary>
        /// Guard
        /// </summary>
        public static bool IsGuard(this MotionType value) { return value == MotionType.Guard; }
        /// <summary>
        /// Damage
        /// </summary>
        public static bool IsDamage(this MotionType value) { return value == MotionType.Damage; }
        /// <summary>
        /// Normal attack
        /// </summary>
        public static bool IsNormalAttack(this MotionType value) { return value == MotionType.NormalAttack; }
        /// <summary>
        /// Special attack
        /// </summary>
        public static bool IsSpecialAttack(this MotionType value) { return value == MotionType.SpecialAttack; }
        /// <summary>
        /// Move or not
        /// </summary>
        public static bool IsMove(this MotionType value) { return value == MotionType.Walk || value == MotionType.Run; }
        /// <summary>
        /// Attack or not
        /// </summary>
        public static bool IsAttack(this MotionType value) { return value == MotionType.NormalAttack || value == MotionType.SpecialAttack; }
        /// <summary>
        /// Whether it is possible to attack
        /// </summary>
        public static bool CanAttack(this MotionType value) { return value == MotionType.Idle || value == MotionType.Walk || value == MotionType.Run || value == MotionType.Guard; }
    }
}
ScriptableObject

Single ScriptableObject.

f:id:okamura0510:20190628013948p:plain

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

namespace GAMEST
{
    /// <summary>
    /// Test data
    /// </summary>
    public class TestData : ScriptableObject
    {
        /// <summary>
        /// Whether it is in test
        /// </summary>
        public bool IsTest;
        /// <summary>
        /// World time
        /// </summary>
        public SerializableDateTime WorldTime;
        /// <summary>
        /// Motion
        /// </summary>
        public MotionType Motion;
        /// <summary>
        /// Weapon ID list
        /// </summary>
        public int[] WeaponIds;
        /// <summary>
        /// Collision time range
        /// </summary>
        public Vector2 CollidableTime;
    }
}
// Auto-generated files
using G2U4S;
using GAMEST;
using UnityEngine;
using System;

namespace GAMEST
{
    /// <summary>
    /// Localization data
    /// </summary>
    public class LocalizeData : ScriptableObject
    {
        /// <summary>
        /// Key
        /// </summary>
        public string[] Keys;
        /// <summary>
        /// Japanese
        /// </summary>
        public string[] Japanese;
        /// <summary>
        /// English
        /// </summary>
        public string[] English;
    }
}
ScriptableObjects

Multiple ScriptableObjects.

f:id:okamura0510:20190628014557p:plain

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

namespace GAMEST
{
    /// <summary>
    /// World data
    /// </summary>
    public class WorldData : ScriptableObject
    {
        /// <summary>
        /// ID
        /// </summary>
        public int Id;
        /// <summary>
        /// Name key
        /// </summary>
        public string NameKey;
        /// <summary>
        /// Map data list
        /// </summary>
        public MapData[] Maps;
    }
}
// Auto-generated files
using G2U4S;
using GAMEST;
using UnityEngine;
using System;

namespace GAMEST
{
    /// <summary>
    /// Map data
    /// </summary>
    public class MapData : ScriptableObject
    {
        /// <summary>
        /// ID
        /// </summary>
        public int Id;
        /// <summary>
        /// Name key
        /// </summary>
        public string NameKey;
    }
}
G2U outputs CSV for Spreadsheet

G2U, the editor for importing Google spreadsheet data into Unity, with commentary for starting from scratch
When using G2U, I referenced the above article.

Install G2U and open “Window / Google2u”
G2U f:id:okamura0510:20190502215457p:plain:w500
Log in to Google

Click “Sign in with Google” to log in to Google.

f:id:okamura0510:20190502215532p:plain

After login, copy the code and paste it into “OAuth Token” then click “Log In”.

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

Open “Workbooks / Account Workbooks”

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

Close the eyes of sheets other than game data

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

Change “Do Not Export” to “CSV”

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

Output CSV with the lower right floppy button

CSV is output to Google2uGen folder.
When updating Spreadsheet, click the lower left reload button then click the lower right floppy button to update.

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

Convert CSV or Excel to Script or ScriptableObject with “Tools / G2U4S”

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

Skill

Support Spreadsheet and Excel

You can use them as follows,

  • Excel for personal development and local environment
  • Spreadsheet for team development

You can use standard functions of Excel, such as calculation formula etc, without any problem.
As we use G2U, we can cooperate with Spreadsheet surely.

Flexible type and type addition

Any user-defined types can be used within the same namespace and the namespace can be changed with G2U4S.xlsx / G2U4SConst / YourNamespace.
Other types are as follows.

Type How to specify
array normal : value1, value2
struct : (value1, value2), (value3, value4)
enum Idle enum name
ScriptableObject  Empty character, due to be decided by the type name.
ScriptableObjects 1  The first column value as the ScriptableObject's key
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
See G2U4S.xlsx / G2U4SConst / BoolTrueValues
string M4u
SerializableDateTime 2019-03-22 00:51
Vector2 1, 1
Numbers with commas are initialized to 0 if you omit the latter.
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

Also, Type can be added with using “G2U4SUtil.Parse (Type type, string str, bool isArray, string assetDir = "")”, specifically, add an if branch in “Parse”.

SerializableDateTime

f:id:okamura0510:20180722170345g:plain:w300
I have made serializable DateTime in Unity
As I wrote in the above article, it was a class originally created for G2U4S.
I think that there are many opportunities to handle time in the game, in fact.

ScriptableObject in ScriptableObject

f:id:okamura0510:20190623013728p:plain
The greatest strength of G2U4S is that ScriptableObject can reference ScriptableObject.
This makes it possible to refer between data and data, so “data” and “program” can be completely separated, and Const and Enum also support it.
As a result planners and programmers can easily share work.

However there is a point to be careful. If you use AssetBundle, it may be redundantly stored in memory, so you should design for performance.

Subenum

Subenum is an extension of enum by method.
For example, if you have the following enum:

f:id:okamura0510:20190623014548p:plain:w350

“IsMove” is expanded as follows,

/// <summary>
/// Move or not
/// </summary>
public static bool IsMove(this MotionType value)
{
    return value == MotionType.Walk || value == MotionType.Run;
}

It can be used as follows.

var motion = MotionType.Walk;

if(motion == MotionType.Walk || motion == MotionType.Run)
{
    // Moving
}

// ↓ 

if(motion.IsMove())
{
    // Moving
}

Treat enum the same way as a class in C#
Originally after writing the above article, I have often tried to extend enum by method, but I felt that there was not much advantage to use because of the following reasons.

  • It takes time to define.
  • There are more places to fix.
  • If I use “using static” of C# 7, I can write shorter without extended method.

However during the development of G2U4S, I thought that if I automatically generate an enum, I should also automatically generate an extended method.
“If I generate them automatically, how about combine multiple enumerators?” I further thought, then this function is completed.

I named it Subenum!

It's like having a small enum inside an enum.

Advantages

  • Reduce the amount of code
  • Improve readability

Disadvantages

  • The cost to call increases due to extended method
  • App size increases

There is a trade-off but I personally like this function.

Localization support

G2U4S can also support localization.
In fact, I made G2U4S to localize the game I developed personally.
Use it as follows, it is a just example and there might be many other ways.

f:id:okamura0510:20190623015943p:plain:w512
[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}");
}
Event: OnInit, OnScriptCreated, OnAssetCreated and OnAssetUpdated

G2U4S is executed in the following sequence, callback is called at the end of each.

Sequence Description
Init Initialize the settings of G2U4S.xlsx, ie, create G2U4SConst, G2U4SEnum.
G2U4SUtil.OnInit() is called.
CreateScript Create scripts for Const, Enum, ScriptableObject, ScriptableObjects.
G2U4SUtil.OnScriptCreated() is called.
CreateAsset Create ScriptableObject, ScriptableObjects assets.
G2U4SUtil.OnAssetCreated() is called.
UpdateAsset Update asset values of ScriptableObject and ScriptableObjects.
G2U4SUtil.OnAssetUpdated() is called

If you want to add some processing after execution, you can use these callbacks.

END

I think this is not so powerful honestly, but is an easy-to-use skill.