kazumalab tech log

流行りとリラックマと嵐が大好きです。技術的ログ。

RailsのSelectタグでenumを使うかつi18nを利用する

いつも忘れるのでメモ。

model

class User < ApplicationRecord
  enum state: { unpublished: 0, published: 1 }
end

i18n file

ja:
  activerecord:
    attributes:
      user:
        email: メールアドレス
        password: パスワード
    enum:
      user:
       state:
          unpublished: 非公開
          published: 公開

view

= f.select :state, User.states.keys.map{|key| [I18n.t("activerecord.enum.user.state.#{key}"), key] }, {}, class: "select-css"

雑にやるならこんな感じ。

テストについて

E2Eテスト

今日ちょっとE2Eテストについて説明してみてって上司から言われ説明してみたら0点いただきました。Wow...ちゃんと勉強しよ。
インテグレーションテストが間違ってたっぽい。
E2Eのテストとインテグレーションテストがごちゃっとしていて、それじゃおかしくなるとの指摘。なんとなくだけど久しぶりに詰められた気がする。
詰められるの嫌だけど、だめなところをしっかり痛いところをついて指摘してくれるのはすごくありがたい。

「3年目でこれはやばくないか?」

まじでそれ。やればやるほどコンピュータの世界知ることが多い。
一つづつ着実に身に着けて行かないと上辺だけでやってたら死ぬと最近思っていた矢先だったので、いい機会だったと思う。

  • Unitテストはクラス単体のテスト
  • Integrationテストはクラス同士の結合テスト
  • E2Eはユーザーケースを自動化したテスト

じゃあRailsのModelの単体テストはUnitテストなのか?という議題に関して、概念的にはNoらしい。
しかし事実上はYesとなっていて、そもそもRailsのModelはActiveRecordを継承しているので、そもそもUnitテストじゃなくなる。
でもそれをUnitテストってことにしちゃおう、みたいな流れ。

Unitテストをガチガチにやるなら別クラスのものはMockとStubを使ってテストする。
Railsだと厳しい感じある。

Controllerテストはどうか、あれはIntegrationの役割をしているが、render_viewsを時折利用していて、Viewと密になっているケースがある。
まぁなにわともあれ、Unitテスト、Integrationテスト、そしてE2Eのテストを書こうね、そしてそれぞれが持つ役割をちゃんと理解しておこうねという話。

RailsだとE2EはFeatureSpecか、SystemSpec。
最近ChromeDriverが使えるようになって前よりも安定してきている気がしている。

最後に、もう一度ちゃんと初めての自動テストを読むことにしてPDFを購入して、1章を読み終えたところだ。

www.oreilly.co.jp

npm first release

npmのpackageをリリースした。その名も rcolor.js

github.com

最近ツヨツヨなエンジニアはみんなライブラリを公開しているっぽいので、ツヨツヨを目指すべくまずは初歩としてライブラリを公開してみたのが始まり。APIとしては単純で、カラーコード(hex)をStringで渡すと反転色を返してくれるというLibrary。Reactとかで最近DOMのStyleを直接弄ったりすることが多くて、ちょっとどうにかならないかなーと思って作った。(世の中にいっぱいある)

最近yarnのほうがよく使うので、自分のプロジェクトでyarnでinstallできればよきかなと思ったが、思った以上にかんたんだった。

今回はES6のclass構文を使って書いた。テストはFacebookが提供しているJest。 使い方はシンプル。

yarn add rcolor.js
import RColor from 'rcolor.js'

const rcolor = new RColor('#ffffff')
rcolor.toReverse() // #000000

www.npmjs.com

packageの公開もすごく簡単だった。公式がおすすめ。

yarnpkg.com yarnpkg.com

Firebase Day1

Firebase、今いろんなところで使われているGoogleが提供しているサービス。 昔むかしにUnityのデータ保存先としてRealtimeDatabaseを使ったことがあるが、もうわすれてしまった。 最近はReactとかにふれる機会が多いので、それをFirebaseのhostingに乗っけて見ようかと思った、ちょっとお試ししてみた。

なんかReactのプロジェクトをいい感じで作ってくれるコマンドがあるのでそれを打って見る。

$ npx create-react-app colors

今回は適当に色を組み合わせて保存するサービスを作ろうかなぁと思ったので、colorsにした。 まぁ特に意味はないんだけど。

$ cd colors
$ firebase init

firebase-cliは別途インストールした。 initで対話形式でプロジェクトに必要なjsonを吐き出してくれる。なるほど。 ちなみに、プロジェクトをFirebase上で作って、リージョンを指定しないとプロジェクト選択でエラーが出るのでWebでポチポチっと。 なるほどなぁ。 そこはこの上ではできないんだ..って感じ。その後、デプロイしてみる。

$ npm run build
$ firebase deploy

この2コマンドぐらいで行けるが、

Error: Cloud resource location is not set for this project but the operation you are attempting to perform in Cloud Firestore requires it

有効になっていないリソースがあればエラーになるっぽい。Web上でポチポチっと有効にしてみたらとりあえずOK。 今日はその後firebase Authenticationを利用してGoogleOauthのログインまで完了した。

