プリッカソン #4でらぁらちゃんを出荷するゲームを作りました

プリッカソンについて

プリッカソンとはプリティシリーズのハッカソンです。 詳しくは下記のイベントページをご覧ください。 prickathon.connpass.com

成果物

らぁらちゃんを出荷するゲームを作りました。

ルールは簡単,「かしこま」と呟くらぁらちゃんが流れてきたら画面をタップし,「かしこまり」と呟く,やや品質に問題があるらぁらちゃんが流れてきたら画面をフリックして違反チケットを貼ります。タイムオーバーになるか,誤った操作をするとそこでゲーム終了となります。一応らぁらちゃん人形という体です。生首じゃないよ。

僕は1日である程度体裁の整った成果物を出したいと考えていたので,使い慣れたフレームワークを用いて簡単なミニゲームを作成することにしました。また,絵を描いても良いとの触れ込みがあったので,その場で素材も用意しました。

使っているフレームワーク/言語は以下の通り

  • TypeScript
  • Phaser
  • webpack

Phaserなら簡単な2Dブラウザゲームが割とすぐできて良い と宣伝しておきます。

参加者について

多様です。Webフロント/サーバサイド/スマホアプリ/機械学習などの分野は軽くカバーしていました。また,作品を作るために絵を描いても音楽を作ってもよいというスタンスが面白く,実際にDTMする人がいたり,僕もiPadで絵を描いていました。

プリッカソンの雰囲気について

僕はプリパラをアニメから知って,まだアイドルタイムも履修していないにわかですが, プリティシリーズが好きなら誰でもウェルカムといった雰囲気で,また,技術レベルを限定することなく,まずはやってみることを大切にしている様子でした。えもちゃん曰く「やってみなくちゃわからない、分からなかったらやってみよう」。まさにそんな雰囲気です。

当日はプリチャン放送日ということもあり,まずは最新話をみるところからはじめたり,途中キンプリとキンプラを同時に流しながら開発するというお茶目な場面もありましたが,ガチで開発したい人は黙々と(ペースを崩されるようなことはなく)作業を続けられていました。みんなで好きなものを共有したい,一方,新しく出会う技術者としての交流/知識共有もしっかりしたい,というバランス感覚があり,主催の方もそのように配慮しているように感じられました。

好きな作品をテーマにしたハッカソンは面白い

参加してよかったです。今後もプリティシリーズをより一層たのしめそうです。

あとは「アイカッ!ソン」なんてものがあったら更に面白くなりますね!?

ビットコインが暴落すると沈没するヴィーナスアーク

なんかできました

最近仮想通貨をはじめたというフォロワーさんが見たいというので作りました。

ヴィーナスアークとはアイカツスターズ!に出てくる,豪華客船型のアイドル学園です。このアイドル学園,資料があまりないので公式の画像を雑に切り抜いてWebからの値を流し込んで30分くらいでこしらえました。

ちょっとした解説

コインチェックのWebSocketAPIを利用して,次々と流れてくる取引額をみて,あらかじめ決めておいたレンジのどの辺りに居るかを正規化して,船の縦のオフセットをいじってるだけです。サムネ映えを狙って,レンジは今の取引額周辺の1430000から1450000円を設定しています。つまり今日の昼間には完全沈没していたことになります。

現実的には1400000-1800000あたりに設定しておいて,気がついたら沈没,あるいは浮上していたといった楽しみ方が良いかと思われます。

コインチェックのAPIはサイトのトップページから見える情報概ね公開API経由で取得できるようです。

完全にアウトな画像を使って居るので,Webサービスとしての公開予定はありません。

ちなみに

僕の資産も沈没中です。

その後の様子

画像を抜いたリポジトリ

https://github.com/gomachan7/btc-venusark

RxSwiftでObservable<Void>をonNext()する方法

もしかしたらRx本来の使い方から逸れているかもしれませんが,引数のいらない連続したイベントを通知するのにも, Rxは便利ですよね。

そこで Observable<Void> ,あるいは PublishSubject<Void> というようなObservableを定義したことがある方も多くいるのではないでしょうか。

