NeosVR内で作ったOTAMAxSTEPゲームでどういう実装したかを書いています。
なお、ゲーム概要と開発フローに関しては1つ前のエントリーをお読みください。
hyoromo.hatenablog.com
※本エントリーはOTAMAxSTEP v1.0.9時点の情報となります。
なにやったかをある程度まとめて書きます。気になる箇所がありましたらゲーム本体のLogiXやコンポーネントを確認ください。
ちなみに各対応「これが正解だ」という事ではなく「このゲームではこうやった」という話である事に注意してください。私はNeos歴半年程度なので熟知して書いている訳では無いです。
はじめに
本エントリーでは所定のLogiXやComponentの参照先を書いています。ここでは断片的な情報しか載せられないため、興味がある部分に関してはゲーム本体の参照先を確認ください。
以下にゲーム本体があります。
- ワールドURL
http://cloudx.azurewebsites.net/open/world/G-Wonder-Works/R-85f48885-db53-4211-bb98-b9cb3c3b0562
- 共有フォルダ
neosrec:///U-hyoromo/R-b0f52610-c4f0-4a8d-87c3-41f87f0e57b6
"JP Publics > 個人 Individual > hyoromo public > Games > OTAMAxSTEP"
ゲーム進行者を決める
Neosは手軽に他セッションへ移動可能で、例えホストであろうが移動出来てしまいます。
移動された後にホスト任せでパルスを流そうとしても、残念ながら流れません。なのでゲーム進行はホスト依存しない作りにします。
ゲームのメイン処理は以下を上から順にチェックしていき、存在するユーザーに処理進行を任せています。
3番目はゲーム進行者として不適切です。
近いユーザーが処理しちゃうため、場合によっては近いユーザーがコロコロ切り替わることで処理が重複する危険性があります*3。
「START GAME」ボタン押下時のLogiX: OTAMAxSTEP/Title/LogiX/GameStart
進行者による発火のLogiX: OTAMAxSTEP/StageEdit/LogiX/CheckRoundStart
状態管理
DynamicVariable(以降DVと略す)にGameStateを登録し、以下のようにゲーム進行に応じて値を更新しています。
実際ゲーム中に使われている名称や値とは少し異なりますが、以下のような感じで管理しています。
State | Value*4 |
---|---|
初期状態のタイトル画面 | 0 |
ブロック配置ターン | 1 |
レースターン | 2 |
レース終了後のスコア計算及びボード更新 | 3 |
ゲーム終了 | 4 |
3のタイミングで勝敗が決したら4、まだ次ラウンドを行う場合は1に更新されます。
処理は値が変更されたらUI表示切り替え、次のState変更条件が揃わないかをゲーム進行者が監視し始めます。
DV state: OTAMAxSTEP/DV/Value 及び OTAMAxSTEP/DV/Value/GameStatus
状態更新
上図のように各処理の冒頭で実行可能なStateかをチェックし、問題なければState更新して処理進行させる感じです。
状態に応じてActive切替
以下のようにコンポーネント完結でActive切替可能です。これでUI/LogiXのSlot Active状態を切り替えます。
- Stateと一致している時のみ表示する場合はValueEqualityDriverを使用
- いくつかのStateに一致している場合はValueMultiplexerを使用
Component: OTAMAxSTEP/Config/UI/PlayingGameEditRoot や OTAMAxSTEP/CountdownTimer/UI/TimerCanvas -Parent
DVのリセット
それとDVはpersistentがtrueの場合、インベントリへ保存しても値が残り続けます。
Stageやインベントリへ保存時に初期設定値に戻って欲しい場合、DynamicValueVariableResetを設定する事でアイテム呼び出し/複製時にリセットされるようになります。
DV reset: OTAMAxSTEP/DV/Value
ローカル処理
レースはローカルにて以下のプレイヤー処理を行っています。それぞれ細かい解説は以降に行います。
- レース開始時
Locomotionをレース専用のOTAMAxSTEPにSwitchLocomotionModuleで切り替える
開始座標へ移動
レース中のユーザースケールに変更 - レース中のタイマー設定
スタートからゴールまでの時間計測は各ユーザーのローカルで行っています。これにより多少同期ズレが発生し、スタートのタイミングがズレても不利にはなりません*5。 - ブロックとの当たり判定
- 死亡(落下)/ゴール判定
- グラブ/トリガーした時のジャンプ
セカンダリジャンプだと利用コントローラーによってはジャンプしにくいため、他ボタンでもジャンプ可能になっています
レース開始時の初期化
レース開始時に以下のような流れでゲーム進行者から各プレイヤーのローカルへと処理するユーザーを変えています。
- ゲーム進行者はレース開始時に各プレイヤーへInstallLocomotionModulesを使ってOTAMAxSTEP Locomotionをインストール
- 無事インストールされるとOTAMAxSTEP Locomotionの子SlotにあるLogiXのOnStartがセッション内の全ユーザーで発火
- 発火先でGetActiveUser*6がIsLocalである場合に限り、先程のローカル処理を実行する
この手段はLocomotionに限らず、ローカルで処理させたいLogiXを対象ユーザーのUserRootSlotへ入れるだけで良いです。
今回の場合はLocomotionがインストール後に各ローカル処理が動作して欲しかったため、このような対応を行っています。
OTAMAxSTEP Locomotion: OTAMAxSTEP/Prefs/GameLocomotionModule/OTAMAxSTEP
カスタマイズLocomotion
歩行Locomotionの一部を変更したOTAMAxSTEP Locomotionをインストールし、レース開始時に強制切替を行っています。
PhysicalLocomotionコンポーネント*7の以下部分を変更しています。
項目 | 説明 |
---|---|
MaximumNormalizedSpeed | ダッシュスピード*8 通常は2倍で、本ゲームだと1.5倍になる |
Grapping | Grab設定 DisabledにするとGrab不可能となり、ColliderのあるブロックをGrabしてすり抜ける等の不正が出来なくなります |
なお、同SlotにCapsuleColliderがありMassが設定出来ます。ユーザースケールを変更した際、Mass値の調整も行ってユーザー質量の調整を行った方が良いようです。
プレイヤーのOnCollision
プレイヤーの死亡/ゴール判定はColliderで行っています。
即死するColliderとゴールのColliderに専用TAG*9を設定し、Hit時にコールされるOnCollisionStartで判定処理を行います。
注意点としては判定はローカルでのみ行いたいため、GetActiveUserがIsLocalUserである事を必ずチェックするようにしています。前述した通り当たり判定はLocomotionと一緒にUserSlotへインストールされており、GetActiveUserはインストールされたUserを返すためこのような条件でチェックしています。
LogiX: OTAMAxSTEP/Prefs/GameLocomotionModule/OTAMAxSTEP/LocalUserModule/LogiX/OnCollision
Grab/Triggerでの独自ジャンプ
NeosVRは歩行時にセカンダリでジャンプ出来ます。ただコントローラーによってジャンプがし難いものがあり、例えばQuestコントローラーだとスティック押し込みなので頻繁に使いたくないという要望がありました。
なので、Grab/Triggerでジャンプ出来るシステムを導入しています。りんきさん/fukuroさんに初め実装して貰い、その後に私の方で改良したバージョンが現行のものです。
苦労した点はNeosVRのプレイヤー原点が割りとズレている点*10。このズレのせいでY軸方向へApply forceする際、原点がColliderに埋もれているとColliderにぶつかってあらぬ方向へ飛んでしまいます。ここの対応は未だ正解が分からなく、今のところはジャンプタイミングで一時的にmass*11を上げる事で対処しています。
本機能は通常ジャンプとの差を埋めるのも苦労しました。本当は通常ジャンプ機能を潰し、セカンダリ時はGrab/Triggerジャンプと同じ挙動にしたかったです。ですがViveコンだと歩きたいのにセカンダリ判定となってしまい、他にもコントローラー依存で問題が発生しそうだったため諦めました。
LogiX: OTAMAxSTEP/Prefs/GameLocomotionModule/OTAMAxSTEP/LocalUserModule/LogiX/Local/JumpController
プレイヤーの死体
レース中に死亡したらアバターをBakeして死んだ場所に残ります。プレイヤーと他プレイヤーに何処で死んだかを認識して貰いやすくするためです。
なお死体はKanon04さんがMMC22で投稿されたSwampmanワールド*12内でのLogiXを頂き、ちょっとイジったものとなります。
注意点としてはアバターをBakeしているため、アバター側にアバタープロテクションが掛かっている場合はBake後のモデルにも掛かっています。そのモデルをゲーム本体内に一時的に配置し、その配置された対象者がセッションから抜けるとプロテクション効果でゲーム本体ごと消失します*13。
なので、Root直下に死体置き場を作って事故が発生しないよう気を付ける必要があります。
LogiX: OTAMAxSTEP/Main/LogiX/DeadBody
レースの着順判定
ゴールしたプレイヤー名をゴールした人を格納するSlotへ追加、ゴールまでに掛かった時間をミリ秒でOrderOffsetへ設定します。
Slot格納する際にOrderOffsetを設定すると「同じ階層に居るSlotが小さい順に並ぶようになる」のでIndex0が1位のプレイヤーとなります。
LogiX: OTAMAxSTEP/Result/LogiX/ScoreCount
Join/Leaveシステム
Join or Leaveボタンが押されたらJoin、もう一度押されたらLeaveします。
Joinの際はレース着順と同様にJoinした人を格納するSlotへ、Slot名にユーザーIDを設定して格納します。
Leaveの際は格納したSlotを消しています。なおJoin中のプレイヤーがセッションから抜けた際、UserLeftがコールされてホストがLeaveさせています。
LogiX: OTAMAxSTEP/UserBoardSystem/LogiX/OnJoinOrLeave
ローカライズ対応
EN/JP/KRの3言語からプレイヤー毎に好きな表示言語を設定出来るようにしています。
内部的に言語設定値をDVで持っておき、ReferenceUserOverride*14で言語設定値がプレイヤー毎に分かれるようにしています。こうする事で多言語ユーザーが混ざってゲームプレイしても、それぞれ見えているのがネイティブ言語なためプレイに支障が無くなります。
DV: OTAMAxSTEP/Config/DV/Value
UIに反映
言語設定値のDVをローカライズ対応が必要な各所へ反映させます。
- ValueMultiplexerを使ってEN/JP/KRで設定する値を設定。Textを言語毎に切り替えたい場合はContentをTargetに設定する
- (1)のIndexには言語設定値を設定する必要がありますが、ローカライズ対象件数が多いならValueCopyでは無くValueMultiDriverを使うとチョット楽
1: OTAMAxSTEP/Config/UI/PublicStageSettings/Image/Header/TitleLabel
2: OTAMAxSTEP/Config/DV/Value/Drive
言語切替
肝心のEN/JP/KRボタンを押下した時の切替処理は上図の通り。
重要なのはCurrentCultureで、LocalUserのロケールIDが取得出来ます。なので、ja-JPなら日本語、ko-KRなら韓国語、それ以外なら英語が設定されるようにしています。
LogiX: OTAMAxSTEP/Config/LogiX/ChangeLang
セッションにJoinしたユーザー言語に自動切替
ユーザーがセッションへ入ったかは以下で分かります。注意点はHostでしかJoin状況が把握出来ない事です。
- Hostがセッション建てて入った場合、OnLoadedが発火
- Host以外がJoinして来た場合、Host側でUserJoinedが発火。下図の右側は言語切替でのLogiXと同じ
Joinしたユーザー自身に前述した「ローカライズ対応」で書いた切替を行わせたいため、Host側でUserJoinedが発火されたらユーザー情報をDVに書き込み、対象DVに変更があれば対象者のみパルスが流れるようにしました*15。
OTAMAxSTEPワールドのLogiX: Root/World/WorldDocs/LogiX
同期ズレ問題の対策
同期ズレは段階的に大きく2つの症状があります。
1. ゲーム進行者と数秒ほど表示までのタイムラグが生じる
頻繁に発生する問題なので多少ズレてもゲーム上問題ない作りにしておきます。
例えば以下のような事を実施しました。
- スタートからゴールまでの経過時間はローカルで行ってレースを公平のものにする
- プレイヤーとの当たり判定はローカルで行う
- ゲーム進行者から各プレイヤーに対してLocomotionをインストールするが、Locomotion切替はローカルで行う
各プレイヤーがインストールし終える前に切替処理が実行された場合、Locomotionが切り替わらない/強制Respawn/セッションから強制切断される現象を確認しました
ゲーム進行者のLogiX: OTAMAxSTEP/Main/LogiX/RoundStart
ローカルのLogiX: OTAMAxSTEP/Prefs/GameLocomotionModule/OTAMAxSTEP/LocalUserModule/LogiX/MainLoop & Init
2. 全く同期されず、新たにSpawnされたSlotが見えない状態
復帰は絶望的です。ゲーム進行に大きな悪影響を与えてしまうため、全く同期されていない事を他ユーザーにも見える形で伝えつつ*16、ゲームからはリタイア扱いとしています。
LogiX: OTAMAxSTEP/Main/LogiX/RoundStart
ブロック配置は1人1つしか置けないルール
ステージエディット時は1人1つのブロックのみ配置可能なルールを設けています。
そのルールを課すために以下の制限を実装しています。
- ブロックを指定範囲内に配置後、2つ目のブロックを持てなくする
- 片手でブロックを持っている最中、反対側の手でブロックを持てなくする
これを実装するにあたり、NeosVR内のGrab特性を掴んでおく必要がありました。
- 直接手でGrabする
- レーザーでGrabする
- レーザーでGrabした状態から手元まで引き寄せ、直接手でのGrabに切り替える*17
特に3つ目が罠で、手に持ち変わるタイミングでOnGrabbableReleasedがコールされるのにOnGrabbableGrabbedがコールされません。そのせいで掴んだ時にロックを掛け、手放した時にロック解除する...といったような対となるよう実装してしまうとバグります。
LogiX: OTAMAxSTEP/StageEdit/Pref/CoreDevice/OnGrab
イベント発火者が他ユーザー更新内容を元にしたイベントを発火してくれない問題
例えばヘッドレスでFireOnChangeイベントをホストに任せた時、その変更対象の値を他ユーザーが更新した場合にホストがFireOnChangeイベント一向に発火してくれない時があります。
そうした場合は他ユーザーの更新対象を参照する際にUpdatingRelayノードを繋げる事で解決するかもしれません。
このノードを介すると他人が更新した値も評価されるようになります。代わりに負荷が増すので何でもかんでも設定すれば良いという訳じゃないようです。
ブロック
ブロックはゲーム進行に応じてDynamicImpulseで各状態処理を行っています。
Tag名 | 状態 | 処理内容の一例 |
---|---|---|
CoreDevice.Spawn | Spawn | 初期化 |
CoreDevice.GrabRelease | ユーザーが配置 | 配置座標と設置者を保存 |
CoreDevice.RoundStart | レース開始 | 砲撃開始 |
CoreDevice.RoundEnd | レース終了 | 砲撃停止 |
CoreDevice.Destroy | ゲーム終了 | 飛び道具Slotの破棄 |
以下の手順で目的のブロックを配置して確認が行いやすいです。
- タイトル画面の左にある設定から、「高度な設定」ボタン押下
- 更に左へ表示されたダイアログにある「DebugMode」をONに切替
- 右側にある「All Blocks」ボタン押下
- 好きなブロックをステージ上へ設置
- ステージ左側にある「Solo play」ボタン押下
これでレース時とほぼ同じ状態で中に入って確認出来ます
各ブロック: OTAMAxSTEP/StageEdit/Pref/TemplateBlocks
ここから各ブロックの簡単な説明を列挙します。ポイントしか記載していないので詳細はゲーム本体にある各ブロックを確認ください。
注意点は以下の通り。
- レース中はユーザースケールを変更しており、そのスケールに合わせてギミックは調整している。もし通常スケールで使う場合は別途パラメータ調整が必要
- 各ブロックには制作頂いた方の名前を記載しているが、最終調整はhyoromoが行ったため一部は制作時点と異なる挙動をしている場合がある*18
- 先に言い訳を書いておくと...作った本人じゃなく私が中身を確認して書いているため間違った事や、情報不足なブロックもあるかもしれません
爆弾
制作者はYOMOGI_MOCHIさん。
爆弾の範囲内に複数RaycastOneノードを使い、当たったブロックを消しています。
Raycastは少なくとも1度はRaycastのデバッグ描画を行って確認する事をオススメします。DebugDurationで描画時間を設定すると表示されます。
1点問題があり、ランダム方向にraycastを飛ばしているため偏ったり小さいブロックに当たらない問題があります。改善策はあるようですが未だ導入はされていません。
OTAMAxSTEP/StageEdit/Pref/TemplateBlocks/ぼんばーまん/logix
雲
制作者はりんきさん。
雲に乗ると少しだけ上へ移動、そして自由落下...を繰り返すギミックです。
CharacterForceFieldが使われており、プレイヤーが雲のColliderに接触した瞬間に上方向へAddForceされます。
OTAMAxSTEP/StageEdit/Pref/TemplateBlocks/Cloud/main/Visual/capsulecollider
大砲
モデル制作者ははるるさん、砲撃システム制作者はMotoMaidさん。
同じ場所へ向けて砲弾が放物軌道を描いて飛びます。何かに接触*19すると爆風エフェクトと共に周囲に居るプレイヤーが死にます。
砲弾のHit判定はMeshColliderにもHitさせたかったためRaycastで行っています*20。
砲弾の放物運動は計算式を書く方法とTrajectoryノードで行う2通りあり、ここでは前者の方法が使われています。
大砲設置時にはBalisticPathMeshを使って弾道予測線を表示しています。このMeshはLocomotion Teleport時に表示されるものと同じです。
OTAMAxSTEP/StageEdit/Pref/TemplateBlocks/Taihou(normal)/logix
OTAMAxSTEP/StageEdit/Pref/TemplateBlocks/Taihou(normal)/pref/Dangan/logix
ミサイル
モデル制作者ははるるさん、砲撃システム制作者はMotoMaidさん。
レース中のプレイヤーからランダムに選んだ人の頭上目掛けて砲撃します。ターゲット位置には赤いアラートマークが表示され、そこから数秒後に上から弾が着弾します。
OTAMAxSTEP/StageEdit/Pref/TemplateBlocks/Taihou(ミサイル)/logix
OTAMAxSTEP/StageEdit/Pref/TemplateBlocks/Taihou(ミサイル)/pref/Dangan/logix
アイスブロック
制作者はginjakeさん。
乗ると滑って移動やジャンプがし難くなります。
ConstantCharacterControllerModifierコンポーネントでTractionForceを低い値で上書きすると、静摩擦力が小さくなるため乗った時に停まり難くなります。
OTAMAxSTEP/StageEdit/Pref/TemplateBlocks/IceBox
回る足場
制作者はKanon04さん。
観覧車のように足場が回ります。
足場にはCharacterParenterコンポーネントが使われているためファイナル床*21になりません。
床のRotationはGlobal rotationでDriveされているため、ブロックを回転させても床の傾きは変わらないのが特徴的です。
OTAMAxSTEP/StageEdit/Pref/TemplateBlocks/回る足場/軸/棒s/足場s/足場/characterparenter
送風機
モデル制作者はberneさん、飛ばすギミック制作者はりんきさん。
浮遊状態で風パーティクルに触れると飛ばされます。
雲と同じようにCharacterForceFieldコンポーネントが使われ、Colliderに触れると上方向に速さが与えられます。
OTAMAxSTEP/StageEdit/Pref/TemplateBlocks/VentilationFan/Cylinder wind
孔明の罠
制作者はginjakeさん。
触れると見える透明ブロックです。
AssetMultiplexerコンポーネントを使って触れる前/触れた後でMaterialを切り替えています。あとRefractMaterialを使うことで屈折して見え難いブロックとなっています。
OTAMAxSTEP/StageEdit/Pref/TemplateBlocks/koumei/materialSwitch
トゲブロック
制作者はakiRAMさん。
NeosVR内でモデリングされ、ハプティクススーツを着ていると触れると強い振動フィードバックがあります。
ハプティクスはCollider TypeにHapticTrigger(HapticStaticTrigger)を設定。HapticVolumeコンポーネントを追加してSensationとIntensityを設定します。Painが一番強いそうです。
他にも「トラバサミ」や「クロスボウ」といった即死する系には全てHapticが入っており、ハプティックスーツを来たakiRAMさんによる範囲/強さが調整されたハプティックスーツ対応ゲームとなっています。
OTAMAxSTEP/StageEdit/Pref/TemplateBlocks/TogeBox/Haptic
槍ブロック
モデル制作者はakiRAMさん、アニメーションはfukuroさん。
時間経過でトゲが出たり入ったりします。プレイヤーはトゲ部分に接触すると即死。
Spawnされてから数秒毎にTweenノードを使ったアニメーションが行われます。
OTAMAxSTEP/StageEdit/Pref/TemplateBlocks/YARIBOX2/logix
ちくわブロック
モデル制作者はMIRA_skさん。落ちるギミック制作者はりんきさん。
スーパーマリオブラザーズ3のちくわブロックをイメージして作って貰いました。乗って少し経つとブロックが落下していきます。
OTAMAxSTEP/StageEdit/Pref/TemplateBlocks/ChikuwaBlock/logix
回転ノコギリ
制作者はberneさん。
触れると即死する回転ノコギリです。回転ノコギリの平面部分にはCharacterParenterコンポーネントが付いており、乗れるよう設計されています。
行ったり来たりするアニメーションはPanner1D/ValueGradientDriver/SmoothValueコンポーネントのセットで行われています。Pannerでアニメーション速度の設定&PingPongをONにする事で往復移動するよう設定、GradientDriverで移動座標を設定、それらの移動をSmoothValueで滑らかにしています。
OTAMAxSTEP/StageEdit/Pref/TemplateBlocks/Rotationg saw L/Saw
シーソー
制作者はGreaseMonkeyさん、りんきさんです。
乗った人の多い方に板が傾きます。CharacterParenterコンポーネントが付いていますが、プレイヤーの視線が変わらないよう設計されています*22。
シーソー板に乗っている全ユーザーの座標をチェックし、どちらに傾けるか決まります。
OTAMAxSTEP/StageEdit/Pref/TemplateBlocks/シーソー/logiX
ジャンプ台
モデル制作者はlilhatategamiさん。ギミック制作者はりんきさん、すらさん。
ジャンプ台部分に接触すると飛びます。
こちらもCharacterForceFieldコンポーネントが使わています。
OTAMAxSTEP/StageEdit/Pref/TemplateBlocks/JumpBox/Step/colliderforse
羽ブロック
羽モデル制作者はlilhatategamiさん。ギミック制作者はginjakeさん、fukuroさん。
ブロックに乗ると矢印方向へ移動します。ブロックから降りると元の位置まで戻っていきます。
プレイヤーが回転して設置出来るブロックなため、6面それぞれにCharacterParenterコンポーネントを追加してプレイヤーが落ちないようにしています。
OTAMAxSTEP/StageEdit/Pref/TemplateBlocks/HaneBox/LogiX
ふるえるブロック
ふるえるギミック制作者はりんきさん、何かをくっつけるシステム制作者はYOMOGI_MOCHIさん。
Wobbler3Dコンポーネントを使ってブロックが震え続けています。
GrabbableReceiverSurfaceコンポーネントによって他ブロックを6面のいずれかにくっつける事が出来、くっつけたブロックも震えます。
ひっつく対象をUltimateDeviceタグ*23付けされたSlotのみにするようWhitelist設定も行っています。
OTAMAxSTEP/StageEdit/Pref/TemplateBlocks/WobblerBox/Visual
OTAMAxSTEP/StageEdit/Pref/TemplateBlocks/WobblerBox/くっつきシステム/Surfaces/up
ベルトコンベア
制作はSloppy McFloppyさん。
乗るとベルトコンベアの回転方向へ少しずつ移動させ、端まで行くと落下します。
OTAMAxSTEP/StageEdit/Pref/TemplateBlocks/SlopppyConveyorBelt2/logix
ブラックホール
制作者はりんきさん。あとFloatyOrbアイテムの見た目をお借りしました。
ジャンプ中に近寄ると中心へ吸い込まれ、中心に当たると即死します。
中心に引き寄せる力はCharacterForceFieldコンポーネントで行われています。
OTAMAxSTEP/StageEdit/Pref/TemplateBlocks/BlackHole
トラバサミ
モデル制作者はakiRAMさん。アニメーション制作者はhantabaru1014さん。
トラバサミが開いた状態時、中心の床を踏むと閉まって即死します。
OTAMAxSTEP/StageEdit/Pref/TemplateBlocks/トラバサミ
回転棒
モデル制作者はlilhatategamiさん、ギミック制作者はりんきさん。
お団子1本1本にCharacterForceFieldコンポーネントが追加されており、回転方向へ少しだけ吹き飛ばされます。
OTAMAxSTEP/StageEdit/Pref/TemplateBlocks/Odango/RootNode/Dango.001/お団子アタック
回転ドア
制作者はlilhatategamiさん。
ビル入口にある回転ドアのイメージで制作して貰いました。
Spinnerコンポーネントで回転させています。Spinnerは他にも「回る足場」や「回転棒」でも使われています。
この手のブロックでは珍しくCharacterForceFieldコンポーネントは使われていません。
OTAMAxSTEP/StageEdit/Pref/TemplateBlocks/Propela/RootNode/poal
鉄球
モデル制作者はYOMOGI_MOCHIさん、アニメーション制作者はhinano.airaさん、Hit時の吹き飛ばしギミック制作者はりんきさん。
振り子運動をしている鉄球です。
CharacterForceFieldコンポーネントが使われているため、当たると吹き飛ばされます。
OTAMAxSTEP/StageEdit/Pref/TemplateBlocks/Pendulum/Model/Sphere
クロスボウ
モデル/アニメーション制作者はMIRA_skさん。ギミック制作者はhantabaru1014さん。
一定間隔毎に矢を射出し、プレイヤーに当たると即死します。
矢が何かに当たって停止するのはRaycast、プレイヤーの死亡判定は矢じり部分のColliderで行っています。
OTAMAxSTEP/StageEdit/Pref/TemplateBlocks/AutoBowgun-0417/Logix
OTAMAxSTEP/StageEdit/Pref/TemplateBlocks/AutoBowgun-0417/Templates/Arrow/Logix
コイン
モデル制作者はlilhatategamiさん、はるるさん。ギミック制作者はりんきさん。
コインに近付くとプレイヤーにコインが付いてくるようになります。他人が持っているコインに近付くと奪うことも出来ます。
DVのPlayerUsersRootには現在レース中のユーザー分だけUserID名でSlot作成されており、コインに触れたプレイヤーが現在レース中の場合のみ追随させるようにしています。これは「シーソー」や「トラバサミ」でも行っており、レース中以外のプレイヤーが間違って当たっても誤動作しないための措置です。
OTAMAxSTEP/StageEdit/Pref/TemplateBlocks/Coin/LogiX/Main -LogiX
ステージ
ステージ制作はStart/Goal/Scoreboard座標指定とステージ専用ブロックを配置し、ステージ切替時に共通モデルの座標更新及びActive切替を行っています。
ステージ追加は以下の手順で簡単に行なえます。割りと簡単に作れると思うので誰か追加ステージ作ってください...
- 上図のいずれかのStageXXを複製
- Start/Goal/Scoreboard座標指定*24
座標更新の確認はステージ切替を行ってください - Blocks内へ好きにブロック配置
各ステージ: OTAMAxSTEP/Stages
ステージ切替LogiX: OTAMAxSTEP/Title/LogiX/OnChangeStage
凝ったステージ紹介
制作者はりんきさん。
スイッチを押して3秒後に上がるエレベーターのあるステージ。
制作者はtomo9さん。
立ち並ぶビルステージ、一部制作者の影やNeosロゴの影演出などある。
制作者はtomo9さん、一部の小物や写真はKanon04さんと私。
家具や物が置かれた部屋ステージ。
制作者はりんきさんとぺれさん。
ゴール手前に吹き荒れている風があるステージ。風に触れると吹き飛ばされます。
締め
このゲームはブロック/ステージ/コアシステム/UIの大きく4つに分ける事が出来たため分業がそれなりに上手くいっていました。
本ゲームの開発期間は約2ヶ月。
コアシステムは95%くらい私が実装し、nonoNoxさんやokazeeeさんに残り手伝ってもらって1.5ヶ月くらい。ブロックは記載した通りほとんど私以外の方が1ブロック数分から数日掛けて作ってくださり、1ヶ月くらいでほぼ全てのブロックが完成。全9ステージは「凝ったステージ紹介」で紹介したように4は他の方、それ以外は私が作っていました。UIはほぼ私が制作して0.5ヶ月くらい。
NeosVRでマルチプレイゲーム作るのは初だったためコアシステムを作るのに物凄く苦労しました。
同期が絡むもの、利用プラットフォームやFPSの環境差による問題には悩まされ。安定させるために開発終盤に1から書き直したものもあります。
―――と、ダラダラ書きましたが主な実装解説は以上となります。
*1:ワールド セッションを建てた人
*2:NearestUserHeadを使って取得する
*3:手を抜かずやるならゲーム参加者を上から順にチェックして処理させる...とかでしょうか
*4:分かりやすいよう仮値を設定しているだけ
*5:ギミックのタイミングによる有利/不利はありますが
*6:LocomotionはユーザーSlot配下へインストールされる
*7:Physicalは歩行/飛行用のLocomotionコンポーネントで、テレポート/NoClipはまた別にコンポーネントが存在する。詳細はこちら
*8:ダッシュ方法は両方のスティックを同じ方向に倒す or スペース押しながら移動
*9:Tag名: UltimateDeath
*10:VRモードでキャリブレーション地点から歩けば歩くほどズレていきます
*11:質量
*12:world url: http://cloudx.azurewebsites.net/open/world/U-Kanon04/R-ce34caba-02fc-476f-a59c-35e7bda5ce5b
*13:2-3回くらいそれでゲーム本体が消えて手戻りました
*14:CreateOverrideOnWriteをTrue、PersistentOverridesをFalseにして使用
*15:「対象者のみパルスを流す」が曖昧かつ自身がなく、やり方としてあまり適切では無いのかもしれません
*16:同期が死んでいると通知しても見えないため
*17:デスクトップモードは直接手でGrab出来ないため、本テストは行えない
*18:それによってバグっているものがあるかもしれない
*19:ColliderではなくRaycastなので実際は接触判定ではないです
*20:MeshColliderにHitしない理由はこちら。色んな人が関わってステージ/ブロックそれぞれのColliderがどうなっているか管理出来なかったため。最初にMeshCollider利用禁止としておけば良かったと反省
*21:移動する足場に乗ってもプレイヤーが移動に追随しない
*22:シーソーの向きを90/180度傾けて設置するとおかしくなりますが
*23:Spawnされる全ブロックの親SlotはCoreDevieとなり、これのTagがUltimateDeviceとなっている
*24:これは後回しにしても良い