FirebaseのAuthenticationを利用するところはこういう感じに書いた。

import React from 'react'
import './App.css';
import firebase from "./firebase-config"

class App extends React.Component {
  constructor(props) {
    super(props)

    this.state = {
      user: null
    }
  }

  render() {
    return(
      <div>
        <h1>Hello! Colors.</h1>
        { !this.isLogin && <button onClick={this.login}>Login</button> }
        { this.isLogin && <p>{this.state.user.email}</p> }
      </div>
    )
  }

  componentDidMount() {
    firebase.auth().onAuthStateChanged((user) => {
      this.setState({ user })
    })
  }

  get isLogin() {
    return this.state.user && this.state.user.uid
  }

  login() {
    const provider = new firebase.auth.GoogleAuthProvider()
    firebase.auth().signInWithRedirect(provider)
  }
}

export default App;

これを ReactDOM.renderレンダリングしてあげればいいんだなぁ。 firebaseのAPIを呼び出すときに毎回認証が必要なので、firebase-config.jsのように書き出しておく。

import firebase from 'firebase/app'
import 'firebase/auth'

const config = {
  apiKey: "APIKEY",
  authDomain: "Domain",
  databaseURL: "databaseURL",
  projectId: "projectID",
  storageBucket: "bucket",
  messagingSenderId: "senderid1234",
  appId: "1:hogehoge:web:1234"
}

firebase.initializeApp(config)

export default firebase

ちなみにFirebaseのConfigのApiKeyはJSファイルに書き出されてしまう。これに対する答えは以下を見れば良さそうだ。

stackoverflow.com

スマブラが発売されたけどホームランコンテストがなかったのでちょっと作ってみる

はじめに

みんなのウェディングのエンジニア@kazumalabです。 この記事はくふうカンパニーアドベントカレンダーのなんと10日目になります!

記事のお題をGraphQLにしてたのですが、タイミングがスマブラ発売と被ってしまいゲームのやりすぎで記事を書けそうありません。 (ほんとミス!)

ですのでお題をチェンジして書こうと思います。全くWeb関係からは離れます!←

スマッシュブラザーズSP

先ほども言いましたが、スマブラが発売されました。 みにいくだけ...と思ってふらっとヤマダ電機に行ったら PayPayで買えば20%Off、運良ければ全額返ってくるという「PayPayキャンペーン」をやっていました。

ええ、その誘惑に負けたわけですね。 ついつい買ってしまいました。

さらにアドカレ当日なのについついやってしまいました。それが以下の写真です。

今年の夏ぐらいにプロジェクターをかったのでその大画面でやるスマブラは格別でした。(そんなことは置いといて...)

今回のアドカレネタ

このスマブラ...ホームランコンテストがない!3DSWiiUまではあったのに... え、じゃあ作ってみてもいいんじゃない?それをアドカレのネタにしちゃおう!というのが今回です。 まぁ結論からいうとホームランを撃ち抜くところまではいかなかったです...。けどダメージを受ければ受けるほどぶっ飛び率が上がっていくところはできたはずです!

そういえば以前に僕は格ゲーを作っていたのを覚えてますでしょうか...はるか昔の記憶です。

これは3年前だったんですね。もっと最近だと思ってました。 時間もないのでどんどん作っていきましょー!

キャラクターを配置

右側がプレイヤ、左側がサンドバックくんです。 サンドバックはもう面倒なのでCylinderそのままです。笑

Playerは有料アセットを使っています。

プレイヤを動かす

今回は簡易的に作るのでCharactorControllerを使ってPlayer.csを実装します。 さらにPlayerのタグをつけておきます。(のちに使う)

private CharacterController charactorController;

void Start () {
    charactorController = this.GetComponent<CharacterController> ();
}

void Update () {
    direction = (Vector3.right * Input.GetAxis ("Horizontal") + Vector3.forward * Input.GetAxis ("Vertical")) * moveSpeed;
    Move (direction);
}

void Move (Vector3 v) {
    charactorController.Move (v);
    if (v.magnitude > 0.01f) { // 向いている方向に向かせる
        transform.rotation = Quaternion.LookRotation (v);
    }
}

とりあえずこんな感じでセットします。 実際にはanimatorで歩くモーションを実装していますが今回は省いています。

あ、カメラはいい感じに(追従する感じなどなど)設定してくださいね!

攻撃する

さて、次はキモとなる攻撃部分を作っていきます。 スマブラではいくつか組み合わせ技がありますが、まずはパンチ一つに絞って実装します。(しかし複数使えるようにはしておく)

今回は与えるダメージ(吹っ飛ばし力)を計算するために必要なデータを準備します。

サンドバックくんのWeight50に設定し、 プレイヤのパンチの威力は以下の図のようになりました。

DMG BKB KBG
5.0 30 100

BKBやKBGについては

www30.atwiki.jp

上記サイトを参考にし、算出についてはスマッシュブラザーズ for WiiUの情報を参考にしました。

Duck Hunt - Kurogane Hammer

ちなみにダックハントのジャブと同じです。

以下のように先ほど動かすために作ったPlayer.csに追記します。