ただ,このVoidが流れるObservableは,onNext() あるいは onNext(Void)という形でVoidを送り込んでやろうとすると,引数を与えてやれという旨のエラーが発生します。

そんなときは onNext(()) としてやると良いです。これは値の入ってない空のTupleを与えてあげていることになりますが,これで通るようになります。

いっそのこと,Void型を取るObservableには,引数を必要としないようなExtensionを定義するとより直感的になりますね。

extension ObserverType where E == Void {
    public func onNext() {
        onNext(())
    }
}

RxSwiftでBindTo可能な独自プロパティを生やす方法

MVVM的なアーキテクチャを採用しようとしたとき,KRProgressHUD のような簡単にLoadingViewを出せるライブラリを使っていると,そのViewのOn/Offの制御をどうしようかと少し悩みます。ViewModelのOutputを普通にSubscribeして制御しても良いですが,できればRxCocoaで拡張されたUIView同様,BindToで見た目を制御したいです。

そこで,そもそもBindToをどうやって実現しているのか本家ソースコードを除くと一目瞭然で,Binder というコンポーネントを利用すれば良いことがわかりました。ObservableのbindにBinderを渡してやることで他のUIViewと同様,自動的にsubscribeしてくれます。

具体的な使い方として,たとえばUIViewControllerに以下のようなextensionを定義してあげると良さげです。 以下の例におけるComputedPropertyのloadingViewはBindTo可能なプロパティを定義している本家コードほぼそのまま持ってきています。 第一引数で渡したインスタンスが,クロージャの第一引数へ,クロージャの第二引数に渡ってくる値が入っています。

extension UIViewController {
    
    func showLoadingView() {
        KRProgressHUD.show()
    }
    
    func hideLoadingView() {
        KRProgressHUD.dismiss()
    }
    
    var loadingView: Binder<Bool> {
        return Binder(self) {(vc, value: Bool) in
            value ? vc.showLoadingView() : vc.hideLoadingView()
        }
    }
}

これで,なんの違和感もなくBindできました。

class TestViewController: UIViewController {
    
    private let disposeBag = DisposeBag()
    
    override func viewDidLoad() {
        super.viewDidLoad()

        // ローディングViewを一定間隔でOnOffさせる。
        Observable<Int>
            .interval(1.0, scheduler: MainScheduler.instance)
            .scan(true) { current, _ in !current }
            .take(3)
            .bind(to: self.loadingView)
            .disposed(by: self.disposeBag)
    }
}

ちなみにUILabelのその他のプロパティも本家の定義と同様にextensionを定義してやることで,Bindableにすることができます。

2018年について

あけましておめでとうございます。

年が明けてから実家でやることがなくてひたすらコードを描くという修行のようなことをしていました。確か,去年も同じような事していたと思います。

今年の目標は月に1本,新しいアウトプットを出せるように頑張ります。

アウトプットとは,軽量リポジトリ1本とか,趣味で内輪にお披露目してるクソゲーとか,ちまちま作ってるゲームの完成とかそんな感じです。
もうちょっと外向きに,出していけたらとおもます。

あと地味な意気込みが一つ。ブログの更新頻度を上げたいです。
Web,アプリ制作その他のお仕事欲しいので宣伝も兼ねて,人の目につくようなことをたくさんやる。そんな1年にしたいです。

PhaserでTintアニメーションをするボタンを作る

最近プライベートではPhaserとTypescriptでWebブラウザゲームを開発しています。

Phaserの知見が溜まって来ているのでいつか放出したいのですが,今回は取り急ぎ表題の通り,クリックすると色がグラデーションで変わっていくボタンの作り方をメモします。 コピペで使えます。

実装

Prototype拡張でPhaser.Buttonにメソッドとプロパティを生やします。

// Button.ts
// 拡張メソッド

declare module Phaser {
    interface Button {
        _tintStep: number;
        _clickTintStart: number;
        _clickTintEnd: number;
        _clickTintDuration: number;
        _isClickTintEnabled: boolean;
        _isAddedTintCallback: boolean;
        _clickTintTweenToStart: Phaser.Tween;
        _clickTintTweenToEnd: Phaser.Tween;
        setClickTint(startColor: number, endColor: number, duration: number): void;
        setClickTintEnabled(enabled: boolean): void;
    }
}

