はじめに
この記事は 「MM2025 開発記」 シリーズ第10回です。
前回の記事では、ゲームの毎フレーム処理を行うgameLoopについて解説しました。
今回は、プレイヤーキャラクターの物理挙動設計について解説します。
GamePlayerクラス
プレイヤーが操作するキャラクターを表すクラス。
構成は以下のようになっている。
class GamePlayer {
constructor() { ... }
update() { ... }
draw(ctx) { ... }
}
-
constructor:プレイヤーキャラクターの初期状態を設定 -
update:物理挙動と状態更新を行う -
draw:Canvas にプレイヤーを描画する
それぞれのメソッドで行っている処理について解説していく。
constructor
ここで定義している値は、大きく分けると以下の4つになる。
- 位置・サイズに関する情報
- 物理挙動(重力・ジャンプ)
- 接地状態の管理
- アニメーション制御用の変数
位置・サイズ
this.x = 400;
this.y = 0;
this.width = 128;
this.height = 128;
-
x:プレイヤーの横位置(今回は画面左寄りに固定) -
y:縦位置(後で地面の高さから計算するため初期値は 0) -
width/height:キャラクターの当たり判定サイズ
this.groundYPercent = 80; // 地面の高さをパーセンテージで指定(75% = 画面の3/4の位置)
地面の高さをパーセンテージで管理すれば、
- 画面サイズが変わっても位置が崩れにくい
- リサイズ時の処理が簡単
というメリットがある。
重力・ジャンプ
this.vy = 0;
this.gravity = 0.65;
this.jumpStrength = -15;
this.isOnGround = true;
-
vy:Y方向の速度 -
gravity:毎フレーム加算される重力 -
jumpStrength:ジャンプ時に与える初速 -
isOnGround:地面に接しているかどうか
直接座標をいじるのではなく、速度を変えて動かすのが自然な物理挙動にするポイントである。
アニメーション制御
this.frame = 0;
this.frameCount = charaImgs.length;
this.frameInterval = 4;
this.frameTimer = 0;
-
frame:現在表示している画像番号 -
frameCount:アニメーション枚数 -
frameInterval:フレーム切り替え間隔 -
frameTimer:経過時間のカウンタ
update
毎フレーム呼び出される処理。キャラクターの動き方が計算される。
やっている処理は以下のようになる。
- 重力による落下処理
- 地面・足場との衝突判定
- 接地状態の更新
- アニメーションフレームの更新
重力と移動
this.vy += this.gravity;
this.y += this.vy;
毎フレーム重力を加え、その速度分だけ位置を更新する。
これにより、自然なジャンプと落下が再現できる。
地面との衝突判定
if (this.y >= groundY - this.height) {
this.y = groundY - this.height;
this.vy = 0;
this.isOnGround = true;
}
地面にめり込んだら位置を補正する。
着地時に速度をリセット&接地フラグを true にする
アニメーションの更新
if (!this.isOnGround) {
this.frame = 0;
} else {
this.frameTimer += scrollSpeed / 7;
}
- 空中:アニメーションを固定
- 地上:ゲーム速度に応じてアニメーションを進める
物理状態と見た目を一致させるための処理。
draw
// ゲームプレイヤーの描画
draw(ctx) {
// プレイヤーの描画
ctx.drawImage(
charaImgs[this.frame],
this.x, // X座標
this.y, // Y座標
this.width, // 幅(キャラのサイズに合わせて調整)
this.height // 高さ(キャラのサイズに合わせて調整)
);
const groundY = this.getGroundY(); // 地面のY座標をパーセンテージから取得
const tileSize = 128;
const tilesX = Math.ceil((canvas.width + scrollX) / tileSize);
const tilesY = Math.ceil((canvas.height - groundY) / tileSize);
for (let j = 0; j < tilesY; j++) {
for (let i = 0; i < tilesX; i++) {
ctx.drawImage(
groundImg,
i * tileSize - scrollX % tileSize,
groundY + j * tileSize,
tileSize,
tileSize
);
}
}
}
updateで計算した座標・フレームを使って現在の状態をそのまま描画する。
さいごに
今回は、GamePlayerクラスで行っている物理挙動系の処理について解説しました。
次回は、歌詞に連動したコインの生成について紹介します。