Rukhankaの物理関係の挙動 unity DOTS ECS

今はECSをチマチマ触っている。ECSで開発をしているというよりは、ECSで自分の作ろうと思っているゲームが作れそうかの検証と言った感じでやっている。

実際にゴリゴリ開発しているわけではないので参考程度にしてください



ECS、なんだかんだで難しいなと思った。し、そもそも現状のECSではできないこともある。ECSでやりたいことは大量のオブジェクトの描画で負荷がかかりすぎないようにしたいというものなので、

  • 大量の静的なオブジェクトはECS
  • 細かい制御はGameObject

という風に使い分けるのが良いだろうなと思った。大抵はそれで解決するのだけれど、ちょっと困ったことは物理で、Unity Physicsを入れると既存のUnityの物理演算(PhyX)が動かなくなる。動かすオプションがあるかもしれないがどの道GameObject側の物理演算とは干渉しない。プレイヤーは道や橋のような静的なオブジェクトの上を歩くことになるので、干渉してくれないと困る。

とりあえずキャラクターの基本的な挙動はこのパッケージで動かすことにした。 www.youtube.com

そして描画回りだったりはGameObjectで動かすというのがいい塩梅の妥協案なのだと思う。このDOTSおじさんもそのやり方を案内している youtu.be

とはいえ当たり判定をどうしたものかなと思った。モーションとかをanimationさせる時に、colliderも一緒にコントロールできないといろいろと不便なんじゃないかなと。少なくとも私の作る予定のゲームでは困りそうだなと思った。

animationで当たり判定のオブジェクトを動かして、それをUnity PhysicsのPhysics Shapeに変換して同期するようなプログラムを書くと。ちょっとめんどくさそう

で、すこし調べたらこのアセットを見つけた。animationをentityにベイクできるライブラリだ Rukhanka - ECS Animation System | アニメーション ツール | Unity Asset Store

どうも物理にも対応しているらしい。と Discordに入ってやり取りをみてると、結構ドンピシャな使い方をしている人を見つけた。どうやらcolliderもanimationできるらしい

このアセットを調べて動かしてみて分かったことをまとめる

  • colliderを持つオブジェクトを動かしたとき、あるいはscaleを変更したときに、オブジェクトのcolliderもちゃんと追従する
  • animationで子オブジェクト側にcolliderがある場合でも問題なく追従する
  • colliderそもそもを大きくしても反映されない

ということが分かった。私は直方体がブンブンできれば良いのでこれで良いが、FPSのような厳密な当たり判定が必要な場合はちょっと対応方法を考え直さないといけないかもしれない

貧乏Unity 開発環境 lfsやgame-ciなど

ちゃんとした業務開発ではあんまり役に立たない知識を授けます

LFS

GitLFSは入れておきたい。なんだかんだアセットの容量はバカにならないし、LFSは使いたいもののGitHub LFSは高い。帯域に課金されるのが辛くて、GitHubActionsを使う時毎回帯域を消費するのがちょっと残念。

UnityVersionControlを使ってみたものの、操作ミスでデータをすべて吹き飛ばすということを二回やらかしたのであきらめた。大規模開発なら使った方が良いのだろうが、ファイルロックも特に必要ないのでGitも使いたい

※git lfs 2.0からファイルロック機能も提供されているらしい

どうしたものかと思っていたが、この記事がいい感じだった。 qiita.com

Googleで課金していて2TB使えるストレージがあるし、かなり良さそうだと思った。rclone周りでちょっと手間取ったが使えるようになった。

game-ci

今一人で作業しているので、正直必要はないのだが、サーバーサイド出身の人間は自動テスト×デプロイの環境が無いと落ち着かないのだ。

TDDっぽい開発フローを採用しているので一応入れておく。なんだかんだ入れておくとgitのpush忘れとかも気づけたりするので良いんじゃないだろうか

github actionでlfsを動かす

LFSをGoogleDriveで代用するのはいいがそれでGithub Actions側でLFS対象ファイルが取れないのは後々困りそうなので対応。

github.com

これをベースに

C:\Users\hogehoge> rclone config file
Configuration file is stored at:
C:\Users\hogehoge\AppData\Roaming\rclone\rclone.conf

で場所が分かるのでこのファイルをwslにもっていって

$ base64 -w 0 rclone.conf

で出た値をRCLONE_CONFIGにいれて、ciを

