【HaxeFlixel】HaxeFlixelでブロック崩しを作る

HaxeFlixel の日本語での解説ページがないのでまとめてみました。
ここではとてもシンプルなブロック崩しの作り方を紹介しています。

## HaxeFlixelのインストール方法

http://haxeflixel.com/documentation/install-haxeflixel/
このページ通り、
```
haxelib install flixel
```
して、
```
haxelib install flixel-tools
haxelib run flixel-tools setup
```
すれば、インストール完了です。基本的に`y`(yes)か、何も入力せずにEnterで問題ないはず。プロジェクトテンプレートは各自の環境に合わせて選択します。個人的には開発環境は IntelliJ idea がオススメです。

## テンプレートプロジェクトを作成
```
flixel tpl -n "MyProject"
```
任意のフォルダで、このコマンドを実行するとプロジェクトテンプレートが作成されます。"MyProject"の部分は好きなプロジェクト名を指定します。
![001.png](https://qiita-image-store.s3.amazonaws.com/0/43772/714b339e-b76b-e1da-9be1-91e622f702d3.png)
するとこんな感じでプロジェクトが作成されます。
IntelliJ ideaなら起動して`Shift+F10`で実行できます。コマンドから実行する場合は、プロジェクトフォルダに移動して、
```
lime test neko
```
などと打ちます。
ただ、テンプレートのみなので、実行してもHaxeFlixelのロゴが出ておしまいです。

## ブロック崩しの作成
準備ができたのでブロック崩しを作成します。
http://haxeflixel.com/demos/Breakout/
公式のデモである「Breakout」
![002.png](https://qiita-image-store.s3.amazonaws.com/0/43772/a58f6f7c-bcd9-f7f3-4128-4e7b8ced17ec.png)
これを作っていきます。

コードはここにあるので、これを参考にします。
https://github.com/HaxeFlixel/flixel-demos/tree/master/Arcade%20Classics/Breakout/source

## Breakoutの仕組み
Breakoutはとてもシンプルな作りで、

* Main.hx
* PlayState.hx

この2つのモジュールで構成されています。Main.hx にアプリの設定が記述されており、PlayState.hx で実際のゲーム部分が記述されています。

## Main.hx

```hx:Main.hx
class Main extends Sprite {
    var gameWidth:Int = 320; // 画面の幅
    var gameHeight:Int = 240; // 画面の高さ
    var initialState:Class<FlxState> = PlayState; // 最初の画面
    var zoom:Float = -1; // -1の場合、自動でズームを行う
    var framerate:Int = 60; // フレームレート
    var skipSplash:Bool = true; // 起動時のスプラッシュ表示
    var startFullscreen:Bool = false; // フルスクリーン描画を行うかどうか
```
Main.hxはフィールドの値のみ書き換えます。

* 画面サイズを320x240
* 最初の画面を PlayState
* 自動ズームを有効
* フレームレートは60FPS

というようにします。

## PlayState.hx

Breakoutに登場するゲームオブジェクトには以下のものがあります。

* bat:プレイヤーが操作するバット(変数名 _bat)
* ball:ブロックを壊すためのボール(変数名 _ball)
* walls:ゲームフィールドの外壁(変数名 _walls)
* bricks:壊すことができるブロック(変数名 _bricks)

まずはバットを実装していきます。

### バットの実装

まず変数名と移動量をフィールドに定義します。

```hx:PlayState.hx
class PlayState extends FlxState {
    private static inline var BAT_SPEED:Int = 350; // バットの移動量
    private var _bat:FlxSprite;
```

そしてcreate()関数にバットの生成処理を追加します。

```hx:PlayState.hx
    override public function create():Void {
        // マウスを消す
        //FlxG.mouse.visible = false;

        // バットを生成する
        _bat = new FlxSprite(180, 230);
        _bat.makeGraphic(40, 6, FlxColor.HOT_PINK); // 40x6の矩形をピンク色で生成
        _bat.immovable = true; // 当たっても動かなくする
        add(_bat);
```

FlxSpriteを生成して、makeGraphicで矩形塗りつぶし＋サイズを決定、衝突しても動かなくするフラグ`immovable`を有効(動作はプレイヤーが完全に制御するため)にして、State(現在の画面のようなもの)にオブジェクトとして登録します。

そしてこれを動かすには以下のように update()関数に記述します。

```hx:PlayState.hx
    override public function update():Void {
        super.update();
        // 何も押さなかった時のために初期化
        _bat.velocity.x = 0;

        // キー入力
        if(FlxG.keys.anyPressed(["LEFT"]) && _bat.x > 10) {
            _bat.velocity.x = -BAT_SPEED;
        }
        else if(FlxG.keys.anyPressed(["RIGHT"]) && _bat.x < 270) {
            _bat.velocity.x = BAT_SPEED;
        }

        if(FlxG.keys.justReleased.R) {
            // Rキーでやり直し
            FlxG.resetState();
        }

        // 画面外に出ないようにする
        if(_bat.x < 10) {
            _bat.x = 10;
        }
        if(_bat.x > 270) {
            _bat.x = 270;
        }

```

何も押さないことがあるので、移動値(velocity)を初期化して、左右キーで移動します。オリジナルのコードにはキーボードだけでなく、タッチ判定も実装されているのですがPCで実行するには不要なので省略しました。

これを実行すると、左右キーでバットが動かせるようになります。

### ボールと壁を実装する

```hx:PlayState.hx
class PlayState extends FlxState {
    private static inline var BAT_SPEED:Int = 350;

    private var _bat:FlxSprite;
    private var _ball:FlxSprite; // ボール

    private var _walls:FlxGroup; // 壁グループ
    private var _leftWall:FlxSprite; // 左壁
    private var _rightWall:FlxSprite; // 右壁
    private var _topWall:FlxSprite; // 上壁
    private var _bottomWall:FlxSprite; // 下壁
```

ボールと壁の変数を上記のように記述します。壁は4つあるので、FlxGroupでまとめて管理できるようにします。

create()関数に、ボールと壁の生成処理を追加します。

```hx:PlayState.hx
    override public function create():Void {
        // 自機を生成する
        _bat = new FlxSprite(180, 230);
        _bat.makeGraphic(40, 6, FlxColor.HOT_PINK);
        _bat.immovable = true; // 当たっても動かなくする
        add(_bat);

        // ボールを生成
        _ball = new FlxSprite(180, 160);
        _ball.makeGraphic(6, 6, FlxColor.HOT_PINK);
        _ball.elasticity = 1;
        _ball.maxVelocity.set(200, 200);
        _ball.velocity.y = 200;
        add(_ball);

        // 壁を作る
        _walls = new FlxGroup(); // 壁グループ

        // 左壁
        _leftWall = new FlxSprite(0, 0);
        _leftWall.makeGraphic(10, 240, FlxColor.GRAY);
        _leftWall.immovable = true;
        _walls.add(_leftWall); // 壁グループに追加

        // 右壁
        _rightWall = new FlxSprite(310, 0);
        _rightWall.makeGraphic(10, 240, FlxColor.GRAY);
        _rightWall.immovable = true;
        _walls.add(_rightWall); // 壁グループに追加

        // 上壁
        _topWall = new FlxSprite(0, 0);
        _topWall.makeGraphic(320, 10, FlxColor.GRAY);
        _topWall.immovable = true;
        _walls.add(_topWall); // 壁グループに追加

        // 下壁
        _bottomWall = new FlxSprite(0, 239);
        _bottomWall.makeGraphic(320, 10, FlxColor.TRANSPARENT);
        _bottomWall.immovable = true;
        _walls.add(_bottomWall); // 壁グループに追加

        // 壁グループを登録
        add(_walls);
```

バットとボールの当たり判定、ボールと壁との当たり判定を実装するには以下のように記述します。

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

        // ...(省略)

        FlxG.collide(_ball, _walls); // ボールと壁との当たり判定
        FlxG.collide(_bat, _ball); // バットとボールの当たり判定
```

実行すると、ボールが飛び回り壁やバットに当たって跳ね返ります。

### ボールの跳ね返り角度を実装する

ただこれですと、ボールが垂直に行き来するだけです。なので、跳ね返り角度を実装します。
先ほどの update() の当たり判定部分にコールバック関数を追加します。

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

        // ...(省略)

        FlxG.collide(_ball, _walls);
        FlxG.collide(_bat, _ball, _cbPing); // 第三引数にコールバック関数を登録
```

そして、_cbPing()関数を実装して、ボールの跳ね返り角度を実装します。

```hx:PlayState.hx
    /**
     * バーとボールの当たりコールバック
     **/
    private function _cbPing(bat:FlxObject, ball:FlxObject) {
        var batmid:Int = Std.int(bat.x) + 20; // バーの中心
        var ballmid:Int = Std.int(ball.x) + 3; // ボールの中心
        var diff:Int = 0;
        if(ballmid < batmid) {
            // ボールが左側にある
            diff = batmid - ballmid;
            ball.velocity.x = -10 * diff;
        }
        else if(ballmid > batmid) {
            // ボールが右側にある
            diff = ballmid - batmid;
            ball.velocity.x = 10 * diff;
        }
        else {
            // 中心はランダムでずらす
            ball.velocity.x = 2 + FlxRandom.intRanged(0, 8);
        }
    }
```

### 壊せるブロックを実装する
最後に壊せるブロックを実装します。

```hx:PlayState.hx
    override public function create():Void {
        // プレイヤーを生成する

        // ... (省略)

        // ブロックを作る
        _bricks = new FlxGroup();
        var bx:Int = 10;
        var by:Int = 30;
        var colors:Array<Int> = [0xffd03ad1, 0xfff75352, 0xfffd8014, 0xffff9024, 0xff05b320, 0xff6d65f6];
        // 20x6でブロックを配置。配置間隔は15x15
        for(y in 0...6) {
            for(x in 0...20) {
                var tmp:FlxSprite = new FlxSprite(bx, by);
                tmp.makeGraphic(15, 15, colors[y]);
                tmp.immovable = true;
                _bricks.add(tmp);
                bx += 15;
            }

            bx = 10;
            by += 15;
        }
        add(_bricks);
```

衝突判定を実装します。

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

        // ... (省略)

        FlxG.collide(_ball, _walls);
        FlxG.collide(_bat, _ball, _cbPing);
        FlxG.collide(_ball, _bricks, _cbHit); // ボールとブロックの衝突判定
    }

    /**
     * ボールとブロックの当たりコールバック
     **/
    private function _cbHit(ball:FlxObject, brick:FlxObject) {
        // ボールを消す
        brick.exists = false;
    }

```

衝突したら、`FlxObject.exists`に`false`を設定することで、ブロックが消滅するようになります。

これで完成となります。