Vue.jsでミニゲームチュートリアル[The Net Ninja]

## 参考・引用
The Net Ninjaの[youtubeチャンネル](https://www.youtube.com/channel/UCW5YeuERMmlnqo4oq8vwUpg)の "Vue JS 2 Tutorial #13 - Simple Punchbag Game" 


## はじめに
パンチボタンを押して、メーターが0になるとボトルが割れるという簡単なVueのチュートリアルですが、手始めにはとても良いと思ったので書きます！（絶賛Vueの勉強中です）
３年前のyoutubeのようでVue2ですが、とってもわかりやすいです。練習には良さげな分量なのでトライします

The Net Ninjaさんがあげてるgithubのリポジトリは[こちら](https://github.com/iamshaunjp/vuejs-playlist/tree/lesson-13)。app内で使用している画像等はここからダウンロードお願いします。階層もこの通りです

イギリス英語ですがちょうどいいスピードで話されていて、英語でもいけちゃうyoutubeです

##1.index.htmlの骨組みを作る

```html:index.html
<!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8">
        <title>VueJS Tutorials</title>
        <link href="styles.css" rel="stylesheet" />
        <script src="https://unpkg.com/vue"></script>
    </head>
    <body>
        <div id="vue-app">
            
        </div>
    </body>

    <script src="app.js"></script>
</html>
```
まずはここからスタート（このような基礎の解説は最初の方のyoutubeでされてます）
"vue-app"の中に3つのエレメントを作っていきましょう。

1.bag image(サンドバッグの写真）
2.bag health(サンドバッグの状態のバロメータ,0になると破壊）
3.game controll(ゲームをコントロールするボタン）

以上の3つを作っていきます

```html:index.html
<div id="vue-app">
    <!--bag image-->
    <div id=="bag"></div>

    <!--bag health-->
    <div id="bag-health">
        <div></div>
    </div>

    <!--game controls-->
    <div id="controls">
        <button>Punch</button>
        <button>Restart</button>
    </div>
</div>
```
vue-appの中を上のように編集します。idをつけているのは、これからCSSで指定していくからです。
bad healthの中でdivタグが2つありますが、2つ目はサンドバッグのバロメータを指定していく用です。
ボタンは、パンチするためのPunchボタンと、サンドバッグのバロメータを満タンにするためのRestartボタン、２つ用意しました。

##2. CSS 編集
まず、エレメントごとに、サンドバッグのCSSを下記のように設定します

```css:styles.css
#bag{
    width: 200px;
    height: 500px;
    margin: 0 auto;
    background: url(./img/bag.png) center no-repeat;
    background-size: 80%;
}
```
ここで先ほどのid="bag" としたdivの中身がきいてきます。id指定した理由はcssの適用範囲ですね。
ちなみに、動画では(/img/bag.png)となってたのですが([github](https://github.com/iamshaunjp/vuejs-playlist/tree/lesson-13)でも）、一個階層もどって,
上記のように"./"としてあげないと動きませんね。

index.jsを開くと、
<img width="875" alt="スクリーンショット 2020-07-19 1.08.25.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/429131/07f5935a-d2dc-800b-7df8-dc4850599927.png">

このようにちゃんと表示されてます。

次に、サンドバッグのバロメータを表示させましょう。タグはbag-healthでしたので、

```styles.css
#bag-health{
    width: 200px;
    border: 2px solid #000;
    margin: 0 auto 20px auto;
}

#bag-health div{
    height: 20px;
    background: crimson
}
```
となります！ここで、1のindex.htmlで記述した通り、二重のdivの内部で、バロメーターを表示させています！　
crimsonは赤色なので、HPのようなものを表しています

こうすることでブラウザを見ると、
<img width="873" alt="スクリーンショット 2020-07-19 1.15.17.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/429131/2395fd4c-bfca-cb9b-20a1-459ebb779b06.png">
バロメータが表示されました！！

ボタンも少し整えましょう。controlsを先ほどの下に下記のように追加します。

```css:styles.css
#controls{
    width: 120px;
    margin: 0 auto;
} 
```
ボタンが真ん中にきました！

##3.ボタンのfunction追加
まずはこの空の状態からはじめましょう

```javascript:app.js
new Vue({
    el: '#vue-app',
    data: {
        
    },
    methods: {
        
    },
    computed: {

    }
});
```
vue-appのエレメントということ以外は何も記載してません。index.htmlのvue-app内を記述していくぞ〜と言うことですね。
まずは、サンドバッグのバロメータとして、変数healthを用意します。初期値は100です。

```javascript:app.js
new Vue({
    el: '#vue-app',
    data: {
        health:100
    },
```
次に、Punchボタンが押されるたびに、healthの値を10減らす関数をmethods内に書き加えます。

```javascript:app.js
new Vue({
    el: '#vue-app',
    data: {
        health:100
    },
    methods: {
        punch:function(){
            this.health -=10;
        }
    },
```
data内のオブジェクトを参照するときは、前にthis.を付けるんでしたね。
Punchボタンの次は、Restartボタンの関数をつくります。

```javascript:app.js
new Vue({
    el: '#vue-app',
    data: {
        health:100
    },
    methods: {
        punch:function(){
            this.health -=10;
        },
        restart:function(){
            this.health=100;
        }
    },
```
restart時には、パラメータを初期値の100に戻せばいいので、上記のようになりますね！

そして、ボタンクリック時にイベントが発火するように、v-on:click メソッドをbuttonにつけましょう！

```html:index.html
<!--game controls-->
<div id="game-control">
    <button v-on:click="punch">Punch</button>
    <button v-on:click="restart">Restart</button>
</div>
```
これでボタンクリック時にそれぞれの関数が呼び出されることになります〜！
しかし、まだ、バーとは連動させてないのでバーのHPが減っていくところはみえませんが・・・

##4.サンドバッグを破壊させたい
パラメータが0になった時、どうしてもこのサンドバッグを破壊させたいんだ・・・
ということで、dataに下記のように追加

```javascript:app.js
new Vue({
    el: '#vue-app',
    data: {
        health: 100,
        ended: false
```
endedは終わったことを知らせるプロパティ。ということは、初期値はfalse、つまり終わってません。
では、どのタイミングでendedをtrueにすれば良いかと言うと、下記のようになります。

```javascript:app.js
    methods: {
       　punch:function(){
            this.health -=10;
            if(this.health<=0){
                this.ended = true;
            }
        },
        restart:function(){
            this.health=100;
            this.ended = false;
        }
    },
```
2箇所追記しました。まずはpunchファンクションで、healthが0以下の時、endedをtrueにします。
一方逆に、restartした場合は、endedではない、つまり、trueにします。

では、このendedプロパティによって、punchボタンの出現を操作しましょう。
endedプロパティがfalse, つまり、ゲームが終わっていない時だけ、Punchボタンを出現させることを念頭におくと、こうなります。

```html:index.html
<!--game controls-->
<div id="controls">
    <button v-on:click="punch" v-show="!ended">Punch</button>
    <button v-on:click="restart">Restart</button>
</div>        
```        
 !は真理値を逆にしているところに注意です。（頭こんがらがる、、逆の逆の〜、、、）

次に、healthの値と、バーの表示を一致させましょう。
こんな時に使うのが、そう、バインディング。

```html:index.html
<!-- bag health bar -->
<div id="bag-health">
    <div v-bind:style="{ width: health + '%' }"></div>
</div>
```    
CSSでバーの中身を表示させているのは、二重になっているうちの、内側のdivでした。
styleアトリビューでstyleを変更させます。
healthの値を%化したものが、バーの中身になります。つまりwidthになります。

##5.ついにサンドバッグ破壊！
ついについにやっと破壊させて終わり〜〜！
ここでもデータバインディングの出番です！！

endedの真理値によって、クラスを適用したりしなかったりするコードを書きます。
下記のようにindex.htmlのbag imageを書きなおします。

```index.html
<!-- bag image -->
<div id="bag" v-bind:class="{ burst: ended }"></div>
```
つまり、endedがtrueのときはburstのクラスと紐付き、falseのときは、そのクラスは出現しません。
開発モードにして、ブラウザで見てみると、ちょうどメーターが0になったときだけburstのクラスが出現するのがわかります。
<img width="954" alt="9F83A38B-A2A4-4795-A3E4-CE82FB4DA8EF.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/429131/8586af84-0a91-21df-d017-867e64bc6c03.png">

では、実際に写真をburstクラスの時に入れ替えましょう。
CSSの「#bag」の下に新たに下記のように追加します。

```CSS:index.css
#bag.burst{
    background-image: url(./img/bag-burst.png);
    background-size: 80%;
}
```
（公式のyoutube、githubではこちらも./にしていませんが、githubの階層だとやはりこれ必要です）
bagのエレメントで、burstクラスを持っている時、という意味ですね。

これでパンチし終わると・・・
<img width="553" alt="スクリーンショット 2020-07-19 2.52.49.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/429131/460e1a64-2a9e-cf6d-4e0b-bec6306c091a.png">
無事破壊できました！！

全てのコードは以下の通りです。（公式のものは[github](https://github.com/iamshaunjp/vuejs-playlist/tree/lesson-13)に上がっています。自分で書き直したので、インデントとか甘いところあれば申し訳ないです。）

このチュートリアル、本当にシンプルオブシンプルで、私みたいなVue初心者にはとても嬉しいものでした。Youtubeも本当わかりやすいです。本は脇に置いてこればっか見てます。
とても基本的なところは理解できたはずなので、次はもう少し複雑なの作ります。（この分量でも記事書くの時間かかってしまったので、Qiitaに投稿するかは別）

```html:index.html
<!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8">
        <title>VueJS Tutorials</title>
        <link href="styles.css" rel="stylesheet" />
        <script src="https://unpkg.com/vue"></script>
    </head>
    <body>
        <div id="vue-app">
            <!-- bag image -->
            <div id="bag" v-bind:class="{ burst: ended }"></div>

            <!-- bag health bar -->
            <div id="bag-health">
                <div v-bind:style="{ width: health + '%' }"></div>
            </div>

            <!-- game control buttons -->
            <div id="controls">
                <button v-on:click="punch" v-show="!ended">Punch</button>
                <button v-on:click="restart">Restart</button>
            </div>
        </div>
    </body>

    <script src="app.js"></script>
</html>
```

```css:styles.css
#bag{
    width: 200px;
    height: 500px;
    margin: 0 auto;
    background: url(./img/bag.png) center no-repeat;
    background-size: 80%;
}

#bag.burst{
    background-image: url(./img/bag-burst.png);
    background-size: 80%;
}


#bag-health{
    width: 200px;
    border: 2px solid #000;
    margin: 0 auto 20px auto;
}

#bag-health div{
    height: 20px;
    background: crimson
}

#controls{
    width: 120px;
    margin: 0 auto;
} 

```
```javascript:app.js
new Vue({
    el: '#vue-app',
    data: {
        health: 100,
        ended: false
    },
    methods: {
        punch:function(){
            this.health -=10;
            if(this.health<=0){
                this.ended = true;
            }
        },
        restart:function(){
            this.health=100;
            this.ended = false;
        }
    },
    computed: {

    }
});
```