name: unity-ci
on:
  workflow_dispatch: {}
jobs:
  test:
    name: test
    runs-on: ubuntu-latest
    steps:
    - name: set rclone
      uses: AnimMouse/setup-rclone@v1
      with:
        rclone_config: ${{ secrets.RCLONE_CONFIG }}
    - name: set git-lfs-agent-rclone 
      run: |
        wget https://github.com/funatsufumiya/git-lfs-agent-rclone/releases/download/v0.0.2/git-lfs-agent-rclone_v0.0.2_linux_x64.zip
        unzip git-lfs-agent-rclone_v0.0.2_linux_x64.zip
        mv git-lfs-agent-rclone /usr/local/bin/git-lfs-agent-rclone
    - run: |
        git config --global lfs.standalonetransferagent rclone
        git config --global lfs.customtransfer.rclone.path git-lfs-agent-rclone
        git config --global lfs.customtransfer.rclone.args $場所
      env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
    - name: Checkout
      uses: actions/checkout@v4
      with:
        lfs: true
    - name: test
      uses: game-ci/unity-test-runner@v4
      env:
        UNITY_LICENSE: ${{ secrets.UNITY_LICENSE }}
        UNITY_EMAIL: ${{ secrets.UNITY_EMAIL }}
        UNITY_PASSWORD: ${{ secrets.UNITY_PASSWORD }}
      with:
        projectPath: ./
        githubToken: ${{ secrets.GITHUB_TOKEN }}

のように書いて完了。lfs: trueを利かせる前にもろもろのconfigを設定することと、checkoutをする前はリポジトリのフォルダが無くてlocalに設定できないので、--global をつけておくことがミソ

ビルドして配布したりとか、マシンをself-hosted runnerに差し替えて諸々のファイル(Libraryなど)をキャッシュしたりとかを今後はしていく

unity ECSでanimation

TL;DR

この動画を見ろ youtu.be

とはいえ検索エンジンで日本語でなにかしら引っかかってくれればうれしいので記事を書く

動画のやり方はざっくりこんな感じ

  • まずはCharacterControllerなりでECSで動くオブジェクトを作る
  • animationを動かす用のGameObjectを生成する。GameObjectなのでECSとは何も関係ない
  • GameObjectでanimationを動かす
  • ECS上のEntityの座標が変わってもanimationを動かすGameObjectは位置が変わらないので、Entityの座標をGameObject側に反映するコードを書く
  • entityを削除したときGameObjectも消えるようにしてあげる

「え?それECSで動いてるって言わないじゃん!」

ごもっともです。とはいえ、GameObjectの物理演算(PhysX)とECS上の物理演算は干渉してくれないので、こういう逃げ道を選ぶことも現状だと必要です。animationさせるオブジェクトの数がしれてるならこのやり方でも特に問題なく動くでしょう。

ちゃんとECSベースで動かすためのアセットも紹介されていて、


GPU ECS Animation Baker assetstore.unity.com


Rukhanka - ECS Animation System assetstore.unity.com


などがある。使用感の個人的な好みとしてはRukhankaだが、どれくらい使い物になるかは未知数なのでいつかのタイミングで検証したい

C#でawsのalb用にヘルスチェックを実装する方法

MagicOnionで動くサーバーアプリケーションをデプロイしていて、albのヘルスチェックを通す必要があった。C#で実装自体はやり方が書いてあって learn.microsoft.com

builder.Services.AddGrpcHealthChecks()
    .AddCheck("sample", () => HealthCheckResult.Healthy());
app.MapGrpcHealthChecksService();

でヘルスチェックを実装できることは分かった。ただ、公式ページにはC#でヘルスチェックを呼び出す方法はあったが、AWSのALB(厳密にはターゲットグループ)でヘルスチェックを通すためにどうすれば良いかわからなくてちょっと躓いたのでメモ

単にパスに

/grpc.sample.v1.Health/Check

と指定すればよいだけだった。grpcのヘルスチェックの実装には仕様があり、

github.com

その仕様によるとCheck()を呼び出すことでヘルスチェックができる。ターゲットグループのパスはhogehoge.co/index.htmlの /index.htmlと同等で、通常のwebアプリならヘルスチェックようのエンドポイントを用意してそれを指定すればよいが、grpcの場合は特殊な記法が指定する必要があってこのような記載になっているらしい

