simplestarの技術ブログ

目的を書いて、思想と試行、結果と考察、そして具体的な手段を記録します。

ゲームデータの永続化2

AWS の DynamoDB というオンラインデータベースの導入について書く予定

をさっそく変更して、次のサンプルプロジェクトの手順をなぞることにしました。
開発者用リソース - Amazon Cognito | AWS

さっそく迷いましたが、最終的にユーザープールの作成方法としてこちらのステップ 1, 2, 3 を実行してみました。

ユーザープールの開始方法。 - Amazon Cognito

いいですか?
ステップ 1, 2, 3 のみですよ。

ソーシャルIDプロバイダーはいくつか選択できますが、Google のみ行いました。

Google での手順リンク先でまた迷いましたが、次のページにて「CONFIGURE A PROJECT」ボタンを発見できました。
https://developers.google.com/identity/sign-in/web/sign-in

Google 側も手順を省略する方向でサイトを整備したみたいですね。
リダイレクト URL など設定するため、作成したプロジェクトのGoogleコンソール画面へ移動すると、すでに認証情報が作られていました。
あとは設定項目にステップで指定された url を記入して完成~

作ったユーザープールの login のURLを開くと次の Google でログイン or 新しくメールアドレスとパスワードでアカウント登録の画面が出ました!

f:id:simplestar_tech:20190324113618j:plain
https://your_user_pool_domain/login?response_type=code&client_id=your_client_id&redirect_uri=https://www.example.com (作成したものによる置換がいるよ!)を開いたときの画面

それぞれでログインすると、AWS Cognito のユーザープールのユーザー一覧ページにて、それぞれのユーザーアカウント情報を確認することができました。

f:id:simplestar_tech:20190324114404j:plain
Google認証と新規email&パスワード認証をしたときのCognitoユーザー一覧画面

ユーザーそれぞれの情報を確認できます、ここでメールアドレスは確認できましたが、パスワードまでは閲覧できませんでした。(これ重要)
これでユーザープールが出来た…

( ゚д゚)ポカーン

え!?次何すればいいの?

迷った挙句、次の手順に従って ID プール名を記入して、上記ユーザープールの ID とアプリクライアントIDをプロバイダーの項目に入力しました。
サインイン後に ID プールを使用して AWS サービスへアクセスする - Amazon Cognito

これで ID プールが出来た。
その ID プールのダッシュボード開いてる状態…

( ゚д゚)ポカーン

え!?次何すればいいの?

迷った挙句、ここからはいよいよ Unity のコードに触れってことらしいですね。
次の手順に従って .unitypackage 群をダウンロードします。
AWS Mobile SDK for Unity のセットアップ - AWS Mobile SDK

> 使用する .unitypackage ファイルに移動して選択します。
どれ?

f:id:simplestar_tech:20190324153201j:plain
AWS SDK for Unity packages

なるほど、今回は DynamoDB というオンラインデータベースを使おうと思っていますので
AWSSDK.DynamoDBv2.3.3.100.1.unitypackage
を展開して利用することにしました。

AWSSDK, Examples, Plugins フォルダが作られそれぞれに SDK, Sample, Plugin が配置されました。

サンプルちょっと眺めてますが、書式古いですねー(Unity ユーザー、上から目線w)
同梱の readme にシーンの構成と使い方が示されています。こちら一読して作業しました。

とにかくシーンビルド構成を

1. DynamoDbExample.unity
2. LowLevelDynamoDbExample.unity
3. TableQueryAndScanExample.unity
4. HighLevelExample.unity

にして実行しろとのこと
そして、それぞれのシーンのゲームオブジェクトのインスペクタにて ID プールの ID を記入し、適宜お使いのリージョン名に書き換えろとのことです。
対処しました。

ほか、オンラインドキュメントで説明のある awsconfig.xml ファイルがないことで混乱しましたが…
混乱したまま進めることにしました。

実装を見るに、認証しないユーザーで DynamoDB の Table を作ったり Table を列挙したり、Table の中身を更新したり、Table の中身を列挙したり、Table を破壊したりしています。
先に IAM の方で、そんな DynamoDB への操作を許可しているか確認してみましょう。

IAM コンソールにはいくつかディフォルトのロールと、今回作成した Cognito* というロールがあります。
未認証のロールの方を選択して、インラインポリシーの追加を選択すると、次の通り DynamoDB をサービスに選ぶと、一覧と設定UIが現れました。

