WienerWiener
  • はじめに
  • 競合比較
  • 価格・入手
  • FAQ
  • 向いているケース
  • ライセンス
  • Unity Asset版とStandalone版の違い
  • 簡易難読化・改ざん検知
  • 整合性検知・デバッグ
  • ローカライズ
  • バージョン管理
  • Google Sheets入力
  • Excel
  • YAML
  • 設定ファイル
  • データフォーマット
  • マスターデータコンバート
  • ValueOnlyフォーマット
  • スタートアップ
  • サンプル
  • Converter
  • YAML Editor
  • Setting Editor
  • 日本語
  • English
  • はじめに
  • 競合比較
  • 価格・入手
  • FAQ
  • 向いているケース
  • ライセンス
  • Unity Asset版とStandalone版の違い
  • 簡易難読化・改ざん検知
  • 整合性検知・デバッグ
  • ローカライズ
  • バージョン管理
  • Google Sheets入力
  • Excel
  • YAML
  • 設定ファイル
  • データフォーマット
  • マスターデータコンバート
  • ValueOnlyフォーマット
  • スタートアップ
  • サンプル
  • Converter
  • YAML Editor
  • Setting Editor
  • 日本語
  • English
  • データフォーマット

    • データフォーマット
    • C#
    • Json
    • PHP
    • SQL
    • CSV
    • マニュアル(html形式)

C#

概要

バイナリファイル(basic.bytes)と、バイナリの読み込み・マスターデータの使用を行うソースコードを出力する形式です。

使用方法

初期設定

  1. 設定ファイルのoutput_formatにC#出力を設定します
  2. バッチorシェルのcreate_and_copy_data_windows_unity.bat / create_and_copy_data_mac_unity.sh と copy_src_unity.bat / copy_src_unity.sh のコピー先を適切に設定します

手順

  1. バッチorシェルのcreate_and_copy_data_windows_unity.batまたはcreate_and_copy_data_mac_unity.shを実行します
  2. 初回またはyamlファイルに変更がある場合、copy_src_unity.batまたはcopy_src_unity.shを実行します
  3. Unity等C#を使用する側にて、WienerManager.csを使用します

使用例

  • Resourcesフォルダにbasic.bytesを入れている場合
using UnityEngine;
using Wiener;

public class WienerSample : MonoBehaviour
{
	public WienerManager Master { get; private set; } = new WienerManager();

	void Start()
	{
		var textAsset = Resources.Load("basic") as TextAsset;
		Master.LoadBasicPack(textAsset.bytes);
		foreach (var v in Master.BasicList)
		{
			Debug.Log($"Id:{v.Id} Detail:{v.Detail}");
		}
	}
}

出力結果

Image from alias

YAMLのHeader定義による出力形式

SourceType

SourceTypeはマスターデータの出力方法を指定します。

Normal

データクラスの出力を行い、basic.bytesからデータを読み取ります。

Enum

enum形式の出力を行います。

Const

定数の出力を行い、データはソースコードに記載されます。

ConstSource

定数ファイルの出力を行い、データはソースコードに記載されます。

ContainerType

ContainerTypeは出力データの形式を指定します。

List

配列形式でデータの出力を行います。

value[] Name;

Dictionary

辞書形式でデータの出力を行います。
Fieldsの最初のColumnがkeyとなります。

Dictionary<key, value> Name;

DictionaryList

辞書形式でvalueが配列形式のデータ出力を行います。
Fieldsの最初のColumnがkeyとなります。

Dictionary<key, value[]> Name;

WienerDictionary

Dictionaryと同じような扱いでデータ探索を行います(Unity: 二分探索 / UE5: TMap)。
Unity では Listと同等のメモリ負荷でメモリ効率が良いですが、二分探索のため検索速度はDictionaryより劣ります。
UE5 ではブループリントで使用するためにTMapを使用します。
Fieldsの最初のColumnがkeyとなります。

WienerDictionary<key, value> Name;

WienerDictionaryList

DictionaryListと同じような扱いでデータ探索を行います(Unity: 二分探索 / UE5: TMap)。
Unity では 二次元Listと同等のメモリ負荷でメモリ効率が良いですが、二分探索のため検索速度はDictionaryListより劣ります。
UE5 ではブループリントで使用するためにTMapを使用します。
Fieldsの最初のColumnがkeyとなります。

