function drawPlayer() {
let img;
let drawWidth = player.width;
if (isAttacking) {
img = attackImg;
drawWidth = 50;
} else if (player.isJumping) {
img = playerImgs[0];
playerFrame = 0;
playerAnimCount = 0;
} else if (Math.abs(player.velocityX) > 1) {
playerAnimCount++;
if (playerAnimCount % 8 === 0) {
playerFrame = (playerFrame + 1) % 3;
}
img = playerImgs[playerFrame];
} else {
img = playerImgs[0];
playerFrame = 0;
playerAnimCount = 0;
}
if (player.velocityX < -1) {
ctx.save();
ctx.translate(player.x + drawWidth / 2, player.y + player.height / 2);
ctx.scale(-1, 1);
ctx.drawImage(img, -drawWidth / 2, -player.height / 2, drawWidth, player.height);
ctx.restore();
} else {
if (img.complete) {
ctx.drawImage(img, player.x, player.y, drawWidth, player.height);
} else {
ctx.fillStyle = "red";
ctx.fillRect(player.x, player.y, drawWidth, player.height);
}
}
}

プレイヤーの状態に応じて、正しい画像を選んで、向きを調整して描画する関数が書かれているよ。
let img;

中身がないよ?

うん。
状態によって画像を切り替えるから、imgという変数を用意するだけでOK!
プレイヤーの状態に応じて表示する画像(img)を選ぶ処理
if (isAttacking) {
img = attackImg;
drawWidth = 50;
} else if (player.isJumping) {
img = playerImgs[0];
playerFrame = 0;
playerAnimCount = 0;
} else if (Math.abs(player.velocityX) > 1) {
playerAnimCount++;
if (playerAnimCount % 8 === 0) {
playerFrame = (playerFrame + 1) % 3;
}
img = playerImgs[playerFrame];
} else {
img = playerImgs[0];
playerFrame = 0;
playerAnimCount = 0;
}

if (条件1) {
// 条件1が true(真)のときに実行
} else if (条件2) {
// 条件1が false で、条件2が true のときに実行
} else if (条件3) {
// 条件1と条件2が false で、条件3が true のときに実行
} else {
// 条件1、条件2、条件3 が全部 false のときに実行
}
条件1:if (isAttacking) {img = attackImg;drawWidth = 50;}
isAttacking が true なら、「今プレイヤーは攻撃中」という意味。
drawWidth = 50;
- 攻撃時の画像は通常より横幅が広い。幅を
50に固定。
通常のplayer.widthでは足りないため、見た目が崩れないように調整。

