Skip to content
第19回 地形との衝突解決

3 / 4 縦移動後の床と天井の衝突解決

このステップの目的

Y軸方向へ移動した命中矩形を調べ、落下時の「床着地」とジャンプ上昇時の「天井頭打ち」をそれぞれ適切な境界へ補正します。

床着地と天井頭打ちの判定分岐

垂直方向(Y軸)の移動では、移動方向によって衝突時の意味が異なります。

  • 下向き移動(moveY > 0:落下中):
    足元が床ブロックに接触したため、床タイルの上端へ押し戻します。これは着地処理であり、接地フラグ onGround_ = true を設定します。
  • 上向き移動(moveY < 0:ジャンプ上昇中):
    頭部が天井ブロックに接触したため、天井タイルの下端へ押し戻します。これは頭打ち(上昇停止)であり、接地ではありません。
Stage.cppcpp
bool Stage::ResolveVertical(dxgame::Rect& rect, int moveY) const
{
    if (moveY == 0)
        return false;

    // 移動後の矩形だけを調べるため、1フレームでタイルを越える
    // 極端な速度ではコリジョンを通り抜ける可能性がある。
    // 極端な速度ではコリジョンを通り抜ける可能性がある
    rect.y += static_cast<float>(moveY);
    const int leftTile = PixelToTile(rect.x);
    const int rightTile = PixelToTile(rect.x + rect.width - 0.001f);
    const int topTile = PixelToTile(rect.y);
    const int bottomTile = PixelToTile(rect.y + rect.height - 0.001f);
    bool hit = false;
    float resolvedY = rect.y;

    for (int y = topTile; y <= bottomTile; y++)
    {
        for (int x = leftTile; x <= rightTile; x++)
        {
            if (!IsSolidTile(x, y))
                continue;

            const float candidate = (moveY > 0)
                ? static_cast<float>(y * TileSize) - rect.height
                : static_cast<float>((y + 1) * TileSize);

            if (!hit ||
                (moveY > 0 && candidate < resolvedY) ||
                (moveY < 0 && candidate > resolvedY))
            {
                resolvedY = candidate;
            }
            hit = true;
        }
    }

    rect.y = resolvedY;
    return hit;
}

天井接触時の挙動(落下への反転)

Player クラスの垂直移動関数 MoveVertical() では、衝突結果を受け取って垂直速度と接地状態を更新します。

Player.cppcpp
void Player::MoveVertical(const Stage& stage, float deltaTime)
{
    remainderY_ += velocity_.y * deltaTime;
    const int pixelMove = static_cast<int>(remainderY_);
    remainderY_ -= static_cast<float>(pixelMove);
    if (pixelMove == 0)
        return;

    dxgame::Rect movedRect = GetHitbox();
    if (stage.ResolveVertical(movedRect, pixelMove))
    {
        velocity_.y = 0.0f;
        remainderY_ = 0.0f;
    }

    position_.y = movedRect.GetCenter().y;
}

天井衝突と接地の区別

垂直方向の衝突検知時に無条件で onGround_ = true を設定すると、天井に接触した際にも接地状態と判定され、空中で再ジャンプが可能になる不具合が発生します。
そのため、接地フラグの更新は必ず moveY > 0(下向き移動)の場合に限定します。

天井接触時に垂直速度 velocity_.y を 0 にリセットすることで、次フレームからは重力によって自然に落下へ転じます。

ステージ外(上下左右の境界)の扱い

Stage::IsSolidTile(x, y) では、ステージ範囲外に対する境界ルールを定めています。

  • 左右端および上端の外側:
    壁(IsSolid = true)として扱い、プレイヤーが画面外へ抜けるのを防ぎます。
  • 下端の外側:
    固体とせず空間(IsSolid = false)として扱います。これにより、穴に落ちたプレイヤーが空中で止まらずそのまま落下し、後の回で実装するミス判定(リスポーン等)に移行できます。
チェックリスト
  • 下向き移動時はタイルの上端へ、上向き移動時はタイルの下端へ矩形が押し戻されている
  • 天井に接触した際、上向き速度が0になって落下へ転じる
  • 天井衝突時に誤って onGround_ = true が設定されないようガードされている
  • ステージ下端の外側が床判定されず、穴への落下が成立する設計になっている

ゲームプログラミング実践