重力とジャンプの実装 - Siv3D/Reference-JP GitHub Wiki

重力の実装

タイトルは重力の実装ですが、加速度は考えずに等速直線運動で済ませます。

やり方は簡単でPlayerのupdate関数内部でy座標を増やすだけです。

update関数は毎ループ呼ばれているので、これでSiv3D君が下に落ちます。

void update()
{
	m_position.y += 10.0;

	if (Input::KeyRight.pressed)
	{
		m_position.x += 5.0;
	}
	if (Input::KeyLeft.pressed)
	{
		m_position.x -= 5.0;
	}
}

当たり判定の実装

このままだと、無限に落下し続けてしまうのでブロックに当たり判定をつけます。

まずは、Block側に点との重なっているかを判定するintersects関数を作ります。

ブロックの当たり判定 = 内部のRectFの当たり判定なので、関数の内容はRectFのintersects関数を呼ぶだけです。

class Block
{
	bool intersects(const Vec2 &shape) const
	{
		return m_region.intersects(shape);
	}
}

この時注意してほしいのが、関数の後ろにconstがついていることです。これはconstメンバ関数といい、後でconst参照渡しをしたいため付けています。今はわからなくても付けておいてください。

次にPlayer側の当たり判定を実装します。

現在、地面と接しているかを表すbool型のm_isGroundedを用意します。

そしてメンバ関数としてBlockオブジェクトを受け取り、現在どれかと当たっているか(=地面に接しているか)を確認してm_isGroundedを変更するcheckGround関数を用意します。

次にさっきupdate関数内で実装した重力を地面に接していない時だけ適用するように変更します。

// 変更がない部分は省略
class Player
{
public:

	void checkGround(const Array<Block>& blocks)
	{
		m_isGrounded = false;

		for (size_t i = 0; i < blocks.size(); i++)
		{
			if (blocks[i].intersects(m_position))
			{
				m_isGrounded = true;
			}
		}
	}

	void update()
	{
		if (!m_isGrounded)
		{
			m_position.y += 10.0;
		}
	}

private:

	bool m_isGrounded;
};

これでBlockとPlayerに必要な機能は揃ったので、あとはMain関数のでplayerのupdate関数を呼ぶ前にcheckGround関数を呼んで地面に接しているかを判定すれば完了です。

// 変更がない箇所は省略
void Main()
{
	while (System::Update())
	{
		player.checkGround(blocks);
		player.update();
	}
}

ジャンプの実装

次にジャンプを実装します。

メンバ変数として残りのジャンプ時間を示すint型のm_jumpFrameを用意します。もちろん初期値は0です。

そして"キャラクターが地面に接している" かつ "ジャンプ中じゃない" 時にキーが押されるとm_jumpFrameを30に設定します。

あとはupdate関数内部でm_jumpFrameが0じゃない時(=ジャンプ中)の時にy座標を下げて、キャラを上に飛ばし、m_jumpFrameを減らします。

update関数は60fpsとすれば一秒間に60回呼ばれるはずなので、30回update関数が呼ばれると0.5秒間上昇します。

// 変更がない部分は省略
class Player
{
public:

	Player() :
		m_position(100, 200),
		m_texture(L"Example/Siv3D-kun.png"),
		m_isGrounded(false),
		m_jumpFrame(0) {}

	void update()
	{
		if (m_isGrounded)
		{
			if (Input::KeySpace.clicked && m_jumpFrame <= 0)
			{
				m_jumpFrame = 30;
			}
		}
		else
		{
			m_position.y += 10.0;
		}

		if (m_jumpFrame > 0)
		{
			m_position.y -= 20.0;
			m_jumpFrame--;
		}
	}

private:

	// 残りのジャンプ時間
	int m_jumpFrame;
};

まとめ

重力と当たり判定を実装しただけで一気にゲームっぽくなったと思います。

次は最後の仕上げに画面のスクロールを作ってみようと思います。

見本

# include <Siv3D.hpp>

class Block
{
public:

	// 引数のないコンストラクタも作っておくといろいろ便利.
	Block() {}

	Block(const RectF& region) :
		m_region(region),
		m_texture(L"Example/Brick.jpg") {}

	// 描画以外の操作をする関数
	void update()
	{
		// 今回は何もない
	}

	// 点との当たり判定を取る関数
	bool intersects(const Vec2 &shape) const
	{
		return m_region.intersects(shape);
	}

	// 描画をする関数(描画操作以外行わないこと.)
	void draw()
	{
		m_region(m_texture).draw();
	}


private:

	// ブロックの領域
	RectF m_region;

	// ブロックのテキスチャ(画像)
	Texture m_texture;
};


class Player
{
public:

	Player() :
		m_position(100, 200),
		m_texture(L"Example/Siv3D-kun.png"),
		m_isGrounded(false),
		m_jumpFrame(0) {}

	void checkGround(const Array<Block>& blocks)
	{
		m_isGrounded = false;

		for (size_t i = 0; i < blocks.size(); i++)
		{
			if (blocks[i].intersects(m_position))
			{
				m_isGrounded = true;
			}
		}
	}

	// 描画以外の操作をする関数
	void update()
	{
		if (m_isGrounded)
		{
			if (Input::KeySpace.clicked && m_jumpFrame <= 0)
			{
				m_jumpFrame = 30;
			}
		}
		else
		{
			m_position.y += 10.0;
		}

		if (m_jumpFrame > 0)
		{
			m_position.y -= 20.0;
			m_jumpFrame--;
		}
		if (Input::KeyRight.pressed)
		{
			m_position.x += 5.0;
		}
		if (Input::KeyLeft.pressed)
		{
			m_position.x -= 5.0;
		}
	}

	// 描画をする関数(描画操作以外行わないこと.)
	void draw()
	{
		RectF(m_position.x - 72.5, m_position.y - 200, 145, 200)(m_texture).draw();
	}

private:

	// プレイヤーの座標
	Vec2 m_position;

	// プレイヤーのテクスチャ(画像)
	Texture m_texture;

	// 地面に接しているか否か
	bool m_isGrounded;

	// 残りのジャンプ時間
	int m_jumpFrame;
};


void Main()
{
	Window::Resize(1280, 720);

	Texture background(L"Example/Windmill.png");
	Player player;
	Array<Block> blocks;

	blocks.push_back(Block({-400, 400, 200, 200}));
	blocks.push_back(Block({-200, 400, 200, 200}));
	blocks.push_back(Block({0, 400, 200, 200}));
	blocks.push_back(Block({200, 400, 200, 200}));
	blocks.push_back(Block({200, 200, 200, 200}));
	blocks.push_back(Block({400, 400, 200, 200}));
	blocks.push_back(Block({800, 400, 200, 200}));
	blocks.push_back(Block({1000, 400, 200, 200}));
	blocks.push_back(Block({1300, 200, 400, 30}));

	while (System::Update())
	{
		for (size_t i = 0; i < blocks.size(); i++)
		{
			blocks[i].update();
		}

		player.checkGround(blocks);
		player.update();


		// 実際には縦横比を合わせるように.
		Rect(Window::Size())(background).draw();

		for (size_t i = 0; i < blocks.size(); i++)
		{
			blocks[i].draw();
		}

		player.draw();
	}
}

← 前の章へ戻る | - 目次 - | 次の章へ進む →


Written by あさちゅん

⚠️ **GitHub.com Fallback** ⚠️