やりたかったこと
自作のゲームに対して、「なんか動きがもっさりしているね」という感想が来ることを避けたい。
自分が作ったゲームのテストプレイばかりしていると忘れてしまうが、世に出ていて世間から「楽しい」と思われているゲームはどれも手触りが良い。
今までは、ゲーム制作終盤でカメラシェイク、エフェクト、音を入れることでそれなりの手触り感を即興工作してきたが、そろそろ自分で計算して手触り感を作れるゲームプログラマーになりたい。
再現性のある手触り感が欲しい。
というわけで、色んな手触り向上パターンを考えて試してみました。
↓ 手触り試作中のゲーム
移動の手触り向上パターン
移動速度補間時間
左上が完成品(移動補間時間:0.1f、停止補間時間:0.02f)
右上:補間時間長め(どちらも0.1f) 左下:補間時間短め(どちらも0.02f)
移動入力の有無で速度補間時間を変えてみる。
(ここでの補間時間は、UnityでいうSmoothDamp関数などで使われる、目標パラメータに到達するまでの指定時間を言う)
移動キーの入力中では補間時間を少しだけ長くすることで、停止状態からの動き出しが滑らかになる。止まる際は補間時間を短くすることで、急ブレーキをかけたような挙動になる。
ゲーム性にもよるとは思うが、基本的に "滑りやすい" という属性を持った地面やキャラクター出ない限りは、停止はすぐにピタッとする方が手触りが良い気がする。
左が完成品(空中移動補間時間:0.2f、空中停止補間時間:0.3f)
右が地上での補間時間と同じものを使った動き
接地状態で補間速度を変えてみる。
停止はピタッとしたいという話だったが、空中だとまた話が変わってくる。
空中にいる状態で急ブレーキをかけるのは物理挙動的に少し違和感がある。空中状態で慣性を表現したい時は、こちらの補間時間は逆に長くする。
(ただ、こちらはゲームによって結構パターンが異なる印象。空中状態でも地上と同じようにピタッと止まるものも多いので、好みとゲーム性に合うかの問題かも)
空中での旋回についても同様。
地上よりも補間時間を長めにすることで "慣性を残したままの旋回" が実現できた。
ジャンプ時の重力
ジャンプした際、上昇中・最高到達点・下降中で "重力のスケール値" を変えてみる。
既存ゲームでジャンプした時に、最高到達点に達した際すぐには落ちず少しだけフワッとする感覚に身に覚えはないだろうか。
それを作ってみたい。
左が完成品(重力スケール上昇中5、頂点0.3、下降中3.5)
右が重力スケールが全て同じ(3.5)のもの
実装としては単純で、プレイヤーのY速度を見て3パターンの重力スケール値に変えるだけ。
微妙な違いではあるが、頂点位置でのフワッと感と、落下するときの地面への吸いつき感が違う。
Unityなどで実装するなら、アニメーションカーブなどを使うのもアリかもしれない。
float gravityScale;
// Y速度を見て、閾値で判定。-0.1f < vel.y < 0.1f なら最高到達点とする、みたいな。
// 重力加速度を計算する
float gravityAcceleration = -9.81f * gravityScale;
context.runtimeState.m_physicsVelocity.y += gravityAcceleration * deltaTime;
エフェクト
エフェクトも手触り感な気がする。やっぱり、ゲーム内での行動に対してのフィードバックがしっかり帰ってくるのは、ゲーム体験としてかなり大事。
走っている時の土ぼこりと、ジャンプ時に衝撃波エフェクトをつけてみた
ちなみに、本ゲームのテクスチャはCanvaを使った図形の組み合わせか、ibisPaintで描いた超手作りテクスチャ。適当に描いたものでも動かしてみると意外とそれっぽく見える。
(特にibisPaintはアニメーション機能もあるのでオススメ。プレミアムプランも月額300円なんて他の有料ソフトに比べれば圧倒的に安い)
効果音なども当然大事。
自分は効果音ラボをよく使わせてもらう。斬撃やショット音などをそのまま使うことも多いが、イメージに合う効果音が一覧にない場合、他の音から切り取ったりピッチや速度を調整したり。
(本ゲームのジャンプ音は↓「アスファルトの上を歩く1」より切り取ったものを加工している)
プレイヤーの行動が増えれば増えるほど、それに付随してエフェクトは増えていくので、設計の最初のうちにエフェクト用のクラスを作ったり、管理方法をしっかり考えておくのが大事。
(開発終盤のハードコーディングで実装しようとすると、該当箇所を探すのが本当に大変なのでやめた方が良い……)
カメラ挙動
余りに奥深い世界。"普通に動かして楽しめる" ボーダーラインが高い。上手く作れなければゲーム性も、グラフィックも、頑張って作ったプレイヤーの手触り感も全てなくなる。上手く作ってようやく "普通のゲーム" になることが出来る。
カメラ挙動にも滑らかさを追求するなら。補間を入れることはもちろん大事だが、 "何を基準に補間を入れて良いのか? 補間を入れるならローカルかワールドか?" というのを考えていかないといけない。
左が完成品 右はオフセット値も含めてカメラ位置を補間したもの。振り回される
例えば、追従するプレイヤーに対するオフセット値も含めてワールド座標で位置補間を行うと、このようにカメラを回転させた際に補間の過程でオフセット値が変化し、振り回されるような、かなり酔う挙動になる。
XMFLOAT3 targetFollowAnchorPosition = CalculateTargetFollowAnchorPosition();
// 補間した後に、絶対的なオフセットをここで加算
XMFLOAT3 lookAtPosition = MiMath::Add(
state.followAnchorPosition,
CalculateCompositionOffset()
);
// オフセット値も含めて補間すると、相対的な位置関係にズレが生じる
// targetFollowAnchorPosition = MiMath::Add(targetFollowAnchorPosition, CalculateCompositionOffset());
// UpdateFollowAnchorPosition(targetFollowAnchorPosition, deltaTime);
// XMFLOAT3 lookAtPosition = state.followAnchorPosition;
高さや距離、エイム中でのキャラクターからの相対位置などは、補間を入れるのはそのパラメータ内にのみしておいて、位置による補間には含めないよう分けて考えてみた。
特にDirectXなどでカメラを位置と注視点から作る場合は、どのオフセット値をどの順で適用するかの計算順序をしっかり考えることが大事。
左が完成品
右はジャンプ時に強い追従。心なしかジャンプ力も下がって見える(当然そんなことは無い)
また、これもゲーム性と好みによるが、カメラの速度補間時間を水平移動・上昇・下降で分けるのもよく見る。3D空間で高さをもって移動できるゲームにおいて、プレイヤーが今上昇しているのか、下降しているのか、カメラが追従し切ってしまうと分かりづらい。
左が完成品 右は落下時にもジャンプと同じ補間時間を適用したもの
しかしその一方、補間時間を長めにしてプレイヤーの動きから遅れるようにすると、プレイヤー回りの状況の把握がしづらくなる問題もある。↑のように落下中にも補間時間を長めに入れたりすると、落ちていけば落ちていくほどプレイヤーが画面外に見えなくなっていくことになってしまう。
そこで今回は、「水平移動と下降中はプレイヤーとほぼ等速」「ジャンプなどで上昇中は少し遅れてくる」 カメラを実装してみた。これにより、激しいアクションの中でもプレイヤーを見失わず、上下の移動でもストレスのないカメラに出来たと思う。
ただ、これは通常時の話。
左が完成品 右がジャンプ時の補間時間を通常と同様にしたもの
このように銃を構えてエイム状態だと、ジャンプ時にカメラが遅れるのは狙いが付けづら過ぎる問題があるので、 「エイム状態の時は状況に寄らずカメラはプレイヤーとほぼ等速」 になるようにした。通常時とは異なり、ちょっとした遅れの時間もほとんど無いように、かなり短めに設定している。
(↑まぁ、これはこれでちょっと面白そうだけど……)
カメラに関しては特に、ゲーム性によって挙動が大幅に変わってくる場所だと思うので、新規ゲームを作るたびに試行錯誤する姿勢を大切にしていきたい。
カメラエフェクト
これもカメラだが今度はエフェクトの話。
プレイヤーの移動に直接かかわってくる部分ではないが、回避行動やダッシュアクション時などに使うこともあるのでここで触れておく。
カメラシェイク。派手な演出には欠かせない。やり方は色々だが、個人的にはPerlinノイズが好き。(Unityには既に関数が常備されているので使ってみて欲しい)
シェイクを適用するなら周波数(1秒間に揺れる数)、強さ、時間を意識したい。
ここらへんの値をしっかり調整しないと、「シェイクを入れてるのになんかもっさりしてる……」ということに成りかねない。
他にも攻撃の方向に沿って縦揺れにしたり横揺れにしたりするゲームも存在する。
↑と強さは変わらないが、前後方向に入れるだけで迫力が増す。ムービーシーンなどには良いかも
また、カメラシェイクするなら奥行き方向にも動かすべきかはしっかり考えたい所。
奥行き感が掴みづらくなるのと酔うので、ゲーム中はカメラ正面から考えて縦横に動かすのが多いと思う。
カメラシェイク演出は派手になる一方、入れすぎるとカメラ酔いや画面の見づらさにもつながるので使いどころにも注意したい。(カメラシェイクは細かく短く一瞬だけ入れるようなゲームも結構多い)
カメラが近づいたのと同じ様に見えるが、よく見ると背景も歪んでいるのが分かる
FieldOvView(視野角)の変更。特に勢いのあるゲーム(レースゲームやパルクールゲームなど)では、スピード上昇時に視野角を狭める演出が多い気がする。
画面効果(ポストエフェクト)
ぶわっとした感じの演出
ブラーエフェクトを画面全体にかけるようなシェーダーを書いてみる。
画面の中で何点かサンプリングして平滑化することでブレたような効果を付けれる。↓は中央から広がるRadial Blurのシェーダーだが、当然横向き、縦向きも作れる。
cbuffer RadialBlurBuffer : register(b0) {
int g_SampleCount; // サンプル数
float g_Strength; // ブラーの強さ
};
float4 main(PS_INPUT ps_in) : SV_TARGET
{
float4 color = float4(0.0f, 0.0f, 0.0f, 1.0f);
// UVを-0.5~0.5に変換
float2 symmertryUv = ps_in.texcoord - float2(0.5f, 0.5f);
// 外側に行くほどこの値が大きくなる(0~0.707)
float distance = length(symmertryUv);
for(int j = 0; j < g_SampleCount; j++) {
// jが大きいほど、画面の外側ほど小さくなる値
float uvOffset = 1 - g_Strength * j / g_SampleCount * distance;
// jが大きくなるにつれてより内側のピクセルをサンプリングしていく
// また画面の外側ほどより内側のピクセルをサンプリングする
color += g_Texture.Sample(g_SamplerState, symmertryUv * uvOffset + float2(0.5f, 0.5f));
}
color /= g_SampleCount;
return color;
}
カメラシェイクやFOV変化同様、勢いをつけるのに便利なエフェクトかつ、カメラの位置や見え方自体は変化しないので、ちょっとした勢いや派手さを付けたい時も便利。
FOV変化とも組み合わせてもスピード感が増して良い。
ジャスト回避時などに一瞬入れているゲームをよく見る。
最後に
もちろん、ここに書いたことが正しいとは限らないし(あくまで学生身分の意見ですので……)他にもプレイヤー移動時に考えるべきことは沢山あるかと思いますが、自分の思う「プレイヤー移動の手触り」について色々書いてみました。
どれも実装としては単純だし、1つ1つを入れようと思えばすぐ出来るとは思うが、重なったり1つ1つの動きにこだわりを入れると、(しっかり設計を考えれていないと)どんどんコードが肥大化していく……。
次は「攻撃編」とか書いてみたい。
