$shibayu36->blog;

クラスター株式会社のソフトウェアエンジニアです。エンジニアリングや読書などについて書いています。

git grepで除外パスを指定しやすくする

git grepにはさまざまな便利グッズがあるのだけど、どうやっても覚えられなくて困っていた。たまに使いたいものとして、特定ファイルは除外する方法があるが、この記法が覚えられない...

例えば、golangでテストファイルとgen/ディレクトリ以下にあるもの以外を検索したいなら、こういう書き方ができる。先頭のコロンも覚えられないし、!も覚えられない...

git grep hoge ':!*_test.go' ':!gen/'
# もしくは
git grep hoge ':(exclude)*_test.go' ':(exclude)gen/'

そういうことで自分が使いやすいようにgit grepをカスタマイズしてみた。 自分だったら --exclude *_test.go と書けるなら覚えられそうだなということで、こういう感じに。

git-grep-extend

#!/usr/bin/env bash

# git grepをさらに使いやすくするツール
#
# git-grep-extend:
#   Usage例:
#   git-grep-extend -i "fuga" --exclude "docs/" --exclude "vendor/"

# 検索パターンやオプション
grep_opts=()
# 除外パスを格納
exclude_opts=()

while [[ $# -gt 0 ]]; do
  case "$1" in
    --exclude)
      # 除外パスを指定できるオプション
      exclude_opts+=( ":(exclude)$2" )
      shift 2
      ;;
    *)
      # grepに渡す他のパラメータ(検索パターンやオプション)を格納
      grep_opts+=( "$1" )
      shift
      ;;
  esac
done

git grep "${grep_opts[@]}" "${exclude_opts[@]}"

これで例えば https://github.com/shibayu36/terminal-shooter からこんな検索ができて便利になった。

# case insensitive / bulletにマッチするが_bulletにはマッチしない / *_test.go以外のファイル / serverディレクトリ以下から検索
git grep-extend -i -e bullet --and --not -e '_bullet' --exclude '*_test.go' server

リアルタイムに2次元位置を同期するサーバーでプレイヤーから弾を発射できるように

clusterのリアルタイム通信サーバーの漸進的な進化のような仕組みを理解したいなと思い、手習い用にMQTT+Protocol Buffersを使ってリアルタイムに2次元位置を同期するサーバーを書いてみている。今回はリアルタイムに2次元位置を同期するサーバーにアイテム情報を追加したの続きで、プレイヤーから弾を発射できるようにしてみた。

できたもの

client側でスペースキーを押すと弾を発射できるようにした。弾はプレイヤーの目の前から発射し、プレイヤーの進行方向に進む。

実装コードはこの辺り。

実装

clientからは「弾を撃つというアクションをしたい」ということだけを送ってもらうようにし、server側で初期位置や方向の計算をする作戦とした。

client -> serverにはplayer_actionトピックのPayloadとしてPlayerActionRequestというprotoの構造で送ってあげる。実際のclientの変更はこれだけ

serverのパケットを受け取った側は、PlayerActionRequestのTypeを見て、gameのShootBulletメソッドへ処理を委譲する。実装はこの辺り

ShootBulletメソッドではプレイヤー前方を計算し、そこに弾を配置するだけ。

弾さえ生成してgameオブジェクトの状態に保持できれば、あとは前回作ったclientへの状態配信の仕組みにしたがって、自動的に弾の移動が全clientに同期される。

これだけでプレイヤーからの弾の発射を実現できた。

まとめ

今回はプレイヤーからの弾の発射を実装してみた。

前回はgameオブジェクトの更新ロジックと、その更新通知を受け取ったらclientに配信するロジックを、別goroutineとして分離しておいた。そのおかげでgameオブジェクトの状態変更だけを実装するだけで実装が終わった。設計大事。

リアルタイムに2次元位置を同期するサーバーにアイテム情報を追加した

clusterのリアルタイム通信サーバーの漸進的な進化のような仕組みを理解したいなと思い、手習い用にMQTT+Protocol Buffersを使ってリアルタイムに2次元位置を同期するサーバーを書いてみている。今回はMQTT+Protocol Buffersを使ってリアルタイムに2次元位置を同期するサーバーを書いてみたの続きで、弾の情報も追加して、server内での一定周期の状態更新と配信について学んだ。

