【#3】PythonでMinecraftを作る。~プレイヤーの動きの改善(慣性の概念)と衝突判定~


Part of pictures, articles, images on this page are copyrighted by Mojang AB.

概要

世界的に超有名なサンドボックスゲーム「Minecraft」をプログラミング言語「Python」で再現するプロジェクトです。

前回の記事: 「【#2】PythonでMinecraftを作る。~モデル描画とプレイヤー実装~」

今回やること

前回は、テストテクスチャを使用してのモデル描画からプレイヤー視点の実装までを行いました。

  • プレイヤーの動きの改善
  • 衝突判定の検討

プレイヤーの動きの改善


前回実装したプレイヤーの動きは不自然だと思いませんか?
これは、「キーを押した時にだけ移動」している為です。
大半の3Dゲームではプレイヤーはスムーズに動きますが、実は内部的にはプレイヤーやオブジェクトの動きは工夫がなされています。

なぜ動きが不自然?

あるオブジェクトやエンティティを時間経過で移動させたい時、例えばfor文やwhile文で繰り返し処理を行ってしまうと、フレーム毎の速度がコンピュータの性能に左右され、本当の意味では「実際の時間」とは言えません。
説明が難しい。分かりにくかったらごめんなさい。

動きが不自然であるのは、プレイヤーの速度が一定であることと、滑らないことが原因と考えられます。
段ボール箱を蹴っても、よほど摩擦力が働く場所でない限り、「キュッ!」と止まることは通常ありませんよね。

解決策

プレイヤーの動きを改善する上において、「dt(DeltaTime)」というデルタ時間がカギになります。
デルタ時間前回のフレームを処理してから今回のフレームが実行されるまでの間に何秒経過したのかを表します。

具体的には、デルタ時間を活用して「Inertia(慣性)」という概念をプレイヤーに対して導入します。
関数on_key_press()はキーボードのボタンを押してから離すまで情報をプレイヤーに渡しますから、キーが押されている間、ある値を仮にnと定義すると、nを押している時間と比例して増加させます。

プログラム中では、nをそれぞれvelocity_xvelocity_zと定義しました。
※Velocity = 速度

#プレイヤーのアップデートイベント
def update(self, dt, keys):
    if keys[key.W]:#front
        self.position[0] += dx #通常の移動処理
        self.position[2] -= dz #通常の移動処理
        self.velocity_x += dt * 10 #dtは非常に小さい値なので10を乗算します

このままでは、velocityは押している時間に比例して、いつまでも増加してしまいます。
そこでupdate時にvelocityの値を一定規則で減らします。

次に最大速度を設定し、値が負であるか、最大速度を超えていないかチェックを行います。
値が負であれば0に戻し、最大速度を超えていれば最大速度に戻します。

player.update()関数内
#現在の値を50で割ったものを減算します
self.velocity_x -= self.velocity_x / 50
self.velocity_z -= self.velocity_z / 50

#負の値であれば0に戻します
if self.velocity_x < 0: self.velocity_x = 0
if self.velocity_z < 0: self.velocity_z = 0

max_inertia_speed = 0.2 #値の大きさに比例して滑ります

#最大速度を超えていれば最大速度に戻します
if self.velocity_x > max_inertia_speed: self.velocity_x = max_inertia_speed
if self.velocity_z > max_inertia_speed: self.velocity_z = max_inertia_speed

#前進にはたらく慣性
self.position[0] += self.velocity_x * dx #ローテーションを考慮に入れつつ現在の座標に足す
self.position[2] -= self.velocity_z * dz #ローテーションを考慮に入れつつ現在の座標から引く

試しに実行すると、以下の様に慣性があることが確認できます。
また、左上のデバッグウィンドウのVelocityも時間に比例して減っていることが確認できます。

これでやっと不自然さが無くなりましたね。
本来のマインクラフトの動きを再現できました。

問題点

斜め移動には対応していません。

衝突判定の実装


MinecraftをUnity等のツールを使用せず実現するにあたって、これが一番の課題です。
全てが1.0×1.0のブロックであれば比較的容易に実装できる(できそう)のに対して、ブロックごとにカスタムで衝突判定(例えば階段ブロックやドア)を実装することを考えると、非常に大きな壁な気がしています。

MinecraftではAABB(Axis-Aligned Bounding Box)で自由にブロックの衝突判定や見た目上だけのバウンディングボックスを定義できます。
それをPythonでも実装したい。流石に厳しいだろうか。
個人的には、Minecraftをデコンパイルして難読化されたソースコードを根性で読む、程度しか思い付きません。

衝突判定について詳しい方がいらっしゃいましたら、是非コメント欄でアドバイスを頂きたいです。

続く

現状はこんな感じです。
※動画サイズの関係でgifアップロードできませんでした。
衝突判定がどうにかなれば、後はひたすらコーディングするだけって感じですね。