【HaxeFlixel】Flappy系ゲームを作る

今回はFlappy系ゲーム「Flappybalt」を作成します。
公式のデモはこちらです。
http://haxeflixel.com/demos/Flappybalt/
![001.png](https://qiita-image-store.s3.amazonaws.com/0/43772/f6fe4288-b14b-78de-4531-4981fc3b5276.png)
見た目はスタイリッシュな雰囲気ですが、タイミングがとてもシビアな激ムズゲームです。

ソースコードはこちらです。
https://github.com/HaxeFlixel/flixel-demos/tree/master/Other/Flappybalt/source

また画像リソースがいくつか必要なので、こちらからダウンロードします。
https://github.com/HaxeFlixel/flixel-demos/tree/master/Other/Flappybalt/assets

* bg.png
* dove.png
* feather.png
* icon.png
* icon.svg
* paddle.png
* screenshot.png
* spike.png

ダウンロードできたら、`assets/images`フォルダにまとめて格納しておきます。

## 起動設定 (Main.hx)

いつものように起動設定をMain.hxに記述します。

```hx:Main.hx
class Main extends Sprite 
{
	var gameWidth:Int = 160;
	var gameHeight:Int = 240;
	var initialState:Class<FlxState> = PlayState;
	var zoom:Float = -1;
	var framerate:Int = 60;
	var skipSplash:Bool = true;
	var startFullscreen:Bool = false;
```

今回は解像度が`160x240`となります。`skipSplash`をtrueにすることでスプラッシュ表示がスキップされるので、トライ＆エラーが素早くできるようにになります。

あと今回は縦長なので、Project.xmlに定義されている画面サイズを変更します。

```xml:Project.xml
<!--These window settings apply to all targets-->
<window width="480" height="720" fps="60" background="#000000" hardware="true" vsync="true" />
```

640x480から、480x720に変更しました。

## 背景の表示 (PlayState.hx)
まずは背景画像を読み込んで表示します。create()関数に読み込み・表示処理を入れます。

```hx:PlayState.hx
class PlayState extends FlxState {
    /**
     * 生成
     **/
    override public function create():Void {
        super.create();

        // 背景画像の表示
        add(new FlxSprite(0, 0, "assets/images/bg.png"));
    }
```

実行すると背景画像が表示されます。
![002.png](https://qiita-image-store.s3.amazonaws.com/0/43772/10edae2f-6a5c-dc87-c36f-0740ad7e58f3.png)

## プレイヤーを動かす
### プレイヤーの実装 (Player.hx)
新規にクラス(Player)を作成して、実装していきます。
まずはコンストラクタです。

```hx:Player.hx
/**
 * プレイヤークラス
 **/
class Player extends FlxSprite {

    /**
     * コンストラクタ
     */
    public function new() {

        // 画面中心に生成
        super(FlxG.width * 0.5 - 4, FlxG.height * 0.5 - 4);

        // 画像読み込み (アニメ付き)
        loadGraphic("assets/images/dove.png", true);

        // アニメ番号2で始める
        animation.frameIndex = 2;

        // アニメーションを登録
        // 1 -> 0 -> 1 -> 2 の順番
        // 12フレームで更新
        // ループなし
        animation.add("flap", [1, 0, 1, 2], 12, false);
    }
```

画面中心に配置し、画像をアニメつきで読み込み、アニメーションデータを作成します。ボタンを押した時だけアニメするのでループはありません。
なおアニメ番号は2の羽を広げた状態にしておきます。
![002.png](https://qiita-image-store.s3.amazonaws.com/0/43772/cf32ce77-0cf9-cc2a-b816-57e978ff387e.png)

続いて更新関数です。SPACEを押すとジャンプするようにします。

```hx:Player.hx
    /**
     * 更新
     */
    override public function update():Void {
    
        if(FlxG.keys.justPressed.SPACE) {
            if(acceleration.y == 0) {
                // 動いていなければ動き始める
                acceleration.y = 500; // 重力設定
                velocity.x = 80; // 右に動き始める
            }
            velocity.y = -240; // ジャンプする
            
            // アニメを最初から再生
            animation.play("flap", true);
        }
        
        super.update();
    } 
```

### プレイヤーをStateに配置 (PlayState.hx)
PlayState.hxを編集して、プレイヤーのインスタンスを作成します。
フィールド`_player`を追加して、create()でnewします。

```hx:PlayState.hx
class PlayState extends FlxState {

    private var _player:Player; // プレイヤー

    /**
     * 生成
     **/
    override public function create():Void {
        super.create();

        // 背景画像の表示
        add(new FlxSprite(0, 0, "assets/images/bg.png"));

        // プレイヤーを生成
        _player = new Player();
        add(_player);
    }
```

更新関数update()に画面端で跳ね返る判定を実装します。

```hx:PlayState.hx
    override public function update():Void {

        if(FlxG.keys.justPressed.ESCAPE) {
            // ESCAPEキーで終了
            throw new Error("terminate.");
        }

        // 画面端判定
        if(_player.x < 5) {

            // 左端に到達
            _player.x = 5;
            // 反対方向に移動する
            _player.velocity.x *= -1;
            // 画像をもとに戻す
            _player.flipX = false;
        }
        else if(_player.x + _player.width > FlxG.width - 5) {

            // 右端に到達
            _player.x = FlxG.width - _player.width - 5;
            // 反対方向に移動する
            _player.velocity.x *= -1;
            // 画像を反転
            _player.flipX = true;
        }

        super.update();
    }
```

`flipX`にtrueを指定すると画像を反転することができます。
そして実行すると、SPACEキーでジャンプして画面端で跳ね返るようになります。
![002.png](https://qiita-image-store.s3.amazonaws.com/0/43772/eb92f8ff-6b53-6398-1061-1a8784979461.png)

## 跳ね返る壁を作る (Reg.hx)
このままだとなぜ画面端で折り返すのかが分からないので、画面端に跳ね返り用の壁を置いて、跳ね返った時に光るようにします。壁画像はプログラム上で作成したものを使用します。なおReg.hxはStateをまたがるようなデータを格納するクラスとして使うようです。

```hx:Reg.hx
class Reg
{
    // 壁の画像データ
    static private var _bitmapData:BitmapData;

    /**
     * 跳ね返り壁の画像データを取得
     **/
    static public function getBounceImage(height:Int):BitmapData {
        if(_bitmapData != null) {
            // 生成済みであればそのまま返す
            return _bitmapData;
        }

        // 色定義
        var WHITE:Int = 0xffFFFFFF;
        var GREY_LIGHT:Int = 0xffB0B0BF;
        var GREY_MED:Int = 0xff646A7D;
        var GREY_DARK:Int = 0xff35353D;

        // ビットマップ生成
        _bitmapData = new BitmapData(8, height, false, GREY_MED);

        var rect:Rectangle = new Rectangle(4, 0, 4, height);
        _bitmapData.fillRect(rect, GREY_LIGHT);
        rect.setTo(0, 1, 1, height - 2);
        _bitmapData.fillRect(rect, GREY_DARK);
        rect.x = 3;
        _bitmapData.fillRect(rect, GREY_DARK);
        rect.setTo(1, 0, 2, 1);
        _bitmapData.fillRect(rect, GREY_DARK);
        rect.y = height - 1;
        _bitmapData.fillRect(rect, GREY_DARK);
        rect.setTo(4, 1, 1, height - 2);
        _bitmapData.fillRect(rect, WHITE);
        rect.x = 7;
        _bitmapData.fillRect(rect, WHITE);
        rect.setTo(5, 0, 2, 1);
        _bitmapData.fillRect(rect, WHITE);
        rect.y = height - 1;
        _bitmapData.fillRect(rect, WHITE);

        return _bitmapData;
    }

```

矩形を組み合わせて作っているのですが、複雑でよくわからないのでここはコピペでいいと思います。ただ理解しておくべきことは、左半分は白い壁で右半分は黒っぽい壁になっているということです。通常は黒い壁になっていて、壁で跳ね返る時に一瞬白い壁になる、という使い方をします。

## 壁を配置して光らせる (PlayState.hx)
ではこの壁を配置してみます。PlayState.hxのフィールドに`_bounceLeft`と`_boundRight`を追加し、create()関数で生成を行います。

```hx:PlayState.hx
class PlayState extends FlxState {

    private var _player:Player; // プレイヤー
    private var _bounceLeft:FlxSprite; // 跳ね返り壁・左
    private var _bounceRight:FlxSprite; // 跳ね返り壁・右

    /**
     * 生成
     **/
    override public function create():Void {
        super.create();

        // 背景画像の表示
        add(new FlxSprite(0, 0, "assets/images/bg.png"));

        // プレイヤーを生成
        _player = new Player();
        add(_player);

        // 跳ね返り壁（左）の生成
        _bounceLeft = new FlxSprite(1, 17);
        // 1パターンあたり4x(height-34)のアニメを生成する
        _bounceLeft.loadGraphic(Reg.getBounceImage(FlxG.height - 34), true, 4, FlxG.height - 34);
        _bounceLeft.animation.add("flash", [1, 0], 8, false);
        add(_bounceLeft);

        // 跳ね返り壁（右）の生成
        _bounceRight = new FlxSprite(FlxG.width - 5, 17);
        _bounceRight.loadGraphic(Reg.getBounceImage(FlxG.height - 34), true, 4, FlxG.height - 34);
        _bounceRight.animation.add("flash", [1, 0], 8, false);
        add(_bounceRight);
    }

```

先ほどRegクラスで作成した画像を使用して、壁画像を配置しています。
そして、壁にあたった時に"flash"アニメーションを再生させてみます。

```hx:PlayState.hx
    override public function update():Void {

        if(FlxG.keys.justPressed.ESCAPE) {
            // ESCAPEキーで終了
            throw new Error("terminate.");
        }

        // 画面端判定
        if(_player.x < 5) {

            // 左端に到達
            _player.x = 5;
            // 反対方向に移動する
            _player.velocity.x *= -1;
            // 画像をもとに戻す
            _player.flipX = false;
            // 左壁光る
            _bounceLeft.animation.play("flash");
        }
        else if(_player.x + _player.width > FlxG.width - 5) {

            // 右端に到達
            _player.x = FlxG.width - _player.width - 5;
            // 反対方向に移動する
            _player.velocity.x *= -1;
            // 画像を反転
            _player.flipX = true;
            // 右壁光る
            _bounceRight.animation.play("flash");
        }

        super.update();
    }
```

画面端判定の最後で、光るアニメの再生関数を呼ぶだけです。
これで画面端で光るようになります。
![005.png](https://qiita-image-store.s3.amazonaws.com/0/43772/1aba93ba-464e-764c-b62c-7bb7c8edd9d5.png)
どうでもいいことですが、壁が光るタイミングのスクリーンショットを取るのはシビアで難しかったです。

## 床と天井に針壁を配置する (PlayState.hx)
床と天井に針の壁を配置します。PlayStateクラスのフィールドに`_spikeBottom`と`_spikeTop`を追加し,


```hx:PlayState.hx
class PlayState extends FlxState {

    private var _player:Player; // プレイヤー
    private var _bounceLeft:FlxSprite; // 跳ね返り壁・左
    private var _bounceRight:FlxSprite; // 跳ね返り壁・右
    private var _spikeBottom:FlxSprite; // 針の床
    private var _spikeTop:FlxSprite; // 針の天井

    /**
     * 生成
     **/
    override public function create():Void {
        super.create();

        // 背景画像の表示
        // ... (省略)

        // 跳ね返り壁（右）の生成
        _bounceRight = new FlxSprite(FlxG.width - 5, 17);
        _bounceRight.loadGraphic(Reg.getBounceImage(FlxG.height - 34), true, 4, FlxG.height - 34);
        _bounceRight.animation.add("flash", [1, 0], 8, false);
        add(_bounceRight);

        // 針の床を生成
        _spikeBottom = new FlxSprite(0, 0, "assets/images/spike.png");
        _spikeBottom.y = FlxG.height - _spikeBottom.height;
        add(_spikeBottom);

        // 針の天井を生成
        _spikeTop = new FlxSprite(0, 0);
        _spikeTop.loadRotatedGraphic("assets/images/spike.png", 4);
        _spikeTop.angle = 180;
        _spikeTop.y = -72;
        add(_spikeTop);
    }
```

針天井ですが、針画像をloadRotatedGraphic()でロードし4パターン指定することで、0度・90度・180度・270度に回転した画像を保持するようになります(第二引数に指定した数値でパターン数が決まります)。それにより、角度を180度回転させて描画させることができます。
なお、loadRotateGraphicの詳細はこちらのページが参考になると思います。

* Flixel Demo – Creating animated and rotated sprites from an un-rotated animated images
    * http://gameonaut.com/wordpress/2011/11/flixel-demo-creating-animated-and-rotated-sprites-from-an-un-rotated-animated-images/

ついでに当たり判定を実装します。

```hx:PlayState.hx
    /**
     * プレイヤーと針との当たり判定をチェックします
     * @return 当たっていたらtrue
     **/
    private function _isHit():Bool {
        if(FlxG.pixelPerfectOverlap(_player, _spikeBottom)) { return true; } // 針床に衝突
        if(FlxG.pixelPerfectOverlap(_player, _spikeTop)) { return true; } // 針天井に衝突

        return false; // 何も当たっていない
    }

    override public function update():Void {

        if(_isHit()) {
            // プレイヤー死亡
            _player.kill();
        }

        if(FlxG.keys.justPressed.ESCAPE) {
            // ESCAPEキーで終了
            throw new Error("terminate.");
        }
```

FlxG.pixelPerfectOverlap()を使用すると、2つのFlxSpriteがピクセルの重なりがあるかどうかで判定を行います。
実行して、針に衝突するとプレイヤーが消えるのですが、これだと何が起きたのかよくわからないので、死亡エフェクトを実装します。

## 死亡エフェクト
### 死亡エフェクトの実装 (PlayState.hx)
エフェクトにはFlxEmitterというクラスを使用します。エミッタとはエフェクトの発生源を指す用語です。PlayState.hxにフィールド`_feathers`を追加して、インスタンスを生成します。

```hx:PlayState.hx
class PlayState extends FlxState {

    private var _player:Player; // プレイヤー
    private var _bounceLeft:FlxSprite; // 跳ね返り壁・左
    private var _bounceRight:FlxSprite; // 跳ね返り壁・右
    private var _spikeBottom:FlxSprite; // 針の床
    private var _spikeTop:FlxSprite; // 針の天井
    private var _feathers:FlxEmitter; // 羽エフェクト

    /**
     * 生成
     **/
    override public function create():Void {
        super.create();

        // 背景画像の表示
        // ... (省略)


        // 針の天井を生成
        _spikeTop = new FlxSprite(0, 0);
        _spikeTop.loadRotatedGraphic("assets/images/spike.png", 4);
        _spikeTop.angle = 180;
        _spikeTop.y = -72;
        add(_spikeTop);

        // 羽エフェクトを生成
        _feathers = new FlxEmitter();
        _feathers.makeParticles("assets/images/feather.png", 50, 32);
        _feathers.setXSpeed(-10, 10);
        _feathers.setYSpeed(-10, 10);
        _feathers.gravity = 10;
        add(_feathers);
    }
```

エミッタの生成後、移動量の最大値や重力の設定をしています。
そしてエフェクト再生用の関数を用意します。

```hx:PlayState.hx
    /**
     * 羽エフェクト生成
     * @param x エミッタのS座標
     * @param y エミッタのY座標
     * @param amount 出現数
     **/
    public function launchFeathers(x:Float, y:Float, amount:Int):Void {
        _feathers.x = x;
        _feathers.y = y;
        // エフェクトを発生
        // 一斉に発生
        // エフェクトの生存時間
        // 発生する間隔
        // 発生する数
        _feathers.start(true, 2, 0, amount, 1);
    }
```

start()関数ですが、第一引数にtrueを指定することで一斉に発生するようになります。そしてエフェクトの生存時間は2秒、発生間隔の指定はなし、発生数をamountとしています。最後の引数はループ回数です。1なので1回発生して終わりということです。こちらのページが参考になります。

* ActionScript入門wiki - FlxEmitter.start()の詳細
    * http://www40.atwiki.jp/spellbound/pages/1554.html

### 死亡エフェクトの再生 (Player.hx)
Player.hxに死亡エフェクトの再生処理を実装します。kill()関数をオーバーライドして実装します。

```hx:Player.hx
    /**
     * 死亡処理
     **/
    override public function kill():Void {
        if(exists == false) {
            // すでに死んでいるなら何もしない
            return;
        }

        // PlayStateを取得
        var state:PlayState = cast(FlxG.state, PlayState);
        state.launchFeathers(x, y, 10);

        super.kill();

        // 画面全体を白フラッシュする
        FlxG.camera.flash(0xffFFFFFF, 1);
        // 画面を揺らす
        FlxG.camera.shake(0.02, 0.35);
    }
```

死亡エフェクトと一緒に白フラッシュと画面揺れを行っています。

これで死亡エフェクトは実装完了です。
![006.png](https://qiita-image-store.s3.amazonaws.com/0/43772/4e61a35b-f7de-6810-1d48-9df47c9bac66.png)

## 左右のパドルを実装する (Paddle.hx)
左右の移動するパドルを実装します。新規にPaddleクラスを作成し、以下のように記述します。

```hx:Paddile.hx
/**
 * パドルクラス
 **/
class Paddle extends FlxSprite {
    
    static public inline var SPEED:Int = 480; // 目標に進む移動速度
    public var targetY:Int = 0; // 目標座標(Y)
    
    /**
     * コンストラクタ
     **/
    public function new(x:Float=0, facing:Int=0) {

        super(0, 0);

        // 画像読み込み
        loadGraphic("assets/images/paddle.png", false);
        if(facing == FlxObject.LEFT) {
            // 左向きなら反転する
            flipX = true;
        }

        // 移動する
        this.x = x;
        y = 0-height; // 画面外に出す
    }
    
    /**
     * 位置をランダムにする
     **/
    public function randomize():Void {
        
        // パドルの位置をランダムで設定
        targetY = cast(FlxG.state, PlayState).randomPaddleY();
        
        if(targetY < y) {
            // 上方向へ移動
            velocity.y = -SPEED;
        }
        else {
            // 下方向へ移動
            velocity.y = SPEED;
        }
    }
    
    /**
     * 更新
     **/
    override public function update():Void {
        
        var bStop:Bool = false; // 停止するかどうか
        if(velocity.y < 0) {
            // 上方向へ移動
            // 次のフレームで目標座標を超えるなら停止する
            bStop = (y <= targetY + SPEED * FlxG.elapsed);
        }
        else {
            // 下方向へ移動
            // 次のフレームで目標座標を超えるなら停止する
            bStop = (y >= targetY - SPEED * FlxG.elapsed);
        }
        
        if(bStop) {
            // 停止する
            velocity.y = 0;
            y = targetY;
        }
        
        super.update();
    }
}
```

移動先の決定にPlayStateのrandomPaddleY()関数を呼び出していますが、これは未実装なので後で実装します。
またupdate()関数の停止チェックですが、次の更新（移動）で目標座標を超えてしまう場合は停止する、という処理をしています。

### パドルの配置と移動先を求める関数を実装する (PlayState.hx)

パドルの配置とrandomPaddleY()関数を実装します。
まずはパドルの生成です。フィールドに`_paddleLeft`と`_paddleRight`を追加し生成を行います。

```hx:PlayState.hx
class PlayState extends FlxState {

    private var _player:Player; // プレイヤー
    private var _bounceLeft:FlxSprite; // 跳ね返り壁・左
    private var _bounceRight:FlxSprite; // 跳ね返り壁・右
    private var _spikeBottom:FlxSprite; // 針の床
    private var _spikeTop:FlxSprite; // 針の天井
    private var _feathers:FlxEmitter; // 羽エフェクト
    private var _paddleLeft:Paddle; // バドル・左
    private var _paddleRight:Paddle; // パドル・右

    /**
     * 生成
     **/
    override public function create():Void {
        super.create();

        // 背景画像の表示
        // ... (省略)

        // 羽エフェクトを生成
        _feathers = new FlxEmitter();
        _feathers.makeParticles("assets/images/feather.png", 50, 32);
        _feathers.setXSpeed(-10, 10);
        _feathers.setYSpeed(-10, 10);
        _feathers.gravity = 10;
        add(_feathers);
        
        // パドル・左・生成
        _paddleLeft = new Paddle(6, FlxObject.RIGHT);
        add(_paddleLeft);
        
        // パドル・右・生成
        _paddleRight = new Paddle(FlxG.width-15, FlxObject.LEFT);
        add(_paddleRight);
        
    }
```

パドルの移動先を決定する関数を実装します。

```hx:PlayState.hx
    /**
     * パドルの移動先を決定します
     * @return 移動先Y座標
     **/
    public function randomPaddleY():Int {
        var a:Int = Std.int(_bounceLeft.y); // 上
        var b:Int = Std.int(_bounceLeft.y + _bounceLeft.height - _paddleLeft.height); // 下

        return FlxRandom.intRanged(a, b);
    }
```

パドルに当たったら死ぬようにします。

```hx:PlayState.hx
    /**
     * プレイヤーと針との当たり判定をチェックします
     * @return 当たっていたらtrue
     **/
    private function _isHit():Bool {
        if(FlxG.pixelPerfectOverlap(_player, _spikeBottom)) { return true; } // 針床に衝突
        if(FlxG.pixelPerfectOverlap(_player, _spikeTop)) { return true; } // 針天井に衝突
        if(FlxG.pixelPerfectOverlap(_player, _paddleLeft)) { return true; } // 左のパドルに衝突
        if(FlxG.pixelPerfectOverlap(_player, _paddleRight)) { return true; } // 右のパドルに衝突

        return false; // 何も当たっていない
    }
```

パドルとの衝突判定を増やしています。
最後に壁にあたった時に壁がランダムで移動するようにします。

```hx:PlayState.hx
    override public function update():Void {

        if(_isHit()) {
            // プレイヤー死亡
            _player.kill();
        }

        if(FlxG.keys.justPressed.ESCAPE) {
            // ESCAPEキーで終了
            throw new Error("terminate.");
        }

        // 画面端判定
        if(_player.x < 5) {

            // 左端に到達
            _player.x = 5;
            // 反対方向に移動する
            _player.velocity.x *= -1;
            // 画像をもとに戻す
            _player.flipX = false;
            // 左壁光る
            _bounceLeft.animation.play("flash");
            // 右のパドルランダム移動
            _paddleRight.randomize();
        }
        else if(_player.x + _player.width > FlxG.width - 5) {

            // 右端に到達
            _player.x = FlxG.width - _player.width - 5;
            // 反対方向に移動する
            _player.velocity.x *= -1;
            // 画像を反転
            _player.flipX = true;
            // 右壁光る
            _bounceRight.animation.play("flash");
            // 左のパドルランダム移動
            _paddleLeft.randomize();
        }

```

当たった壁と反対側にあるパドルが動くようにしています。

実行するとパドルが出現し動くようになって、衝突判定が行われます。
![007.png](https://qiita-image-store.s3.amazonaws.com/0/43772/9a2398b6-a63c-e9a4-57b6-b24beb510a1f.png)

## ゲームオーバー・リトライを実装する (Player.hx)

何回もプレイできるようにします。
Playerクラスのkill()処理を修正します。

```hx:Player.hx
    /**
     * 死亡処理
     **/
    override public function kill():Void {
        if(exists == false) {
            // すでに死んでいるなら何もしない
            return;
        }

        // PlayStateを取得
        var state:PlayState = cast(FlxG.state, PlayState);
        state.launchFeathers(x, y, 10);

        super.kill();

        // 画面全体を白フラッシュする
        FlxG.camera.flash(0xffFFFFFF, 1, onFlashDone);
        // 画面を揺らす
        FlxG.camera.shake(0.02, 0.35);
    }

```

白フラッシュに`onFlashDone`をコールバック引数として渡しています。これにより白フラッシュが終わった時に、`onFlashDone`が呼び出されることとなります。
では、この関数を実装します。

```hx:Player.hx
    /**
     * 白フラッシュが終わった時に呼び出される関数
     **/
    public function onFlashDone():Void {

        // プレイヤーを復活させる
        revive();

        // Stateリセット
        cast(FlxG.state, PlayState).reset();
    }

    /**
     * 復活処理
     **/
    override public function revive():Void {

        // 画面中心に戻す
        x = FlxG.width * 0.5 - 4;
        y = FlxG.height * 0.5 - 4;

        // 動きを止める
        acceleration.x = 0;
        acceleration.y = 0;
        velocity.x = 0;
        velocity.y = 0;

        super.revive();
    }
}
```

白フラッシュが終わった後、プレイヤーを初期状態に戻すために revive()関数を呼び出しています。そして、PlayStateのreset()を呼び出していますが、これは未実装なので実装が必要です。

PlayStateのreset()関数を実装します。

```hx:PlayState.hx
    
    /**
     * リセット処理
     **/
    public function reset():Void {

        // パドルを初期位置に戻す
        _paddleLeft.y = 0 - _paddleLeft.height;
        _paddleRight.y = 0 - _paddleRight.height;

    }
```

やっていることはパドルを初期位置に戻しているだけです。
実行すると、死亡後、初期状態に戻るようになります。

## スコア・セーブの実装
### 変数定義 (Reg.hx)
最後にスコア・セーブの実装を行います。
Stateをまたがるので、必要な変数はReg.hxに定義します。

```hx:Reg.hx
class Reg
{
    // 壁を光らせる様の画像データ
    static private var _bitmapData:BitmapData;
    
    // 現在のスコア
    static public var score:Int = 0;
    
    // ハイスコア
    static public var highScore:Int = 0;
    
    // セーブデータ
    static public var save:FlxSave;
```

スコア`score`、ハイスコア`highscore`、セーブデータ`save`の3つを定義します。

### スコアセーブ呼び出し (Player.hx)
次にPlayerクラスでスコアセーブの呼び出しを行います。

```hx:Player.hx
    /**
     * 白フラッシュが終わった時に呼び出される関数
     **/
    public function onFlashDone():Void {

        // プレイヤーを復活させる
        revive();
        
        // セーブを呼び出す
        PlayState.saveScore();

        // Stateリセット
        cast(FlxG.state, PlayState).reset();
    }
```

白フラッシュが終わった後にセーブします。この関数は未実装なので、PlayStateクラスに実装します。

### スコア処理・セーブ処理の実装 (PlayState.hx)
そもそもスコアを表示する仕組みがないので、スコア表示を実装します。
PlayStateにフィールド`_scoreDisplay`と`_highScore`を追加し、テキストを生成します。
それと、セーブデータ名`SAVE_DATA`の定義もします。

```hx:PlayState.hx
class PlayState extends FlxState {

    private var _player:Player; // プレイヤー
    private var _bounceLeft:FlxSprite; // 跳ね返り壁・左
    private var _bounceRight:FlxSprite; // 跳ね返り壁・右
    private var _spikeBottom:FlxSprite; // 針の床
    private var _spikeTop:FlxSprite; // 針の天井
    private var _feathers:FlxEmitter; // 羽エフェクト
    private var _paddleLeft:Paddle; // バドル・左
    private var _paddleRight:Paddle; // パドル・右
    private var _scoreDisplay:FlxText; // スコアテキスト
    private var _highScore:FlxText; // ハイスコアテキスト
    
    static inline private var SAVE_DATA:String = "FLAPPYBALT"; // セーブデータ名
    /**
     * 生成
     **/
    override public function create():Void {
        super.create();

        // 背景画像の表示
        // ... (省略)

        // パドル・右・生成
        _paddleRight = new Paddle(FlxG.width-15, FlxObject.LEFT);
        add(_paddleRight);

        // スコアテキストを生成
        _scoreDisplay = new FlxText(0, 180, FlxG.width);
        _scoreDisplay.alignment = "center";
        _scoreDisplay.color = 0xff898996;
        _scoreDisplay.size = 24;
        add(_scoreDisplay);
        
        // セーブデータからハイスコアをロードする
        Reg.highScore = loadScore();
        
        // ハイスコアテキストを生成
        _highScore = new FlxText(0, 40, FlxG.width, "");
        _highScore.alignment = "center";
        _highScore.color = 0xff868696;
        add(_highScore);
        
        // ハイスコアデータがあればそれをテキストに設定
        if(Reg.highScore > 0) {
            _highScore.text = Std.string(Reg.highScore);
        }
    }

```

ハイスコアのロード関数loadScore()が未実装なのでこれも実装が必要となります。

まずはセーブ関数です。

```hx:PlayState.hx
    /**
     * セーブ処理を行います
     **/
    static public function saveScore():Void {
        Reg.save = new FlxSave();
        if(Reg.save.bind(SAVE_DATA)) {
            if((Reg.save.data.score == null) || (Reg.save.data.score < Reg.score)) {

                // 未セーブまたはハイスコア更新であれば保存
                Reg.save.data.score = Reg.score;
            }
        }

        // セーブデータ書き込み
        Reg.save.flush();
    }
```

セーブをするたびにFlxSaveのインスタンスを生成し、セーブデータ名をバインドし、dataに代入して、flushで書き込みを行います。

続いてロード関数の実装です。

```hx:PlayState.hx
    /**
     * ロード処理を行う
     **/
    static public function loadScore():Int {
        Reg.save = new FlxSave();

        if(Reg.save.bind(SAVE_DATA)) {
            if((Reg.save.data != null) && (Reg.save.data.score != null)) {

                // 無効なデータでなければスコアを返す
                return Reg.save.data.score;

            }
        }

        // セーブデータが存在しない
        return 0;
    }
```

これでセーブ・ロードはできるようになりましたが、スコア計算の処理がまだ入っていないので実装します。update()にスコア加算とテキスト更新処理を入れます。

```hx:PlayState.hx
    override public function update():Void {

        if(_isHit()) {
            // プレイヤー死亡
            _player.kill();
        }

        if(FlxG.keys.justPressed.ESCAPE) {
            // ESCAPEキーで終了
            throw new Error("terminate.");
        }

        // 画面端判定
        if(_player.x < 5) {

            // 左端に到達
            // ... (省略)

            // スコア計算
            Reg.score++;
            _scoreDisplay.text = Std.string(Reg.score);
        }
        else if(_player.x + _player.width > FlxG.width - 5) {

            // 右端に到達
            // ... (省略)

            // スコア計算
            Reg.score++;
            _scoreDisplay.text = Std.string(Reg.score);
        }
```

最後にreset()関数にスコアの初期化処理を入れます。

```hx:PlayState.hx
    /**
     * リセット処理
     **/
    public function reset():Void {

        // パドルを初期位置に戻す
        _paddleLeft.y = 0 - _paddleLeft.height;
        _paddleRight.y = 0 - _paddleRight.height;
        
        // スコア初期化
        Reg.score = 0;
        _scoreDisplay.text = "";
        Reg.highScore = loadScore();
        
        if(Reg.highScore > 0) {
            _highScore.text = Std.string(Reg.highScore);
        }

    }
```

ようやくこれで完成です！
![008.png](https://qiita-image-store.s3.amazonaws.com/0/43772/0101326a-d1be-ae2c-bd4a-c423d674d974.png)
スコア・ハイスコアが表示され、正しく保存されているのを確認します。