行動の細分化と行動可能リストを用いた強化学習

まだまだいろいろと検証しないといけないことが多いのですが、終わった後に記事にしようとしてもおそらく細かいところを覚えていないので、いまわかっている段階でメモ

アジェンダ

  • こんなゲームを作っています
  • 読んだ本
  • 実装など

ゲーム

趣味でゲームを作っています。といってもロジックだけで絵やUIに関してはほとんど手を付けられていませんが

  • カードがあって
  • それを召喚して
  • 同じカードを重ねて進化して
  • 相手のライフを削り切ったら勝ち

みたいなゲームです。

相変わらず完成させるモチベがなく、ふと強化学習をやってみたい気分になり、やってみることにしました。

ただ、強いAIを作ればよいのではなく、パラメータ調整に利用したいので以下のようなこだわりがあります

  • 人間のプレイデータによる教師あり学習は行わない(自動生成ならセーフ)
  • 学習時間はできるだけ短くする

この二つの条件を満たせるように強化学習の仕組みを作ろうとしています

読んだ本

強化学習」を学びたい人が最初に読む本 「強化学習」を学びたい人が最初に読む本 | 伊藤 真 | 工学 | Kindleストア | Amazon

この本をじっくり読みました。これ以外の本は特に読んでません。ちょっと手詰まり感もなきにしもあらずなので、強化学習系の他の本も読みつつ、より賢くしていこうかなと思います

強化学習とは

ゲームにおける強化学習では、実際にゲームが動いている環境と、それを用いて学習するエージェントがあります。

流れとしては

  1. エージェントは環境に対して、今の環境情報(ターン数、HP、盤面情報)を受け取ります
  2. そして、その環境情報をもとに、エージェントが行動を選択します
  3. 行動した選択をもとに、環境は報酬を与えます
  4. エージェントはもらった報酬をもとに、行動が妥当かどうかを学習します

といった形で行い、報酬ベースでエージェント自ら学習していくという特徴があります。

DQN

今回はDQNと呼ばれる手法をメインに採用しました。Q学習のQテーブル、Q値を深層学習に置き換えたものです。

Q値とは何ぞやとなると思うので軽く説明すると、環境が与えられたときに各行動でどれくらい将来的に報酬がもらえるかを返すものです。詳しく説明すると長くなるので書籍を参照してください。

行動の細分化

このDQNを自分が開発しているゲームに適用するにあたって問題になるのは行動の多さです。

例えば、モンスターの攻撃を行うとすると、

どのタイルのモンスターか(タイルの数23通り) * モンスターの行動パターン(攻撃、進化など5通り) * どのタイルの攻撃先モンスター(タイルの数23通り)

23 * 5 * 23 = 2645

で2645 通りになります。これだけでも多いのですが、タイルが複数選択になったりすると、どんどん行動空間が膨れ上がってしまい、学習が現実的ではなくなります。

そこで、モンスターの攻撃のときは

  1. タイルを選択する
  2. 攻撃を選択する
  3. タイルを選択する

という風に格ゲーのコマンドの要領で細分化することで、行動空間を小さくすることにしました。このやり方の場合行動の数は

タイルの数 + 手札の数 + モンスターの行動の数 + ターンエンド(1)

で40通りくらいで済みます

それに合わせて、環境からはタイル選択など直前に行った行動と、「行動可能リスト」というものを与えるようにしました。行動可能リストはその状態で選択できる行動をリスト化したもので、もしモンスターのいるタイルを選択した場合は

  • 「攻撃」
  • 「移動」
  • 「進化」
  • 「ターンエンド(これは色々あって常に選択できるようになっている)」

を行動可能リストとしてエージェントに与えるようにしています。こうすることで、エージェントはどの行動を選択できるのか判断できるようになります

不可能な行動にペナルティを与えるのではだめなのか

こういった強化学習を実装するにあたって、一般的なパターンはエージェントが不可能な行動をしたときに敗北処理を行ったりすることだと思いますが、そのやり方は行っていません

  • 全行動に対して不可能な行動の比率が高いので、ランダムで行動したとき可能な行動を選択できない
  • 魔法カードなどはカード選択後の行動にバリエーション(カードを使うだけ、タイルを選択する等)があり、これを学習させるのは難しい

といった理由からエージェントはランダム行動を行う時も行動可能リストから選んで行うようにしています。