WienerDictionaryList<key, value> Name;

C#ソースコードの使用方法

ロードやデータアクセスは全てWienerManagerクラスを通じて行います。

マスターデータのロード

LoadBasicPack

basic.bytesのロードを行います。
ファイルパスまたはバイナリを指定します。
コンバート時のソースとデータに不整合がある場合はロードに失敗します。

Result LoadBasicPack(string path);
Result LoadBasicPack(byte[] buffer);

LoadBasicPackFromHash

開発用のロードメソッドです。
basic.bytesのロードを行います。
LoadBasicPackに比べてロードは遅くなります。
ファイルパスまたはバイナリを指定します。
コンバート時のソースとデータに不整合があってもロードに成功します。
バイナリに存在してソースに存在しないデータは無視され、ソースに存在してバイナリに存在しないデータはdefault値が入りますが、難読化データはおかしな値となるので注意してください。
ロードに失敗したマスター一覧をonMissingTargetsから取得できます。
WienerManager.SetMissingDelegateでデリゲートを登録しておくことで、正常に読み込みを行えなかったマスターのアクセスを検知できます。

Result LoadBasicPackFromHash(string path, Action<string[]> onMissingTargets);
Result LoadBasicPackFromHash(byte[] buffer, Action<string[]> onMissingTargets);

マスターデータへアクセス

マスターデータのロードが完了している状態でWienerManagerの各プロパティからアクセスできます。
プロパティはHeader定義のNameで作成されます。
次のサンプルはBasicDictionaryとBasicDictionaryListを作成した時WienerManagerで作成されるプロパティを表示しています。

定義YAML

header:
  ...
  source_type: Normal
  container_type: Dictionary
  name: BasicDictionary
  ...
header:
  ...
  source_type: Normal
  container_type: DictionaryList
  name: BasicDictionaryList
  ...

出力されるWienerManager.cs

public partial class WienerManager
{
	...
	public Dictionary<int, BasicDictionaryData> BasicDictionary { get { if (BasicDictionary__ == null) onMissing?.Invoke("BasicDictionary"); return BasicDictionary__; }}
	public Dictionary<int, BasicDictionaryListData[]> BasicDictionaryList { get { if (BasicDictionaryList__ == null) onMissing?.Invoke("BasicDictionaryList"); return BasicDictionaryList__; }}
	...
}

その他

難読化されたデータの改ざん検知

SetFalsifyDelegateメソッドでデリゲートを登録しておくと、難読化されたデータのメモリ上の改ざんを検知できます。

void SetFalsifyDelegate(Action<string> onFalsify);

ロード時の不整合マスターデータアクセス検知

SetMissingDelegateメソッドでデリゲートを登録しておくと、 LoadBasicPackFromHashでロードした際に、正常にロードできなかったマスターデータへのアクセスを検知できます。

void SetMissingDelegate(Action<string> onMissing);

C#クラス

WienerDictionaryクラス

WienerDictionary<TKey, TValue> は、Unity 向けの省メモリな辞書風コンテナです。ソート済みデータを二分探索で検索します。UE5 ではブループリント対応のため TMap を使用します。

インデクサで存在しないキーを指定した場合は Dictionary<TKey, TValue> と同じく KeyNotFoundException を投げます。 列挙時は KeyValuePair<TKey, TValue> を返します。

namespace Wiener
{
	public class WienerDictionary<TKey, TValue> : IEnumerable<KeyValuePair<TKey, TValue>>
		where TKey : IComparable
		where TValue : IWienerDictionaryData<TKey>, new()
	{
		public int Count => values.Length;

		public IEnumerable<TKey> Keys { get; }

		public IEnumerable<TValue> Values { get; }

		public TValue this[TKey key] { get; }

		public WienerDictionary(WienerDataReader reader, WienerString[] stringList, DateTime?[] dateTimeList, bool hash = false);

		public bool ContainsKey(TKey key);

		public bool TryGetValue(TKey key, out TValue value);

		public IEnumerator<KeyValuePair<TKey, TValue>> GetEnumerator();
	}
}

WienerDictionaryListクラス

