GOAPについて
GOAPの概要
GOAPは、ゲームAIを作る手法の一つ。
エージェント(ゲームAI)はまず初めに実行可能なアクションの集合と達成すべき目標(ゴール)を与えられる。
それぞれのアクションにはPre ConditionとPost Conditionという項目が設定されていて、それぞれのConditionはアクションをするために必要な状態と、アクションをした結果どのような状況が生まれるかというものを定義している。
AIの行動はアクションを並べることによって定義され、あるアクションのPre Condition は一つあとのアクションのPost Conditionと一致していなければならない。
ゴールもConditionで定義され、あるアクションを起こした時に、Post ConditionがゴールのConditionに一致すればそのエージェントは目標達成となる。
GOAPの実装
Action: Precondition, Actionの中身, Post Condition
Planner: 与えられたworld state、actionの集合、ゴールからアクションのQueueを返すクラス
Agent: アクションを実行するゲームAI。
WorldStates: WorldのConditionを表したクラス。
この4つのクラスが必要最小限。
Agentが与えられたActionの集合とPlannerを使ってゴールするために必要なActionの経路を探索し、それを実行する。
Tips
Actionはなるだけシリアライズ可能な値のみで定義できるようにした方がいい。そうすればそれぞれのアクションをScriptable Objectで量産できるようになるし、アクションが他のクラスにあまり依存しなくなるためクリーンになる。
Agentも、ゴールとアクションの集合によってのみ定義できるようにした方が綺麗で楽。
Unity
canfireならshoot
->canfire = false-
> 0.5秒後にcanfireをtrueにする Invoke(Canshoot, 0.5)
こんな感じの実装もあるんだね〜便利。
Event
event += 関数
event -= 関数
MVP patternでの使用例
Health:Model
Health presenter: Presenter
Healthがint health, public event healthchanged, public void UpdateHealth, public void ChangeHealthを持つ
Health presenterはOnHealthChangedをhealthChanged eventにサブスクライブしておく。
HealthPresenterがChangeHealthでHealthを変更すると、ChangeHealth内でUpdateHealthを呼び出し、UpdateHealth内でhealthChanged?.Invokeが呼び出される。
healthChanged?.InvokeによってOnHealthChangedが呼び出され、PresenterがViewを更新する。
State patternが素晴らしすぎる
FSM or GOAP
Unity Netcode for GameObjectの備忘録
マルチプレイヤーゲームを作りたい!
そもそも僕がゲーム制作を始めたきっかけはマルチプレイヤーゲームを作りたかったからなのだが、いざゲーム開発の情報収集をしてみるとどうやらマルチプレイヤーゲームを作るというのは途方もなく大変なことらしい。 というわけでしばらくシングルプレイヤー用のゲームのプロトタイプなどを作って試行錯誤していたのだが、やはり初めの動機とは違うことをしているとだんだんとモチベが下がっていってしまう。というわけでおとといあたりからマルチプレイヤーゲームを作ることを考えだした。技術と熱意、どっちかだけあるとしたら絶対熱意の方がいいよね。仕事じゃなくて自分の幸せのためにやっていることなんだから。
Unity Netcode for Gameobject
ここから本編。といってもただの備忘録です。自分なりの要点をとりとめもなくつらつらとまとめていく。内容も順番もごちゃ混ぜのTips形式になっちゃったかも。
PlayerPref
これはNGOとは関係ないけど大事なこと。PlayerPrefはPlayerのPreference保持しておくための静的クラス。このクラスを用いることで、ゲームの設定などをレジストリに保存しておける。今回はサーバーに接続するクライアントを一位的に識別するために用いた。NGOではクライアントを識別するときにClientIdというものが使えるが、これは接続するたびに変わってしまう変数なので、接続が切れちゃったりすると使えない。
GUIDを生成してクライアントを識別して、そのGUIDをPlayerprefで保存しておけば一位的に識別できるので便利だった。BossRoomのサンプルでは、これとClientIDを組み合わせて、再接続したときに切断した時の状態からプレイできるように実装していてすごかった。
Networkvariable<T>
このNetworkvariableで保持した値はネットワーク間で同期される。ここでちょっと落とし穴があるのだが、NetworkvariableにはRead/Writeにアクセス権が設定されており、ReadはOwnerだけかEveryone、WriteはServerだけかOwnerだけ。これ以外のクラスが書き込みしようとすると、エラーは起きないままスルーされるのでバグの原因になる。気をつけよう。ちなみにTはINetworkSerializableを実装していなければいけない。
ServerRpcとClientRpc
[ServerRpc]または[ClientRpc]属性をつけたメソッドはサーバーとクライアント間で通信をするための関数。Rpcとは、ネットワーク上のコンピューターから別のコンピューター上のメソッドを実行するための方法のこと。UnityではNetworkObjectがサーバーとクライアント間で同期されるので、サーバー上とクライアント上にそれぞれインスタンスが存在している。[ServerRpc]をつけたメソッドはサーバー側のインスタンスで実行され、[ClientRpc]をつけたメソッドはクライアント側のインスタンスで実行される。NGOではサーバー側での処理とクライアント側での処理を一つのファイルに一緒に書けるのだが、ここら辺は初見だとマジで頭がこんがらがる。
IsOwnerとかIsServerとかIsClientとかで条件分岐させることで綺麗に(?)一つのコードに両方の処理をまとめれるっぽい。ちなみにRpcをつけたメソッドに渡せる引数は値型かつINetworkSerializable出なきゃいけない。
所有権 Ownership
ここら辺はまだよく理解できていない。
ドキュメント読んでるときにスポーンっていう単語がよく出てきて、オブジェクトのインスタンス化と何が違うんだろうって疑問に思っていたけどどうやら同じことっぽい。
標準でスポーンされたネットワークオブジェクトの所有権はサーバー側にある。
ネットワークプレイヤープレファブの所有権はクライアント側にある。
オブジェクトの所有権は変更可能。
ClientID
これどうやって入手すんねん
NetworkObject.Singleton.LocalCoientIdで自分のClientIDを入手できるっぽい。
NEtworkObject.Singleton.ConnectedClientsIdsはサーバーのみアクセス権を持つ。
INetworkSerializable
NetworkVariableとServerRpcによって同期される値はINetworkSerializable interfaceを実装している値型でなければならない。つまり、クラスはそのまま共有できない。
例えばキャラクターがあるアクションを同期したければ、アクションのクラスをそのまま送るのではなく、アクションを特定するためのIDやターゲットの情報などを構造体に入れて送り、送った先でその情報を元にアクションクラスを生成し、実行するという風にするのがいいっぽい。というかそれしかない気がする。
マルチプレイヤーゲーム開発の難易度
これはゲームの種類によって大きく変わるらしいのだが、めちゃくちゃ大きく分けると決定的可否決定的かで分かれるらしい。決定的というのは、プレイヤーのインプットとその状況から次の状況への変化が一対一で対応しているということ。これならゲームはいろんな値を送受信したりすることなく、プレイヤーがどのボタンを押したかのみを共有すれば状態を同期できるので比較的簡単に実装できそうな気がする¥。
これからの課題
・クリーンなコードを書くこと。今の実力ではマルチプレイヤーゲームなんて作ったらスパゲッティコード化不可避。こういうのってベストプラクティスとかあるのかな?
そういえば長年アップデートされ続けてきたCounterStrikeのコードはぐちゃぐちゃすぎて誰もコードの全貌を理解できていないっていう話があったな。
Beautiful simplicity of Counter Strike
Counter Strikeは自分中ではすべてのゲームの中で最も素晴らしいゲームデザインを持っていると思う
Counter Strikeと同じくらいすごいと思うゲームや面白いと思うゲームは少なからずあるが、それらの多くはグラフィックやストーリー、音楽などの側面に大きく依っている。しかし、ゲームデザイン、すなわちそもそものゲームとしての面白さ、完成度としてはCounter Strikeが僕にはドンピシャなのだ。
このゲームについては書こうと思えばいくらでも書けちゃうので、この記事では以下の項目に絞って書く。
・簡潔性と複雑さ
・リスクとリターン
・Easy to learn, hard to Master.
・平等かつリーズナブルな非対称
・確率、非決定的、実力さがどこに現れるか
・小さな勝利と大きな勝利
説明に映る前に、このゲームのルールだけ確認しておく。
Counter Strikeではプレイヤーはテロリストとカウンターテロリストの2チームに分かれて戦う。それぞれのチームの人数は五人。テロリストの目的は指定された爆破地点に爆弾を設置して爆破することであり、カウンターテロリストの目的はそれを阻止することである。この攻防を毎ラウンド繰り返し、先に16ラウンド取ったチームが勝利となる。
簡潔性と複雑性(not 煩雑)
まず、Counter Strikeについて話すときにどうしても欠かせないのは、タイトルにもあるとおりこのゲームの簡潔性である。このゲームのプレイヤーが操作する要素は大きく分けて、移動・射撃・グレネードを投げる・爆弾の設置及び解除の4つだけである。移動はfpsならお馴染みのwasd式移動にしゃがみ、ゆっくり歩く、ジャンプ、これだけである。次に射撃。これもマウスで相手にエイムを合わせて銃を撃つだけ。次にグレネード。このゲームには4種類のグレネードがあり、それぞれフラッシュ版、スモークグレネード、火炎瓶、手榴弾の4つである。おそらく名前からそれぞれの使い方や効果は察してもらえると思う。最後に爆弾の設置と解除。爆弾の設置は指定した地点に行ってキーを押すだけ。解除は爆弾に近づいてボタンを押すだけ。
以上がプレイヤーの操作の全てである。とてもシンプル。また、このゲームの攻防に影響を与えるのは上に述べたプレイヤーの操作が全てである。
しかし、たったこれだけの要素に味方と敵、武器の種類、武器の重さ、5vs5、視野と足音、制限時間、マネーシステムといったルール(おそらくこれも他のfpsと比べて非常に基本的かつ最小限のルール)が加わるだけでまるで化学反応のように予期せぬ無限の可能性が生まれるのだ。
めんどくなったのでそのうちまた続き書きます。
結局言いたいのは、最小限かつシンプルな要素でどんなゲームよりも奥深いゲームプレイが生み出せているということ。そして、このゲームにおいては小さな勝敗の積み重ね大きな勝敗に唾がり、さらにその積み重ねがより大きな勝利につながるということ。それぞれの勝利には純粋なプレイヤースキルの他に、タイミングや流れ、リスクとリターンなどの要素が加わり、結果は決定的ではなく確率的になるということ。実力差は確率に現れるが、絶妙な制限時間とラウンド数が確率が収束する前のタイミングで定められていることで、実力差を適度に反映しつつもどちらか勝つかを絶対に予測することは不可能となっていること。また、最後にはプレイするのは人間だから、試合展開は決して予想通りにならず、各試合こと、各ラウンドごと、またもっと小さいラウンドの中での勝負ごとに非常にダイナミックな展開が生まれること。
このゲームの全てが好きすぎる。
挫折、備忘録
実装できたもの
グリッドベースのステージ、移動、攻撃システム、原始的なHPバー
ターンベースの行動システム
実装できなかったもの
ユニットごとに視界を設定し、視界に入っているオブジェクトと入っていないオブジェクトを分けること
なぜ実装出来なかったか
・そもそも視界の連続的な性質と、グリッドベースの離散的な性質が全く相容れなかったこと。この二つが共存できるような視界システムの定義が無理だった。
・↑に尽きる
まとめ
・ターンベースの簡単なゲーム
・グリッドベースの簡単なゲーム
・簡単なRaycastの仕様理解
・C#とUnityの使用をちょっと理解した
・Cinemachine、プロトタイプ作る時には便利すぎる
・面白いゲームを作りたい、が、面白いゲームを思いつく発想力も技術力もない!
課題
・非同期処理のちゃんとした理解
・デザインパターンを覚える
・ゲームの発想力...
・ゲーム以外の私生活をちゃんとする。ちゃんと...
雑記
2Dか3Dか。グリッドベースか、自由な移動系にするか
そもそも何を達成したいのか
キャラクターの移動の実装 in Unity
今回の記事は上の動画シリーズの備忘録
まじで大事なyield returnとIEnumrator
フレームごとにだんだん遷移していくような処理はたいていIEnumratorで拡張しつつ書いていくと綺麗に実装できる
StartCoroutineとIEnumratorのウロボロス
StartCoroutine(PerformPlayerturn)
PerformPlayerTurnがStartCoroutine(PerformEnemyTurn)を呼び出す
PerformEnemyTurnがStartCoroutine(PerformPlayerTurn)を呼び出す
それぞれの処理の中には多くのyield return IEnumratorやyield return WaitForSeconds()が...
ゲームって非同期処理が要なのね。面白いなあ
簡単な動きのIEnumratorの実装例
IEnumrrator(Vec3 targetPos)
{
(必要があれば)isMovingをtrueにする
if(targetpos != transform.pos)
{
フレームごとの処理
yield return null
}
transform.pos = targetPos
(必要があれば)isMoving = false
yield return null
}
IsWalkable関数などで呼び出し元から移動可能かどうかを判定してから呼び出すのがわかりやすい。
ターンベースバトルの実装
ゲームのステートが定義されていることを前提とする。
まず、ステートを変更して初期化する関数を実装する
マネージャーオブジェクトはそれぞれのステートごとに分岐して別々の処理を行う。
それぞれのステートごとの処理は関数にわけて実装するとクリーンになる。
ScriptableObject
アセットとしてすぐ作れる機能が便利そう。詳しい使い方とか設計理念はしらん。
初めてのボイチャ。CSGOにて。
CSGOやってて初めてVCをちゃんと使ってコミュニケーションした
今日はパソコン用のマイクを買った。というわけでCSGOでVCをつかって人とコミュニケーションを取ってみた。キルを取ったら「ナイス!」負けたら「ドンマイ」「ナイストライ!」、ハイライトとったら「キエエエエエエエ!!!」。どれも普段からチャットで書いているのと同じ内容だが、声に出して伝えるとなんとも言えないむず痒さがむくむくと湧き起こり、なんだかくすぐったかった。人と会話しながらゲームをするのは一人で真顔でプレイしてる時とは全く別の体験である。楽しい。生まれてこのかた20年、オンラインゲームをプレイし続けてきたが、今までのゲーム体験はなんだったのかと思うような衝撃だった。
日本人がいた
数戦していると、日本人と会った。第一声は、
「Monika(自分のゲーム名)、話せるー??」
突然日本語で味方から話しかけられ、びっくりして声が出なかった。このゲーム日本人いたのか...!!!。そしてじわじわと込み上げてくる不安。
そもそもなぜ僕がCSGOとかいう日本ではめちゃくちゃに知名度の低いゲームをしているのかというと、それは日本ではめちゃくちゃ知名度が低いからである。僕はコミュニケーションが苦手だけどオンラインゲームで対戦するのは好きだ。日本人とかいう同じ言語を共有する相手とマッチングしたら嫌でもコミュニケーションを取らなければならない。そうなってしまえば結局ぎこちない雰囲気になって、お互い引きつった笑みを浮かべながらでフレンド交換をして、それでSteamをNinja Modeに設定して一生誘われないことを祈ることになってしまうが容易に想像できる。こんな僕には、外国人という地球から月くらいまでの社会的精神的距離が予め設定されている相手の方が心地よいのだ。付かず離れず。外国人相手だとナイス、どんまい、ナイストライ、この3つの単語だけでコミュニケーションが成立するので非常にやりやすい。(ここまで0.1秒)
うああ、何か返答しなければ。
「話せるよー↑!!」
まずい!!久しぶりに日本語を発声したので声が裏返ってしまった!!ガラスのハートにピシピシとひびが入る音が聞こえる。どうしy...
「イエーい!!!、頑張ってきましょ〜!!」
泣きそうになった。ここまで人の温かみを感じたのは初めてだった。その後のことは記憶にない。おそらく刺激が強すぎる記憶が自己防衛反応により自動的に削除、デリートされたのだろう。
コミュニケーションという高すぎるハードルを超えるのを諦めて逃げてきたCSGOでまさかコミュニケーションが求められることになるとは。僕はこれからどうしていけばいいのでしょう。
中国人がいた
久しぶりに出会った日本人と別れて次の試合、
「Monika!!へいMonika!!ヨロシクゥ!!なんかシャベェぇって〜!!」
うあああ、また日本人だ!!緊張から解放された心がまた急速冷凍されていくのを感じた。もうマイク返品しようかな。とりあえず何か返さなければ...
「い、いえ〜い(震え声)」
言葉のチョイスが酷すぎる。語彙力も皆無。
「オオーーーー!!!Mo↓ni↑ka!!ヨロシク!ヨロシク!オマエハモウシンデイル!」
なんだコイツ・・・。いや・・・まさか・・・!!!コイツ外国人だ!!!!!!
冷凍され砕けかけた心が間一髪で急速解凍されていく。心と同時に口元もつい緩み、
「ナイストゥミーチュー!ナイストゥミーチュー!〇〇(相手の名前)!!」
その後返答はなかった。僕の心は決して成就することのない淡い期待とともに再びゆっくりと冷えていった。
ゲームをプレイする時は部屋を明るくして画面から離れてvcを友達と繋ぎながらプレイしましょう
これは今日初めて気づいたことなのだが、友達とゲームをすると、夜一時か二時、よも老けてきた頃で解散となる。そこで、おやすみ〜と眠気の混じった声で挨拶を交わして落ちるのだが、このおやすみの挨拶をする習慣が非常にいい。この挨拶をすることでそこでゲームをスパッとやめて眠りにつくことができるのだ。
普段一人でゲームをしていると、なかなかやめどきが見つからない。満足したら辞めようと思っているのだが、普通の人間はオンラインゲームを一人でプレイしていて満足することなどあるわけがないので、結局寝落ちするまで延々とゲームを続けてしまうのだ。
しかし、友達とプレイしていた場合は、みんなのみんなへの配慮と常識のすり合わせにより、遅い時間になると一旦ブレーキがかかる(「もう寝ない?」ってなる)。さらに、みんなでプレイすることにより、「友達と遅い時間まで楽しくゲームをプレイした」という「思い出」の結実が確認され、脳の社会的欲求が幾分か満たされる。最後に、「おやすみ」と言い合うことで寝ることの約束が軽く交わされる。これによって、ゲームに一区切りがつき、程よい満足感があり、眠らなければという気持ちが強くなっている状態が生まれる。これは寝るしかない。でも、一人でプレイしていた場合はこの3つのどれも得られない。寝るには自分で自分を依存から引っぺがすような強い精神力が必要なのだ。無理だね。
ゲームをプレイする時は部屋を明るくして画面から離れてvcを友達と繋ぎながらプレイしましょう