アクションゲームにおける壁の当たり判定など with OpenSiv3D

去年11月に投稿した「アクションゲームのジャンプを滑らかにしよう with Siv3D その1&その2」はおかげさまで多くの人に閲覧していただき大変嬉しい限りです。

今回の記事では今まで触れなかった「壁の当たり判定」とぞの他諸々を実装していきたいと思います。

f:id:movementi:20180410215450j:plain

今回からOpenSiv3Dでの製作となります。

前回までの記事

movement.hatenablog.jp movement.hatenablog.jp 完全に続きものだから先にこっち読んでくださいね。

Siv3Dのバージョン

OpenSiv3D 0.2.3を使います。

コード全文

コピペするだけで動きます。

アクションゲームのジャンプを滑らかにしよう with Siv3D その2からさらに手を加えた形になります。

大本のサンプルコードは以下から
スクロールの実装 · Siv3D/Reference-JP Wiki · GitHub

注意

OpenSiv3D ver0.2.3にはExample/Brick.jpgが用意されていないので過去のSiv3Dプロジェクトから持ってくるか、適当な画像を準備しておくといいでしょう。なくてもブロックが黄色一色になるだけで動きます。

f:id:movementi:20180410215449j:plain
Example/Brick.jpgが無いとこうなります。(動きはします)

小さな改良点紹介

キー入力

int8 wide() { return KeyRight.pressed() - KeyLeft.pressed(); };

関数1つで左右の入力を受け取れるようになります。trueは1になる仕様を利用したものです。

m_position.x += wide()* m_speed.x;

m_speed.xの数値をいじることで移動速度を自由に操作することが出来ます。

ゲームオーバーの実装

  if (player.getPos().y > 1000) {
            System::Exit();
        }

プレイヤーの座標が一定以下になるとプログラムを終了させます。実際のゲームで使うならタイトル画面に戻るなりゲームオーバー画面出すなりしましょう。

キャラクターの向きを変える

新しいメンバ変数m_isRightを用意します。wide()関数でキャラクターの向きを取得して描画時に.mirrored(m_isRight)と入れればOKです。

//変数操作部分
 if (wide() == 1) m_isRight = true;
 if (wide() == -1) m_isRight = false;

キャラクターのサイズを変数に入れる

const Vec2 m_size;
draw関数に直打ちされていたプレイヤーのサイズ(テクスチャのサイズ)を変数に入れるようにしました。
コレによりサイズの変更及び参照に強くなります。 constメンバ変数はコンストラクタで初期化するようにしましょう。

プレイヤーの当たり判定を取得

const RectF get_region() const {
        return RectF(m_position + Vec2(-m_size.x / 2, -m_size.y), m_size);
    }

プレイヤーの当たり判定をRectF型で取得します。ちなみにm_positionはプレイヤー足元中央の座標です

壁の当たり判定

当記事のメインコンテンツです。ブロックの左右にも当たり判定をつけましょう。
まず前回までのコードとの違いとしてmain関数で読んでいたcheckGround関数をupdate関数内のY座標の更新直後に呼ぶように変更しました。update関数に引数を持たせる形での実装です。

update関数の流れ

  • Y座標の移動
  • Y座標の補正
  • X座標の移動
  • X座標の補正
  • ループ こういう流れになっています。
//class Block
std::pair<double, double> pair_sideX() const
    {
        return{ (m_region.left().begin.x + m_region.left().end.x) / 2.0, (m_region.right().begin.x + m_region.right().end.x) / 2.0 };
    }

  template <class Shape2DType>
    bool intersects(const Shape2DType &shape) const
    {
        return m_region.intersects(shape);
    }

下準備用の関数です。
pair_sideX()はブロックの左側の辺と右側の辺のX座標をstd::pair<double, double>で返します。当たり判定の計算に用います。
intersects()はテンプレートを使って汎用性を高めました。Siv3Dの関数名が統一されているおかげですね。

 //壁に接しているかを更新する関数
    void checkWall(const Array<Block>& blocks)
    {
        //ブロックの壁部分とプレイヤーの位置補正
        for (size_t i = 0; i < blocks.size(); i++)
        {            
            if (blocks[i].intersects(get_region().right().stretched(-FLT_EPSILON)))
            {
                auto[left, right] = blocks[i].pair_sideX();
                m_position.x = left - m_size.x / 2 - FLT_EPSILON;
            }
            if (blocks[i].intersects(get_region().left().stretched(-FLT_EPSILON)))
            {
                auto[left, right] = blocks[i].pair_sideX();
                m_position.x = right + m_size.x / 2 + FLT_EPSILON;
            }
        }
    }

今回の肝となる関数です。キャラクター当たり判定の右(左)側の辺をFLT_EPSILONだけ短くしたLine型とブロック(RectF型)との当たり判定を取ります。

FLT_EPSILONとはfloatにおいて1.0f+FLT_EPSILON != 1.0fになる一番小さな数で10^-5以下です。

詳しくはコチラ。 FLT_EPSILON - cpprefjp C++日本語リファレンス

なぜ当たり判定を短くするのか

床と天井で反応しないようにするためです。そのままだと床と天井の接触判定もtrueを返してしまい床に触れる→左端へ移動でゲームになりません。

f:id:movementi:20180410215448j:plain
get_region().right().stretched(-FLT_EPSILON)の図解わずかに床から浮いてる

プレイヤーの位置補正

//構造化束縛っていいます。
auto[left, right] = blocks[i].pair_sideX();
//m_position.xはプレイヤー足元中央
m_position.x = left - m_size.x / 2 - FLT_EPSILON;

pair_sideX()関数からブロックの座標を取得して位置の補正をします。
- FLT_EPSILONを付けないとプレイヤーとブロックが接触したままになり、次のフレームのcheckGround()でY座標の補正がかかってブロックの上に乗ってしまうので注意してください。

改善点

  • ゲームオーバー時の処理
  • 天井に頭をぶつけたときの処理

などなどまだまだ足りないことだらけですね。

最後に

左右の当たり判定が付くことでゲームとしての完成度がグッと上がりましたこの記事がゲームプログラミングに興味のある誰かの役に立てたのなら幸いです。

前回までの記事

movement.hatenablog.jp movement.hatenablog.jp