ゲームで身につけるオブジェクト指向設計(実装編)

# 理屈はさておき書こう

- 理屈編
[ゲームで理解するオブジェクト指向設計の基本](https://qiita.com/kaku3/items/a875fe1c098509b1fccd)

若者の興味を引こうとゲームを題材にした記事を書いたのですが、「ふーん」みたいな反応を頂きまして、コレジャナイなと反省しまして、サンプルミニゲームを作成しました。

![the-sheeps-in-the-desert.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/136165/7df663f9-e70e-f7f7-67e9-fedf8f140de2.gif)

楽しそうですか？楽しそうですよね！

試しに遊んでみる⇒[[砂漠のひつじ : GitHub pages]](https://kaku3.github.io/the-sheep-in-the-desert/)

初心者でもメモ帳とブラウザ(Chrome)さえあれば作れる様に、webpack や vue-cli などは使わずに、素の html, css, javascript (+ jQuery, Handlebars, GSAP3) で作成しました。
※canvas は未使用

※完成していないですが、github でソース公開していますので是非ダウンロードして完成させてください。

[砂漠のひつじ : GitHub](https://github.com/kaku3/the-sheep-in-the-desert/)

サンプルを用意するに至った理由は、**『英語を読めるけど書けない』** という方は、 **『書かないから書けない』** のと同じで、プログラムも **自分で考えて書かないと書ける様にならない** と思ったからです。

近年では python や Vuejs などでちょこちょこっと書けばやりたいことができてしまうので、オブジェクト指向な実装をする機会があまりない(逆に言うと必要がない)と思いますので、**自分で考えて書く機会** として利用して頂けないかと思い作成しました。


# 本サンプルコードと設計について

本サンプルコードはいくつかのクラスが協調して動作する最小のサンプルを目指しました。
結果、デザインパターン的に正しくない、オブジェクトとしてどうなの？といった実装も多々あります。
ゲームバランス調整する部分に至っては定数を定義していないところもあります。

こちらは「あー、残念なコードだなー」と思いながら直しつつ、設計について少し考えてもらえればと思います。

理屈編でも少し書きましたが **「完璧な設計」** にした結果、クラスをたらい回しにされたり、なぜかメンテナンスが大変になったりすることがあります。一貫性のあるコードは推理がしやすくて読みやすい事は間違いないのですが、 **目的のシステムに対して大げさすぎる設計** は **「完璧な設計」** なのかとか。

業務システムのプロジェクトに参画して、オブジェクト指向で設計ができないとどうしても実装できないケースがあるかというと **たぶんない** のですが、コード量が増えるとある程度系統だった設計をしていないと保守が大変なのも間違いないです。

なので「大げさ」でもなく「無策」でもなく丁度よい設計をできるように、息を吐くように書けるまで反復して書いた方がよいのかなとは思います。

# 実装解説

## html template

[Handlebars](https://handlebarsjs.com/) を使用。
Vue.js の様にダブルブラケット部分を変数で置き換えられて便利。

``` 
* index.html
    <template id="object-smoke">
        <div id="smoke-{{id}}" class="smoke"></div>
    </template>
```
``` 
* smoke.js
    // template 読み込み / compile
    static template = Handlebars.compile($('template#object-smoke').html())

    // 置き換え、dom に追加
    Smoke.scene.append(Smoke.template(this))
``` 

## クリックに反応しない要素の作成

ひつじや、照準をクリックに反応しないように。

```
* css
    pointer-events: none
```

## Game Loop

setInterval に class object や method を渡せないので、 static method を利用した。
Game class を複数作ることもないのでとりあえず。

```
* main.js
class Game
    static self

    static create() {
        Game.self = new Game()
    }

    static exec() {
        let self = Game.self
        if(self.currentScene != self.nextScene) {
            self.changeScene()
        }
        self.execScene()
    }

    constructor() {
        this.currentScene = SCENE.INIT
        this.setNextScene(SCENE.GAME_INIT)
        this.setNextScene(SCENE.TITLE_INIT)
        setInterval(Game.exec, FPS)
    }    
```

2020/09/26 追記：
iOS Safari で、static field 使えないのでコード修正しました。
class 定義中に、object の個数分だけ必要な field と、class につき一つしかない field の違いを学習できるように入れておいたのですが…

- iOS safari で static field を使えない ⇒ [Can I use](https://caniuse.com/?search=static%20field)
- ES2015 の仕様にもない ⇒ [stack overflow](https://stackoverflow.com/questions/40367392/static-class-property-not-working-with-babel)

```
// 修正前
class Game
    static self

    static create() {
        Game.self = new Game()
    }
    ...

// 修正後
const Game_ = {
    self: null
}

class Game {

    static create() {
        Game_.self = new Game()
    }
    ...
```

## アニメーション

[GSAP3](https://greensock.com/gsap/)を使用。
基本はタイムラインを作って、キーフレームを追加。
アニメーション開始時、更新時、終了時などにイベント設定可能。

```
* smoke.js
    // set : アニメーションせずに、即時に座標指定
    gsap.set(this.selector, { left: x - ww, top: y - hh, opacity: 0.25, scale: 0.25 })
    // timeline 作成。
    this.tl = gsap.timeline({
        onComplete: (params) => {
            const self = params
            $(self.selector).remove()   // アニメーションが終わったら dom から自身を削除
        },
        onCompleteParams: [ this ]
    })
    // left, opacity, scale 値を、duration で指定した秒数で変化させる。
    this.tl.to(this.selector, { left: x + FIELD_SIZE_X * 2, opacity: 0.75, scale: 3, duration: 0.75, ease: Power1.easeIn })
```

## 画像

ibis Paint X を使用。
スマホで簡単に描けます。

# おわりに

ローコードやらノーコードとか言ってる昨今、オブジェクト指向のサンプルコードはあんまり需要なさそう[^1]だと思いつつ興味を引こうとミニゲームを作成しました。

ミニゲームにはプログラムの様々な要素が入っているので練習には丁度よいかなと思います。

ソースコード：
[砂漠のひつじ : GitHub](https://github.com/kaku3/the-sheep-in-the-desert/)

[^1]: 需要はなさそうだけど知ってて損はないし役に立ちます