何かの箱

06.敵の配置

上:t, 下:b, 左:f, 右:h *操作が反映されない場合、画面部分を一度クリックしてみてください

敵を考える

シューティングぽいものを考えたので、とりあえず自機狙いの弾が基本かなと思います。 そういうわけで、2つのオブジェクト間の距離や角度を計算できるようにします。
        /* 前略 */
        // get position info to target
        function getPosInfo(base, target) {
          const dx = target.x - base.x;
          const dy = target.y - base.y;
          const dd = Math.sqrt((dx ** 2) + (dy ** 2));
          let deg = Math.acos(dx / dd) * 180 / Math.PI;
          if (dy < 0) {
            deg = 360 - deg;
          }
          return {
            dx: dx,
            dy: dy,
            dd: dd,
            deg: Math.floor(deg) % 360,
          };
        }
        /* 以下略 */
      
角度が分かれば、速度を設定してあげることで、目的の弾が用意できそうです。 今回は描画起点のx,y座標をそのまま使っていますが、オブジェクトにはサイズもありますから、計算用の中心点的な情報をオブジェクトに持たせて、それを使って計算するほうが期待した角度になると思います。

敵を用意する

弾が作れそうなので、それを発射する敵を用意しましょう。 少し長く思うかも知れませんが、createMoveObject関数の単位で分割して見ていくと分かりやすいと思います。
        /* 前略 */
        // enemy & bullets
        let enemies = [];
        let bullets = [];
        const enemy = createMoveObject(
          createImageData([
            '[8]',
            '#^#',
          ], "#"), 20, 4, function() {
            if (!('counter' in this)) { // 初回は個別情報を追加
              this.counter = 23;
              this.bulletCounter = 0;
            }
            // 左右に動き続ける
            this.x += this.vx * this.sp;
            this.y += this.vy * this.sp;
            if (this.x < 1) { this.vx = -this.vx; }
            if (this.x >= screen.width - this.width) { this.vx = -this.vx; }
            this.bulletCounter ++; // 弾を撃つタイミング
            if (this.bulletCounter > this.counter) {
              const deltaWk = getPosInfo(this, myship).deg;
              const delta = (deltaWk > 180) ? deltaWk - 360 : ((deltaWk < -180) ? deltaWk + 360 : deltaWk);
              bullets.push(
                createMoveObject(createImageData(['o']), this.x+1, this.y+1, function() {
                  this.x += this.vx * this.sp;
                  this.y += this.vy * (this.sp / 2);
                  if (this.x < 0 || this.x > screen.width || this.y < 0 || this.y > screen.height) {
                    this.type = 9; // type=9 は破棄対象の扱いとする
                  }
                }).setSp(1.5).setDeg(delta + 360)
              );
              this.bulletCounter = 0;
            }
          }
        ).setDeg(0).setSp(1.0);
        enemies.push(enemy);
        /* 以下略 */
      
弾を発射するにもある程度の間隔がないと避けられませんので、そのあたりのカウンタを用意しておき、動きの処理のついでに発射判定まで実施しています。 ここでは23という値を設定していますが、とくに意図した値ではありませんので、適当に変更してよさそうな感じにしてください。ここを変数化したりすると、難易度調整にも使えそうです。 また、今後複数の敵も出せるように、それぞれ配列に格納するようにしました。 関数の配列に関数の戻り値を使ったり、複数の処理を組み合わせて配列格納も含めて実装しているため、ぱっと見た感じでは複雑そうに見えるかも知れませんが、ひとつひとつの処理はこれまで実装してきた内容と変わりません。 弾が画面外に出れば不要になりますので、何かに使えるかもと用意していたtypeを削除OK判断に使うことにしました。

メイン処理

配列を使うようにしたので、メイン処理を変更します。
        /* 前略 */
        function gameFrame() {
          const t = performance.now();
          myship.move();
          for (const e of enemies) {
            e.move();
          }
          for (const b of bullets) {
            b.move();
          }
          bullets = bullets.filter(b => b.type != 9);
          screen.clear();
          drawMoveObject(myship);
          for (const e of enemies) {
            drawMoveObject(e);
          }
          for (const b of bullets) {
            drawMoveObject(b);
          }
          const delta = performance.now() - t;
          setTimeout(gameFrame, spf - delta);
        }
        /* 以下略 */
      
このとき、どの順序で動きの処理をして画面に反映するのかを制御することで、各種オブジェクトの重ね合わせも実現します。 描画前にtypeを判定してfilterで除去した配列を再代入して更新しています。 当たり判定も何もありませんが、それっぽい見た目になりました。

リソース

gconsole.css gconsole2.js g06.js

関連

00.画面表示 01.文字出力 02.無限ループ 03.画像出力 04.キー操作 05.速度を調整 07.ゲームにする 08.ゲームパッド