f:id:simplestar_tech:20190324163932j:plain
IAM ポリシーの編集はビジュアルエディット可能

これらを設定して保存してあげれば、一時認証のときに DynamoDB を操作することができますね。
さらにリクエストする IP アドレスの範囲設定までできたので、自宅からのアクセスのみに絞ってみました。
これで、第三者から勝手に DB 編集されずに済みますね!安全である

さて、Unity サンプルを実行するとなにやらエラーが発生したので、解決に取り掛かります。(わけわからん時に触るサンプルはだいたい修正しないと動かないものです。めんどくさい)

エラー内容から調べると、次の解決方法に出会いました。
Unity2017.3.0p4 で AWS SDK が例外を吐くようになったので原因を調べてみた

具体的には DynamoDBExample.cs にて、次の行を追加します。

        void Start()
        {
            UnityInitializer.AttachToGameObject(this.gameObject);
            AWSConfigs.HttpClient = AWSConfigs.HttpClientOption.UnityWebRequest; // ←この行で UnityWebRequest 使うように指定

うごきました!
Create Table ボタンを押すと Forum, ProductCatalog, Reply, Thread の四つの DynamoDB Table が作られました。
Table の Describe も行えましたし、Table のDelete もできました。

IAM に権限がないとアイテムの追加に失敗し、IAMポリシーを編集すれば、ゲーム起動中に権限が切り替わって Table 内のアイテム操作が行えることも確認しました。

オンラインデータベースにブロック情報を書き込んだり、読み出したりする時に参考になるサンプル実装はこんな感じでした。

        int bookID = 1001;
        DynamoDBContext Context;

        void Start()
        {
                var _CognitoPoolRegion = RegionEndpoint.GetBySystemName("ap-northeast-1");
                var Credentials = new CognitoAWSCredentials(IdentityPoolId, _CognitoPoolRegion); // ID プールの ID を文字列で指定
                var Client = new AmazonDynamoDBClient(Credentials, _DynamoRegion); // ユーザープール使っていない、つまり未認証の権限ロールを使用することになります。
                Context = new DynamoDBContext(Client); // アイテム操作は Context というものを介して操作する模様
        }

        private void PerformUpdateOperation()
        {
            // Retrieve the book. 
            Book bookRetrieved = null;
            Context.LoadAsync<Book>(bookID,(result)=> // Update なので、値が取れることは期待
            {
                if(result.Exception == null )
                {
                    bookRetrieved = result.Result as Book; // ここは期待通りなら強制キャスト
                    // Update few properties.
                    bookRetrieved.ISBN = "222-2222221001";
                    bookRetrieved.BookAuthors = new List<string> { " Author 1", "Author x" }; // Replace existing authors list with this.
                    Context.SaveAsync<Book>(bookRetrieved,(res)=> // 更新したデータ構造をそのままコピーします
                    {
                        if(res.Exception == null)
                            resultText.text += ("\nBook updated");
                    });
                }
            });
        }

    [DynamoDBTable("ProductCatalog")] // データ構造はこれらの属性が必要
    public class Book
    {
        [DynamoDBHashKey]   // Hash key.
        public int Id { get; set; }
        [DynamoDBProperty]
        public string Title { get; set; }
        [DynamoDBProperty]
        public string ISBN { get; set; }
        [DynamoDBProperty("Authors")]    // Multi-valued (set type) attribute. 
        public List<string> BookAuthors { get; set; }
    }

上記コードで更新した ProductCatalog テーブルのアイテム id = 1001 を AWS DynamoDB コンソールで選択すると、以下のような値が確認できました。

f:id:simplestar_tech:20190324181528j:plain
上記コードで作った Table の一つのアイテム

最初は、愚かしいデータ構造にしてみて、どんなレスポンスでゲームが遊べるのか試してみましょうか
やりたいことができるようになってきたのでいったん記事は完成ということで

追記:
未認証による操作はできたのですが、認証を通した後の操作は?
気になりますよね。

Google の場合はここに示されている手順で 3つのクライアントIDを作成して、もろもろ登録して Unity に Googleプラグインを入れて操作することになるそうです。
Google (ID プール) - Amazon Cognito

AWS Cognito の User プールは?

ユーザープールを ID プールと統合するに示される通り
ユーザープールID を使ったプロバイダ名 cognito-idp..amazonaws.com/ を使って

                        Credentials.AddLogin(CognitoIdentityProviderName, initiateAuthResponse.Response.AuthenticationResult.IdToken);
                        Credentials.GetIdentityIdAsync(responce =>
                        {
                            Debug.Log("Logged In with refreshed IdToken : " + responce.Response);
                        });

で、認証情報にユーザープールを統合できるそうです。
おや initiateAuthResponse.Response.AuthenticationResult.IdToken はどうやって手に入れるのか
参考ページはこちら
UnityからAWS Cognito Identity Providerで認証機能を実装する(User_Auth_Flow) - Qiita

どうしても認証を通して作業したいので、次はこちらの記事を参考に認証を通した後の IAM ロールで操作できることを確認してみます。

ゲームデータの永続化

■前書き
しばらくぶりで記事の書き方を忘れてしまいました。

ここ最近は新たに Unity でゲームを作っていてちょうど見た目はこんな感じのサンドボックスゲームです。

f:id:simplestar_tech:20190321221355j:plain
テキトー

ゲームデータの永続化の部分を後回しにしたおかげで、後半になってスケジューリングまわりの挙動確認をしたり
どこにどうやってデータを置いて、これを引き出すべきか思案しなければならないことに(逆に最初からも難しいですが)

そこで最近 AWS の DynamoDB とか良いんじゃないかなって思い始めています。
高速読み書きできるオンラインデータベースです。

Unity での導入方法としてとても親切でわかりやすい記事も見つけました。
UnityでAWSを使うのはゼンゼンコワクナイヨー(シカモ無料デイケルヨ!) - Qiita

ちょっと触ってみようじゃないですか

■導入

Amazon Prime 契約されている方も多いかと思いますが
それより簡単に Amazon Web Services のアカウント登録が完了できます。

いざ、AWS 使うぞーと思ったら、どれにしますか?とこの画面が表示されます。

f:id:simplestar_tech:20190321223018j:plain
AWS 一覧

あまりに多く、AWS の人たちも全部に通じている人は少ないんだとか…
まずは認証認可の仕組みを学んで、ゲーム側からキーとなる文字列を使うとオンラインのデータベースを読み書きできるようにしてみようと思います。

参考記事では Cognito の ID プール使うとありますね。

AWS Cognito

f:id:simplestar_tech:20190321223528j:plain
AWS Cognito

ID プールボタンを押すことにしました。


認証プロバイダーに FacebookTwitter を選べるけど、コンシューマー登録IDとそれに関する秘密鍵を書き入れないといけないとある…
いきなり何してよいかわからなくなった、怖い…

ここで Cognito について基本的な部分を調べてみます。
AWS Cognitoについて調べてみた - Qiita

1.UserPoolにログインしてTokenを取得
2.取得したTokenをFederated Identitiesに渡す
3.Federated IdentitiesがTokenを検証
4.検証が完了したらSTSから一時クレデンシャルを取得
5.ユーザに一時クレデンシャルと統合IDを返す
6.統合IDと一時クレデンシャルを利用してAWSリソースにアクセス

確かにそうなんですけど…これだけではわからない
読んで得た知見:新出単語多くて、関連情報が引き出せただけでした。

上記用語と意味ですが、以下の参考リンクの方をすべて読む&理解したときにわかってきます。(結局全部読まないとダメでした)
よくわかる認証と認可
AWS Black Belt Online Seminar 2017 AWS Cognito
Amazon Cognitoによる認証はSTSのweb identity federationとどう違うのか!?

用語理解

認証:アクセスしてきた人が誰か特定すること(パスワードが合っていることを確認して認めるとか)
認可:認めた相手にオンライン操作を許可すること(権限の範囲を決めて、自由に操作させるとか)
OAuth: 歴史的に認証情報を渡さずに認可の権利だけ与える仕組みが流行り、そのときに色々取り決めた認証システム
MFA: 普通パスワードだけで入れるところ、指紋認証を加えるとか、パスポートのコピーを提出するとか、とにかく複数の要素で認証すること…あ、Multi(複数)-Factor(要素) AuthN(認証) の略字っぽい
認証しな認可ある?:あるっぽい、電車乗るのに切符買えばいい、誰でも切符あれば乗れる(は?切符持っているという要素を認証してない?)意味不明…と思いきや、誰でもいいというところが認証なしと言えるかも

認証は証明書を検証すること、認可は鍵を発行すること
参考:
よくわかる認証と認可 | DevelopersIO

ユースケースから見た AWS の Cognito

ユーザー認証を簡単に行いたい→ Congnito 使え
TwitterFacebook でログインしたい→ Cognito 使え
課金ユーザーにだけ限定コンテンツ配信したい→ Cognito 使え
オンラインサービスをユーザーにも提供したいがアクセスキーは提供したいくない→ Cognito 使え
デスクトップPCモニタ、ゲーム機画面、スマホ、ノートパソコンでまったく同じことしたい→ Cognito 使え

何度も同じこと書くようで悪いですが…
1.ユーザーデバイス上で Twitter にログインし、アプリで使う秘密の文字列(トークン)を取得
2.Twitter とかを認証プロバイダーと呼ぶことにして、それらを使わない場合は Cognito の User Pools にユーザーアカウント情報を記録するからこれを認証に使える?
3.とにかくトークンを取得する
4.そのトークンを Cognito の Identities に見せろ、一時的な認可の鍵を渡してもらえる
5.その鍵で許された範囲のオンラインコンテンツ操作が行える(期間限定で)

です。

用語確認:
認証 Provider:Twitter のユーザー名・パスワードなどでログインしてトークンを提供する人
Cognito Your User Pools : トークンを渡す人
IAM(アイアム): Cognito User Pool の User Group と認可の結合をする人
Cognito Identities : トークンをもらって IAM の通りの認可用の鍵を発行して渡す人
Temporry Credential : Cognito Identities からもらった一時的な認可用の鍵(ログインしてトークン得て、トークン渡して手に入れる一時的な鍵)

サービス提供者が行う具体的な準備

Cognito で User Pool を作成する
Cognito で Identity Pool を作成する
Cognito で User Pool と Identity Pool を結合
IAM で Identity Pool に許可する操作を追加
専用 SDK 使ってクライアントデバイスから、ログイン→トークン取得→Temporary Credential取得のコードを実装する
その Temporary Credential を使って AWS リソースを操作(例えば DynamoDB への書き込み)を行うコードを実装する

と言った内容をイメージしたが、合っているのかな?

参考:
AWS Black Belt Online Seminar 2017 AWS Cognito


…ここも読むしかなさそうだな
Amazon Cognito(アイデンティティおよびデータ同期) | AWS

読んで得た知見:外面だけの文言も、また前提知識を求めるものばかりで要点がつかめない…

仕方ない、やっぱりここを全部読みます。(途中で日が暮れたのでやめました…)
Amazon Cognito とは - Amazon Cognito

ディレクトリって何をイメージすればいんですか?

2004年の記事ですが、こちら読みました。
ユーザー・アカウントを一元管理できる「認証ディレクトリ」に注目
認証を一回にする、簡単にするために、みんなで一緒のものを使おう…おれのシステム使えー!という戦国時代があったんですね…という感想を得ました。
WebシステムやC/Sシステムなどが混在した環境でアカウントの一元管理をするため考え出されたもの、それが「認証ディレクトリ」だ
一元化したユーザー情報のことを「ユーザー情報」と呼ばずに「ディレクトリ」と呼ぶことにしたらしい。なんで?
すべてのユーザーのアカウント情報を集め、管理するシステムを「統合ディレクトリ」とも呼ぶ、なんで?
クライアントマシンにエージェントと呼ばれるサービスを忍ばせて、統合ディレクトリからID・パスワードを取り出して、勝手にサイトへアカウント情報を送信する仕組みらしい。
なのでエージェントを仕込めば、ユーザーはID・パスワードの入力から解放されるそうな
安全ですね!

関連してプロビジョニング(Provisioning)という用語も理解しようと思う
…セキュリティの確保という意味らしいが、具体的にはユーザー・アカウントのライフサイクル(生成/配布/更新/廃棄あるいは失効)を管理/運用する機能のことをさす

歴史的に 2004 年頃はまだ統合ID管理製品が発展途上期だった様子
なるほど、知らずに高校生やってたぜ…

公式ドキュメントの本文にもどります

Amazon Cognito は、SOC 1~3、PCI DSS、ISO 27001 に準拠し、HIPAA-BAA に対応しています。

わかんねーよ(笑)
公式ドキュメントのこういう専門用語理解できないと何もわからなくするように突き放す文章スゲーと思うよ。
わざとわからなくするドキュメントコンテストで公式ドキュメント群は全部上位取れるよw

結局概要をつかんでみてわかったのはユーザープールとIDプールの二つがあって
ユーザープールがアカウント情報を管理するシステム
IDプールがサービスごとのアクセス権限を管理するシステム
アプリ実装するときはユーザープールにIDとパスワード送ってトークンを取得するように記述し
そのトークンをIDプールに渡して、一時的認証情報に交換してもらい
その一時的認証情報をSDKに渡せばAWS操作できるよ、ということだった。

具体的な手順は、まずは一個簡単なプロジェクト作るところからどうぞ…とのことなので、次のサンプル手順をなぞってみることにします。
開発者用リソース - Amazon Cognito | AWS

次の記事へ続く
ゲームデータの永続化2 - simplestarの技術ブログ

ECS 何処かなと思ったらこんな感じでした

Unity:高画質スクリーンショット

ゲーム画面を高画質で記録したい

次のスクリプトをシーンに追加して、InputAction で KeyBoard > PrintScreen を指定したらできました。

PrintScreenshot.cs

using UnityEngine;
using UnityEngine.Experimental.Input;

public class PrintScreenshot : MonoBehaviour
{
    public InputAction inputAction;

    void Awake()
    {
        inputAction.performed += ctx =>
        {
            ScreenCapture.CaptureScreenshot(System.IO.Path.Combine(Application.dataPath, "../ScreenShots/" + System.DateTime.Now.ToString("yyyyMMdd-HHmmss") + ".png"), 4);
        };
    }

    public void OnEnable()
    {
        inputAction.Enable();
    }

    public void OnDisable()
    {
        inputAction.Disable();
    }
}

参考記事

tsubakit1.hateblo.jp

Unity:ゲームの土台プロジェクト

前書き

Unity ゲームエンジンそのものが土台といえば土台かもしれませんが、Unity でのゲーム作りの土台となると
おおよそ形は決まってくるもので、ゲームの面白さのアイディアなどはその土台の上にのせるだけ

よく上司から勧められるゲームジャムのイベントに「行こう!」という気になれないのは
この土台を作れる or いつでも引き出しから出せる状態にないからと言いたいのですが
ならば、すぐに土台を用意できるように用意しようと思った次第

これまで書いてきた記事を参照しながら、土台の作り方を詳細に記録します。

目次

最初にやるのはバージョン管理

プログラムは資産ですから、長く使うことになるだろうプロジェクトのバージョン管理は必須だと思います。
GitHub を使うのはユーザーが多くて無料という単純な理由です…

simplestar-tech.hatenablog.com

この記事には紹介は無いですが、最近は Source Tree による直感的なソース管理に切り替えています。
新規で Unity プロジェクトを作ったら、まずは空プロジェクトの状態で公開します。
README でどのようなプロジェクトにするか明記して、あとはその目標に足りていない部分を見つけて足していきましょう。

Unity の基礎実装

Unity はエディタ操作でゲーム作りのかなりの領域をカバーしています。マニュアルの各コンポーネントの概要にざっと目を通せばそれに気づけますが…
docs.unity3d.com

これを全部読み終えて感じることは、ゲーム作りの本質はC#プログラミングにあるという一点だけです。
上のマニュアルで紹介されたコンポーネントは、ブロックでいうところの「洗練されたカッコイイ部品」といった役割を果たし
それぞれを縫い合わせる糸、接続のための粘土ともいえるプログラムは、私たちが創らなければなりません。

いきなり極寒の地に裸で放り出されるわけですが、そこには基本的な縫い合わせ方や、粘土をこねる順番や型というものがあるので、そのうちの一つを今回紹介したいと思います。

成果物はこちらに公開していきます。
github.com

プロジェクトのフォルダ構成

Unity 使う上では超重要、自分のスタイルはこちら
Unity初心者向けアドバイス - simplestarの技術ブログ

ちゃんと考えたい人はこちらが参考になるかも
r-ngtm.hatenablog.com

チームロゴの表示

これ Splash Screen と言って Player Settings のこちらで設定できます。

f:id:simplestar_tech:20190106112616j:plain
Splash Screen 設定
今回適当なロゴを使ってみました。
f:id:simplestar_tech:20190106144836g:plain
スプラッシュスクリーンを自作のロゴに変更した結果
ここはプログラミング関係なかったね…

タイトル画面

上記動画にも映っている Press Space Key が点滅表示されているだけのシーン
uGUI と Animation 使うだけです。

忘れちゃった時のために、ウィンドウの出し方だけ記録しますね。

  • Window > Animation > Animation メニューをクリック
  • Create Animation をして、Properties に Text Mesh Pro Text の Font Color を指定し、キーフレームを移動してから Font Color を変更、Add KeyFrame ボタンを押す
  • 作られた .anim ファイルを Text に追加した Animation コンポーネントに設置する

タイトルメニューの表示

Space キーを押すとアクティブなパネルが切り替わる実装について記事を書きました。
simplestar-tech.hatenablog.com

パネルにて GridLayoutGroup コンポーネントを利用すると、自動的にボタンが整列して配置されるので、システマチックで整った UI を見ることができます。

f:id:simplestar_tech:20190106180144g:plain
タイトルメニューの表示とボタンイベント処理

ボタンを押すとゲームが終了するのは、ボタンに次の記事で示すスクリプトを割り当てているからです。
simplestar-tech.hatenablog.com

New Game への移行とローディング画面

少し前に作った VRM を初期フレームで読み込むシーンを NewGame としましょう。
その時のローディング中の画面表示方法について示します。

といっても以下のページを参考にしました。
gametukurikata.com

加工した Slider に次の LoadingSlider.cs をアタッチするだけです。

using System.Collections;
using UnityEngine;
using UnityEngine.UI;

[RequireComponent(typeof(Slider))]
public class LoadingSlider : MonoBehaviour
{
    internal IEnumerator LoadCoroutine(AsyncOperation async)
    {
        while (!async.isDone)
        {
            var progressVal = Mathf.Clamp01(async.progress * 1.1f);
            if (_slider == null)
            {
                _slider = GetComponent<Slider>();
            }
            _slider.value = progressVal;
            yield return null;
        }
    }

    Slider _slider;
}

GameManager

Script 名を GameManager にするとアイコンがギアに変わります。
GameManager はゲーム内に一つだけしか存在しませんので、それを強制する C# プログラミングテクニック「シングルトン」を使います。

いつかの記事を発見、こちらを参考に GameManager クラスを作ります。
Unity:シーンに一つだけしかアタッチできないSingletonMonoBehaviour+実装例 - simplestarの技術ブログ

ファイル選択による VRM のロード

こちらのアセットを導入して、ファイル選択結果で得たパスを使ってロードするように書き換えます。
assetstore.unity.com

InputSystem の InputActions によるカメラカメラ操作

次の記事の通り
simplestar-tech.hatenablog.com

これからの Unity は新しい InputSystem を使うことになります。

高画質でスクリーンショット

機能を付けておきました。
simplestar-tech.hatenablog.com

動的地形生成

簡易的に次のコードで終わらせた。
TimeUpdate はなかなか便利

LevelPlaneGenerator.cs

using System.Collections;
using UnityEngine;

public class LevelPlaneGenerator : MonoBehaviour
{
    [SerializeField]
    GameObject levelPlanePrefab;

    void Start()
    {
        StartCoroutine(TimeUpdate());
    }

    IEnumerator TimeUpdate()
    {
        yield return new WaitForSeconds(4.0f);
        while (true)
        {
            Instantiate(levelPlanePrefab, new Vector3(_levelOffset += 2, _levelOffset / 5.0f, _levelOffset), Quaternion.identity, transform);
            yield return new WaitForSeconds(4.0f);
        }
    }

    int _levelOffset = 0;
}

土台プロジェクト完成

この記事では、次のツィートのようなゲームの土台の作り方を詳しく示しました。

去年からずっとゲームの面白さのコアな部分を作る方法を調べていたのですが
今回はフレーム的な、どんなゲームにも含まれる要素を作ってみました。

今後はこれを土台に、メインのゲーム要素を色々と載せ替えながら作業を記録していこうと思います。

Unity:InputSystemのInputActionsによるカメラ操作

ゲームパッドの左スティックでカメラの移動
右スティックでカメラのパン・チルト調整を行うように実装してみました。

次のクラスを Camera にアタッチすると機能します。

TPSCameraBehaviour.cs

using UnityEngine;
using UnityEngine.Experimental.Input;

[RequireComponent(typeof(Camera))]
public class TPSCameraBehaviour : MonoBehaviour, ICameraActions
{
    [SerializeField]
    TPSCameraInput cameraInput;

    void Awake()
    {
        cameraInput.Camera.SetCallbacks(this);
    }

    void Start()
    {
        _camera = GetComponent<Camera>();
    }

    void Update()
    {
        var nextEuler = _camera.transform.eulerAngles;
        nextEuler += _cameraRotateEuler;
        _camera.transform.eulerAngles = nextEuler;
        _camera.transform.LookAt(_camera.transform.forward + _camera.transform.position);
        _camera.transform.position += _cameraVelocity;
    }

    void OnEnable()
    {
        cameraInput.Enable();
    }

    void OnDisable()
    {
        cameraInput.Disable();
    }

    public void OnLeftStick(InputAction.CallbackContext context)
    {
        var value = context.ReadValue<Vector2>() * 0.1f;
        _cameraVelocity = _camera.transform.right * value.x;
        _cameraVelocity += _camera.transform.forward * value.y;
    }

    public void OnRightStick(InputAction.CallbackContext context)
    {
        var value = context.ReadValue<Vector2>();
        _cameraRotateEuler.y = value.x;
        _cameraRotateEuler.x = -value.y;
        _cameraRotateEuler.z = 0;
    }

    public void OnLeftClick(InputAction.CallbackContext context)
    {
        Ray ray = _camera.ScreenPointToRay(_mousePosition);
        // GameManager.Instance.SetAgentDestination(ray);
    }

    public void OnMousePosition(InputAction.CallbackContext context)
    {
        _mousePosition = context.ReadValue<Vector2>();
    }

    Camera _camera;
    Vector3 _cameraVelocity = Vector3.zero;
    Vector3 _cameraRotateEuler = Vector3.zero;
    Vector2 _mousePosition = Vector2.zero;
}

上記で登場する TPSCameraInput ・ ICameraActions クラスは自動生成されたものです。
Assets > Create > InputActions で TPSCameraInput という名前で InputActions を作成すると作られます。
内部の ActionMaps に Camera を追加すると ICameraActions クラスが作られます。

f:id:simplestar_tech:20190112185357j:plain
Create>InputActions で TPSCameraInput を作成・編集した様子

参考にした記事

tsubakit1.hateblo.jp

Unity:uGUIボタンとゲームロジックの縫い合わせ方法

前書き

ユーザーによる画面入力とゲームのロジックが接続される箇所を探すと、ボタンが大部分を占めます。
ボタンを押した時にどのプログラムが起動するかを UI オブジェクトから漏れなく、間違いなく辿れなければなりません
(※個人的な意見です)

が、そうしないプログラムが多く、実際メンテナーに大きなプロジェクトを引き継いだ時
ボタンを押したらバトルが始まるのに、そのボタンを押した時に実行されるプログラムがコードから見つけられない、処理フローを限定できないという大問題が発生します。

これを避ける良いプログラミング例が無いかなぁと考えて、自分は以下のようにしています。

QuitGameButton に割り当てるスクリプト実装

QuitGameButton.cs

using UnityEngine;
using UnityEngine.UI;

[RequireComponent(typeof(Button))]
public class QuitGameButton : MonoBehaviour
{
    void Start()
    {
        _button = GetComponent<Button>();
        _button.onClick.AddListener(OnClick);
    }

    private void OnClick()
    {
        Application.Quit();
#if UNITY_EDITOR
        UnityEditor.EditorApplication.isPlaying = false;
#endif
    }

    Button _button;
}

Unity のチュートリアルだとインスペクタで呼び出す関数を onClick イベントに登録しますが、それだとインスペクタの設定項目とコード実装接続を人間が行わなければならず
ヒューマンエラーが起きやすく、作業者の集中リソースを消費して会社の労働力が減ってしまいます。

そこで、上記の通り、Start 関数内で AddListner 関数を用いてコード側でボタンとロジックを接続するようにしました。
このルールが徹底されていれば、押下するボタンに割当たっているスクリプトを調べれば、漏れなく処理フローを、間違えることなく把握できるようになります。

(このルールが徹底されていれば…問題はちょっと実装が面倒くさい点ですね、多くの人はインスペクタからイベントに関数を登録してしまいます…)