忍法壁抜けの術

スーパーメリオ


重力操作で天井から地面に落下する時、低い位置にある1ブロック分(16x16ピクセル)の厚さのレンガやブロックをすり抜けてしまうバグを発見しました。

めり込む男、メリオ。

原因と解決法


まず、垂直方向にマリオを移動させるメソッドは次のようになっていました。

  // Verlet法によるジャンプの実装。
  CGPoint tmpOrigin = self.origin;
  self.y += (self.y - prevOrigin_.y) + gravityAcceleration_;
  prevOrigin_ = tmpOrigin;

このように、現在いる位置に現在の垂直方向の速度を足し合わせて垂直方向に移動します。

そして、このコードの下の部分で当たり判定を行い、当たっていたら当たる直前の座標まで戻す、というやり方をしていました。

あまり高くない所からの落下ならば、このやり方でも問題はないのですが、このゲームでは重力操作による天井から地面までの急降下が頻繁に起こります。滞空時間が長くなる分、重力加速度を受ける時間も長くなり、落下速度がどんどん速くなります。その速度が一定値を超えた時、上記のやり方では当たり判定をし損ねてしまうということです。

これを解決するにはループを1つ設けて、単位速度(1ピクセル)ずつ移動させながら当たり判定を行うことです。

  // Verlet法によるジャンプの実装。
  CGPoint tmpOrigin = self.origin;
  float verticalVelocity = (self.y - prevOrigin_.y) + gravityAcceleration_;
  float unitVelocity = (verticalVelocity>0.0)?1.0:-1.0;
  prevOrigin_ = tmpOrigin;
  
  for (int i = 0; i < abs(verticalVelocity); ++i) {
    self.y += unitVelocity;
    
    // ここで当たり判定
  }

ビームとブロックとの当たり判定でも同様のバグがあり、ブロックに対してゼロ距離射撃をするとビームが通り抜けてしまっていましたが、同じ修正を施すことで解決しました。