JavaScriptでミニゲーム開発

#はじめに
canvasを用いてブラウザで実行できる簡単なゲームを紹介していきたいと思います。

##ゲーム内容(ALL YOU NEED TO DO IS ESCAPE)
ゲームの内容は簡単です。Enemyに当たらないように逃げてください。Enemyは壁(ブラウザの端)に当たると加速します。使用するブラウザによってはEnemyの動きが悪くなるかもしれません。EnemyとMy_characterが当たるとGAME OVERです。window sizeは1920*1080になります。

- `window.innerHeight`が969
- `window.innerWidth`が1920になります

うまく開かない場合はmain関数内の`if(!(WINDOW_WIDTH == 1920 || WINDOW_HEIGHT == 969))`の条件を消してみてください

```javascript:main関数
        function main() {
            if(!(WINDOW_WIDTH == 1920 || WINDOW_HEIGHT == 969)) {
                window.alert("You must resize window {innerWidth: 1920, innerHeight: 969}\nYou need to open window size: 1920*1080");
                return;
            } else {
                //自分生成
                create_my_character();
                count_timer();

                let size = Math.floor(Math.random() * 51) + 20;                    
                //敵生成
                let move_speed = 1;
                create_canvas(size, size, 1500, 350, move_speed);
            }
        }
```

