【8】アリクイあくしょんコード解説 260行目〜

JavaScript
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)

  • プレイヤーの横移動速度 velocityX1より大きい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 の結果
00
11
22
30 ←ここがポイント!余り0に戻る
41
52
60

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);
    }
  }
}
たかまる
たかまる

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

コメント

タイトルとURLをコピーしました