🎮 Unityを愛する努力 〜コード自動生成で開発体験を変える〜
みなさまはUnityが大好きでしょうか? 私は愛しております。
💡 Unityが選ばれる理由
- 初心者でも優しいゲームエンジン
- C#をベースにしているため扱いやすい
- UEよりもネットに様々な知見や技術ブログが多い
けれども、私は現状不満なところがあります。
みなさんもこれを読んでいるということは、Unityを愛せるけど、不満なところがある人だらけだと思います。
なので、私は少しでも愛せる努力をするためにある事をしました。
📌 目次
Unityのコードの一部自動化(非AI)
私がUnityで現状不満なのが、コード生成の部分です。
using UnityEngine;
public class Test : MonoBehavior
{
}
よく使われるコードですが、ではこういうのはどうでしょうか?
using UnityEngine;
public class TestData
{
}
using UnityEngine;
public class ATestData : TestData
{
}
using UnityEngine;
public class BTestData : TestData
{
}
どうでしょう? プログラマーならこのめんどうくささはわかると思います。
😩 あるあるな煩わしさ
- 同じようなスクリプトの生成
- 同じような書き方なのに手動入力
そこの煩わしさを、できるだけ改善したのが今回私が開発中であるサポートライブラリです。
🧩 SupportChigadio
| 項目 | 内容 |
|---|---|
| 必要ライブラリ | Addressable / UniTask |
| ステータス | 開発中(一部自動生成でエラーが出るファイルがあり修正中) |
| 目的 | 開発環境での煩わしさをできるだけフリーにすること |
その中で、今回は一部の機能をご紹介します。
🗂️ データクラス生成
独自のデータ型をUIで作り自動スクリプト生成できる機能です。
プログラマー以外でも、プランナーなど他の人でも視覚的にデータを入力・編集できるメリットがあります。
🚀 セットアップ手順
- GitHub からリポジトリを取得
- 中にある
app_normal.batをクリックして起動 - ブラウザで
http://localhost:8000/にアクセス
📋 ClassDataID(データ型)
左のスライドバーから ClassDataID を選択すれば、このような画面になります。
ここで、簡易的にデータクラスをコードではなくUIで設定できます。
各データの変数の設定や、データ入力も対応しています。
✅ 操作の流れ
- 編集が終われば 保存ボタン を押す
- C#生成ボタン を押す
- 最初のGridまで戻り、全バイナリ生成 / ヘッダー生成 / TableID生成 のボタンをすべて押す
すると、app_normal.bat が置いてあるフォルダパスの一つ上のパスに data というフォルダが生成されます。
その中の class_data_id フォルダに自動生成されます。
🔧 自動生成される実際のコード(例:PlaceMap)
Row定義
using System;
using System.IO;
using System.Collections.Generic;
using UnityEngine;
using GameCore.Tables.ID;
using GameCore.Enums;
namespace GameCore.Tables
{
public class PlaceMapRow : BaseClassDataRow
{
[SerializeField]
protected PlaceMapTableID table_id;
public PlaceMapTableID TableID { get => table_id;}
[SerializeField]
protected string name;
public string Name { get => name; } // 名前
[SerializeField]
protected bool use;
public bool Use { get => use; } // 使用フラグ
[SerializeField]
protected Dictionary<GameCore.Enums.SeatPlaceID, GameCore.Classes.PlaceData> place_map = new Dictionary<GameCore.Enums.SeatPlaceID, GameCore.Classes.PlaceData>();
public Dictionary<GameCore.Enums.SeatPlaceID, GameCore.Classes.PlaceData> Place_map { get => place_map; } // プレスデータ(辞書)
public override void Read(int id,BinaryReader reader)
{
table_id = (PlaceMapTableID)id;
int len_name = reader.ReadInt32();
name = System.Text.Encoding.UTF8.GetString(reader.ReadBytes(len_name));
use = reader.ReadBoolean();
place_map = new Dictionary<GameCore.Enums.SeatPlaceID, GameCore.Classes.PlaceData>();
int place_map_count = reader.ReadInt32();
for (int place_map_i = 0; place_map_i < place_map_count; place_map_i++) {
GameCore.Enums.SeatPlaceID place_map_key;
place_map_key = (GameCore.Enums.SeatPlaceID)Enum.ToObject(typeof(GameCore.Enums.SeatPlaceID), reader.ReadInt32());
GameCore.Classes.PlaceData place_map_val;
place_map_val = new GameCore.Classes.PlaceData();
place_map_val.Read(reader);
place_map[place_map_key] = place_map_val;
}
}
}
Table定義
using System;
using System.IO;
using System.Collections.Generic;
using UnityEngine;
using GameCore.Tables.ID;
using GameCore.Enums;
namespace GameCore.Tables
{
public class PlaceMapTable : BaseClassDataID<PlaceMapTableID, PlaceMapRow>
{
static PlaceMapTable()
{
RowIndex = new PlaceMapRowIndex();
TableId = TableID.PlaceMap;
RegisterReferenceLoader(); // 依存先プリロード用に自分自身を登録
}
public override void Read(BinaryReader reader)
{
PlaceMapTable.Table.Clear();
int rowCount = reader.ReadInt32();
int colCount = reader.ReadInt32();
var colNames = new string[colCount];
var colTypes = new string[colCount];
for(int i=0; i<colCount; i++) {
int len = reader.ReadInt32();
colNames[i] = System.Text.Encoding.UTF8.GetString(reader.ReadBytes(len));
len = reader.ReadInt32();
colTypes[i] = System.Text.Encoding.UTF8.GetString(reader.ReadBytes(len));
}
RowIndex.Read(reader, true); // 行インデックスブロックを読み進めつつキャッシュしておく(高速な連続読みはそのまま維持)
for(int r=0; r<rowCount; r++) {
var enumVal = (PlaceMapTableID)Enum.ToObject(typeof(PlaceMapTableID), reader.ReadInt32());
var row = new PlaceMapRow();
row.Read(r + 1,reader);
Table[enumVal] = row;
}
}
}
}
TableID Enum
using System;
namespace GameCore.Tables.ID
{
public enum PlaceMapTableID
{
None = 0,
PlaceMap_CafeMap = 1,
PlaceMap_01 = 2,
PlaceMap_02 = 3,
PlaceMap_03 = 4,
PlaceMap_04 = 5,
PlaceMap_05 = 6,
PlaceMap_06 = 7,
PlaceMap_07 = 8,
PlaceMap_08 = 9,
PlaceMap_09 = 10,
PlaceMap_10 = 11,
PlaceMap_11 = 12,
PlaceMap_12 = 13,
PlaceMap_13 = 14,
PlaceMap_14 = 15,
PlaceMap_15 = 16,
PlaceMap_16 = 17,
PlaceMap_17 = 18,
PlaceMap_18 = 19,
PlaceMap_19 = 20,
PlaceMap_20 = 21,
PlaceMap_21 = 22,
PlaceMap_22 = 23,
PlaceMap_23 = 24,
PlaceMap_24 = 25,
PlaceMap_25 = 26,
PlaceMap_26 = 27,
PlaceMap_27 = 28,
PlaceMap_28 = 29,
PlaceMap_29 = 30,
Max
}
}
これがすべて自動生成されます。
また、class_data_id フォルダの中に all_class_data.bytes があるので、これをAddressableに登録して終了です!
あとはデータを呼び出す前に、このbytesの中を読み取り事前にデータをロードする方法は、
/// これをどこかで呼び出す
await LoadAsyncCore();
/// Coreという名前は、最初の一枚目の画像でPlaceMapはCoreと設定しているため
💻 コードでのデータの見方
では、読み込んだデータをどうコードで呼び出すか?
こちらも呼び出し方は自動で作られます。
まず、自動で作られるコード(今回の例:PlaceMap)ですと
using System;
using UnityEngine;
using GameCore.Tables;
using GameCore.Tables.ID;
using System.Collections.Generic;
namespace GameCore.Tables
{
public static class PlaceMapIDExtensions
{
public static PlaceMapRow GetRow(this PlaceMapTableID id)
{
if (PlaceMapTable.Table.TryGetValue(id, out var row))
{
return row;
}
else
{
return null; // または throw new KeyNotFoundException()
}
}
public static int ToInt(this PlaceMapTableID id)
{
return (int)id;
}
public static int ToIndex(this PlaceMapTableID id)
{
return (int)id - 1;
}
public static PlaceMapTableID ToPlaceMapTableID(this int id)
{
return (PlaceMapTableID)id;
}
public static void ForID(Action<PlaceMapTableID> action)
{
if (action == null) throw new ArgumentNullException(nameof(action));
for (EnumIDIter<PlaceMapTableID> id = PlaceMapTableID.PlaceMap_CafeMap; id < PlaceMapTableID.Max; id++)
{
action(id);
}
}
public static List<PlaceMapTableID> FindAll(Func<PlaceMapTableID, bool> predicate)
{
if (predicate == null) throw new ArgumentNullException(nameof(predicate));
var results = new List<PlaceMapTableID>();
for (EnumIDIter<PlaceMapTableID> id = PlaceMapTableID.PlaceMap_CafeMap; id < PlaceMapTableID.Max; id++)
{
PlaceMapTableID value = id;
if (!Enum.IsDefined(typeof(PlaceMapTableID), value))continue; // 無効な値はスキップ
if (predicate(value))results.Add(value);
}
return results;
}
public static PlaceMapTableID Find(Func<PlaceMapTableID, bool> predicate)
{
if (predicate == null) throw new ArgumentNullException(nameof(predicate));
for (EnumIDIter<PlaceMapTableID> id = PlaceMapTableID.PlaceMap_CafeMap; id < PlaceMapTableID.Max; id++)
{
PlaceMapTableID value = id;
if (!Enum.IsDefined(typeof(PlaceMapTableID), value))continue; // 無効な値はスキップ
if (predicate(value))return value;
}
return PlaceMapTableID.None; // デフォルト値(必要に応じて変更)
}
}
}
これも自動生成されます。
なので、先ほどの事前読み込み(Load)をしたあとに——
var row = PlaceMapTableID.PlaceMap_CafeMap.GetRow();
var name = row.Name; ///一枚目の画像のとおり、カフェが入る
これだけです。
自分からコードを手動ではなく、ある程度の自動生成でのコード。
これにより、より開発速度が上がると思います。
🔮 さいごに
ここまで書きましたが、まだまだSupportChigadioは他の自動生成に対応しております。
🛠️ 対応予定の機能
- Matrixの対応
- Stateなど
エラーがまだ出ている場所もありますが、これからも開発を進めたいと思います。
⭐ 気になった方はぜひGitHubをチェックしてみてください → SupportChigadio