##必要なもの
- ブラウザ(chromeがおすすめ)
- ソースコード([github](https://github.com/hibi221b/simple_js_game))

##当たり判定
**position: absolute;**をしているため左上絶対参照になることに注意してください。xは右に行けば行くほど値が大きくなり、yは下に行けば行くほど値が大きくなります
![hit.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/281815/ed6b8890-4b89-a5e2-447f-de957cc0652b.png)

##create_canvas関数
**create_canvas(width, height, left, top, move_speed)**
この関数内ではEnemyを作成します。

- `document.createElement("canvas");`で<canvas></canvas>を作成
- `canvas.style.position = "absolute";`これで絶対配置にします

```javascript
            let canvas = document.createElement("canvas");
            canvas.style.position = "absolute";
```
####canvasを描画
毎回Enemyの色が変わります
`rgb(${rgb.r},${rgb.g},${rgb.b})`は`rgb(0~255, 0~255, 0~255)`になります

CANVAS.arc();に関しては[さまざまな図形を描く](http://www.html5.jp/canvas/how2.html)で詳しく解説されています

```javascript
            let canvas_context = canvas.getContext("2d");
            let rgb = {
                r: Math.floor(Math.random() * 256),
                g: Math.floor(Math.random() * 256),
                b: Math.floor(Math.random() * 256),
            };
            canvas_context.fillStyle = `rgb(${rgb.r},${rgb.g},${rgb.b})`;
            canvas_context.beginPath();
            canvas_context.arc(width / 2, height / 2, width / 2, Math.PI*2, false);
            canvas_context.fill();
```

####divタグ内にcanvasを追加
main_fieldはグローバル変数でdivタグのidを取得しています
main_fieldの子要素にcanvasを追加しています。これで動的に\<canvas>\</canvas>をdivタグ内に作成することができました。

```javascript
main_field.appendChild(canvas);
```
####Enemyを上方向に動かす
- top > 0でEnemyのtopが0より大きい時でflagがfalseの場合(Enemyが一番上まで達していないとき)

`top -= move_speed;` 
減算している理由は上方向へEnemyを移動させるためにyを減らす必要があるから

- Enemyが一番上まで達したらelse

move_speedにadd_speedを加算することで1から1.001になります(スピードが速くなる)
`up_way_flag = true;`これで一番上まで来たことを表します
`down_way_flag = false;`にしてdown_way関数を呼び出します

下右左もup_way関数と要領は同じなので省略します

```javascript
            let add_speed = 0.001;
        
            function up_way(){
                if(top > 0 && up_way_flag == false) {
                    top -= move_speed;
                    canvas.style.top = top + "px";
                } else {
                    move_speed += add_speed;
                    up_way_flag = true;
                    down_way_flag = false;
                    down_way();
                }
            }
```
#####setIntervalでEnemyを動かす
`setInterval(() => {}, time);` 
0.001秒ごとにsetInterval内を繰り返します(1秒 = 1000)

`let my_character = document.getElementById("my_character");`
自分を取得してparseInt()でmy_characterのleft, top, width, heightを整数に変換します

- if内(Enemyとmy_characterが当たった時)

`setTimeout(() => {my_character.style.visibility = "hidden";},  100);`
これはmy_characterのvisibilityをhiddenにしたりvisibleにしたりすることで、当たったことをユーザーに知らせます。(ポケモンの効果は抜群だeffect)

`clearInterval(main_interval);`
これで繰り返し処理を止めます

`main_field.removeChild(canvas);`
Enemyをdivタグ内から消去します

`gameover_message();`
GAME OVERしたことを知らせる関数を呼び出す

`up_way();`と`left_way();`を呼び出しているためEnemyは最初に左斜め上方向へ動く

```javascript:index.htmlのcreate_canvas関数内
            let main_interval = setInterval(() => {
                let my_character = document.getElementById("my_character");
                let m_left = parseInt(my_character.style.left);
                let m_top  = parseInt(my_character.style.top);
                let m_width = parseInt(my_character.width);
                let m_height = parseInt(my_character.height);

                if(
                    left + width    >= m_left             &&
                    left            <= m_left + m_width   &&
                    top + height    >= m_top              &&
                    top             <= m_top + m_height
                ) {
                    setTimeout(() => {my_character.style.visibility = "hidden";},  100);
                    setTimeout(() => {my_character.style.visibility = "visible";}, 200);
                    setTimeout(() => {my_character.style.visibility = "hidden";},  300);
                    setTimeout(() => {my_character.style.visibility = "visible";}, 400);
                    setTimeout(() => {my_character.style.visibility = "hidden";},  500);
                    setTimeout(() => {my_character.style.visibility = "visible";}, 600);
                    
                    clearInterval(main_interval);   
                    main_field.removeChild(canvas);  
                    gameover_message();
                }

                up_way();
                left_way();

            }, 1);
```

##create_my_character関数
自分が操作するcanvasを作成します

####矢印キーで移動
addEventListenerの第一引数に"keydown"を指定。ボタンが押されたときにキーコードを返します
`console.log(e.keyCode);`で確認できます 

- ←が押された: 37
- ↑が押された: 38
- →が押された: 39
- ↓が押された: 40

`switch(e.keyCode)`で押されたkeyに応じて処理します

**case: left_button**
left_valが0より大きい時(my_characterの~~top値~~left値が0より小さくなると左端の画面をはみ出てしまう)
`left_val -= 50;`
`canvas.style.left = left_val + "px";`
left_valを減算することでmy_characterを左に50px移動させる

上右下もこれと同じ要領なので省略

```javascript
            let left_button = 37;
            let top_button = 38;
            let right_button = 39;
            let bottom_button = 40;
            let left_val = parseInt(canvas.style.left);
            let top_val  = parseInt(canvas.style.top);
            addEventListener("keydown", (e) => {
                switch(e.keyCode) {
                    case left_button:
                            if(left_val > 0) {
                                left_val -= 50;
                                canvas.style.left = left_val + "px";
                            }
                            break;
                    case top_button:
                            if(top_val > 0) {
                                top_val -= 50;
                                canvas.style.top = top_val + "px";
                            }
                            break;
                    case right_button:
                            if(left_val + 50 + 20 < WINDOW_WIDTH) {
                                left_val += 50;
                                canvas.style.left = left_val + "px";
                            } 
                            break;
                    case bottom_button:
                            if(top_val + 50 + 20 < WINDOW_HEIGHT) {
                                top_val += 50;
                                canvas.style.top = top_val + "px";
                            }
                            break;                            
                    
                }
            });
```

##count_timer関数
一応動きますが改善したい...

```javascript
        function count_timer() {
            ...
            ...
            let sec = 0;
            let min = 0;
            TIMER = setInterval(() => {
                sec++;  
                if(sec < 10) {
                    sec = "0" + sec;
                }
                if(sec > 59) {
                    sec = "0" + 0;
                    min++;
                    if(min < 10) {
                        min = "0" + min;
                    }
                }
                if(min >= 60) {
                    clearInterval(TIMER);
                }
                p_tag.textContent = min + ":" + sec;
            }, 1000);
        }
```

##gameover_message関数

`document.getElementById("timer").style.visibility = "visible";`
hiddenからvisibleにしてタイムを表示させます
`clearInterval(TIMER);`で計測を止めます TIMERはグローバル変数です
`setTimeout(() => {}, TIME)`で5秒後にmain_fieldからmy_characterを削除します

```javascript
        function gameover_message() {
            ...
            ...
            document.getElementById("timer").style.visibility = "visible";
            clearInterval(TIMER);

            setTimeout(() => {
                main_field.removeChild(document.getElementById('my_character'));
            }, 5000);
        }
```

##おわりに
最後まで見ていただきありがとうございました

##更新
- 定数のWINDOW_HEIGHT,WINDOW_WIDTHを足したり引いたりしていたため修正しました。

```javascript
if(left_val + 50 + 20 < WINDOW_WIDTH)
if(top_val + 50 + 20 < WINDOW_HEIGHT)
if(top + height + 40 < WINDOW_HEIGHT && down_way_flag == false)
if(left + width + 40 < WINDOW_WIDTH && right_way_flag == false)
```
- カンマが抜けていたため修正しました

```javascript
            let rgb = {
                r: Math.floor(Math.random() * 256),
                g: Math.floor(Math.random() * 256),
                b: Math.floor(Math.random() * 256),
            };
```
- `Math.floor(Math.random() * 50) + 20` これだと20~69までだったため修正しました

```javascript
//20~70を生成
let size = Math.floor(Math.random() * 51) + 20;
```

- left_valが0より大きい時(my_characterの~~top値~~が0より小さくなると左端の画面をはみ出てしまう)

```
left_valが0より大きい時(my_characterのleft値が0より小さくなると左端の画面をはみ出てしまう)
```

##参考文献
- [JavaScriptではじめる プログラミング超入門 (かんたんIT基礎講座)](https://www.amazon.co.jp/JavaScript%E3%81%A7%E3%81%AF%E3%81%98%E3%82%81%E3%82%8B-%E3%83%97%E3%83%AD%E3%82%B0%E3%83%A9%E3%83%9F%E3%83%B3%E3%82%B0%E8%B6%85%E5%85%A5%E9%96%80-%E3%81%8B%E3%82%93%E3%81%9F%E3%82%93IT%E5%9F%BA%E7%A4%8E%E8%AC%9B%E5%BA%A7-%E6%B2%B3%E8%A5%BF-%E6%9C%9D%E9%9B%84/dp/4774189707)

この書籍の「CHAPTER6ラケットゲームを作成する 6-3ボール移動とラケット移動を組み合わせる」の当たり判定を発展させました。参考になりました。

- [Canvas](http://www.html5.jp/canvas/)

canvasの基本的な使い方が書かれています。