// private

function removeTween(context: Phaser.Button) {
    if (context._clickTintTweenToStart != null) {
        context._clickTintTweenToStart.stop();
        context.game.tweens.remove(context._clickTintTweenToStart);
        context._clickTintTweenToStart = null;
    }
    if (context._clickTintTweenToEnd != null) {
        context._clickTintTweenToEnd.stop();
        context.game.tweens.remove(context._clickTintTweenToEnd);
        context._clickTintTweenToEnd = null;
    }
}

function addToEndTween(context: Phaser.Button): Phaser.Tween {
    return context.game.add.tween(context)
        .to({ _tintStep: 100 }, context._clickTintDuration * ((100 - context._tintStep) / 100), Phaser.Easing.Default, false)
        .onUpdateCallback(() => {
            context.tint = Phaser.Color.interpolateColor(context._clickTintStart, context._clickTintEnd, 100, context._tintStep, 1);
        }).start();
}

function addToStartTween(context: Phaser.Button): Phaser.Tween {
    return context.game.add.tween(context)
        .to({ _tintStep: 0 }, context._clickTintDuration * (context._tintStep / 100), Phaser.Easing.Default, false)
        .onUpdateCallback(() => {
            context.tint = Phaser.Color.interpolateColor(context._clickTintStart, context._clickTintEnd, 100, context._tintStep, 1);
        }).start();
}

Phaser.Button.prototype._tintStep = 0;
Phaser.Button.prototype._isAddedTintCallback = false;
Phaser.Button.prototype._isClickTintEnabled = false;
Phaser.Button.prototype._clickTintTweenToStart = null;
Phaser.Button.prototype._clickTintTweenToEnd = null;

Phaser.Button.prototype.setClickTint = function (startColor: number, endColor: number, duration: number) {
    this._clickTintStart = startColor; // 起点の色。InputUpした時点の色からこの色へ向かう
    this._clickTintEnd = endColor; // InputDownした時点の色からこの色へ向かう
    this._clickTintDuration = duration;

    if (this._isAddedTintCallback) {
        return;
    }
    this._isAddedTintCallback = true;

    this.onInputDown.add(() => {
        removeTween(this);

        if (this._isClickTintEnabled) {
            this._clickTintTweenToEnd = addToEndTween(this);
        }
    });

    this.onInputUp.add(() => {
        removeTween(this);

        if (this._isClickTintEnabled) {
            this._clickTintTweenToStart = addToStartTween(this);
        }
    });
};

Phaser.Button.prototype.setClickTintEnabled = function (enabled: boolean) {
    this._isClickTintEnabled = enabled;
    removeTween(this);
};

この拡張定義はエントリーポイントのファイルにインポートするなりよしなに読み込んでいただけると使えるようになります。 使い方は以下のような感じ。

// exsample.ts
const button = this.game.add.button(0, 0, "ResourceName");
button.tint = 0xffffff;
button.setClickTintEnabled(true);
button.setClickTint(0xffffff, 0x838383, 200);

実際の動作イメージ

f:id:azaraseal_7:20171210175515g:plain

補足

色の変化のStep数を適当に100としていますが,もっと細かく刻んでも問題ないと思います。Tintアニメーションが走っている最中にボタンから手を離しても,その状態の色から元の色に違和感なく戻っていきます。これは現在どのステップまでアニメーションが進んでいるかを自身のインスタンスに記録しておくことで,ちゃんと元に戻るまでの適切なdurationがTweenを作成されるたびに計算されるためです。

Tweenのインスタンスを使いまわせたら一番良いのですが,うまくいきませんでした。

当然今回定義した関数やプロパティにユーザは自由にアクセスできてしまうので,扱いには少し注意が必要です。

Phaserについて思うこと

Phaserはかなり多くの機能を提供してくれるゲームエンジンなのですが,こういう凝ったUIを作ろうとすると,かなり苦労する印象です。スクロールビューとリストビューあたりも,デフォルトでは提供されておらず,ライブラリもあることはあるのですが,かなり微妙なのでこれからもPhaserを使っていくとしたら自作の機運があります。。。