public Dictionary<string, Dictionary<string, float>> actions;
public enum Actions {
    panch
};
public Actions action;
public bool enableHit = false;

private void Start () {
    actions = new Dictionary<string, Dictionary<string, float>>();
    Dictionary<string, float> actionInfo = new Dictionary<string, float>();
    actionInfo.Add("DMG", 5.0f);
    actionInfo.Add("BKB", 30f);
    actionInfo.Add("KBG", 100f);
    actions.Add(Actions.punch.ToString(), actionInfo);
}

private void Update () {
    Attack();
}

public void Attack () {
    if (Input.GetMouseButton (0)) {
        animator.SetInteger ("Attack", 4);
        action = Actions.punch;
        StartCoroutine (AttackTime ());
    } else {
        animator.SetInteger ("Attack", 0); // Attack0にしないと無限に攻撃が動く
    }
}

private IEnumerator AttackTime () { // すこし時間を開けないと攻撃があたり判定されない
    enableHit = true;
    yield return new WaitForSeconds (0.5f);
    enableHit = false;
}

public float getDamage () {
    return actions[action.ToString()]["DMG"];
}

public float getKBG () {
    return actions[action.ToString()]["KBG"];
}

public float getBKB() {
    return actions[action.ToString()]["BKB"];
}

actionsのkeyには攻撃名、valueには与えるダメージが入るようになっています。 Enumで定義したActions一覧からactionsを作ります。

今回はアニメーションをSetIntegerで設定しました。 enableHittrueの時だけ相手に攻撃が当たるようにしています。

action = Actions.punch;

ここでは何の攻撃をしたかを残すようにしています。 これでクリックをすると攻撃アニメーションになり、攻撃可能時間が0.5秒だけ有効になります。(ちょっと微妙仕様)

攻撃を与える、受ける

さてついにサンドバックくんが攻撃を受けるようにします。 まずは攻撃を受ける部分をEnemy.csを作り書いていきます。これはサンドバックくんにコンポーネントとして貼り付けます。

private Rigidbody rb;
private float weight = 50f;

private void Start() {
    rb = GetComponent<Rigidbody>();
}

public void ReceiveDamage(Player target) {
    if (isHit) {
        isHit = false;
        hitpoint += target.getDamage();
        if (rb) {
            // ここのVector.up本当は技の角度とユーザーのスティックから変更するべき
            rb.AddForce((target.transform.forward + Vector3.up) * KnockBack(target));
        }
    }
}

private float KnockBack(Player target) {
    return ((hitpoint * (0.1f + target.getDamage() * 0.05f) * 200 / (weight + 100) * 1.4f + 18) * target.getKBG() * 0.01f + target.getBKB());
}

吹っ飛ばし力を計算する式が以下のサイトに載っていたので引用します。

www30.atwiki.jp

KB = [{敵% * (0.1 + ダメージ * 0.05) * 200 / (敵重量 + 100) * 1.4 + 18} * KBG * 0.01 + BKB] * 補正値

これが先ほどEnemy.csに記述したKnockBack関数になります。

次に攻撃した時に当たった判定をする部分を実装します。

まずは攻撃する側(プレイヤ)の手の部分にColliderをつけます。この時isTriggerにチェックを入れます。Rigidbodyはなくても良さそうです。 スクリプトを書き、手の部分にアタッチします。上の画像でいうとCollisionController.csになります。

public class CollisionController : MonoBehaviour {

    private Player player;
    private GameObject particle; // これは当たった時に発火するParticleなのでお好み
    private Enemy hitEnemy;

    // Use this for initialization
    void Start() {
        player = GameObject.FindGameObjectWithTag("Player").GetComponent<Player>();
    }

    // Update is called once per frame
    void OnTriggerStay (Collider other) {
        if (other.tag == "Enemy") {
            hitEnemy = other.GetComponent<Enemy> ();
            if (player.enableHit && hitEnemy) {
                if (particle == null) {
                    particle = (GameObject)Instantiate (player.hit_particles [0], this.transform.position, player.hit_particles [0].transform.rotation);
                    other.GetComponent<Enemy>().isHit = true;
                    other.GetComponent<Enemy>().ReceiveDamage(player);
                }
            } else {
                particle = null;
                hitEnemy = null;
            }
        }
    }
}

あとは最後にUIをごにょっとすると...

ちょっとスマブラっぽくなりました!

まとめ

今回はノックバックのみの実装になってしまいましたが、継続して開発して「タメ技」とか「バットを使ったタメ技」などを入れてみたいですね。 なかなかスマブラを作るのは厳しいのであれは7000円でも安いと思う...

みんな、、、スマブラ休暇とろうな!(嘘です!!!!!!!!!!!!!!!!!笑)

今回書いたコードは本当はInterfaceを作ってベースとなるクラスを用意して、Enemy、Playerに継承する形にしていました。 ただ、多分そこまで解説できなさそうだったので、冗長ですが、別ものとしてブログ用に実装しました。ホームランコンテスト自分が攻撃を受けることないですし!

明日は @tatsuo48さんのAWSの新サービスについて何か書いてくれるみたいです! お楽しみに!