はじめに
今回は調べても微妙にヒットしづらい内容、2Dゲームにおいて物理を使った加速度、最大速度、摩擦力を考慮した物体の左右移動(上下移動も可)を実装していきます。
物体は接地していて上から見下ろす状態とする。
CPPコード
#include "DxLib.h"
#include "Input.h"
#include "Vec2.h"
#include <algorithm>
// 物体の状態を表す構造体
struct ObjectState {
Vec2 position;
Vec2 velocity;
};
// 物理パラメータ
const auto acceleration = 0.4f; // 加速度
const auto friction = 0.2f; // 摩擦係数
const auto maxSpeed = 0.1f; // 最大速度
const auto gravity = 9.8f; // 重力加速度
const auto mass = 1.0f; // 質量
// 左に移動
ObjectState MoveLeft(const ObjectState& currentState, const float deltaTime) {
// 新規の状態を作成
ObjectState newStateLeft = currentState;
// 等加速度運動の実装
// 新規の状態の速度から加速度*時間を引く
newStateLeft.velocity.x -= acceleration * deltaTime;
// 摩擦の実装
// 新しい状態の速度に摩擦係数*垂直抗力を足す
newStateLeft.velocity.x -= friction * mass * gravity;
// 最高速度の設定
if (newStateLeft.velocity.x < -maxSpeed) {
newStateLeft.velocity.x = -maxSpeed;
}
// 位置を更新する
newStateLeft.position.x += newStateLeft.velocity.x * deltaTime;
// yの値は固定
newStateLeft.position.y = 240.0f;
// 更新後の状態を返す
return newStateLeft;
}
// 右に移動
ObjectState MoveRight(const ObjectState& currentState, const float deltaTime) {
// 新規の状態を作成
ObjectState newStateRight = currentState;
// 等加速度運動の実装
// 新規の状態の速度に加速度*時間を足す
newStateRight.velocity.x += acceleration * deltaTime;
// 摩擦の実装
// 新規の状態の速度から摩擦係数*垂直抗力をを引く
newStateRight.velocity.x -= friction * friction * mass * gravity;
// 最高速度の設定
if (newStateRight.velocity.x > maxSpeed) {
newStateRight.velocity.x = maxSpeed;
}
// 位置を更新する
newStateRight.position.x += newStateRight.velocity.x * deltaTime;
// yの値は固定
newStateRight.position.y = 240.0f;
// 更新後の状態を返す
return newStateRight;
}
// 移動関数
ObjectState MoveCharacter(const ObjectState& currentState, const float leftDeltaTime, const float rightDeltaTime) {
//タイムステップが0であればvelosityを初期化する
if (leftDeltaTime == 0 && rightDeltaTime == 0) {
// ポジションを指定しただけの新規の状態を作成して返す
ObjectState emptyState;
emptyState.position = currentState.position;
return emptyState;
}
// 新規の状態を作成
ObjectState newState;
// 状態の更新
newState = MoveLeft(currentState, leftDeltaTime);
newState = MoveRight(newState, rightDeltaTime);
return newState;
}
// プログラムは WinMain から始まります
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{
// ウインドウモードで起動
ChangeWindowMode(TRUE);
if (DxLib_Init() == -1) // DXライブラリ初期化処理
{
return -1; // エラーが起きたら直ちに終了
}
// 初期処理
// インプットクラスを作成
Input input;
// 現在のステータスを作成 初期位置と初速度
ObjectState currentState = { Vec2{310.0f,240.0f},Vec2{0,0} };
SetDrawScreen(DX_SCREEN_BACK);
// シミュレーションの時間パラメータ
auto leftDeltaTime = 0.0f; // 左移動のタイムステップ
auto rightDeltaTime = 0.0f; // 右移動のタイムステップ
// ゲームループ
while (ProcessMessage() != -1)
{
// このフレームの開始時刻を覚えておく
LONGLONG start = GetNowHiPerformanceCount();
// 描画を行う前に画面をクリアする
ClearDrawScreen();
// インプットクラスの更新
input.Update();
// 左に移動
if (input.IsHoled("Left")) { // ←キーが押されている間
// 時間の経過
leftDeltaTime += 1.0f;
}
else {
if (leftDeltaTime > 0.0f) {
leftDeltaTime -= 1.0f;
}
}
// 右に移動
if (input.IsHoled("Right")) { // →キーが押されている間
// 時間の経過
rightDeltaTime += 1.0f;
}
else {
if (rightDeltaTime > 0.0f) {
rightDeltaTime -= 1.0f;
}
}
// 状態の更新
currentState = MoveCharacter(currentState, leftDeltaTime, rightDeltaTime);
// プレイヤーの描画
DrawCircle(currentState.position.intX(), currentState.position.intY(), 10, GetColor(255, 255, 255), true);
// 画面が切り替わるのを待つ
ScreenFlip();
// FPS60に固定する
while (GetNowHiPerformanceCount() - start < 16667) {}
}
DxLib_End(); // DXライブラリ使用の終了処理
return 0; // ソフトの終了
}
解説
とりあえずこれで物理を利用した左右移動ができます。
このプログラムでは左右キーを入力している間タイムステップを増加させていて、それを"加速度×時間"と"摩擦係数×重力加速度×質量"の公式に当てはめて等加速度運動と摩擦力を実装しています。等加速度運動の計算で求めた速さから摩擦力を引けば速度のシミュレーションができます。
下にあるのが今回使用した公式です。
v=v_0+at
f=μN
N=mg
f=摩擦力
μ=摩擦係数
N=垂直抗力
m=質量
g=重力加速度
摩擦係数を高くしすぎると反対方向に走り出す
速度が最大速度を超えた場合、最大速度に固定されるためそこからは等速直線運動になり常に一定の速度で移動することになります。
物理ガチ勢からすればタイムステップを減少させて減速させているなど言語道断だと思いますが基本的な物理はできていると思いたいです🥺🥺🥺
終わりに
いかがでしたでしょうか。
今回は物理を使った左右移動を実装しましたが、正直これ系のプログラムは終わりが見えず、完璧を求めようとしたら無限に時間が喰われてしまうため、この記事のコードもまだ不満がありますが妥協しています。
基本的に自分が見直せるように記事を書いているのでわかりづらいところもあると思いますが、なるべくわかりやすくぼちぼち投稿していきたいと思います。いずれはコード内で使用しているInputクラスやVec2クラスも記事に書き起こしたいと思っています。
follow me!