エッジ検出 & マウスキャプチャ
このパートでは、オブジェクト間の接触を検出するためにArea3Dノードを活用する方法を学びました。コインや弾丸、スパイクなどのアイテムをキャラクターが操作できるように実装しました。では更に改良点を見ていきましょう:マウスイベントの取得機能の追加、コインアニメーションの実装、そしてキャラクターが落ちないようにエッジ検出を実装します。
マウスのキャプチャ
マウス操作における一つの問題点は、マウスを画面横から横へ移動させる過程で、最終的にゲームウィンドウの枠を越えて画面上部まで移動してしまうことです。この問題を解決するためには、「マウスカーソルをロックする」必要があります。しかし、これを実装すると、ウィンドウのクローズやその他の操作ができなくなり、完全にマウスが使えなくなってしまいます!再びマウスを自由に使えるようにする仕組みが必要になります。
まず、マウスをキャプチャするには、メインシーンに以下を追加してください。
func _ready():
Input.set_mouse_mode(Input.MOUSE_MODE_CAPTURED)
この設定で最初の部分が完了しました。これでマウスがゲームウィンドウに捕捉されます。次に、「Esc」キーを押したときにマウス操作を再開させる必要があります。これもメインスクリプトに追加してください。
func _input(event):
if event.is_action_pressed("ui_cancel"):
Input.set_mouse_mode(Input.MOUSE_MODE_VISIBLE)ゲームを実行して実際に試してみます。
マウス操作がキャプチャされていない場合はその動きを無視するようにします。キャラクタースクリプト内で、_unhandled_input() 関数内の該当行を変更してください。
if event is InputEventMouseMotion and Input.get_mouse_mode() == Input.MOUSE_MODE_CAPTURED:現在別の問題が発生しています。「Esc」キーを押した後、キャプチャ状態に戻る手段がありません。以下の方法でこれを解決します。ウィンドウをクリックすると:
func _input(event):
if event.is_action_pressed("ui_cancel"):
Input.set_mouse_mode(Input.MOUSE_MODE_VISIBLE)
if event.is_action_pressed("shoot"):
if Input.get_mouse_mode() == Input.MOUSE_MODE_VISIBLE:
Input.set_mouse_mode(Input.MOUSE_MODE_CAPTURED)この実装は問題なく動作しますが、マウスクリックを再取得するためにクリックすると、同時に弾丸も発射されてしまいます。これはマウスクリックでも同様の処理が行われるためです。これを解決するには、入力イベントを「処理済み」としてマークし、Godotが他のノードにこのイベントを渡さないようにすればよいでしょう。
if event.is_action_pressed("shoot"):
if Input.get_mouse_mode() == Input.MOUSE_MODE_VISIBLE:
Input.set_mouse_mode(Input.MOUSE_MODE_CAPTURED)
get_tree().set_input_as_handled()現在は、最初のクリックでマウスをキャプチャしても、弾丸が発射されることはありません。
アニメーション付きコイン
現在の手順では、前回作成したコインアセットをより動的で魅力的なものに仕上げていきます。Coin.tscnファイルを開き、シーンにAnimationPlayerを追加してください。
「アニメーション」ボタンをクリックして、「新規作成」を選択し、 「bounce」という名前の新しいアニメーションを作成してください。1秒の再生時間で十分ですが、ループ機能を有効にすることを忘れないでください。
コインの2つのプロパティをアニメーション化します。位置(Y軸方向、上下)と回転です。スクラバーが時刻0にあることを確認し、両方の[変換]と[回転角度]にキーフレームを追加してください。
スクラバーを0.5秒の位置に移動させ、翻訳ベクトルの Y 成分を0.3に設定し、キーフレームボタンをクリックします。次に、スクラバーを1.0まで完全に動かし、回転を Y 軸方向で 180° の時点でキーフレーム化します。
アニメーションの仕上がりを確認するには「再生」ボタンを押してください。各個別キーフレームをクリックして、イージング値を調整してみます。
最後に、「自動再生」ボタンをクリックして、ゲームを実行するとアニメーションが自動的に開始されるようにしてください。
エッジ検出
最後に、キャラクターが崖から転落して命を落とすのを防げるか試してみよう。
以下の手順で進めてください。
まず、プレイヤーノードにRayCastノードを追加してください。これは細い青色の線として表示されます。デフォルトでは、キャスト方向 プロパティは (0, -1, 0) に設定されており、下向きを指しています。これは正しい設定ですが、さらに前方に移動させて、キャラクターの前面に向かって下向きを指すようが必要です。
さらに、[有効]プロパティも確認してください。これをチェックしないと、レイキャストは機能しません。
スクリプト側では、前進時にレイキャストが障害物に衝突していないか確認し、安全に移動できるかを判断します。
現在の状況は以下の通りです。
if Input.is_action_pressed("move_forward"):
velocity += -transform.basis.z * speedレイが衝突している場合、速度のみを加算する方法を試してみます。
if Input.is_action_pressed("move_forward") and $RayCast.is_colliding():
velocity += -transform.basis.z * speed試してみてください。端まで近づくと止まりますが、ちょっと待ってください - ジャンプして前進したことはありますか?今や空中にいるときは前方への移動がキャンセルされます! 一からやり直しです。
現在、地面にいるかどうかも確認する必要があります。そうすることで、前方に_歩きながら_レイが衝突しなくなると、何も行わないようにします。それ以外の場合は、通常通り移動します。
if Input.is_action_pressed("move_forward"):
if is_on_floor() and !$RayCast.is_colliding():
pass
else:
velocity += -transform.basis.z * speedなぜこのように書いたのか、疑問に思われるかもしれません。読み進めてください― そこにはきちんとした理由があります!
これは動作しますが、コードの何かがしっくりきません。不要な条件文があります。これを簡略化できるはずです。ほんの少々のブール代数を駆使すればできます。
条件分岐のロジックは基本的に次のようなものです。
if A:
do_nothing
else:
do_somethingこれは以下と同等です。
if not A:
do_somethingしたがって実際に行うべきことは、条件式を以下のように変換することです。
if !(is_on_floor() and !$RayCast.is_colliding())この式は動作しますが、読みにくいです。簡略化できませんか?「ド・モルガンの法則」というブール代数の手法を使えばできます。ド・モルガンの法則によれば、以下の関係が成り立ちます。
not (A and B) = not A or not Bこのように適用でき、以下の結果が得られます。
if Input.is_action_pressed("move_forward"):
if !is_on_floor() or $RayCast.is_colliding():
velocity += -transform.basis.z * speed英語では:
「床が見えない場合、または目の前に床がある場合は前進してください」
これは非常に簡素化された実装例です。例えば、画面の端から横方向や後ろ向きに移動することは不可能なままです。ぜひこのアイデアをさらに発展させ、改善を加えてみてください。ゲームによっては、ゆっくり落下する場合は許容されますが、高速で移動している場合は停止させられる仕組みになっているものもあります。
まとめ
このチュートリアルでは、ゲームに小さな改良を加えました。マウス操作の実装は3Dゲーム全般(特に一人称視点など)で非常に有用であり、レイキャスティング手法も多岐にわたる応用ができます。ここでは一例を紹介したに過ぎません。追加したアニメーションは非常にシンプルなものですが、良いスタート地点となります。今後さらに多くの場面で活用していく予定です。
次のセクションでは:空間領域構成法(CSG)を使用した方法について解説します。
このレッスンの動画版はこちらでご覧いただけます。