Appearance
第17回 加速度、摩擦、移動の端数
4 / 4 操作感を数値から比較する
このステップの目的
加速度、最高速度、摩擦の3つのパラメータを個別に変更し、プレイヤーの立ち上がり、最高速度、および停止距離に与える影響を比較・検証します。
3つのパラメータが移動に与える影響
GameTuning.h に定義されている3つの数値を変更することで、操作感を調整できます。
GameTuning.hcpp
namespace dxgame::platformer::tuning::player
{
// 水平方向の最高速度(ピクセル/秒)
inline constexpr float MaxSpeed = 240.0f;
// 水平方向の加速度(ピクセル/秒^2)
inline constexpr float Acceleration = 1260.0f;
// 水平方向の摩擦減速度(ピクセル/秒^2)
inline constexpr float Friction = 1500.0f;
}これらのパラメータは、それぞれ以下の役割を持ちます。
| パラメータ | 小さくした場合 | 大きくした場合 | 設計上の役割 |
|---|---|---|---|
Acceleration(加速度) | 最高速度に達するまで時間がかかる | 素早く最高速度に達する | 入力への応答性の調整 |
MaxSpeed(最高速度) | 全体の移動速度が低い | 全体の移動速度が高い | ゲームの進行テンポの調整 |
Friction(摩擦) | キーを離しても長く滑る | キーを離すと素早く停止する | 操作の制動距離の調整 |
1箇所ずつの比較とデバッグ表示による数値確認
パラメータを調整する際は、**「一度に複数の数値を同時に変更しない」**という原則を守ります。Acceleration と Friction を同時に変更すると、操作感の変化がどちらの数値によるものか判別が困難になります。
デバッグ表示への速度出力
プレイヤーの現在速度を数値で確認するため、F1 のデバッグ表示に velocity_ の値を出力します。
Game.cppcpp
if (debugVisible_)
{
const Vector2 velocity = player_.GetVelocity();
DrawFormatString(14, 104, GetColor(255, 255, 255),
"Velocity: X=%.1f, Y=%.1f", velocity.x, velocity.y);
}動作確認の手順
プロジェクトをビルドして実行し、以下の項目を順に確認します。
- 加速の確認:
- 左右キーを押した際、速度が
0から最高速度(240.0)まで連続して増加することを確認します。
- 左右キーを押した際、速度が
- 減速停止の確認:
- 最高速度で移動している状態でキーを離し、速度が徐々に落ちて
0で静止することを確認します。
- 最高速度で移動している状態でキーを離し、速度が徐々に落ちて
- 切り返しの確認:
- 右移動中に左キーを入力した際、減速して速度が
0を通過し、反対方向へ加速することを確認します。
- 右移動中に左キーを入力した際、減速して速度が
- 低速時の微小移動の確認:
- キーを短く入力した際、端数蓄積によって数ピクセル移動できることを確認します。
チェックリスト
- 加速度、最高速度、摩擦の3つのパラメータの役割の違いを説明できる
- キー入力開始から最高速度まで滑らかに加速することを確認できる
- キーを離した際、摩擦によって0を通り越さずに停止することを確認できる
- 左右の切り返しがスムーズに行われることを確認できる
まとめと次回への引き継ぎ
第17回では、物理運動の基礎である加速度、摩擦、そして微小移動を保持する端数蓄積処理を実装しました。これで水平方向の滑らかな移動挙動が整いました。
次回(第18回)は、垂直方向(Y軸)の運動として「重力、ジャンプ、着地」を実装します。重力加速度を適用して落下させ、足元の床タイルを検知して着地(位置補正)させることで、ジャンプアクションの基本構造を構築します。
参考実装
今回追加・変更するファイルの一例です。宣言や補助処理を確認したいときに開いてください。自分のコードへ必要な変更を反映し、既存の調整値や素材をそのまま上書きしないようにします。
Source/Platformer/Game.cpp
Source/Platformer/Game.cppcpp
#include <Windows.h>
#include <string>
#include "DxLib.h"
#include "Config/ApplicationConfig.h"
#include "Platformer/Game.h"
#include "Platformer/Tuning/GameTuning.h"
namespace dxgame::platformer
{
bool Game::Initialize()
{
if (!images_.LoadAll())
return false;
constexpr wchar_t StagePath[] = L"Assets/Maps/Stage01.csv";
if (!stage_.Initialize(StagePath))
{
const std::wstring message = std::wstring(StagePath) + L"\n" +
stage_.GetLastLoadError();
MessageBoxW(nullptr, message.c_str(), L"Map Load Error", MB_OK);
return false;
}
ResetStage();
return true;
}
void Game::MainLoop()
{
// 小数分の待ち時間を次フレームへ持ち越す
double nextFrame = GetNowCount();
while (ProcessMessage() == 0)
{
input_.Update();
if (input_.IsHeld(Key::Escape))
break;
Update(dxgame::config::DeltaTime);
ClearDrawScreen();
Draw();
ScreenFlip();
nextFrame += dxgame::config::FrameMilliseconds;
const int waitMilliseconds =
static_cast<int>(nextFrame - GetNowCount());
if (waitMilliseconds > 0)
WaitTimer(waitMilliseconds);
else
nextFrame = GetNowCount();
}
}
void Game::Update(float deltaTime)
{
UpdateDebugToggle();
player_.Update(input_, deltaTime);
camera_.Update(player_.GetPosition(), stage_.GetWorldWidth());
}
void Game::Draw() const
{
DrawBackground();
stage_.Draw(camera_.GetX(), images_);
player_.Draw(camera_, images_);
DrawHud();
DrawDebug();
}
void Game::DrawBackground() const
{
images_.Draw(ImageId::Background, { 0.0f, 0.0f,
static_cast<float>(dxgame::config::ScreenWidth),
static_cast<float>(dxgame::config::ScreenHeight) });
}
void Game::DrawHud() const
{
const int textColor = GetColor(255, 255, 255);
DrawString(12, 10, L"LEFT/RIGHT: MOVE", textColor);
DrawString(12, 30, L"Z: JUMP", textColor);
DrawString(12, 50, L"F1: DEBUG ESC: QUIT", textColor);
}
void Game::DrawDebug() const
{
if (!debugVisible_)
return;
const dxgame::Rect playerDrawRect =
camera_.WorldToScreen(player_.GetDrawRect());
DrawBox(static_cast<int>(playerDrawRect.x),
static_cast<int>(playerDrawRect.y),
static_cast<int>(playerDrawRect.x + playerDrawRect.width),
static_cast<int>(playerDrawRect.y + playerDrawRect.height),
GetColor(80, 240, 255), FALSE);
const dxgame::Rect playerRect =
camera_.WorldToScreen(player_.GetHitbox());
DrawBox(static_cast<int>(playerRect.x), static_cast<int>(playerRect.y),
static_cast<int>(playerRect.x + playerRect.width),
static_cast<int>(playerRect.y + playerRect.height),
GetColor(255, 80, 80), FALSE);
const dxgame::Rect goalDrawRect =
camera_.WorldToScreen(stage_.GetGoalDrawRect());
DrawBox(static_cast<int>(goalDrawRect.x),
static_cast<int>(goalDrawRect.y),
static_cast<int>(goalDrawRect.x + goalDrawRect.width),
static_cast<int>(goalDrawRect.y + goalDrawRect.height),
GetColor(80, 240, 255), FALSE);
const dxgame::Rect goalRect =
camera_.WorldToScreen(stage_.GetGoalTrigger());
DrawBox(static_cast<int>(goalRect.x), static_cast<int>(goalRect.y),
static_cast<int>(goalRect.x + goalRect.width),
static_cast<int>(goalRect.y + goalRect.height),
GetColor(255, 220, 40), FALSE);
DrawBox(8, 76, 300, 178, GetColor(0, 0, 0), TRUE);
const int textColor = GetColor(255, 255, 255);
const dxgame::Vector2& position = player_.GetPosition();
const dxgame::Vector2& velocity = player_.GetVelocity();
DrawFormatString(14, 84, textColor,
L"pos: %.1f, %.1f", position.x, position.y);
DrawFormatString(14, 104, textColor,
L"velocity: %.2f, %.2f", velocity.x, velocity.y);
DrawFormatString(14, 124, textColor, L"camera: %d",
camera_.GetX());
DrawFormatString(14, 144, textColor, L"csv: %d",
stage_.IsLoadedFromCsv() ? 1 : 0);
DrawString(14, 164,
L"cyan: draw red: player yellow: goal", textColor);
}
void Game::UpdateDebugToggle()
{
if (input_.IsPressed(Key::Debug))
debugVisible_ = !debugVisible_;
}
void Game::ResetStage()
{
player_.Reset(stage_.GetPlayerSpawn());
camera_.Update(player_.GetPosition(), stage_.GetWorldWidth());
}
}Source/Platformer/Player.cpp
Source/Platformer/Player.cppcpp
#include <algorithm>
#include "Platformer/Camera.h"
#include "Platformer/ImageManager.h"
#include "Platformer/Input.h"
#include "Platformer/Player.h"
#include "Platformer/Tuning/GameTuning.h"
namespace dxgame::platformer
{
void Player::Reset(const dxgame::Vector2& position)
{
position_ = position;
velocity_ = {};
remainderX_ = 0.0f;
}
void Player::Update(const Input& input, float deltaTime)
{
const bool moveLeft = input.IsHeld(Key::Left);
const bool moveRight = input.IsHeld(Key::Right);
if (moveLeft && !moveRight)
{
velocity_.x -= tuning::player::Acceleration * deltaTime;
}
else if (moveRight && !moveLeft)
{
velocity_.x += tuning::player::Acceleration * deltaTime;
}
else if (velocity_.x > 0.0f)
{
velocity_.x = std::max(0.0f,
velocity_.x - tuning::player::Friction * deltaTime);
}
else if (velocity_.x < 0.0f)
{
velocity_.x = std::min(0.0f,
velocity_.x + tuning::player::Friction * deltaTime);
}
velocity_.x = std::clamp(velocity_.x,
-tuning::player::MaxSpeed, tuning::player::MaxSpeed);
remainderX_ += velocity_.x * deltaTime;
const int pixelMove = static_cast<int>(remainderX_);
remainderX_ -= static_cast<float>(pixelMove);
position_.x += static_cast<float>(pixelMove);
}
void Player::Draw(const Camera& camera, const ImageManager& images) const
{
images.Draw(ImageId::Player,
camera.WorldToScreen(GetDrawRect()));
}
dxgame::Rect Player::GetHitbox() const
{
return tuning::player::Hitbox.MovedBy(position_);
}
dxgame::Rect Player::GetDrawRect() const
{
return tuning::player::DrawRect.MovedBy(position_);
}
const dxgame::Vector2& Player::GetPosition() const
{
return position_;
}
const dxgame::Vector2& Player::GetVelocity() const
{
return velocity_;
}
}Source/Platformer/Player.h
Source/Platformer/Player.hcpp
#pragma once
#include "DxGame/Rect.h"
#include "DxGame/Vector2.h"
namespace dxgame::platformer
{
class Camera;
class ImageManager;
class Input;
// プレイヤーの位置、横速度、移動端数を管理する
class Player
{
public:
void Reset(const dxgame::Vector2& position);
void Update(const Input& input, float deltaTime);
void Draw(const Camera& camera, const ImageManager& images) const;
dxgame::Rect GetHitbox() const;
dxgame::Rect GetDrawRect() const;
const dxgame::Vector2& GetPosition() const;
const dxgame::Vector2& GetVelocity() const;
private:
dxgame::Vector2 position_ = {};
dxgame::Vector2 velocity_ = {};
float remainderX_ = 0.0f;
};
}Source/Platformer/Tuning/GameTuning.h
Source/Platformer/Tuning/GameTuning.hcpp
#pragma once
#include "Config/ApplicationConfig.h"
#include "DxGame/Rect.h"
namespace dxgame::platformer::tuning
{
// 距離はピクセル、時間は秒、速度はピクセル/秒、
// 加速度と重力はピクセル/秒²を基本単位とする。
// 加速度と重力はピクセル/秒²を基本単位とする
namespace player
{
// 中心座標を基準にした表示範囲と地形判定範囲
// 画像本来の大きさとは独立して調整する
inline constexpr Rect DrawRect =
Rect::Centered(32.0f, 48.0f).MovedBy({ 0.0f, -8.0f });
inline constexpr Rect Hitbox = Rect::Centered(24.0f, 30.0f);
// 左右入力中の加速度、最大速度、無入力時の減速度
// 速度の単位はピクセル/秒、加減速度はピクセル/秒²
inline constexpr float Acceleration = 1260.0f;
inline constexpr float MaxSpeed = 270.0f;
inline constexpr float Friction = 900.0f;
}
namespace stage
{
// タイル画像の元サイズとは独立した、ワールド上の1マスの大きさ
inline constexpr int TileSize = 32;
}
namespace camera
{
// 追従対象を画面内のどのX座標へ置くか
inline constexpr float TargetScreenX =
dxgame::config::ScreenWidth * 0.5f;
}
namespace enemy
{
// 敵の中心座標を基準にした表示範囲と命中範囲
inline constexpr Rect DrawRect = Rect::Centered(32.0f, 32.0f);
inline constexpr Rect Hitbox = Rect::Centered(28.0f, 28.0f);
}
namespace goal
{
// ゴール画像の表示範囲と、クリア成立に使う範囲
inline constexpr Rect DrawRect = Rect::Centered(32.0f, 32.0f);
inline constexpr Rect Trigger = Rect::Centered(20.0f, 28.0f);
}
}