YAMADA TAISHI’s diary

ゲームについてとか私の日記とか。このブログのあらゆるコードは好きにどうぞ。利用規約があるものは記事内のGitHubのRepositoryのリンクで貼られていると思うので、そちらを参照ください。

URPのライティングについて

こんにちは、やまだたいし( https://twitter.com/OrotiYamatano )です。
前に書いてあったライトの知見を共有します。

目次


ライト周りで困ることは多い


Unityで負荷の少ない環境を作るとき影の解像度をいじったりしたくなる。
しかしこのあたりがしっかりとまとまっているサイトは意外と多くない。

今回はまとめていきたい。

前提条件


URP

Unity 2021.3以降

何によってライトが決まるのか


実際にライトが対象に当たるとき光源となるのは何なのか、
何が影響して光っているように見えるのかいくつか要因がある。

レンダリング順に並べると下の画像の様になります。
設定でいうと
レンダリングパイプラインの種類
・Lightingウィンドウの設定
・メッシュのEmission設定(静的・動的)
・メッシュの設定(静的・動的)
・ライトソースの設定(静的・動的)
・プロジェクト設定のクオリティ
・プロジェクト設定のグラフィックス
・ポストエフェクトの設定
あたりを見れば良い
様々な設定があるが、
まずは簡単に説明できる動的ライティングの説明から。

動的ライティング


まずライトソースがリアルタイム or Mixになっていると動的(リアルタイム)な影を生成するようになる。
また影を作るオブジェクトが静的(static)とマークされていないことでリアルタイムに影を生成する。
(※Lighting設定でmixになっていると挙動が変わってくるので注意)

ライトの質をコント―ロールするにはURPアセットの編集が必要。
(プロジェクト設定のグラフィックスのAssetに設定されている)

MainLight→メインライトのリアルタイム描画を有効にするかどうか
Cast Shadow:影のON/OFF
Shadow Reslution:影の解像度
Additinonal Lights→追加ライトのリアルタイム描画を有効にするかどうか
影の解像度(Shadow Reslution)はShadowのDistanceベースで貼り付けが行われる。
なので影の解像度を高くしたければ、Shadow Reslutionを上げるか、Distanceを縮めると良い。
Cascade:距離に応じて影の解像度を変える設定。
Depth BiasとNormal Bias→シャドウアクネと呼ばれるアーティファクトが発生しないように補正処理を行うものらしい。
処理負荷が高いので要調整。

静的ライティング


ライトは動的に生成すると重い。
そのため、一度影の計算をしておいてシャドウマップ・ライトマップとして保存しておく事ができる。
静的ライトを利用するには、影を落とすオブジェクト(メッシュ)を静的(Static)にする必要がある
また、ライトソースの設定をミックスorベイク(baked)にする必要がある

LightingウィンドウのSceneタブのMixedLighing→BakedGlobalIlluminationにCheck☑をいれ、Generate Lighingのクリックでベイクが実行される
オブジェクトに対するライトマップの解像度は、オブジェクトごとに決められる。
ライトマップScaleというパラメーターがメッシュレンダラーに存在する。
(小さくすると荒くなる)

ライトマップ全体の解像度はMaxLitghMapSizeにて決められているので遠くのオブジェクトは解像度を低くして近距離は高くすると良い。
また、小さな静的オブジェクトはディティールまで見ないため、ソレで大きなライトマップの範囲を使用するのはもったいないため、メッシュレンダラーでライトプローブへのベイクをOFFにすることを推奨。

(Mixライティングでライトプローブの影響を受けるようにすれば小さいオブジェクトまでベイクする必要がなくなる。ライトプローブについては後述)
また、LightingウィンドウのSceneタブの
Direct Smples(直接光サンプル数),Indirect Samples(間接光サンプル数),Environment Samples(環境光サンプル数)にてサンプル数を設定できる。
使用するユニットのテクセルに依存する。
Bounces→光の跳ね返りの回数
Filtering→光の到達工合が変わってくるため、フィルタで自然な影に加工する方法
(Autoが自動でAdvanedが上級向け設定)
フィルタリングはuvチャートという単位で適用されるらしい。
(今回は上級向け設定は割愛)

混合ライティング


混合ライティングは動的ライトと静的ライトをうまく組み合わせてライティングするもの。
MixするにはパイプラインAssetにて混合ライティング(Mixed Lighting)をONにする必要性があり、ライトソースの設定をミックスにする必要がある。
種類は主に3つ。

  • Baked indirect(間接光のベイク)
  • Subtractive(減算モード)
  • Shadow musk(シャドウマスク)/Distance Shadow musk(ディスタンス(距離)シャドウマスク)

Shadow muskだけShadow muskとDistance Shadow muskの設定に分かれる。
種類はLightingウィンドウのシーンのタブ、LightingModeで指定が必要。
また混合ライティングを実現するためにベイクライトを動的オブジェクトに反映させる方法がある。
こちらはライトプローブの説明にて解説。

Baked indirect(間接光のベイク)


間接光のみをベイクし、影や直接光は通常通りリアルタイムに処理を行う。
リアルタイムのため遠距離の影は生成されない。
同じシーンでディレクショナルライトをOFFすることによって夜を表現出来たりする。
遠距離を見る必要がない、上から視点や霧の濃いゲームに適している。

Subtractive(減算モード)


静的なオブジェクトほぼ全てをベイク。ライト、影、間接光。
リアルタイムなオブジェクトにも影を落とすことが出来る。
減算でベイクするため要影色調整。(リアルタイムな影の色と差異が出てしまう)
また光沢情報はライトマップの解像度に依存するため、静的オブジェクトに対する光沢(スペキュラ)情報が大きく失われることになる。
(動的なオブジェクトはベイクしないので、ライトプローブ設定を細かにすることで明るさを反映させる必要がある)
(シャドウオンリィというオプションもあるがココでは説明割愛)
とにかく軽くしたい場合に有効。

Shadow musk(シャドウマスク)/Distance Shadow musk(ディスタンス(距離)シャドウマスク)


Shadow muskは大きく2種類
静的なオブジェクトの影の情報と間接光の情報のみベイク。
静的なオブジェクトのシャドウマスクを事前に生成。
(動的なオブジェクト以外は)質感表現はリアルタイムのため光沢(スペキュラ)もきれいに出る。
(ShadowRadiusによりソフトな影も可能)
影と事前ベイクの影が同じ質感で影を落とす。
静的なオブジェクトの影はダイナミックなオブジェクトに影をおとさない。
(ライトプローブ設定を細かにすることで明るさを反映させる必要がある)
(シャドウオンリィというオプションもあるがココでは説明割愛)
1オブジェクトに対して4つしかライトが割り当てられない。
(マスクにRGBAのテクスチャを使っている関係上)
(ダイナミックな影とは相性が悪いが)Shadow muskはかなり綺麗な影を落とすので画にこだわりたい場合に有効。
Distance Shadow muskは広いステージで違和感なく表示できるので広いステージを作る場合に有効。
Distance Shadow musk(ディスタンス(距離)シャドウマスク)
近距離はリアルタイムシャドウで、遠距離はシャドウマスクのものを使うオプション
プロジェクト設定のクオリティにて設定可能

明かりの種類


(なお、やまだたいしによる分類です)

1.ライトソース


直接光(Direct lighting)

  • ポイントライト
  • スポットライト
  • リアライト

距離減衰が存在する光源。
電球やスポットライトなどで使われる。
それぞれエリアライトはリアルタイムで使えないなど様々な制約がある
光の跳ね返りなどは含まない。
距離減衰をし、距離に応じて影の濃さが変わる。

ディレクショナルライト(指向性ライト,directional lighting)

  • 太陽光など大きな光

距離減衰をしない平行な光。
何処から光が指しているかにかかわらず影を作る。
本物の太陽は距離減衰をするが、Unityでは距離減衰をしないライトを使用する。

2.エミッション(Emission)


エミッシブマテリアル。光るオブジェクト。
Materialが直接自己発光してるかのように見えるオブジェクト。
設定により、単純にBloom処理されるだけか、ライトとして処理されるか変わる。
光源として利用したい場合、
エミッションプロパティを有効にし
Lightmap Staticに設定する必要がある。
RealtimeGIで利用するか、BakedGIで利用するか選択する。
(選択したGIが有効でないと動作はしない)

Bloomの調整

HDRPならパラメーターの
Use Emission Intensity をチェックして
Emissive Color に色を指定、
Emission Intensity の指定
Exposure weightの指定を行い露出の強さを変更をすることで調整が出来る
URPではポストエフェクトで調整が必要
URPAssetにてHDRを有効化し
MaterialにもHDR(ハイダイナミックレンジ)の色指定が必要
(RGBに1以上の値を格納するためで1を超えると発光する)
またポストエフェクトにてBloomのポストエフェクトをかける

3.アンビエントライト(Ambient light,環境光)


拡散環境光(アンビエントライト)として知られているライトは 、シーン周囲すべてに存在するライト。
URPではLighingウィンドウのEnvironmentタブのEnvironmentにて設定が出来る。
シーン全体を暗くしたい場合、この設定のライトを暗くする必要がある。

4.間接光(Indirect lighting)


光が反射した結果出る光。
ライトから出た光が1回以上反射してから届く光。

ライトの種類についてまとめ


4は1~3までの結果を元に下記GIにて生成される。
Lightingウィンドウの設定によって挙動が変わってくる。
最初に大雑把にリアルタイムで
ライトを配置してどのようなイメージになるかシュミレーションしてから
各ライトや設定をベイクor Mixに変更し調整するのをオススメしたい。

グローバルイルミネーション(大域照明,GI,Global illumination)


空間表現をシミュレーションする。
まっとうな間接光では計算が長いためソレを軽量化するための仕組み。
簡単にいうとUnityでは間接光(照り返し)に関する全体設定。
GIにはいくつかの処理がある。
Unityではベイクした/動的なライトマップ、放射照度のボリューム、光伝播ボリューム、ベイクした/動的なライトプローブ、ボクセルベースのGI、距離フィールドベースのGIなどがある。
Unity内部ではEnlighten というソフトを利用しているらしい
(リアルタイム以外の利用は非推奨。リアルタイムGIではコレが利用されるが後に廃止になるかも?)
Progressive CPUというのが現在は主流。
(Progressive GPUに変わってくるかも)
GIが無効の場合、静的ライトは動作しない。
リアルタイムGIはGI情報が随時更新される。
昼→夜など変わる場合に有効。

ライトマッパー(lightmapper)


光線を射影し、ライトバウンスを計算し、結果のライティングをテクスチャに適用する。
ライトマップとライトプローブのデータを生成。

ライトマップ


3Dメッシュ上の光の強度や色をあらかじめ「ライトマップ」というテクスチャに焼き込んでおくもの。
プログレッシブライトマッパーというツールを使って生成。
(プログレッシブライトマッパーはパストレーシングに基づく高速のライトマッパーシステム )

ライトプローブ


ライトマップ同様、ライトプローブはシーンのライティングに関する “ベイクした” 情報を格納します。
両者の違いは、ライトマップはシーンの サーフェス に当たるライティングの情報を格納するのに対し、ライトプローブはシーンの なにもない空間 を通り抜けるライトの情報を格納する。
つまり、ベイクライトを動的オブジェクトに反映することが出来る。
ライトプローブはライトプローブグループをシーンに配置することでベイクすることが出来る。
動的オブジェクトは最も近くにあるライトプローブからサンプリングを行い、ライトの結果に合成する。
ライトプローブは影は提供されない、ライティング情報と明かりの程度、色を提供する。
利用するライトプローブは1つではなく4つほど。

リフレクションプローブ


金属の反射に関する部分です。
割愛。
要望があるなら書きます。

参考記事


  

まとめ


様々な要素が絡み合うが、基本的な要素は単純。
一度使い方を理解してしまえばスマホ、コンシューマー様々なケースに対応した適切なライティングを設定できると思った。

がんばえー

【前編】UnityのAddressableを個人制作で使いこなす

こんにちは、やまだたいし( https://twitter.com/OrotiYamatano )です。
UnityのAddressablesを個人でも導入したいと思い今回はそれの備忘録です。

目次


Addressablesを個人でも導入したいわけ


Unityでアセットを読み込む場合いくつかの方法がある。

・Unityのシーンファイルに紐づけてしまう方法
・Resourcesに格納してしまう方法
・Streaming Assetsとして格納してしまう方法
・AssetBundlesとして管理する方法
・Addressablesを使う方法
・ContentLoadModuleを使う方法

後、例外的にEntitiesはContentLoadModule上に独自システムが構築されており、Content managementという名前の機能がある。

現在企業でも使われている方法がAddressablesだ。
AssetBundlesを使っている企業もいるがAssetBundlesは使いづらくAddressablesへ移行が進んでいる。

特段気にせず、Unityシーンに全て紐づけてもいいが、アプリのサイズが大きくなってしまう問題点や、
Unityのシーンファイルを開いたときのメモリ使用量が格段に上がってしまう。

次に考えるのはResourcesだが、Unityとしては非推奨になっている。

learn.unity.com

個人的には個人開発者であればResourcesでも構わないと思うが、
ストアのアプリを更新せずに中のリソースを更新したりできる利点などもあるので是非に一歩先に行きたい個人開発者は導入を考えたい。

また、多言語対応する場合はこの機能を使うのが主流となっている。

今回は私の勉強がてらAddressableについて深堀りしていこうと考えている。

正直使うだけなら他の方々のブログを参考にしていただいたほうが良いだろう。
www.hanachiru-blog.com

そもそもAddressablesとはなにか?


アセット管理システム。
非同期ロードを使用して、あらゆる依存関係のコレクションが存在する、任意の場所からロードを行うことができる。

unity.com

どうやって管理していくか


Addressableはかなり複雑だ。
しっかり管理するならCDNも必要だし、何と言ってもメモリ管理が複雑になる。
まずは手始めにローカルのリソースを使う方法を調べていく。

ダウンロード


まずは PackageManager によりインストールを行います。
現在(2023/12/23)インストールできるもので最新は2.0.6となっている。
安定版は1.21.19だ。

docs.unity3d.com

インストールすると、以下のようなメニューが追加される。

Create Addressables Settingsをクリック。

するとAssets配下にAddressableAssetsDataという名前でAddressablesを利用するためのデフォルト設定で初期設定ファイル郡が生成される。

初期設定


Addressablesの初期設定はいくつかある。
各種ScriptableObjectで構成され保存されている。

AddressableAssetSettings


基本設定だ。

docs.unity3d.com

Profile(プロファイル)

どうやってAddressablesを使うかの設定。

  • Local
    ローカルでの設定。
  • Remote
    リモートでの設定。
  • BuildTarget
    ビルドターゲットの名前
  • New Entry
    ビルドしたアセットの保存先

LocalとRemoteで設定できる内容はバンドルの場所だ。
ちなみにバンドルとはAddressablesの対象としたアセットのことだが、圧縮済みのビルド済みデータのこと。
Local設定でありながらリモートの場所の設定ができたり、
その逆、Remote設定でありながらLocalの指定ができたりする。ややこしい。

  • Built-In
    ローカルコンテンツ用の場所プレイヤービルドに自動的に含まれる
  • Editor Hosted
    エディターの [ホスティングサービス] で使用するパス定義
  • Cloud Content Delivery
    Cloud Content Deliveryのパス。ちなみにCloud Content DeliveryとはUnity公式のCDNみたいなもの。
  • Custom
    自由に設定ができる。

ホスティングサービスってどこのことを言っているかというとココ

今回はLocalで使うのでどちらとも一旦ビルドインにしておく。

ちなみにLocalとRemoteどちらを使うのか指定は別途する箇所があるのだがコチラは後述する。

Diagnostics (診断)

ログ出力のためのオプション。

  • Send Profiler Events
    プロファイラーイベントを有効化
  • Log Runtime Exceptions
    アセットのロード操作で発生したランタイム例外をログに記録

Catalog (カタログ)

どこからなんのアセットをとってくるか情報が書かれているCatalog,そのカタログの設定。
ちなみにLocalのみでアセットを管理している場合はCatalogの存在はほとんど意識する必要はない

  • Player Version Override
    カタログのPlayerVersionの上書き
  • Compress Local Catalog
    Localのカタログの圧縮設定をするか
  • Build Remote Catalog
    リモート カタログを構築設定.
    読み込む場所を指定する。
  • Only update catalogs manually
    リモート カタログの自動チェックが無効化

Update a Previous Build(ビルド更新設定)

  • Check for Update Issues
    Check for Content Update Restrictions ツール(アセットに問題があったときに対処するツール)で
    どのように対処するかの設定?もしくはどのような場合に更新させるかの設定。読んでもよくわからんかった。
    List Restricted Assets (recommended) と書いてる通りList Restricted Assetsが推奨そうなのでList Restricted Assetsで良さそうだ。
    多分リストに差分があった場合に更新してくれる。
  • Content State Build Path
    コンテンツ状態ビルド先の設定

Downloads (ダウンロード)

  • Custom certificate handler
    Httpsの証明書確認用。
    Unityengine.Network.CertificateHandlerクラスを拡張して実装するらしい。
  • Max Concurrent Web Requests
    最大WebrRequest数,2~4 が最適な同時リクエスト数らしい。
  • Catalog Download Timeout
    カタログダウンロードのタイムアウト時間の設定。
    0はタイムアウトなし.

Build (ビルド)

  • Build Addressables on Player Build
    Addressableがプレイタービルドの一部としてビルドされるかどうか?
    Build Addressables content on Player Build→プレイヤーのビルド時に常に Addressables コンテンツをビルドする
    Do not Build Addressables content on Player Build→Addressables コンテンツをビルドしない。Addressables コンテンツを変更した場合は手動でのビルドが必要。
    Use global Settings (stored in preferences)→ エディターの環境設定に従う。
  • Ignore Invalid/Unsupported Files in Build
    無効なファイルがあったときの挙動の指定。
    チェックがある場合はビルドを中止するのではなく、それらのファイルを除外
  • Unique Bundle IDs
    ビルドで一意のバンドル名を作成するかどうか
  • Contiguous Bundles
    効率的なバンドルレイアウトを作成するかどうか。
  • Non-Recursive Dependency Calculation
    有効にすると、アセットに循環依存関係があるときにビルド時間が短縮
    注意点:循環依存関係によっては、このオプションを有効にするとロードできない場合があるらしい.

Build and Play Mode Scripts (ビルドスクリプトと再生モードスクリプト)

Play Mode Scriptでの読み込み設定
どのようにビルドプロセスを処理をさせるかそれぞれ処理が入っている。
特に弄ることはなさそう。

Asset Group Templates(アセットグループテンプレート)

作られるアセットグループのテンプレ設定を置いておく。

Initialization Objects(初期化オブジェクト)

Addressables.InitializeAsync呼んだときの初期設定をここに詰め込むっぽい。
Unity公式として設定があるのはキャッシュ初期設定だけだけど、
ユーザーでカスタマイズしてScriptableObject形式で処理を追加できるらしい?
特段触る必要はなさそう。

Group settings(グループ設定)

Addressablesにはそれぞれグループごとに設定ができる。

Content Update Restriction

  • Prevent Updates(アップデート禁止)
    チェックが入っている場合、バンドル内のアセットが変更されている場合のみ、バンドル全体が再構築
    チェックがない場合は毎回上書きされる

Content Packing & Loading

  • Build & LoadPaths
    biuld path設定.
    Profile(プロファイル)で設定した内容

  • Advanced Options

    • Asset Bundle Compression
      圧縮形式の指定
    • Include In Build
      このグループのアセットをコンテンツビルドに入れるかどうか。
    • Force Unique Provider

    • Use Asset Bundle Cache
      リモート配布するバンドルをキャッシュするかどうか。

    • Asset Bundle CRC
      ロード前に、バンドルの整合性を確認するかどうか
      Disabled(無効)、Enabled, Including Cached (有効、キャッシュされたものを含む)、Enabled, Excluding Cached (有効、キャッシュされたものを除く)
    • Use UnityWebRequest for Local Asset Bundles
      ローカル AssetBundle アーカイブをロードするときに、AssetBundle.LoadFromFileAsync ではなく
      UnityWebRequestAssetBundle.GetAssetBundle を使用
    • Request Timeout
      リモートバンドルのダウンロードのタイムアウト間隔。
    • Use Http Chunked Transfer
      バンドルのダウンロード時に HTTP/1.1 チャンク転送エンコーディング方式を使用するか
      非推奨らしいので使わなくていい。
    • Http Redirect Limit
      バンドルのダウンロード時のHttpリダイレクト許容数。-1だと無制限.
    • Retry Count
      ダウンロードが失敗したときに再試行する回数。
    • Include Addresses in Catalog
      カタログにアドレス文字列を入れるかどうか
      入れない場合はカタログサイズが小さくなるらしい.
    • Include GUIDs in Catalog
      カタログに GUID 文字列を入れるかどうか。
      AssetReference を使用してアセットにアクセスするなら必須らしい。
      入れない方が軽量化できるらしい
    • Include Labels in Catalog
      カタログにラベル文字列を入れるかどうか
      ラベルを使用しない場合にOffにできる、まあない方が軽量化できるらしい
    • Internal Asset Naming Mode
      AssetBundle 内のアセットの ID を決定の指定
      Full Path: プロジェクト内のアセットのパス
      Filename: アセットのファイル名(もちろん同一ファイル名禁止)
      GUID:GUID
      Dynamic:(文字情報がすくなくなるから)推奨らしい グループ内のアセットに基づいて作成できる最も短い ID
    • Internal Bundle Id Mode
      AssetBundle が内部的にどのように識別されるかを決定の指定
      Group Guid:推奨グループの一意の ID
      Group Guid Project Id Hash:グループ GUID とクラウドプロジェクト ID
      Group Guid Project Id Entries Hash: グループ GUID、クラウドプロジェクト ID
    • Cache Clear Behavior
      キャッシュから AssetBundle を消去するタイミングを決定
      ClearWhenSpaceIsNeededInCache :キャッシュにスペースが必要な場合はクリア
      ClearWhenWhenNewVersionLoaded:新しいバージョンが正常にロードされると、バンドルはキャッシュから削除
      古いの残ってても仕方ないしClearWhenWhenNewVersionLoadedでよくね?
    • Bundle Mode
      バンドルにパックする方法の指定
      Pack Together: すべてのアセットを含む 1 つのバンドルを作成
      Pack Separately: グループ内のプライマリアセットごとにバンドルを作成
      Pack Together by Label: 同じ組み合わせのラベルを共有するアセットのバンドルを作成
      うーん、特にこだわりが無ければPack Togetherか?
    • Bundle Naming Mode
      AssetBundle のファイル名を作成する方法の指定
      Filename: グループ名から派生した文字列
      Append Hash to Filename: グループ名から派生した文字列+バンドルハッシュを付加した文字列
      Use Hash of AssetBundle: バンドルハッシュ
      Use Hash of Filename: Filenameから計算されたハッシュ
    • Asset Load Mode
      アセットを個別にロードする (デフォルト) か、グループ内のすべてのアセットを常にまとめてロードするか指定
      Requested Asset and Dependencies(Requestアセットとその依存)が推奨らしい

    • Asset Provider
      「AssetBundle内のアセット」をロードするためのプロバイダークラスを定義
      カスタムで作ってなければAssets from Bundles Provider

    • Asset Bundle Provider
      「AssetBundle」をロードするためのプロバイダークラスを定義
      カスタムで作ってなければ AssetBundle Provider

だいぶ理解が進んだ


だいぶ理解が進んだが、行数も多くなってきたので今回の記事はココまでにしておく

今回ばかりは長くなりそうな予感。

広告掲載初めて1年経ったので広告収益を晒す

こんにちは、やまだたいし( https://twitter.com/OrotiYamatano )です。
今日は私のブログに広告を掲載し始めてから1年が経ちました。そこで、この1年間の広告収益について皆さんと共有したいと思い記事化しました。

目次


なぜ広告収益化を始めたのか


このブログは、Unityに関する知識を雑に共有する場として利用していました。
特にここ1年は月の初め、もとい1週間ちょいで1000PVを取得するようになったので
せっかくだから収益化し
このブログで年に一度ピノが食べれたら嬉しいなと考えたのと、
収益化自体がちょっとしたネタにもなると考え収益化を始めました。

収益の予測と実際


通常のブログではPV単価が0.3円程度とされています。
私のブログの読者は技術者が多く、Adblockを使用している方も多くなると考えました。
そこで実際の単価は0.1円程度だろうと予測しました。

実際に1年のPV数と予測を照らし合わせて見ようと思います。
実際のPV数は49,314
収益は6,927円でした。

ちなみに実際の収益のスクショ。
ちょうど一年のときのスクショは忘れたので今日時点の情報↓(AdBlockのBlockを最近有効化したので、ちょっと増えてる)

単価にすると0.14円

つまり、予測にかなり近い形になりました。

クリック率が低い理由は、いくつかの要因が影響していると考えらます。
例えば現在、はてなブログの無料プランにおける元の広告と、私が設置したGoogle Adsenseの広告が重複して表示されてしまっています。
そのため広告の量が増えてしまっています。
その広告の多さが私の広告へのクリック率に影響を与えている可能性があります。
現在のクリック率は、こうした要因を考慮すると、理解できる範囲内の結果かもしれません。

まぁ月に1本程度しか書いてないのに月577.25円と考えると満更でもないかなぁという感じです。

しかしここ最近は閲覧数が伸びている割に収益率が悪くなっているので、申し訳ないですが、AdBlockのBlockを入れさせて貰うことにしました。

稼いでくれた記事について


デザイナー向け記事

この1年間で特に注目されたのは、AIに関する記事やデザイナー向けの技術記事でした。
デザイナーは記事を書くことが比較的少ないため本ブログにアクセスが集まったのだと思います。
また、AIに関する記事もとい StableDiffusionの記事は特にアクセスが多くこの記事だけで1500円以上稼いでくれたように思います。

orotiyamatano.hatenablog.com

また、サブスタンスペインターの記事も定期的に見られている。

orotiyamatano.hatenablog.com

技術記事


爆発的な閲覧数は得られないものの、技術記事は一度書くと定期的に閲覧者が得られたように感じます。

ネタ記事は一時的に訪問者が増えて閲覧数が伸びますが、一過性であることが多いです。
しかし、技術記事は探し求めてたどり着く方が多いのか定期的に閲覧者がいるように思います。
また学校の授業などで引用されることがあるのか平日にドカッと閲覧数が伸びることがあります。

もちろんインプレッション稼ぎのバズ記事はお金になるのですが個人的に技術記事を大事にしていきたいです。

ちなみに最近の人気記事はこんな感じ。

今後について


今後も技術記事を大事にしながら書いていきます。

はてなプロに申し込むと一番安くなるプランで月600円です。
現在月577.25円なのでAdBlockのBlockを入れればはてなプロ分は達成出来そうです。

AdBlockをいれて調子が良ければはてなプロにしてしまうのもアリかなぁと考えています。
収益がゼロになりますが、ずっとはてなブログ無料なのもカッコ悪いので……。 今後も年一で収益を晒せればいいなぁと思います。

これからもこのブログをよろしくお願いします。

所感


趣味半分で書いていたブログですが、転職時に読んだことあるかも知れないと言われたり、
実際に仕事場でやまださんの記事引用したことあるよ!とお声がけしていただくこともあり、
微力ながらゲーム業界に貢献出来てるのかもなぁと思いました。

今後も学んだことを雑に共有するというスタンスで続けていきたいです。

以上、やまだたいしでした。

(今更ながら)2023年の振り返りと2024年の抱負

2024年ですね。
こんにちは、やまだたいし( https://twitter.com/OrotiYamatano )です!
今更ですが、色々落ち着いたように思うので
本記事では何をしたのか振り返り、今年は何をしていくのか考えていこうと思います。

去年の記事↓
orotiyamatano.hatenablog.com

目次


振り返り

個人制作


去年はツールのモジュール化がはかどりました。
かといって、何かしらリリース出来たわけじゃないので引き続きやってきたい。

会社の退職


Twitterでは軽く触れましたが、実は退職してます。
3月末退職して、4月から新しい会社にて勤務してます。
次の会社もゲーム関連で、今度はソシャゲ寄りです。
詳しいことは言えませんが、結構あてにして貰えてるので充実感があります。
前職の勤務先に対してはまずは最初に悪い会社ではなかったように感じたと言っておきます。
私が年収アップしたいという目標を抱いていて会社に対して年収アップをせがんだとして、
ある程度はアップしたかとは思いますが、果たして私自身が納得し働き続けられるだろうかと思ったから転職しました。
真っ当に評価されて給与が上がったならともかく、私が2年以上かけてやっていた社内勉強会が給与に大きく反映されていなかった点など考えると、やはりモチベーションとしては下がらざるを得ませんでした。
会社としては安く使える社員は安く使いたいと思うのは順当ですので企業というしがらみの中では仕方ないのかなとも思いました。
ちなみに転職して(元が低かったのもありますが)年収が100万アップしました。

転職活動については色々言いたいこともあります。
大変だったと言っておきます。

私を希望する会社さんは割といました。
しかし、私の希望年収が100万アップだったので、
この経歴でこの年収はなにか問題があったんじゃないかと訝しむ会社さんや
100万アップという言葉に金の亡者として白い目を向けてくる面接官も少なくなかったです。

転職時に年収相対評価なのはどうかと思いました。
(まぁ明確なポートフォリオがなかったのが悪いんですが)

それとは反対に全然払うよという会社さんや面接で是非にと言ってくれる方々もいました。
逆に私が意識高すぎだと思われミスマッチだと思われ選考から落ちた会社さんもいました。

とにかく雑多に受けてしまったせいで、
転職活動疲れで適当に試験に挑んでしまったりした会社さんや
選考途中で蹴ってしまった会社さんには申し訳ないことをしたなと思いました。

運動した


去年も大体週一で筋トレをしました。
少しだけ胸筋がついたかも?
まぁ体形はそんなに変わらなかったので、今年は栄養管理や睡眠にも注意を向けてみたいと思います。

2023年目標達成度


去年の目標はコチラでした。
・引き続き筋トレする
・映画を15本以上観る
・年収アップ
・何かしらゲームをリリースする

出来たのか?

筋トレは風邪気味だった週を除いて大体できました。
筋力UPはまだまだという感じですが、胸筋のボリュームがすこし変わった気がします。

映画を15本見る→コレは無理でした。

年収アップ→転職できたので叶いました。
また微々たるものですがブログ収益化は面白かったです。

何かしらゲームをリリースする→
無理でしたが、色々ライブラリをモジュール化出来たので良かった部分もあります。
また、個人開発用の公式サイトを立ち上げることが出来たので亀の歩みですが進んでいます。

↓公式サイト
orotiy-sgames.web.app

トータルの所感としては達成できなかったのがほとんどでしたが、
年収アップは叶ったので、目標をたて目標に向き合って頑張ること自体には意義があることだと思いました。

2024年の目標


今年の目標はコチラです!
・婚活を進める
・何かしらゲームをリリースする
・健康に気を配る
・引っ越し

詳しい内容について述べていこうと思います。

婚活を進める


数年前身内を亡くして大分立ち直った今、30歳になりました。
普通に寂しいと思うことが増えました。
なので婚活を進めたいです。
正直女性と接するのもニガテなのでどうなることやらと思ったので、
婚活を進めるという弱気な目標にしました。
エンジニア向けに婚活攻略的なブログも書けると面白いかも知れませんね。
(Unity関連で追ってる方には申し訳ないですが)

何かしらゲームをリリースする


リリースしたいゲームやゲーム内容は決まっています。
後は作るだけで4割は出来ているのですが、正直進める暇がないぐらい本業が忙しいです。
休日出勤するぐらい。
その次のゲームも考えているのでクソゲーでもいいから、さっさと作ってしまいたい。

健康に気を配る


残業も多くなりそうですし、筋トレだけではなく、睡眠や栄養にもやはり気を配るべきだと思ったので、このような内容にしました。
筋トレは引き続きやるとしてしっかりと睡眠を取ることで仕事のパフォーマンスも上がります。
気を配って行きたいです。

引っ越し


歯の矯正が終わったら引っ越しをしたいと考えています。
収入も上がったし猫を飼いたいのでペット可な物件を会社から遠くない位置に欲しい。
今の住所に設定しているものも多いと思うし一苦労だと思うので引っ越しを目標としてあげました。
できれば9月ぐらいにしたい。

所感


30歳という節目を迎えると自分の才能の限界や周りの成功が見えてきます。
若くして成功した人が歴史上には少なくないのでやはり、夢を諦める人も多いと思います。
こんなとき、遅咲きで偉業を成し遂げた人の話が出てきます。伊能忠敬は50歳に隠居してから14年の歳月をかけて日本地図を作ったんだぞと。
しかしながらこの言葉には私は首を傾げます。
確かに当時55歳が寿命とされていた日本で14年かけて地図をつくったのはすごい。
だが、彼は24歳には祭礼騒動という困難を乗り越えているし、天明の大飢饉のとき(38歳)、私財を投げ打って米や金銭を分けて、地域の窮民を救済したりもしている。
どれも一朝一夕で出来ることではない。
つまり大きなことを成し遂げずに30歳を迎えてしまえば後咲きも難しいものではないかと考えてしまう。

去年末に中学の同窓会があった。
ソコで楽しげにしている同級生たちを見ていると東京でゲームを作っているということは一つの家庭を作ってそれを守るのに比べてどれだけ小さいことかと心底羨んでしまう。
それにやはり冴えない中学時代を送っていたこともあり、周りからは相手にされる様子もなくなんだか過去が未来に舞い戻って来たかのようだった。とても惨めだ。
私もパートナーとなる人がいたら気にならないのだろうかと、とても結婚したくなった。

こんなオタクでも、いい人いたらTwitterまで連絡して欲しい……。
今年もがんばっていくぞ、以上やまだたいしでした。

【追記版】いいかげんエクセルでマスターデータ管理辞めません?UnityでのJson,SQLiteを使ったマスター管理

こんにちは、やまだたいし( https://twitter.com/OrotiYamatano )です。
前回で終了しようと思ったのですが、
最終的な使い方が詳細にわかりづらい、
実際に使うには想像しづらい
と思い実務に耐えゆる設計をしたリポジトリを公開することにしました。

前回の記事はこちら
orotiyamatano.hatenablog.com

目次


公開リポジトリ


github.com

リポジトリの解説


基本的には前回、前々回で書いた解説通りの実装をしています。
その差異を今回は解説します。

  • VContainerの利用

今回はVContainerを利用しDIすることでシングルトンを避けています。

  • ProjectSettingの追加

様々なPath情報をSQLiteDataBaseSettingsというScriptable Objectに逃して、
それを参照するように改変しました

  • プリプロセッサディレクティブの追加(ついでに設定を変更するエディタ機能も)

プリプロセッサディレクティブを参照することで、SQLiteを使うかMaster Memoryを使うか出し分けができるようになりました。
Connection_MasterMemoryというシンボルを追加するとエディタでもMasterMemoryを参照するようになります。
(ビルド時はMasterMemory参照)

また、それに伴いシンボルを追加できるエディタ拡張も追加。

  • Addressablesの追加

MasterMemoryを追加しMasterMemoryの直読みをやめました。

以上です。

まとめ


これで大分実務に耐えゆる設計になったかと思います。
使ってみてください。

【後編】いいかげんエクセルでマスターデータ管理辞めません?UnityでのJson,SQLiteを使ったマスター管理

こんにちは、やまだたいし( https://twitter.com/OrotiYamatano )です。
後編です。 前編はこちら orotiyamatano.hatenablog.com

目次


1. DBeaverのダウンロード
2. SQLiteに必要なセットをインストールする
3. DBeaverでデータをつくる
4. SQLiteとUnityの接続設定
5. スクリプトからの一時データの書き出しとSQLiteデータの更新
6. MasterMemoryの設定
7. MasterMemory用に書き出し
8. MasterMemoryで読み込む

前編では5. スクリプトからの一時データの書き出しとSQLiteデータの更新までやりました。
後編は最後までやっていきましょう。

導入手順

6. MasterMemoryの設定


MasterMemoryの導入に移ります。

github.com

UnityのMasterMemoryはMessagePack依存ですので
MessagePackのunitypackageをダウンロードインポート(Getting Started(Unity)を読んでね)し、
次にMasterMemoryのパッケージをインポートします。

Unity上で動かすのはmpc.zipとMasterMemory.Generator.zipが必要になるのですが、
導入も面倒なので今回は.NET Coreの方で利用しようと思います。

(早くソースジェネレーター対応して欲しいですね。してくれたらこの辺考えなくて良いのですが……)

本記事で解説しても良いのですが、
もうインストール方法を書いている方がいるので参照させていただきます。
light11.hatenadiary.com

↑に倣ってインストール完了したらマスターメモリ用のクラスを作っていく。
とその前にSQLiteとMaster Dataとで同じような構成のクラスができるようになる。
同じように扱うためにInterfaceを用意する。

public interface ITestTable
{
    public int Id { get; set; }
    public string TextVal { get; set; }
}

前編で作ったTestTable.csにも適用しておく。
そして、Assets/Scripts/MasterフォルダにMasterMemory用のクラスを用意した。

using MasterMemory;
using MessagePack;

namespace Master  // 本記事では必須
{
    [MemoryTable("TestTable"), MessagePackObject( true )]
    public class MasterMemoryTestTable : ITestTable
    {
        [PrimaryKey] public int Id { get; set; }
        public string TextVal { get; set; }
    }
}

(Table名はAttributeから取得するのでクラス名は完全一致の必要はない)

そしてデータを格納するためのフォルダとしてAssets/Scripts/MasterにGeneratedというフォルダを作成。

ProjectRootで以下コマンドを打つ.
するとMasterMemory関連のスクリプトが生成される。
dotnet-mmgen -inputDirectory ./Assets/Scripts/Master -outputDirectory ./Assets/Scripts/Master/Generated -usingNamespace "MasterMemory"

-inputDirectory には↑で作ったクラスが入ったディレクトリを指定します。
-outputDirectory-usingNamespace で指定した名前空間に所属するクラスが生成されます。

次はMessagePackの関連のデータを生成する。
mpc -input ./Assets/Scripts/Master -output ./Assets/Scripts/Master/Generated

これでようやく関連クラスが生成された。

7. MasterMemory用に書き出し


さて、最低限の準備が出来たところで、
最終的にはbyte形式にした方が良いだろうと思うのでTabeDataEditor.csを以下のように改変した。

ClassLoaderに↓も追加

   public static void DynamicCopyPropertiesWithCommonInterface(object source, object target, Type interfaceType)
    {
        foreach (var prop in interfaceType.GetProperties())
        {
            var sourceProp = source.GetType().GetProperty(prop.Name);
            var targetProp = target.GetType().GetProperty(prop.Name);

            if (sourceProp == null || targetProp == null || !targetProp.CanWrite) continue;
            var valueToCopy = sourceProp.GetValue(source);
            targetProp.SetValue(target, valueToCopy);
        }
    }

SQLiteの中に入っているデータをすべてMasterMemory用に変換し書き出す処理として
SQLiteToMasterMemoryボタンを追加した。

SQLiteの値を取得してくる処理をメソッド化して共通化.
GetSQLiteValueメソッドに。

すべてのMasterのNameSpaceとTablesのNameSpaceのクラスを取得し、
同じInterfaceを所持していた場合にGetSQLiteValue で取得したデータをリフレクションを使ってIntaface経由で値を変換している。

最終的にはAssets/Master/Binary/Master.bytes へ保存という流れだ。
多分変換処理は大分重い処理だと予測されるため、やるのであれば頻繁にはおすすめしない。
開発環境ではSQLiteのデータを使いビルドデータではSQLiteをMasterMemoryへ変換されたデータを使うなどと利用分けすると良さそうだと思う。

このスクリプトにはMaster Memoryに新規追加されたクラスの処理は書かれていないので毎回生成コマンドを叩く必要があるが、まぁソースジェネレーター対応がされればその辺考えなくても済むんだろうなぁと思ってます。

(早くきて……)

(どうしても気になる方はmpc.zipとMasterMemory.Generator.zipをUnityに入れてエディタ拡張からそれぞれ生成処理を叩くのを作っても良いかもしれない,もしくはbatファイルを用意して叩くとか?)

6. MasterMemoryで読み込む


さて、書き込み処理が出来たところで、最後、MasterMoemoryのbyteデータの読み込みです。

DBLoad.csを書き換えてそこで読み込むようにしてみます。

using UnityEngine;

public class DBLoad : MonoBehaviour
{
    private async void Start()
    {
        var data = await TestTableRepository.GetDataAsync(1);
        Debug.Log(data.TextVal);
    }
}
using Cysharp.Threading.Tasks;
using Tables;

public static class TestTableRepository
{
    public static async UniTask<ITestTable> GetDataAsync(int index)
    {
        ITestTable data;
#if true // プリプロセッサディレクティブで使いわけしたりデータキャッシュできる仕組みにできるとさらに良さそう
        data = MasterMemoryData.DB.MasterMemoryTestTableTable.FindById(index);
#else
        var path = Application.dataPath + "/db/testdb";
        var db = new SQLiteAsyncConnection(path);
        data = await db.GetAsync<TestTable> (1);
#endif
        return data;
    }
}
using System.IO;
using MasterMemory;
using UnityEngine;

public static class MasterMemoryData
{
    private static MemoryDatabase _db;

    // とりあえず、シングルトンで処理(DIにするともっと良いよね)
    public static MemoryDatabase DB
    {
        get
        {
            if (_db == null)
            {
                DownloadMasterData();
            }
            return _db;
        }
    }

    private static void DownloadMasterData()
    {
        const string binaryPath = "Assets/Master/Binary/Master.bytes";

        var data = LoadBinaryData(binaryPath);
        if (data != null)
        {
            _db = new MemoryDatabase(data);
        }
    }
    
    // Note:実際はAddressablesとかで読んでくると良さそう.
    private static byte[] LoadBinaryData(string binaryPath)
    {
        if (File.Exists(binaryPath))
        {
            var binary = File.ReadAllBytes(binaryPath);
            return binary;
        }

        Debug.LogError("ファイルが見つかりません: " + binaryPath);

        return null;
    }
}

とりあえず、今回はシングルトンで処理してみました。
こちらもSQLite同様問題なく処理できました。

またInterfaceが共通化されているので場合に応じてSQLiteを読むか、
MasterMemortを使うかプリプロセッサディレクティブなどで差別化可能です。
工夫してみてください。

まとめ


とりあえず、今回はNameSpaceは決めで行い、ファイル格納箇所も直値を指定しましたが、ファイル格納箇所についてはProject Settingsなどに逃せばもう少し柔軟な設定が可能だと思います。

またSQLiteは許容する型が少ないですがMaster Memoryに格納する際にプロパティにて文字列から該当の型へ変換するロジックを書くことで、本来の型で処理することも可能です。

エクセル撲滅のため皆さん色々工夫してゆきましょう!

以上、やまだたいしでした。

【前編】いいかげんエクセルでマスターデータ管理辞めません?UnityでのJson,SQLiteを使ったマスター管理

こんにちは、やまだたいし( https://twitter.com/OrotiYamatano )です。
エクセルが嫌いすぎるので本記事を書くことにしました。

目次


なぜエクセルを辞めたいのか?


現在ゲーム業界ではエクセルにてデータマスターを更新するのが普通になっています。
しかし、正直エクセルデータでは後からのカラム変更がやりづらいなどがあります。
競合が発生したりデータを入力するのも一苦労です。
いい加減15年前以上前から続いているエクセルをやめたいです。
今回は代案の提案です。
SQLiteを使ってみましょう。

前提


今回はUnity前提です。
なおかつWinodws環境で64bit前提。
Unityバージョンは(UnitySQLiteAsyncが2018.3以降対応なので)2018.3以降を想定。(今回は2022.3.10f1を使用)
SQLiteでデータの扱いをします。

インストールが必要なソフトウェア


DBeaver
これだけ。
厳密に言うと他にもあるのだけど、DBeaverを通じて勝手にインストールされ意識する必要がない。
DB構築も設定もコレでできるため、今回はコレを選定。

プログラマーがUnity環境に入れるライブラリ


これは必須ではないが、ゲームとしてSQLiteをそのまま使うのはセキュリティ上、不安が残るため
最終的にMasterMemoryに変換する(またSQLiteは環境依存もあったりするので)
github.com

SQLiteを読み込むためのライブラリ
github.com

導入手順


手順は以下の通り

1. DBeaverのダウンロード
2. SQLiteに必要なセットをインストールする
3. DBeaverでデータをつくる
4. SQLiteとUnityの接続設定
5. スクリプトからの一時データの書き出しとSQLiteデータの更新
6. MasterMemoryの設定
7. MasterMemory用に書き出し
8. MasterMemoryで読み込む

前編では5. スクリプトからの一時データの書き出しとSQLiteデータの更新までやりましょう。

1. DBeaverのダウンロード


コチラよりインストール
dbeaver.io

今回はWindows前提で解説する。

インストーラーは特に気にせずデフォルトで設定.

2. SQLiteに必要なセットをインストールする


新規で開いて、右クリック

SQLiteを選択

するとライブラリ等ダウンロードしていいか聞かれるのでそのままダウンロードする。

適当なフォルダに名前をつけてパスを指定する。

(このようにDBファイルができる)

4. SQLiteとUnityの接続設定


今回使うのはこちら

github.com

UnitySQLiteAsyncです。
UniTaskベースでsqlite-netを使ったライブラリです。
割りと更新が近いのと非同期対応していたので選定しました。
最悪、sqlite-netを使うという手もあります。
まずはこちらのREADMEにあるパッケージをインポートします。

実際に接続する前に
DB読み込みをするための仮データを作ります。

後は適当にぽちぽち作って行きます。

SQLiteの解説は割愛。
とりあえず、今回はテストでIdとTextValというカラムを追加し、
IdにIndex設定をしました。(Index設定したら勝手にPKになるっぽい)

各種カラムや設定を追加しCtrl+Sを押すと↓のようなウィンドウが出てくるので接続をしてDBを更新する

適当にカラム追加。

後は、テーブル構造を定義したクラスと……。

using SQLite;

namespace Tables  // 本記事では参照に必須なので追加しておく
{
    [System.Serializable]  // SQLiteでは必須ではないが本記事では必要なので追加しておく
    public class TestTable
    {
        [AutoIncrement, PrimaryKey, Indexed] public int Id { get; set; }
        [MaxLength(64)] public string TextVal { get; set; }
    }
}

適当なオブジェクトにアクセスするスクリプトをアタッチすると……。

using SQLite;
using UnityEngine;

public class DBLoad : MonoBehaviour
{
    async void Start()
    {
        var path = "C://Test/Testdb";
        var db = new SQLiteAsyncConnection(path);
        TestTable test = await db.GetAsync<TestTable> (1);
        Debug.Log(test.TextVal);
    }
}

ログが表示される。

ここまでくれば文字列で読んだデータを各種必要な情報に変換すれば良いので、
マスターデータとしては最低限の役割は果たしたといえる。

5. スクリプトからの一時データの書き出しとSQLiteデータの更新


UnitySQLiteAsyncを使ったSQLiteの読み込みを行った。
しかし、現状では競合する問題点は解決されていない。
そこで一時データとして書き出しと読み込み更新機能を作っていく。

今回はJsonの書き出しと読み込みのために以下のpackageをPackage Managerから取り込んでおきます。

docs.unity3d.com

com.unity.nuget.newtonsoft-jsonをAdd Package する

JsonUtilityを使いたかったのですが、そちらだとSQLiteのライブラリのアトリビュートが邪魔をしてしまったり、プロパティを利用するとJsonとしての書き出しが出来なかったので入れることにしました。

読み取り書き込みにあたり、
以下のようなエディタ拡張のコードを書きました。

using System;
using System.Collections.Generic;
using System.IO;
using Cysharp.Threading.Tasks;
using Newtonsoft.Json;
using SQLite;
using UnityEditor;
using UnityEngine;

public class TableDataEditor : EditorWindow
{
    [MenuItem("Tools/Table Data Editor")]
    public static void ShowWindow()
    {
        GetWindow<TableDataEditor>("Table Data Editor");
    }

    void OnGUI()
    {
        if (GUILayout.Button("Save Data"))
        {
            SaveData();
        }

        if (GUILayout.Button("Load Data"))
        {
            LoadData();
        }
    }

    private static void SaveData()
    {
        var classes = ClassLoader.LoadAllClasses("Tables"); 

        var path = Application.dataPath+"/db/testdb";  // テーブルの格納先変えました
        var db = new SQLiteAsyncConnection(path);
        foreach (var classType in classes)
        {
            SaveClass(classType, db).Forget();
        }
    }

    private static void LoadData()
    {
        var classes = ClassLoader.LoadAllClasses("Tables"); 

        var path = Application.dataPath+"/db/testdb";  // テーブルの格納先変えました
        var db = new SQLiteAsyncConnection(path);
        foreach (var classType in classes)
        {
            LoadClass(classType, db).Forget();
        }
    }

    private static async UniTask SaveClass(Type classType, SQLiteAsyncConnection db)
    {
        // AsyncTableQuery<T> オブジェクトを取得
        var tableMethod = db.GetType().GetMethod("Table")?.MakeGenericMethod(classType);
        if (tableMethod == null)
        {
            return;
        }

        var tableInstance = tableMethod.Invoke(db, null);
        var toListAsyncMethod = tableInstance.GetType().GetMethod("ToListAsync");
        if (toListAsyncMethod == null) return;

        // UniTask から List<T> を非同期的に取得
        dynamic task = toListAsyncMethod.Invoke(tableInstance, null);
        var listInstance = await task;

        // JSONにシリアライズしてファイルに保存
        var json = JsonConvert.SerializeObject(listInstance, Formatting.Indented);
        var filePath = Path.Combine(Application.dataPath, classType.Name + ".json");
        await File.WriteAllTextAsync(filePath, json);
        Debug.Log("Saved: " + filePath);
    }
    
    private static async UniTask LoadClass(Type classType, SQLiteAsyncConnection db)
    {
        var filePath = Path.Combine(Application.dataPath, classType.Name + ".json");
        if (!File.Exists(filePath))
        {
            Debug.Log("File not found: " + filePath);
            return;
        }

        try
        {
            // テーブルが存在しない場合は作成
            await db.CreateTableAsync(classType);

            var json = await File.ReadAllTextAsync(filePath);
            var listType = typeof(List<>).MakeGenericType(classType);
            var listInstance = JsonConvert.DeserializeObject(json, listType) as System.Collections.IList;

            if (listInstance != null)
            {
                foreach (var item in listInstance)
                {
                    await db.InsertAsync(item);
                }
                Debug.Log("Data loaded and inserted into database.");
            }
        }
        catch (Exception e)
        {
            Debug.LogError("Error loading JSON from file or inserting into database: " + e.Message);
        }
    }
}

このスクリプトはTablesというName Space配下のスクリプトを全て取得し、
Jsonに書き出したり読み込んだりするスクリプトです。

これでJsonをGitベースで管理することでマスターがダイレクトに競合することなく、
実装ができます。
(今考えるとオートインクリメントでの処理は結合時に死ぬことがあるのでやめた方がいいですね)

↓貼り忘れていたClassLoaderクラス

using System;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;

public static class ClassLoader
{
    public static IEnumerable<Type> LoadAllClasses(string namespaceName)
    {
        var classList = new List<Type>();
        var assemblies = AppDomain.CurrentDomain.GetAssemblies();

        foreach (var assembly in assemblies)
        {
            classList.AddRange(
                assembly.GetTypes().Where(
                    t => t.IsClass && t.Namespace == namespaceName && !t.IsSubclassOf(typeof(MonoBehaviour))
                )
            );
        }

        return classList;
    }
}

まとめ


前編では、一時データの読み込みと、
Jsonへの書き出し、Jsonからの読み込みを行いました。
後編では実際にマスターメモリーのセットアップと、
実際にマスターメモリーでのデータ取扱を行っていこうと思います。