WienerDictionaryList<TKey, TValue> は、Unity 向けのグループ化された値を扱う省メモリな辞書風コンテナです。仕組みは Dictionary<TKey, TValue[]> とほぼ同じですが、ソート済みデータを二分探索で検索し、内部では TValue[][] だけを保持します。UE5 ではブループリント対応のため TMap を使用します。

インデクサで存在しないキーを指定した場合は Dictionary<TKey, TValue[]> と同じく KeyNotFoundException を投げます。 列挙時は KeyValuePair<TKey, TValue[]> を返します。

namespace Wiener
{
	public class WienerDictionaryList<TKey, TValue> : IEnumerable<KeyValuePair<TKey, TValue[]>>
		where TKey : IComparable
		where TValue : IWienerDictionaryData<TKey>, new()
	{
		public int Count => values.Length;

		public IEnumerable<TKey> Keys { get; }

		public IEnumerable<TValue[]> Values { get; }

		public TValue[] this[TKey key] { get; }

		public WienerDictionaryList(WienerDataReader reader, WienerString[] stringList, DateTime?[] dateTimeList, bool hash = false);

		public bool ContainsKey(TKey key);

		public bool TryGetValue(TKey key, out TValue[] value);

		public IEnumerator<KeyValuePair<TKey, TValue[]>> GetEnumerator();
	}
}

WienerManagerクラス

namespace Wiener
{
	public class WienerManager
	{
		/// <summary>
		/// basic.bytesの読み込みを行う
		/// </summary>
		/// <param name="path">basic.bytesのパスを指定</param>
		/// <returns></returns>
		public Result LoadBasicPack(string path);

		/// <summary>
		/// basic.bytesの読み込みを行う
		/// </summary>
		/// <param name="buffer">basic.bytesのバイナリを指定</param>
		/// <returns></returns>
		public Result LoadBasicPack(byte[] buffer);

		/// <summary>
		/// basic.bytesの読み込みを行う
		/// 
		/// このメソッドは開発中の使用を想定しています
		/// Wienerは高速読み込みを行う仕組み上、ソースとバイナリが常に一対である必要がありますが
		/// 開発中はソースとバイナリがずれることがあります
		/// LoadBasicPackFromHashを使用すると不整合なデータは無視してロードを成功させます
		/// onMissingTargetsにロードで不整合なマスター一覧が返ってくるので、ログ表示等に使用してください
		/// またSetMissingDelegateメソッドでデリゲートを登録しておくことで、不整合なマスターのアクセスを検知できます
		/// 
		/// </summary>
		/// <param name="path">basic.bytesのパスを指定</param>
		/// <param name="onMissingTargets">ロード失敗リストを受け取るデリゲート</param>
		/// <returns></returns>
		public Result LoadBasicPackFromHash(string path, Action<string[]> onMissingTargets);

		/// <summary>
		/// basic.bytesの読み込みを行う
		/// 
		/// LoadBasicPackFromHashのバイナリ読み込み版
		/// 
		/// </summary>
		/// <param name="buffer">basic.bytesのバイナリを指定</param>
		/// <param name="onMissingTargets">ロード失敗リストを受け取るデリゲート</param>
		/// <returns></returns>
		public Result LoadBasicPackFromHash(byte[] buffer, Action<string[]> onMissingTargets);

		/// <summary>
		/// 難読化されたデータの改ざん検知登録
		/// 
		/// SetFalsifyDelegateメソッドでデリゲートを登録しておくと
		/// 難読化されたデータがメモリ上の改ざんを検知できます
		/// 
		/// </summary>
		/// <param name="onFalsify">改ざん検知デリゲートを指定</param>
		public void SetFalsifyDelegate(Action<string> onFalsify);

		/// <summary>
		/// 非ロードマスターデータアクセス検知登録
		/// 
		/// SetMissingDelegateメソッドでデリゲートを登録しておくと
		/// LoadBasicPackFromHashでロードした際に、ロードできなかったマスターへのアクセスを検知できます
		/// 
		/// </summary>
		/// <param name="onMissing">非ロードマスターデータアクセス検知デリゲートを指定</param>
		public void SetMissingDelegate(Action<string> onMissing);
	}
}

サンプル

Basic/Basic.xlsm#辞書

ABC
IdName
1ご飯
2パン
3パスタ

yaml

