なんちゃってブラウザゲームを作った話


## ■はじめに
### 読んだらいい人、読んでほしい人
+ ゲーム作ってみたい方。どうやって作ったの？が気になる方
+ JavaScript触れるから、アドバイスしてやろうというお優しい方

### こんなゲームを作りました↓
直線上に現れる敵をジャンプと攻撃で回避し、ハイスコアを狙うゲームです。

![symple_jumping_game (1).gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/676445/374106da-87c5-75de-fba3-f90060cf417c.gif)



### ゲーム作成のきっかけ
+ プログラムをやってみたい　→　何か自分で作ってみよう！　
 ⇒何が楽しいかな　→　ゲーム？！
 と短絡思考で着手しました。

## ■やったこと大雑把に
1. youtubeで動画を見ながら、ゲーム制作の大体の構造を学ぶ
1. テーマ、企画を考える
1. コーディング、プログラミング
1. ゲームバランス整える
1. ビルド
1. 事後作業(github,qitta)

以下、順に詳しく説明します。

## 1.youtubeで動画を見ながら、ゲーム制作の大体の構造を学ぶ
勉強させていただいた動画↓

https://www.youtube.com/watch?v=-y5OJA9z8bU&pp=ygUP44Ky44O844Og5L2c5oiQ

Part1~6まであります。素地はこの動画を見て作成しました。
ゲームを組み立てる機能や設計について、ソースコードを交えながら説明してくれるのが
とても分かりやすかったです。

## 2. テーマ、企画を考える
ゲームの内容は、直線上に現れる敵をジャンプと攻撃で回避し、ハイスコアを狙います。
ちなみに、登場するプレイヤー、敵、味方は、私の家族をモチーフにしています（笑）

player…　 母親
enemy1…　私 (眠そうなキャラ)
enemy2…　弟 (足が速いので、速いスピードでプレイヤー追い越し邪魔します)
enemy3…　父親 (よく寝ているので、母親に布団をもぎ取られます。布団が吹っ飛んだ~)
friend…　　妹 (誰に対しても愛想がよく、優しいのでプレイヤーの体力を回復させます)


