Unityネットワーク学習素体
WTF?
学習教材用にUnity+ネットワークのプロジェクトを作成する。ネットワーク側が重要なため、ゲーム自体はトップビューの2Dアクションという実現のしやすい形をとる。
前提となる知識
この学習教材をスムーズに理解するために、教育的な観点で事前に理解を深めておいた方が良い知識。
- ネットワークの歴史
- ネットワークの始まり
- 現代ネットワーク
- ビデオゲームとネットワークの邂逅
- プロトコル
- OSI基本参照モデルとTCP/IP
- IP
- TCP,UDP
- HTTP
- その他のプロトコル
- HTTP通信
- 基本概念
- HTTPメッセージの中身を知る
- メッセージボディへの工夫
- テキスト送信の工夫(→データフォーマット。JSON,XML,CSV)
- メッセージの暗号化
- ネットワークの実構築
- ネットワークの構成要素
- クライアント
- サーバ
- ハードウェア(CPU、メモリ、ストレージ、NIC)
- ソフトウェア
- OS
- HTTPサーバソフト
- DBサーバソフト
- その他の装置、機器
- 構成モデル
- クライアントサーバ
- P2P(全てのクライアントが互いに通信しあう。サーバとクライアントを兼任する)
- クライアントホスト(クライアントのうち1台がサーバを兼任、スター型P2Pともいう)
- ローカルエリアネットワーク(LAN)の構築
- 設置
- 通信テスト
- 通信内容の解析、解析ツール
- ネットワークの構成要素
- 様々なサーバの役割
- 設置場所の違いで捉えたサーバ
- オンプレミス
- クラウド
- ハイブリッド
- サービス提供方法の違いで捉えたサーバ
- SaaS(ソフトウェアの実行結果を返すサービス。ソフトウェアの本体がサーバ上にある)
- PaaS(リクエストを判断し、特定のプラットフォームを動作、プラットフォームの動作結果を見た目なり、データなりでレスポンス。プラットフォーム本体がサーバ上にある。プラットフォーム上で生成されたデータとその利活用がソフトウェア?という判断とはならないため、この区分が存在する)
- IaaS(インフラストラクチャ(=ハードウェアリソース)本体がサーバ上にある)
- mBaaS ( モバイルのバックエンド(=サービス、プラットフォーム、インフラストラクチャのどれであるかは問わず)本体がサーバ上にある)
- 利用方法の違いで捉えたサーバ
- Listen Server(リッスンサーバ:クライアントホストモデルにおいてのサーバに該当する。ゲーム系サーバの一種)
- Dedicated Server(専用サーバ:所爲ネットワークの向こう側の処理を一任されたサーバ。リクエストとレスポンス、ブロードキャストなど。ゲーム系サーバの一種。クライアントサーバモデルの典型)
- Matching Server(マッチングサーバ:P2P,クライアントホストを実現するための前提としてのみ利用するサーバ)
- API Server(汎用性の高い機能を外部から手軽に利用できるように提供しているサーバ。○aaSは大体これ)
- 設置場所の違いで捉えたサーバ
実行環境
Unity 2020.3.15fと様々なクラウドインフラサービス。
ゲームの詳細
オフライン実装
簡素に済ますつもりだったので1シーンで簡潔に。次の「画面」を持つ。
- タイトル
- オプション
- ログインボーナス
- ワールド
- メニュー
- インベントリ
- ショップ
- メッセージボード
- ロビー
- ダイアログ
オンライン実装準備
- PUN2編:Photon Unity NetWorking2+Photon Cloudを利用したお手軽実装体験
- アカウント登録
- Unityへ拡張機能インポート
- 各種設定、制作
- GCP編:GoogleCloudPlatformを用意し、本格的なバックエンド(インフラ)実装体験
- サーバーマシン
- ハードウェア
- CPU、メモリ、ストレージ、ネットワーク帯域、ロケーションの選択と価格
- ソフトウェア
- OSや初期設定など
- HTTPサーバ(Webサーバ、アプリケーションサーバ)
- Node.js利用(クッソ超お手軽版)
- Apache利用(しっかりやる版、Tomcat+Java)
- DBサーバ
- RDB実装(PostgreSQL)
- ユーザーテーブル(安全でない)
- アイテムテーブル
- SQL
- RDB実装(PostgreSQL)
- ハードウェア
- サーバーマシン
- Firebase編:モバイル特化したクラウドサービスを利用することによる実装体験(時間に余裕があれば)
- サーバーマシン(アプリケーション領域の確保、自動スケール)
- サービス利用1:Authentication、アカウント管理
- サービス利用2:Firestore、NoSQL体験(RDB以外でもデータ管理が存在する)
- サービス利用3:Cloud Functions、API運用サービス体験
- サーバーマシン(アプリケーション領域の確保、自動スケール)
- セキュリティ
- HTTPセキュリティ
- DBセキュリティ
- アカウント管理
オンライン実装
オフライン実装の全体像を把握したところで、次の項目について順番に実装を行う。
- ユーザーデータ管理
- 認証
- ステータス
- アイテム
- マスターデータ
- ログインボーナス
- ゲーム内時間の管理
- 日跨ぎ処理
- メッセージボード
- 運営メッセージ
- アイテム利用期限
- プレゼント
- ショップ(ガチャ)
- ショップ(アイテム課金)
- ショップ(プレゼント)
- プッシュ通知
- マッチングロビー
- 同期処理
- 完全同期
- 一部同期
- なんちゃって同期
- 運営
- ログ収集
- ログ分析
- クラッシュレポート
- KPI(重要業績評価指標)
- バージョンアップ
- メンテナンスモード
- 端末引継
- 実績
- コンタクトリスト(=フレンド、フォロー、フォロワー)
- ランキング
- テキストチャット
- スタミナ
- イベント
参考
オフライン実装日記1
まずは準備。必要なPackageの導入、画像や音声などの素材のインポートを行なっていく。新規プロジェクト作成時点で「2D」を選択していれば大体必要なものは入っています。
- 必須
- 2D Animation
- 2D Pixel Perfect
- 2D Sprite
- 2D TileMap Editor
- TextMeshPro
- Unity UI
- 任意
- Animation Importer(Pixelart Animation Importer for Unity):画像編集ツールとの連携効率化
- AudioManager:音声素材の導入効率化
次に、画像と音声などの環境リソース。こんな感じのが必要かなー・・・と、いう感じでざっくり準備。
- BGM
- タイトル
- ワールド
- ショップ
- SE
- メニューin,out
- 決定、キャンセル
- 攻撃
- やられ
- ミッションクリア、失敗
- アイテムの取得、放棄
- アップテンポなジングル(重要なアイテム入手)
- 画像
- 背景タイル
- アイテム
- プレイヤー
- モンスター
- ボス
- UI素材(土台、各種ボタン)
- フォント
- 何種類か適当に
オフライン実装日記2
初期設定+集めた素材で土台を組み上げていく。今回のスクラップでは次の内容を取り扱う。
- ワールド
ワールド
インゲームの基本構造を作る。
タイルマップ
タイルマップ用のテクスチャをインスペクターでいじる。SpriteModeをMultiple、Mesh TypeをTight、PixelPerUnitを16、FilterModeをPoint、CompressをNoneあたりで一度Applay。続けて、Splite Editorで16x16サイズで切断。
Menu>Window>TilePaletteでTilePaletteウィンドウを出した後、Create New Paletteで1辺16*16pxのPalleteを新規作成、作成したパレットに切断済のテクスチャをドラッグアンドドロップ。
最後にHierarchyに右クリック>2D Object>TileMap>Rectangularを配置(実際生成されるのは親Gridと子TileMapの2オブジェクト)。TileMapは下地、障害物、アクションの発生するものとで3種類欲しかったのでTileMapを複製、障害物、アクション発生するものはOrder in Layerの値を上げておく(便宜上、以下TileMap2,TileMap3とする)。その後は適当にタイリングをする。
続けて、TileMap2にTilemap Collider2Dコンポーネントをつける。ぶつかったら物理的に制限するようにしたかったので、Projectウィンドウで右クリック>Create>2D>Physics Material 2Dのファイルを生成してパラメータを調整、Tilemap Collider2DコンポーネントのMaterialプロパティにアタッチした。
TileMap3は、「特定のタイルマップのみに個別のアクションをつける」ということをしたかったので、Tilemap Collider2Dコンポーネントをつけるが、isTriggerをチェックしておき物理判定は発生しないように調整しておく。(後で複製し、空のGameObjectに格納してまとめて管理する)
この後追加する各種ゲームオブジェクトと描画順序が狂わないようにSorting Layerなどを調整しておきましょう。
ターゲットカーソル
マウスカーソルに追従する形で動くオブジェクトを用意する。プレイヤーの進行方向や攻撃方向に影響する。
次のコンポーネントをつける。
- TargetFollow2D(自作C#Script)
ねぎたま氏のマウス追従スクリプトを参考にしました。
プレイヤー
画面内をXY平面で自由に動くようにする。左クリックでターゲットカーソルの位置に動き、右クリックで攻撃をする。WASDでも操作可能にする。
- Circle Collider 2D
- Rigitbody 2D
- PlayerBase(自作C#Script)
トップビューのゲームになるのでRigitBodyではGravity Scaleプロパティを一旦0としておく。
ターゲットカーソルの位置を追いかける処理が意外と大変でした(クリック位置に近づくと停止する処理が特に)
最後に、Projectウィンドウにドラッグ&ドロップしてPrefab化させる。
これは、将来的に1画面に複数のプレーヤーが出現することを踏まえ、PlayerManagerのGameObjectを親として、PlayerBaseを元に「自身や他プレーヤーを任意の数複製し管理する」というための足掛かり。
攻撃弾、攻撃判定
ゲーム内の攻撃を表現するため、2種の攻撃判定オブジェクトを生成する。一つは、発動者中心に一定の大きさの衝撃波を一瞬発生させるタイプ(以下、近距離攻撃)。もう一つは特定の方向へ長時間まっすぐ飛ぶタイプ(以下、遠距離攻撃)。プレーヤー、敵、双方で利用するのでなるべく汎用的かつ低容量で済むようにする。次のコンポーネントをつけておく。
- 2D Circle Collider
- 2D Rigitbody
- AttackBase(自作C#Script)
完成したら、近距離攻撃と遠距離攻撃のゲームオブジェクトを作成し、上記のAttackBaseをコンポーネントとして追加、それらを一旦Prefab化、そしてAttackManagerに登録する。AttackManagerでは
他のColliderとの当たり判定には必ずOnTriggerEnter2DもしくはOnCollisionEnter2Dの2D系判定メソッドを使うこと。3D(上記の2Dの文字がない版)を使ってしまうともれなく判定しません。
アイテム
とりあえず、画面内に配置しておくだけ。アイテムは画面内に複数配置されることが当たり前となるため、親ゲームオブジェクト(ItemManager)を作って、その配下で管理する。次のコンポネントをつけておく。
- 2D Circle Collider
- ItemBase(自作C#Script)
完成したら、いくつかアイテム用のゲームオブジェクトを作成し、上記のItemBaseをコンポーネントとして追加、Spriteも適当なものを当て、それらを一旦Prefab化、そしてItemManagerに登録する。
モンスター
一旦ボスは置いておき、通常モンスターのみ実装。一定時間おきに通常弾を打つか、プレーヤーに近づくかのどちらかを行う。モンスターも数が多くなりそうなので親ゲームオブジェクト作ってその下で管理。
- 2D Circle Collider
- 2D Rigitbody
- MonsterBase(自作C#Script)
完成したら、いくつかモンスター用のゲームオブジェクトを作成し、上記のMonsterBaseをコンポーネントとして追加、Spriteも的なものを当て、それらを一旦Prefab化、そしてMonsterManagerに登録する。
ワールドマネージャー
現在のゲーム進行状況を管理する。何Wave目なのか、平和な状況なのか、メニュー見ている最中なのか。最終的には通信状況も管理させる。一旦、今回作成した各種マネージャーを管理させる。
- PlayerManager
- AttackManager
- ItemManager
- MonsterManager
設定後、「平和、プレーヤーは一人」の状態でテストプレイをする。敵の生成、アイテムの生成、攻撃などが通るか、アイテムは取得(インベントリは未実装なのでDestroyするだけ)などができるかテストしましょう。
ワールドの進行チェック
最後に、作成したものを一通り繋げてゲームの基本的な遊びが実現できるか確認する。
- 拠点からステージに移動する
- 複数のウェーブを切り抜ける
- ステージから拠点に戻る
オフライン実装日記その3
続けて以下の2点を実装する。が、その前に大体のUIの仮組みをぱぱっとしてしまう。
- メニュー
- タイトル
UIの仮組み
World以外の要素は大概uGUIでの実装になるため、ここでUIを仮組みしてしまう。
メニュー
用意していた下地画像やボタンアイコンなどを使用して適当に整形する。なお、各種ボタンについてはInspectorから設定するのではなく、ボタンにしたい部分にButtonコンポーネントを取り付け、スクリプトからAddListenerでリスナー関数(押されたらやってほしいことを書いた関数)を紐付けする形で運用する。
合わせて、各メニュー項目もほとんどImageとTextを使って組みまくる。大体のレイアウトを下書きしておいてあったので見た目そっくりに無骨に作る。
タイトル
やっつけで用意。一旦WorldManagerから遷移を制御できるようにしておく。
オフライン実装日記その4
アイテム周りを調整し、インベントリを実装する。
- インベントリ
仕様
ひとまず実現するものは以下の通り
- アイテム
- アイテム実体(ItemEntity):表示用に加工されたアイテム情報。Monobihavior
- アイテムマネージャー:クライアント上でアイテム情報を整理し、WorldやInventoryに反映する。倉庫、辞書の初期化と整理。通信なども担当(例:倉庫を開く前に通信、最新情報を反映してから開く)
- アイテムを入手した場合、ローカルで一旦プール。ウェーブ終了時に結果反映→DB反映
- アイテム倉庫(ItemWarehouse):ユーザのアイテム保有情報。
- アイテム辞書(ItemDictionary):アイテム1個1個の仕様を確認するシステム、ローカル
- インベントリコントロール
- アイテム倉庫の内容の表示(倉庫に問い合わせ>アイテム実体にうつしとり、整列して表示)
- アイテム実体をドラッグ&ドロップ
- ファンクションへアイテム実体をセット
- ファンクションのアイテム実体を解除
- アイテム実体から説明文を参照して、画面下部に表示
アイテム1個の仕様
- アイテムID
- アイテムグラID
- アイテムレアリティ
- アイテム名
- アイテム分類
- アイテム効果ID : アイテム効果は別途実装(しばらく後回し)
アイテム実体の仕様
- GameObject(transform,position,scale)
- アイテム
- 表示用の実グラフィックへの参照
- 該当アイテムの個数
オフライン実装日記その5
設計を全面的に見直した。図で以下に残す。
オフライン実装日記その6
-
アカウント情報
-
ダイアログ
-
ログインボーナス
-
ショップ
-
メッセージボード
-
ロビー
-
オプション(見送り)
オンライン実装日記 その1
実装を始める前に
まず、前提を整理。
- ゲームはオフライン単体で動作している
- サーバは一旦GCPを使って立てる
- ゲーム本体はHTTP使った通信を行う( proxy環境下で学習できない点を抑制 する )
- 実装内容(実装項目)については以前のスクラップを投稿を参照
オンライン実装日記 その2
サーバー側で実装するもののメモ
一旦GCEのIaaS環境で地味に構築して制作、後半 GAE+各種サービスを利用したものに変更する方向でいきたい。
コンピューティング
テスト用でアクセス数も多くないはずなのでとにかく低性能で問題なし。ひとまず次の内容で準備。
- Google Computer Engine(GCP)を使用。
- Linux(デフォがDebianなのでそのまま採用)
- Node.js(Apacheがでも良いけど手軽さ優先)
- PostgreSQL
データベース管理
RDBへの保存が最適なデータに合わせてテーブルを作り、開発アカウントで接続する。過剰に増える予定のないマスターデータはJSONっていうケースも近年は多いみたいですが、ひとまずテーブル管理で。
- テーブル名(スキーマ)
- AccountMaster( id, uid, password, mail, name, level, coin, magicstone, lastlogin, lastmessaged, secretQ,secretA)
- ItemMaster
- ItemWarehouse
- ItemPresentHistory
- MonsterMaster
- AttackMaster
- ProductMaster
- PurchaseHistory
- GachaMaster( id, group, rarity, weight, itemId, quantity )
- AchievementList
- AchievementItem
- ContactList
ストレージ管理
静的なコンテンツについて、どんなものが必要か列挙。ほとんどJSON。画像や音声については現時点では言及しない(全てローカルのクライアントに存在しているものとする)
- Dropable(一つのjsonファイル→データベースで頑張って表現してもいいけどあえて)
- Messageboard(メッセージ収納ディレクトリとその配下の多数のjsonファイル)
- QuestMaster
アプリケーション管理
サーバ側のアプリケーション(プログラム)処理として次のものを作る。一部、ハードウェア(利用するクラウドサービス)要件で解決できるものはそれでよし。
- Login関連
- 認証処理,Autentication
- 各種マスターの配信処理
- Account
- ItemMaster
- MonsterMaster
- AttackMaster
- ItemWarehouse
- ProductMaster
- QuestMaster
- Messageboard
- DroptableMaster
- Loginbonus
- アイテム配布処理(受取済の場合、配布をしない)
- アイテム受け取りの記録(日時)を取る
- World関連
- Quest配信処理
- プレイヤー同期処理
- アタック同期処理
- モンスター同期処理
- アイテム消費処理
- MenuInfo関連
- Accountの配信処理
- MenuInventory
- ItemWarehouseの配信処理
- アイテム消費処理
- MenuShop関連
- 商品購入処理(InventoryMasterの更新、AccountMasterの更新、PurchaseHistoryの更新)
- ガチャ抽選処理(+商品購入処理)
- MenuMessage関連
- Accountの「最終メッセージ受信日」以降に追加、更新されたjsonを全て配信する
- Accountの「最終メッセージ受信日」を更新する。
- メッセージからのアイテム配布(受理済かどうかの確認)
- Matchinglobby関連
- Matching構築(onMemory。ファイルだとI/Oで恐らくスタックする)
- MatchingListの生成と配信
- MatchingIdを使った検索
- Matching参加処理
- Matching状態変更の通知
保留の処理
- 高度な認証処理
- セッション維持
- 個人情報対応(ユーザーの要望に応じて変更可能とする)
- 実績
- コンタクトリスト、友達申請
- プレゼント
- 利用期限アイテム
- プッシュ通知(ゲーム内の簡易な奴、機種跨ぐやつは難しい?)
- 定常処理(日跨ぎ時などにタイトルへ戻す処理、バージョンアップを確認してタイトルへ戻す処理)
- 端末引継
オンライン実装日記 その3
クライアント側で実装する挙動(実装箇所)についてのメモ
Unityのプロジェクト上は学習教材のため、「ここでこうしたい!」というコメントを残していると野暮と感じ、重要な事項は番号だけ割り振っておき、こちらに該当箇所でどんなことを行うかを記載し、参照する形をとる。
- TitleController.cs
- topic 01 : 現在オンラインかどうかチェックする(サーバへping)
- LoginController.cs
- topic 01 : idとパスワードを検証する→検証が終わるまで次の画面に行かせない
- topic 02 : idとパスワードを記録し、入力を自動化(省略)する
- topic 03 : セキュリティーを考慮した改良を行う(Basic→Digest,Bearerなど)
- topic 04 : id+password以外の本人検証方法を検討、実装する
- LoginbonusController.cs
- topic 01 : 入手アイテムを表示し、インベントリに追加する→確認が終わってから次の画面へ
- topic 02 : その日が入手済かどうかの記録、日跨ぎ処理→入手済であれば即次画面へ
- topic 03 : 日によって異なるアイテムの配布
- MenuItemInventoryController.cs
- topic 01 : 最新のインベントリ情報を受信→表示
- topic 02 : ダイアログ等でその場でアイテム使用→アイテム倉庫情報を更新→送信
- topic 03 : 使用期限アイテムの作成、アイテムの時間消滅
- MenuInfoController.cs
- topic 01:自身のアカウント情報を受信→表示
- topic 02 : 実績
- topic 03 : プレイヤー検索機能
- topic 04 : コンタクトリスト
- MenuMessageboardController.cs
- topic 01 : 最新のメッセージ内容の受信と表示
- topic 02 : プレゼント発行と享受の管理
- topic 03 : メッセージの未読既読
- topic 04 : 友達申請
- MenuShopController.cs
- topic 01 : 商品リストの更新、ガチャの更新
- topic 02 : 商品購入確定→アカウント情報の更新(魔法石orコイン減少)→入手アイテムはインベントリ見てねダイアログ表示
- topic 03 : ガチャ実行→アカウント情報の更新(魔法石減少)、抽選、抽選結果の格納→入手アイテムはインベントリ見てねダイアログ表示
- topic 04 : 複数ガチャ対応、特殊ガチャ対応(確定枠処理、ガチャ毎に異なる排出率など)
- topic 05 : ゲーム内コインで魔法石購入 and 魔法石でゲーム内コイン購入
- topic 06 : プレゼント機能(前提:コンタクトリスト)
- topic 07 : 外部決済処理
- DialogController.cs
- topic 01 : ランキング反映
- WorldController.cs
- topic 01 : マッチングロビーにおける処理
- topic 02 : マッチングの解除、回線落による退室
- topic 03 : 拠点にいる間のプレイヤー同期(同期方法の検討、定期的な情報取得)
- topc 04 : クエスト間のプレイヤー同期
- topic 05 : クエスト間のアタック同期
- topic 06 : クエスト間のモンスター同期
- topic 07 : クエスト報酬の整理(前提:DB管理 )
- MatchinglobbyController.cs
- topic 01 : チーム構築(TeamIdを生成、マッチング処理実行を通信)、生成が成功後画面を更新する
- topic 02 : チーム一覧の取得、表示の更新
- topic 03 : チーム生成、他プレーヤーに生成が完了した通知
- topic 04 : 構築中のチームが解散されたことを他プレーヤーに通知
- topic 05 : 他者のチームに入室する命令を通信→通信の結果(既に部屋が満杯の可能性もある)によって画面に情報を反映する
- その他
- AccountManagerを改良する
- c
オンライン実装日記 その4
長くなったので記事(Webブラウザとクラウドサービスを使った遠隔仮想環境でゲーム用の低予算バックエンドを拵える話 ))として追い出した
- 事前準備+GCEで遠隔仮想環境を構築
- テキスト編集環境を用意する
- アプリケーションサーバを用意する
- データベースサーバを用意する
- 取り回しの良さを向上させる
間違えてたこと
Cloud Shell, Cloud Shell Editorはそれ単体で動作するプロダクトだった。
Cloud Shellを起動すると、起動者のプロジェクトに紐づけられたe2-micro相当のインスタンスが起動し、その中で行う作業に限定してCloud Shell Editorが起動するって寸法だった。事前に作ったGCEなどで別のインスタンスを作ってあったとしても、それでCloud Shell Editorは起動できないようです。とはいえ、Cloud ShellのインスタンスからSCPでファイルを送ったり、gcloudコマンド使ってGCEのインスタンスにはログインはできるみたいなのであまり困らないか・・・?
おそらく理想的な使い方はGAEとの組み合わせで、Cloud Shell上で(Cloud Shell Editorで)作ったアプリ、構築した環境をごっそりデプロイ(出力)するやり方みたいだ
さもなくば以前やったみたいにCode Labをどこででも環境に依存せずにCloudで試せるっていう使い方だろうか?
オンライン実装日記 その5
サーバ側が出来上がったのでそのサーバへアクセスする処理をクライアント側で実装する。
クライアントサイド
- テキスト取得 https://qiita.com/ponchan/items/65aeb43e8fea8da0bcac
- 画像取得 https://www.hanachiru-blog.com/entry/2019/07/12/233000
- ファイルのダウンロードと保存https://spphire9.wordpress.com/2016/08/06/unitywebrequest%E3%81%A7%E3%83%95%E3%82%A1%E3%82%A4%E3%83%AB%E3%82%92%E3%83%80%E3%82%A6%E3%83%B3%E3%83%AD%E3%83%BC%E3%83%89%E3%81%97%E3%81%A6%E4%BF%9D%E5%AD%98%E3%81%99%E3%82%8B/
NetworkManager
Task, async/await
HTTPClient
オンライン実装日記 その6
クライアント、サーバの実装と作り込み。
Message受信
MessageManagerにSaveMassageMasterがあったけど引数がMessage型のみだった。Appサーバの返答手段的にテキスト処理で保存する版を用意すべし
次に問題になったのがファイル名。SaveMassageMaster(Message)は中身の日付データを読み取って自動でYYYYMMDDhhmmssをファイル名のトップに入れてたので問題なかったが、自作するとそこどうするか問題になる。JSONの中身をまた読み取ってもいいけど、楽だったのはファイル数を数えて連番対応を取ること。LoadMassageMaster()内にフォルダ情報を取得する処理があるので流用する。
OnStart()の調整。これは挙動をどうするかの
なかった。
- 複数のレスポンスを送り返せる(=1個のレ スポンスごとにJSONを送り返せる)
- 1個のレスポンスしか送り返せない(=MessageBodyにJSONをたくさん埋め込む必要あり)
どうやら2っぽい?
となると、サーバ側AppでJSONを全て繋いだファイルを作り、クライアントに返した後、クライアントでパースする処理を挟む必要があると思われる
↑の場合のサーバ側の処理(app.get関数内で実行)
const fs = require('fs');
var result="";
app.get('/jsons',(req,res)=>{
fs.readdir('.', (err, files) => { //filesは所定のフォルダ内に存在したファイル名の配列
files.forEach(file => {
fs.readFileSync('path'+'/'+file,'utf-8',(err,data)=>{ // dataはbuffer→utf-8エンコード
result += data + '@';
});
});
});
});
result.slice(0,-1);//末尾の@マークを削る
res.send(result);
fs.readFile関数は非同期、 fs.readFileSync関数は同期。ここではファイルの読み込みを確実に終わったことを確認してレスポンスを返したいので、 fs.readFileSync関数を採用する。
オンライン実装日記 その7
データベースにデータを格納して、それを入手、更新する処理。
AccountMaster構築
date型:postgreSQLでは年月日ならdate型、時分秒ならtime with timezone型、YYYYMMDDhhmmssならtimestamp with timezone型がある。UTC(標準時)ベースで時刻は考えること!!ローカル時間!!
参考 https://www.postgresql.jp/document/9.4/html/datatype-datetime.html
ItemMaster構築
ItemWarehouse構築
phpadmin4のインストールが済んでいれば、テーブル(とカラム)が用意できていればインポート機能使ってCSVファイルを読み込む形でOK。ただし、各要素を""で囲まないと読み込めない仕様っぽい
アカウント情報受信
まずは、AccountMasterのみを使って自身のアカウント情報を表示する部分を実装しましょう。
データベースから出力したデータをcsvファイルで保存するならfast-csvモジュールとかが良い感じ?
const Pool = require("pg").Pool;
const fastcsv = require("fast-csv");
const fs = require("fs");
const ws = fs.createWriteStream("bezkoder_postgresql_fastcsv.csv");
fastcsv
.write(jsonData, { headers: true })
.pipe(ws)
.on("finish", function() {
console.log("Write to bezkoder_postgresql_fastcsv.csv successfully!");
});
write,on,pipeのメソッドは全てStream系メソッド、StreamAPI(中間操作)と呼ばれるもの。
特に、onは第1引数で指定した「イベント」をトリガーに第2引数の関数を実行する。
endはストリームが最後まで到達した時
finishはストリームの出力が完了した時
また、中間操作は記述順に動作するようになっており、pipe前にon書いちゃうと狙った動作をしなかったので注意。HTTPレスポンスに出力したcsvファイルを間違いなく返したい場合は、第1引数'finish'の時の第2引数にて、res.sendFile('xxx.csv')を実行すれば良き。
一応、公式ドキュメントはこちらからどうぞ
ここまでくればあとはクライアント側でHTTPレスポンスのCSVファイルを処理して、反映させれば良い。
ただし、アカウント情報を表示する処理がクライアントベースで作られており、「通信の処理結果を待ってから」表示されるようになっていない、ここは将来的にコルーチン化して「offline時はそのまますぐ表示する」から、onlineになったところで「通信結果を待ってから表示する」様に変更した方が良い感じ。
倉庫情報受信
アイテム倉庫の情報を受信する時につまづいたところをば。まずは抽出用の正解SELECT文。
SELECT * FROM itemwarehouse WHERE "userID"=1 ORDER BY "itemID" ASC;
- 列名は""で囲む必要があった。
- 抽出された情報は必ずしもitemID順ではない、正しい順序で抽出したければ、ORDER BYをしろ。
- とはいえ、ソートは正直いうと見た目を調節するクライアント側の仕事であるため無理にする必要無
- Unity側ではcsvで送られてきたレスポンスもテキストで取得してしまうため、「カンマ区切」「改行コード区切」で2回csvを切断せねばならず、少しめんどくさい。
C#の多次元配列定義はstring[,]と行うらしい・・・えぇ・・・
オンライン実装日記 その8
ログインボーナスを実現する。ログインボーナスのあれこれ。
ログインボーナスとは?
毎日ログインすることでアイテムを配布するゲームの施策。色々なタイプがある。
- 毎日同じ
- ○日周期で同じアイテムを配布 → サーバー側でログインボーナステーブルを用意する必要あり
- 最初の30日のみ配布 → 道場
毎回同じでない場合、「今何日目」なのかを判断しなければいけないため、この情報をどこに持たせるのか考えないといけない。ケースとして多いのは、おそらくアカウント情報か実績情報のどちらかかと思われ。
なお、日本における朝4時(オンラインでありがちのデイリータスク更新時間)はUTCにおける19:00に相当する。
処理の流れ
- クライアント→アプリケーションサーバへログインボーナスくれ(with userid)のリクエスト
- ※実際ここでは、本人確認のためのセキュリティチェックが必要。
- 1:アプリケーションサーバでuseidからDBへアカウント情報を取得(必要なのは、最終ログイン時刻だけ)
- 2:現在のアクセス時刻(=アプリケーション実行時のサーバ時刻)を取得
- 3:毎日(その日)の基準時を取得。例)日本の朝4時=UTCでいう19:00
- 最終ログイン時刻を比較して基準時より前か確認( 1 < 3 )
- 基準時とアクセス時刻を比較して、基準時より後か確認( 2 > 3 )
- 時刻情報の内部は整数だったはずなので、引き算で+かーか確認すればOK
- 双方の条件が満たされた場合にのみ、ItemWarehouseに規定のアイテムを追加
- 規定アイテムがなかったら追加(INSERT)、規定アイテムがあったら更新(Update)する
- この部分については別機能でも使用するので独立した関数になっていると良い
- 当該アカウントの最終ログイン時刻を更新する
- 倉庫情報ItemWarehouse+一連の動作結果(ログインボーナスの成否)をクライアントにレスポンス
- クライアントで結果に合わせた結果表示を実施
Javascriptでタイムスタンプ合わせるの難しすぎぃ!!