header:
  input_path:
  - path: Basic/Basic.xlsm
  sheet: 辞書
  row: 1
  base_type: Normal
  source_type: Normal
  container_type: Dictionary
  output_path: Basic
  name: BasicDictionary
fields:
- key: Id
  name: id
  type: Int
- key: Name
  name: name
  type: String
...

出力CS

ソースコードにはマスターデータの値は含まれていないためbasic.bytesのロードが必要になります。

/// <summary>
/// Automatically generated code
/// </summary>
using System;
using System.IO;
using System.Collections.Generic;

namespace Wiener
{
	public partial class BasicDictionaryData
	{
		public int Id { get; private set; }
		public string Name => stringList[NameStringIndex].Value;
		public uint GroupId { get; private set; }

		WienerString[] stringList;
		uint NameStringIndex;

		public static (Dictionary<int, BasicDictionaryData>, bool) Create(string path, WienerString[] stringList, DateTime?[] dateTimeList)
		{
			var buffer = File.ReadAllBytes(path);
			var reader = new WienerDataReader(buffer);
			return Create(reader, stringList, dateTimeList);
		}

		public static (Dictionary<int, BasicDictionaryData>, bool) Create(WienerDataReader reader, WienerString[] stringList, DateTime?[] dateTimeList)
		{
			reader.GetKeyHashTable(false);
			var length = reader.ReadInt32();
			var value = new Dictionary<int, BasicDictionaryData>(length);
			for (var i = 0; i < length; ++i)
			{
				var key = reader.ReadInt32();
				var val = new BasicDictionaryData();
				val.Load(reader, stringList, dateTimeList);
				value.Add(key, val);
			}
			return (value, false);
		}

		public static (Dictionary<int, BasicDictionaryData>, bool) CreateFromHash(WienerDataReader reader, WienerString[] stringList, DateTime?[] dateTimeList, List<string> errorList)
		{
			var error = !reader.HashSeek(Hash);
			if (error)
			{
				errorList.Add("BasicDictionaryData");
			}
			var hashTable = reader.GetKeyHashTable(true);
			var length = reader.ReadInt32();
			var value = new Dictionary<int, BasicDictionaryData>(length);
			for (var i = 0; i < length; ++i)
			{
				var key = reader.ReadInt32();
				var val = new BasicDictionaryData();
				val.Load(reader, hashTable, stringList, dateTimeList);
				value.Add(key, val);
			}
			return (value, error);
		}

		void Load(WienerDataReader reader, WienerString[] stringList, DateTime?[] dateTimeList)
		{
			this.stringList = stringList;
			Id = reader.ReadInt32();
			NameStringIndex = reader.ReadUInt32();
			GroupId = reader.ReadUInt32();
		}

		private static readonly string Hash = "04ac9e6b16032778741f344174dfc928";

		static readonly Dictionary<string, Action<BasicDictionaryData, WienerDataReader>> HashLoadDict;
		static BasicDictionaryData()
		{
			HashLoadDict = new Dictionary<string, Action<BasicDictionaryData, WienerDataReader>>()
			{
				["462479fdd1ee4f66f2488ac46a489fff"] = LoadId,
				["e3a369b99caa37cd8990e202315e8eff"] = LoadName,
				["f9f3f95ae38a75495795f08f7dfdec99"] = LoadGroupId,
			};
		}
		public void Load(WienerDataReader reader, List<(string hash, byte type)> keyHashTable, WienerString[] stringList, DateTime?[] dateTimeList)
		{
			this.stringList = stringList;
			foreach (var v in keyHashTable)
			{
				if (HashLoadDict.TryGetValue(v.hash, out var action))
				{
					action(this, reader);
				}
				else
				{
					reader.SkipReader(v.type);
				}
			}
		}
		static void LoadId(BasicDictionaryData self, WienerDataReader reader)
		{
			self.Id = reader.ReadInt32();
		}
		static void LoadName(BasicDictionaryData self, WienerDataReader reader)
		{
			self.NameStringIndex = reader.ReadUInt32();
		}
		static void LoadGroupId(BasicDictionaryData self, WienerDataReader reader)
		{
			self.GroupId = reader.ReadUInt32();
		}
	}
}
最終更新:: 2026/06/12 2:44
Contributors: artisan-sawasaka
Prev
データフォーマット
Next
Json