## 3.コーディング、プログラミング(ソースコードの備忘録)
全体に関しては[GitHub](https://github.com/minahow3/platform_game.git)上にアップロードしています。
ここでは一部のソースコードのみ紹介します。

### ループ処理

setIntervalを用いて、16msに1回(60fps)update関数、draw関数が呼び出されるようにしています。また、gameoverになった際は、clearIntervalでループを止めています。
~~~
function fps(){
  var fps = setInterval(()=> {
    update();
    draw();
    if(scene == Scenes.GameOver){
      console.log('stop');
      clearInterval(fps);
    }
  },16);
}
~~~

### キー入力
スペースでジャンプ、"F"キーで攻撃アクションが出力されるようにしています。

ジャンプ入力について、フレーム毎にy= speed + accelerationで変化を
つけました。ジャンプはy軸方向の移動かつy軸は下方向に正のため、初期値はspeedが負、accelerationが正になっています。
~~~
  // スペースキーが押されたときの処理
  if(e.key === ' '){
    player.speed = -20;
    player.acceleration =1.0;
    }
~~~

攻撃アクションは、Fキーを押下した際に、plyaerとenemy3が一定距離以内の場合に発動し、enemy3を吹き飛ばします。
具体的な処理としては、場にいるすべての敵からenemy3だけに絞りこみ、playerとの距離が攻撃のあたり判定距離以内であるかを確認します。あたり判定有の場合、enemy3のy座標がスポーン時よりかなり高い位置に再設定されます。またおまけ要素として、playerのアニメーションが0.5秒間tornade要素つきに変更されるようにします。
~~~
  // Fキーが押された時の処理
  if(e.key === 'f' || e.key === 'F'){
    for(const e of enemies){
      if(e.type == 'enemy3'){
        var diffX3 = (e.posx - player.posx)**2;
        var diffY3 = (e.posy - player.posy)**2;
        var distance3 = diffX3 + diffY3;
        var r3 = (e.r+ player.r)**2;
        var action_r = ((e.r + 150)+ player.r)**2;
        if(distance3  > r3 && distance3 <= action_r ){
          e.posy = 150;
          e.speed = 20;
          player.image.src = './image/player_tornade.png';
          setTimeout(()=>{
            player.image.src = "./image/player.png";
          },"500"); 
    }
~~~


### 敵の状態更新
ランダムな確率でenemy1~3とfriend1が、ランダムにスポーンされます。
`point 1` 必ずフレーム外から、enemy,friendがスポーンされます。
`point 2` EnemyクラスからnewEnemyインスタンスを作成し、enemies配列に挿入し、敵を管理しています。
~~~
    if (Math.random() < 0.01) {
      const enemyType = enemyTypes[Math.floor(Math.random() * enemyTypes.length)];
      const newEnemy = new Enemy();
      newEnemy.type = enemyType.type;
      newEnemy.image = new Image();
      newEnemy.image.src = enemyType.src;
      if(newEnemy.type == 'enemy2'||newEnemy.type == 'enemy3'||newEnemy.type == 'friend1'){
      newEnemy.posx = Math.floor(Math.random() *1081)+1080;
      }else if(newEnemy.type == 'enemy1'){
      newEnemy.posx == 0;
      }
      newEnemy.posy = 600;
      newEnemy.r = enemyType.r;
      newEnemy.speed = enemyType.speed;
      newEnemy.acceleration = enemyType.acceleration;
      newEnemy.clash_flg = enemyType.clash_flg;

      // 新しい敵をenemies配列に追加
      enemies.push(newEnemy);
    }
  
    // 範囲外の敵を除外
    enemies = enemies.filter((e) => (e.posx <= 1080) || (e.posx >= 0));

~~~

### 当たり判定

オブジェクトを円と仮定し、中心間の距離と半径の和の差の大小で衝突判定をおこないました。
player, friend, enemyにそれぞれ半径(r)を持たせ、三平方の定理を用いて距離を求めています。またオブジェクトに衝突フラグ(clash_flg)を持たせることで、スコア加算時に確認したり、複数回当たり判定が行われるのを防いでいます。
~~~
    for(const e of enemies){
      var diffX1 = (e.posx - player.posx)**2;
      var diffY1 = (e.posy - player.posy)**2;
      var distance1 = diffX1 + diffY1;
      var r1 = (e.r+ player.r)**2;

      if(player.heart < 3 && distance1 <= r1 && e.clash_flg == false && e.type == 'friend1'){
        player.heart += 1;
        e.clash_flg = true;
        console.log('heal');
      }else if(distance1 <= r1 && e.clash_flg == false && (e.type == 'enemy1' ||e.type == 'enemy2'||e.type == 'enemy3')){
          player.heart -= 1;
          e.clash_flg = true;
          console.log('damage');
        }
      };
~~~
### htmlとcss
主にシーンごとに画面上に表示する要素を入れ替えるために、使用しました。
`createElement` 　…　htmlタグを作る
`setAttribute`　　…　idをセットする
`appendChild` 　　…　.htmlファイルに挿入する　
~~~
const button_area = document.createElement("div");
  button_area.setAttribute("id",button_area);
  var button_area_style = button_area.style;
  var button_area_styles = {
    display: 'flex',
    position: 'fixed',
    width: '1080px',
  }
  for(var style in button_area_styles){
    button_area_style[style] = button_area_styles[style];
  }
  document.body.appendChild(button_area);

~~~

## 4. ゲームバランス整える
ここは、確立やリスポーン地点の倍率などをよしなに調整しました。


## 5. ビルド
ふりーむに投稿しました。現在審査中です。
クリエイター登録して、およそ15分ほどでファイルのアップロードも可能でした！


https://www.freem.ne.jp/brand/14233

## 6. 事後作業(github,qitta)


### 余談)githubに挙げるのにめちゃ苦労した
gitのインストールは事前にしていたが、リポジトリの意味が分からず、苦戦・・・
いろいろ調べた結果、SSHの設定ができてなかったために、commitでエラーが出てしまっていたという点でした。


## ■最後に
今回は、私が作っていて楽しい自己満足のゲームができてしまいました（笑）
アルゴリズムの基本の "Ｋ!" は知れたのではないかと満足です。
拙い説明、ソースコードのため、間違いや改善点は随時修正していこうと思います。
ちなみに、今回自己満足だけでなくQittaで記事を投稿しようと思った理由として、
### qiitaに残す理由
+ 今後の転職を見据えて自分のプログラムを磨きたい
+ 言語化して忘れないようにしたい
+ 自分の今の立ち位置を明確化する

がありました！引き続き精進していこうと思います。
以上、最後までお読みいただきありがとうございます。

## ■参考
https://www.youtube.com/watch?v=-y5OJA9z8bU&pp=ygUP44Ky44O844Og5L2c5oiQ