条件2:ジャンプ中の判定
else if (player.isJumping) {
img = playerImgs[0];
playerFrame = 0;
playerAnimCount = 0;
}
player.isJumping
プレイヤーがジャンプ中かどうかを判定。
img = playerImgs[0];
ジャンプ中のプレイヤー画像を playerImgs 配列の0番目(全コード10行目:立ち画像)に設定。
playerFrame = 0; / playerAnimCount = 0;
アニメーション用のカウンターやフレーム番号をリセット。ジャンプ中は歩行アニメーションを止めたい、という意図。
条件3:プレイヤーが左右に動いているときに、歩行アニメーションを再生する処理
else if (Math.abs(player.velocityX) > 1) {
playerAnimCount++;
if (playerAnimCount % 8 === 0) {
playerFrame = (playerFrame + 1) % 3;
}
img = playerImgs[playerFrame];
else if (Math.abs(player.velocityX) > 1)
- プレイヤーの横移動速度
velocityXが1より大きい(1は含まない)、つまり 左右どちらかに動いているときに実行。 Math.abs()は絶対値をとる関数なので、左に進んでいる(マイナス)ときも対象。
🔢 絶対値とは?
数値の正負を無視して「どれだけ離れているか」だけを見る。
たとえば:
| 式 | 結果 |
|---|---|
Math.abs(5) | 5 |
Math.abs(-5) | 5 |
Math.abs(0) | 0 |

player.velocityX が「-5」でも「+5」でも → 移動しているとみなしたい。
Math.abs() を使えば「方向に関係なく、動いているか」を判断できるよ。
playerAnimCount++;
- フレームごとにカウントアップ。
アニメーションの更新タイミングを決めるためのカウンター。

++?

++ は「1だけ足す」というよく使う操作で、
インクリメント(increment)っていうよ。
if (playerAnimCount % 8 === 0)
% ←これは「割ったときのあまり」を計算する演算子。
9 % 2 → 1 // 2で割ると 4余り1 8 % 4 → 0 // 4で割ると 2ちょうど割り切れる
=== は「型も値も同じかどうかをチェック」。
playerAnimCountが8の倍数になったときだけ、次のアニメーションフレームに進む。- つまり、8フレームに1回アニメが進むので、滑らかでスローな歩きアニメになる。

どうして8の倍数って意味になるの?

playerAnimCountを8で割って、あまりが0と同じかチェックする。
「8で割って余りが 0」になるのは、8の倍数だけだよ。
たとえば、0、8、16、24、32 … などなど
これらの数は ぜんぶ 8 で割り切れる ⇒ 余りが 0
playerFrame = (playerFrame + 1) % 3;
- フレーム番号を1つ進める。
% 3によって、0 → 1 → 2 → 0 → ...と 3つの画像がループ。- つまり、歩行アニメが3コマ構成のループになっているということ。
| playerFrame + 1 の値 | (playerFrame + 1) % 3 の結果 |
|---|---|
| 0 | 0 |
| 1 | 1 |
| 2 | 2 |
| 3 | 0 ←ここがポイント!余り0に戻る |
| 4 | 1 |
| 5 | 2 |
| 6 | 0 |
| … | … |
img = playerImgs[playerFrame];
playerImgs の中から、今の frame 番号の画像を取り出して img に入れる
if (player.velocityX < -1) {
プレイヤーが左に動いているときに、左右反転して画像を描画するという処理。

ctx.save(); // 1. 現在の状態を保存 ctx.translate(...); // 2. プレイヤーの中心へ原点を移動 ctx.scale(-1, 1); // 3. 原点中心に左右反転(=プレイヤーだけが反転) ctx.drawImage(...); // 4. プレイヤー画像を中心から描画 ctx.restore(); // 5. 状態を元に戻して、他の描画に影響させない
save()
現在の「スタイル」「変形」「位置」などを記憶。
キャンバスの原点(描き始めの場所)を、player(オブジェクト)の中心に移動
ctx.translate(player.x + drawWidth / 2, player.y + player.height / 2);
translate(x, y)
絵を描く起点(0, 0)を、新しい場所に移動する関数!
player.x + drawWidth / 2
- プレイヤーの左端(x座標) + 横幅の半分 → 横の中心位置
player.y + player.height / 2
- プレイヤーの上端(y座標) + 高さの半分 → 縦の中心位置

/ 2の
” / “ は割り算の演算子だよ。
この場合、2で割るってことだね。

どうしてキャンバスの原点をplayerの中心に移動させるの?
このあとに 回転や 反転(scale) をするから!
- 回転や反転は、「今いる原点」を中心に行われる。
- だから、先に原点をplayerの中心に動かしておく必要があるんだよ!
scale(x, y);
座標のスケール(倍率)を変える命令。
ctx.scale(-1, 1);
・x横方向は「-1倍」 → 左右反転(向きが逆)
・y縦方向は「1倍」 → そのまま(上下は変わらない)つまり、右を向いて描いた画像が、左向きに描かれるように
画像(img)をキャンバスに描く
ctx.drawImage(img, -drawWidth / 2, -player.height / 2, drawWidth, player.height);

画像は原点を中心に「左上の角(0,0)」から描かれる。
画像の中央と原点が合うように、画像の位置をずらすよ。

| 引数 | 意味 |
|---|---|
img | 表示したい画像 |
-drawWidth / 2 | 原点から左(-)に半分ずらす(=左上のX座標) |
-player.height / 2 | 原点から上(-)に半分ずらす(=左上のY座標) |
drawWidth | 表示する画像の幅 |
player.height | 表示する画像の高さ |

「中心に合わせる」と便利な理由は…
| やりたいこと | 中心を軸にするとラク! |
|---|---|
| 左右反転 | 左右対称に反転できる |
| 回転 | ぐるっと自然に回せる(中心回転) |
| カメラや当たり判定処理 | 中心点基準で位置を管理しやすい |
ctx.restore();
キャンバスの描画状態を元に戻す命令。ctx.save(); で保存した状態に戻すのが ctx.restore();
反転が不要なとき(=通常表示) の描画処理
} else {
if (img.complete) {
ctx.drawImage(img, player.x, player.y, drawWidth, player.height);
} else {
ctx.fillStyle = "red";
ctx.fillRect(player.x, player.y, drawWidth, player.height);
}
}
}
| 条件 | 処理内容 |
|---|---|
img.complete === true | プレイヤー画像をそのまま描画(drawImage) |
img.complete === false | 読み込み中 → 赤い四角で仮のプレイヤーを描画 |
画像がまだ読み込まれてないとき drawImage() だけ書いても 何も表示されない。
なので、その間だけ**赤い四角で「代替表示」**をしている。
まとめ
| ステップ | 目的 |
|---|---|
| 1. 状態判定 | プレイヤーが攻撃・ジャンプ・移動・静止のどれかを判定 |
| 2. 画像選択 | 状態に応じた画像・アニメーションを選ぶ |
| 3. 向き調整 | velocityX に応じて左右反転するか判断 |
| 4. 描画実行 | 状態と向きに応じて drawImage() または仮描画 |
function drawPlayer() {
let img;
let drawWidth = player.width; // プレイヤーの幅(後で反転時などに使う)
// 状態①:攻撃中
if (isAttacking) {
img = attackImg; // 攻撃用の画像を使う
drawWidth = 50; // 攻撃中は幅を一時的に変更(攻撃モーション用)
}
// 状態②:ジャンプ中
else if (player.isJumping) {
img = playerImgs[0]; // ジャンプ中は静止画像を使う
playerFrame = 0; // アニメーションのフレームを初期化
playerAnimCount = 0; // アニメーションのカウントも初期化
}
// 状態③:横に移動中(速度が1以上あるとき)
else if (Math.abs(player.velocityX) > 1) {
playerAnimCount++; // フレーム更新用のカウント
if (playerAnimCount % 8 === 0) {
playerFrame = (playerFrame + 1) % 3; // 0,1,2 の3枚でアニメループ
}
img = playerImgs[playerFrame]; // 現在のフレーム画像を取得
}
// 状態④:静止中
else {
img = playerImgs[0]; // 立ち画像(静止時)を使う
playerFrame = 0; // フレーム初期化
playerAnimCount = 0;
}
// 左向きに移動しているとき(velocityX が負で -1 以下)
if (player.velocityX < -1) {
ctx.save(); // 現在のキャンバス状態を保存
// プレイヤーの中心を原点に移動
ctx.translate(player.x + drawWidth / 2, player.y + player.height / 2);
// 左右反転(x 方向を反転、y はそのまま)
ctx.scale(-1, 1);
// 原点(プレイヤー中心)から画像を描画(画像の中心が原点にくるよう調整)
ctx.drawImage(img, -drawWidth / 2, -player.height / 2, drawWidth, player.height);
ctx.restore(); // 反転などの影響を他に残さないよう状態を復元
}
// それ以外(右向き・静止など)
else {
// 画像が読み込まれていれば描画
if (img.complete) {
ctx.drawImage(img, player.x, player.y, drawWidth, player.height);
}
// 画像がまだ読み込み中なら赤い四角で代用
else {
ctx.fillStyle = "red";
ctx.fillRect(player.x, player.y, drawWidth, player.height);
}
}
}

このページはここまで!
次の解説で待ってるね!
コメント