CharacterアクターにはSwim能力、つまり泳ぐ能力が存在しています。
Characterアクターの記事でSwimについても書こうと思っていたのですが、いろいろと準備が必要だったりするので別枠で記事にすることにしました。
CharacterアクターをSwim状態にするにはFly状態と同様にMovementModeを直接変更する方法があります。
しかしこの方法ではほぼ意味がありません。
泳ぐには場所が必要で、地面の上は当然泳ぐ場所ではありません。
重要なのは泳ぐ場所を作成し、そこに入ったら泳ぐ状態にすることです。
そこでまずはプールを作りましょう。
FirstPersonテンプレートから新しいレベルを作成し、プールを作成してみました。
ただの四角いプールと真ん中に床を置いています。これらはコリジョンが存在しています。
水を表現するためのモデルは薄めの板で、これはコリジョンを設定していません。
また、この板は影を落とさないようにする必要があります。そうしないと水中が真っ暗になってしまいます。
プールは作りましたが、これはあくまでも見た目だけの話。
水の板なんかはほんとにただの板ですので、これだけではCharacterは泳いでくれません。
Characterを泳がせるにはPhysics Volumeを用います。
これをレベルに配置し、以下のようにプールと水面に合わせて大きさを調整します。
ある程度ののりしろはつけておきましょう。
Physics VolumeはCharacterMovementに対して影響を与えるVolumeです。
Detailsパネルの以下の2つがSwimに関するパラメータとなります。
Water VolumeをONにするとこのVolumeに入った段階でCharacterはSwim状態に遷移します。
Volumeから脱出するとSwim状態から別の状態に遷移しますが、以下の場合にはSwim状態から強制的に遷移します。
・Jump命令でジャンプを行うとFalling状態へ遷移します
地面に着地するとWater Volume内であればSwim状態へ遷移します
・Fly状態に強制的に遷移するとSwim状態からFly状態へ遷移します
再びWater Volume内に入るとSwim状態へ遷移します
Fluid Frictionは水の抵抗です。
大きな値であればその分水の抵抗が増して移動が遅くなったり慣性が効きにくくなります。
ただ、デフォルトではSwim状態での慣性が強く効きすぎるのである程度大きくしておいた方がいいかもしれません。
さて、実はこれだけでSwim状態は実現できます。
しかし、これだけではあまり泳いでいるように見えません。
本来であればモーションも加えたいところなのですが、今回は残念ながらそうもいかないので最低限操作とポストプロセス程度は泳いでいるっぽくしようかと思います。
まず、現在がSwim状態かどうかを調べる関数を作ります。
残念なことに、CharacterアクターにはIsSwimmingといった命令やSwim状態に遷移したら発行されるOnSwumのようなイベントも存在していません。
操作系をいじる上ではIsSwimming命令くらいはやはり欲しいので、まずはそこから作ります。
bool値を返すため、関数として作成します。
作成する関数のグラフは以下の通りです。
Local Is Swimmingはローカル変数です。
この関数が作成できれば操作関係をいろいろいじれるようになります。
まずは水中で操作をカメラ方向に移動できるようにしましょう。
テンプレートのMyCharacterは移動ベクトルをアクターの前方ベクトル、および右ベクトルから取得しています。
一人称視点ではわかりにくいですが、カメラの方向はコンポーネントの方向でしかなく、アクターは常に水平方向を向いています。
つまり、アクターの姿勢から方向ベクトルを生成しても水平方向にしか移動できないわけです。
そこで、GetControlRotation命令で操作している際の回転を取得します。これがカメラの姿勢と一致します。
ただし、前回記事でも書いたとおり、ControlRotationから取得したベクトルを地上移動でも使用してしまうとカメラの向きによって移動速度が変化してしまいます。
ですので、Swim状態とそうでない状態で方向ベクトルを変化させることにします。
SelectVectorはbool値によって2つのベクトルのうちどちらかをピックアップする命令です。
また、GetControlRotationの結果を一旦Breakし、Rollを0.0にリセットする理由は、何らかの理由でRoll値が0.0以外の値になっていると右方向のベクトルが上や下を向いてしまうからです。
まあ、問題ないならそのまま使ってもいいんですけどね。
この状態で水の中に入ると水中にいる間はカメラ向きの方向に移動することができます。
次に水中で銃を撃てないようにしますが、これは割愛します。
まあ、普通にIsSwimmingの戻り値をチェックしてFalseなら撃てないようにするだけです。
次はジャンプです。
水中ではジャンプが出来ないのが普通かと思いますが、デフォルトではジャンプできてしまいます。
また、前述したとおりジャンプするとFalling状態へ遷移し、床につくまでSwim状態に遷移しません。
これでは水中っぽくないのでジャンプ出来ないようにしましょう。
ただ、水中から出た際にジャンプできないと地上に戻れないので、2段ジャンプの仕組みを利用してFalling中でもジャンプできるようにします。
水中から水面上に向けて移動すると定期的にFalling状態へ遷移し、すぐにSwim状態に戻るという状態になります。
このFalling状態でジャンプキーを押すとジャンプできるようにしています。
まあ、あまりよい実装とはいえませんが、とりあえず簡単な実装方法ということで。
操作関係はここまで。
次はポストプロセス関係をやっていきましょう。
というのも、今の状態だと水の中に入ってるって気がしないです。
ですのでポストプロセスで水っぽい表現をしてみます。
まず、水の範囲として作成したPhysics Volumeを複製します。
Scene OutlinerでPhysics Volumeを選択、Ctrl+Wで複製します。
次に複製したVolumeを選択し、[Details] -> [Actor] -> [Convert Actor] でPostProcessVolumeに変換します。
Physics Volumeに入ればこのVolumeにも入ることになるので、水に入った段階でこのポストプロセスが有効になります。
水に入った場合は画面を少し青っぽい色にしたいですね。
なのでTintで全体的に青くして、ついでにちょっとそれっぽくするためにVignetteも入れましょう。
これで水中のシーンが少しそれっぽくなりますね。
下のスクリーンショットの左が使用前、右が使用後です。
まあ、それっぽいかな、とは思います。
最後におまけのポストプロセスを。
水中から水面に顔を出した際にカメラに水滴を付けたいと考えますよね。
今回は簡易ではありますが、そんなポストプロセスを作成してみます。
カメラが水面から出たかどうかを確認する方法としては本来はカメラのワールド座標が水面を出たら、としたいところです。
まあ、できることは出来るんですが、今回は面倒なので水のVolumeのOnActorEndOverlapイベントで対応します。
しかし、Water Volumeを使うといい場所でイベントが発生しないので、Water Volumeを複製してTrigger Volumeに変換して利用します。
Water Volumeより下の方に配置しますが、どの程度かは何度かプレイして調整するといいでしょう。
次にGlobalPostProcessVolumeを作成します。
新しいPostProcessVolumeを作成し、[Details] -> [Post Process Volume] -> [Unbound] をONにするとVolumeのサイズに関わらずシーン全体を覆うPostProcessVolumeとして動作します。
マテリアルは以下のようにします。
法線テクスチャはUE4のフォーラムから持ってきました。
Material Parameter CollectionはレベルBPからパラメータを変化させるために使用しています。
このスカラー値が0.0の場合は水滴処理が無効な状態になります(内部的には動作していますが)。
パラメータを変化させるためのBPグラフは以下のようになります。
Timelineは0.1~0.0まで1秒で補間するだけの単純なものです。
GlobalPostProcessVolumeにこのマテリアルを設定し、実際に水面から出てみるとこんな絵になります。
水が下に流れるようにするともっと良くなるかもしれませんね。今回はやってませんが。
水中動作は基本的にこんなかんじでなんとかなると思うのですが、CharacterMovementの不具合なのか仕様なのかわからない挙動が一点あります。
水面に出た状態でプールの縁に移動すると、壁に触れたあたりで唐突に下方向に引っ張られるような動作をします。
詳しく調査するにはコードを読まないとダメそうですが、一人称視点だと何が起こったのかわかりにくくてよろしくないですね。
というわけで、今回はここまで。
追記
水滴マテリアルを下方向に流すようにしてみました。
パラメータとしてSlideというスカラー値を入れて、これをDropPowerに合わせて0.0~0.1まで1秒間で変化させています。
マテリアルは以下のように作成しました。
複雑そうに見えますが、そんなに難しくはないです。
マテリアルにしろBPにしろ、コードにするとそんなに難しくない計算が複雑に見えてしまうのはちょっとアレですよね。