実装

行動の組み合わせを定義する

  • 召喚 手札カード選択→召喚選択→タイル選択
  • 進化 タイル選択→進化選択→手札カード選択
  • 移動 タイル選択→移動選択→タイル選択
  • 攻撃 タイル選択→攻撃選択→タイル選択

という風に定義しました。

HTTPサーバーを立てる

エージェントから環境へアクセスするためのサーバーを立てます。私のゲームはロジックがUnity非依存なので、ASP.NET Coreで鯖を立てました。どの道オンラインゲームで提供するときはUnityを使わずにサーバーを立てたいので、こういう作りにしています。

モンキーテストのプレイデータを学習させる

ランダムに行動したプレイデータをC#で作成し、エージェントに学習させます。単にランダムに行動しても戦いが終わらない(というかデッキが切れて負ける)ので、モンスターの移動選択をしたときは前側のタイルを選択する確率を上げたりして対戦に決着がつくようにしました。また、Q値の更新は通常選択した行動に対してのみ行われますが、モンキーテスト時は行動可能リストにない行動に対するQ値は-99999(敗北時の報酬)を入れて学習させています。

モンキーテストが落ちまくったので直すのが結構大変でした。

通常の強化学習

一定確率でランダム行動するエージェントで、強化学習を行います。モンキーテストのプレイデータを学習させているので、ある程度報酬をもらうように動いてくれます。ターンエンドするだけの相手に対しては安定して勝てるようになりました。現時点で学習にかかっている時間は10分なのでそこそこ上出来かなと思っています。

モンキーテストも同様ですが、タイル選択後の行動数的にターンエンドが選ばれる確率が高いので、ターンエンドが選ばれにくくなるよう実装の工夫が必要です。

今後

魔法カードなどいろんな要素を増やして学習できるか調べようかなと思っています。後、デッキ編成もAIが自分で考えてできるようにしてほしいのですが、それをどのように実装するかは考えているところです。

AWSソリューションアーキテクトアソシエイトに合格しました

ちなみに受かったのは4月なのでもう4か月くらい経ってしまいました。

AWSは業務でゴリゴリに使うことはないですが、EC2とかlambdaを無料枠で使ったりくらいはしたことがある程度の経験です。

AWS認定資格試験テキスト AWS認定ソリューションアーキテクト-アソシエイト https://www.yodobashi.com/produt/100000086601241124/

AWS認定ソリューションアーキテクト-アソシエイト問題集 https://www.yodobashi.com/product/100000009003459352/

を二週間くらいかけて頭から読んで問題を解いたら合格できました。本番解いて思ったのは、思ったよりテキストに載っていない最新の情報が出題されることと、出題されている問題の日本語があやしかったことです。このあたりをカバーしたいならAWSの公式で提供されている模擬問題を解いておけば、万全の状態で試験に臨めるかなと思います。

オートスケールまでのタイムラグをSQSを間に挟むことで対応するような考え方は知らなかったので勉強になりました。

エディタ拡張でScene View上で常に輪郭を表示する

layoutgroupだったり、スクリプトで動的に何かを生成する都合上、表示上はなにもないがサイズが欲しいrectだったりというのが画面レイアウトするうえで出てくることがある。 これらは選択したらrectの輪郭が表示されるが、親のrectからはみ出ていないか確認しながらサイズ調整をしたいので、常に表示されてほしいと思った。

ので。

[InitializeOnLoad]
public class CreateFrameOnSceneView
{
    static CreateFrameOnSceneView()
    {
        SceneView.duringSceneGui += OnGui;
    }
    private static void OnGui(SceneView sceneView)
    {
        var objs = GameObject.FindGameObjectsWithTag("Frame");
        foreach (var obj in objs)
        {
            var v = new Vector3[4];
            obj.GetComponent<RectTransform>().GetWorldCorners(v);
            Handles.color = new Color(0, 0, 0, 1);
            Handles.DrawLines(new Vector3[]{ v[0], v[1], v[1], v[2], v[2], v[3], v[3], v[0]});
        }
    }
}

これでTagに「フレーム」を当てると常に表示されるようになる。

f:id:Piffett:20211201191140p:plain

だいぶ見にくいが、見にくいくらいでちょうどよい。 FindGameObjectsWithTagを高頻度で読んでしまっているのでちょっと不安