最終的にはプレイヤーが弾を打って当たると負けるみたいな動作を実現したいが、最初からそこに向かうのは大変なので、まずは次のような仕様で弾情報を追加する。

  • 弾は一定時間ごとにランダムで出現して、一定スピードで移動する
  • 弾は盤面から外に出ると消える
  • まだやらないこと
    • プレイヤーが弾を発射できる
    • プレイヤーと弾の当たり判定をする

できたもの

自分を緑、他の人を赤、弾を黄色で表示している。プレイヤーはキーボードの矢印キーで移動し他のプレイヤーに位置同期しつつ、弾は自動で移動する。弾は盤面外に出たら消える。

実装コードはこの辺りにある。

大まかな作戦

プレイヤーについては、client -> serverでプレイヤー状態変更のPUBLISHを受けたらserverが全クライアントにプレイヤー状態をブロードキャストするようにしていた。しかしアイテムについてはserver側で挙動をコントロールしたい。

そこで次のような作戦とした。

  • アイテムの状態(位置情報など)をserver側で一定周期で更新する
  • アイテム状態が更新されたらserver -> clientにアイテム状態をMQTTでブロードキャストする

イメージはこんな感じ。

実装

mainでアイテム状態の一定周期更新 + アイテム情報配信のgoroutineを2つ起動

まず(1) GameStateを一定周期で更新し続けるgoroutineと、(2) GameStateの更新の通知を受けたらclientにMQTTで配信するgoroutineの2つを起動する。

  • (1) gameState.StartUpdateLoopを実行すると、GameStateを更新し続けるgoroutineを起動し、更新通知を受け取るchannelを取得できる
  • (2) controller.StartPublishLoopを実行すると、itemsUpdatedChに通知があったらアイテム状態を配信するgoroutineを起動する

また実行して様子を見れるように弾はランダムに自動生成するようにしている。

gameState.StartUpdateLoopでは一定周期でゲーム状態の更新をする

GameState側の実装から見ていく。

StartUpdateLoopでは、time.NewTickerを使って16.7ms = 60fpsでゲーム状態の更新をする。これはGameState.updateを一定周期で呼んでいるだけ。

GameState.updateでは

  • 今のアイテムリストを取ってきて、1つずつ状態更新をしていく。状態更新ロジックはアイテム側のItem.Updateの実装に任せるようにして、Item.Updateがtrueを返してきた時は状態が変わったと見なす
  • 盤面から出たかの判定はアイテムというよりゲーム状態側に共通でロジックを持っていいかなと思ったので、盤面外の検知+削除を行う
  • もし1つでもアイテム更新があれば、updatedItemsChを通して更新通知をする
    • 更新されたアイテム情報をchannelから通知してもいいが、いったん今回は更新があったことだけを通知する

弾の更新はItem interfaceを実装したBullet.Updateで行う。Bulletは30回更新がかかったら(= 60fpsで0.5秒)、指定した方向に1マス動くということにして、

  • Updateのたびにtickをインクリメントしていく
  • tickが指定した更新回数=moveTick以上になったら移動し、tickをリセットする

のように実装した。

以上で、一定周期でゲーム状態を更新し、アイテム状態に更新があったらchannelで通知できるようになった。

controller.StartPublishLoopでアイテム状態更新があったらアイテム状態をclientに配信する

ロジックはこのあたり

まずitemsUpdatedChに通知が来たら、publishItemStatesメソッドを呼び出してアイテム状態の配信を開始する。

  • クライアントの状態更新のためActiveなアイテム状態の送信と、削除されたアイテム状態の送信をする
  • アイテム状態の配信は、item_stateトピックで、Protocol Buffersで定義したItemStateをPayloadとして持たせて配信する

まとめ

今回はリアルタイムに2次元位置を同期するサーバーにアイテム情報を追加し、弾が自動で動くようにしてみた。一定周期での状態更新や、更新があった時のみserver -> clientにMQTTパケットを送信するみたいなやり方について学べて良かった。