【Unity】State Machine Behaviour について

はじめに

こんにちは、ソーシャルゲーム事業部のUnityエンジニアのアファトです。 この記事はカヤックUnityアドベントカレンダー2016の15日目の記事になります。

ゲームにおいて、モーションに合わせるロジックはいろいろな方法で実装できますが、場合によって、実装しづらいや調整しにくいケースがあります。そのために Unity 5 から導入された State Machine Behaviour という仕組みがあります。アニメーションロジック または アニメーションに近い・依存するロジックをこの仕組みを利用することで、実装しやすくなり、メンテナンスもしやすくなります。

State Machine Behaviourについて

State machine behavioursはMecanimで作られたAnimationのロジックをよりコントロールしやすくするため、Animator Controllerの中にあるanimator stateやsub-state machinesにアタッチできるscriptです。StateのinspectorのAdd Behaviourボタンを押すと State Machine Behaviour (SMB) が利用できます。 image

新しいScriptを作る場合、以下の様なテンプレートができます。

using UnityEngine;
using System.Collections;

public class NewStateMachineBehaviour : StateMachineBehaviour
{

    // OnStateEnter is called when a transition starts and the state machine starts to evaluate this state
    //override public void OnStateEnter(Animator animator, AnimatorStateInfo stateInfo, int layerIndex) {
    //
    //}

    // OnStateUpdate is called on each Update frame between OnStateEnter and OnStateExit callbacks
    //override public void OnStateUpdate(Animator animator, AnimatorStateInfo stateInfo, int layerIndex) {
    //
    //}

    // OnStateExit is called when a transition ends and the state machine finishes evaluating this state
    //override public void OnStateExit(Animator animator, AnimatorStateInfo stateInfo, int layerIndex) {
    //
    //}

    // OnStateMove is called right after Animator.OnAnimatorMove(). Code that processes and affects root motion should be implemented here
    //override public void OnStateMove(Animator animator, AnimatorStateInfo stateInfo, int layerIndex) {
    //
    //}

    // OnStateIK is called right after Animator.OnAnimatorIK(). Code that sets up animation IK (inverse kinematics) should be implemented here.
    //override public void OnStateIK(Animator animator, AnimatorStateInfo stateInfo, int layerIndex) {
    //
    //}
}

そこにStateMachineBehaviourを継承して、五つ関数をoverrideします:

  • OnStateEnter stateに入るときに一回呼ばれます。初期化のためなど
  • OnStateUpdate stateの中に行うUpdateはこの関数で対応できます
  • OnStateExit stateが完了する時、ほかのstateに移動するとき一回呼ばれます。clean upのためなど
  • OnStateMove root motionに関するロジックはこの関数で対応できます
  • OnStateIK Inverse Kinematics に関するロジックこの関数で対応できます

この五つ関数は全部同じ引数が渡されます: animatoranimatorStateInfolayerIndex

  • animator はアタッチされるstate machine behaviourのAnimatorのコンポーネントの参照です。このコンポーネントに GameObject の参照があるので MonoBehaviour とのやり取りができます。
  • animatorStateInfo は現状 state の状態の情報です。animation clip の長さに対する正規化した time などの情報が入ってます。
  • layerIndex は state machine behaviour のstateのレイヤーの情報。ベースレイヤーだと 0 になります。

State Machine Behaviourで出来ること

State に依存するロジックなどは SMB で実装しやすくなります。以下の Animator Controller で SMB ができることを試しに作ってみましょう。

image

5つ states: ChargeJumpAttackReturnEnd というアニメーションの流れで、End以外の state に入る時に Sound Effect (SE) を再生できる SMB を作ります。

using UnityEngine;
using System.Collections;

public class SoundEffectPlayerSMB : StateMachineBehaviour
{
    public AudioClip soundEffectClip;
    public float delay;

    private float elapsedTime;
    private AudioSource audioSource;

    // OnStateEnter is called when a transition starts and the state machine starts to evaluate this state
    override public void OnStateEnter(Animator animator, AnimatorStateInfo stateInfo, int layerIndex)
    {
        if (null == audioSource)
        {
            // 例えば AudioSource というマネージャーコンポーネントがあったら
            var audioSourceObj = GameObject.Find("AudioSourceManager");
            audioSource = audioSourceObj != null ? audioSourceObj.GetComponent<AudioSource>() : null;
        }

        if (null != soundEffectClip && 0 >= delay)
        {
            PlaySoundClip();
        }
    }

    // OnStateUpdate is called on each Update frame between OnStateEnter and OnStateExit callbacks
    override public void OnStateUpdate(Animator animator, AnimatorStateInfo stateInfo, int layerIndex)
    {
        if (null == soundEffectClip)
        {
            return;
        }

        elapsedTime += Time.deltaTime;
        if (elapsedTime >= delay)
        {
            PlaySoundClip();
        }
    }

    private void PlaySoundClip()
    {
        if (null == audioSource) { return; }
        if (null == soundEffectClip) { return; }

        audioSource.PlayOneShot(soundEffectClip);
        soundEffectClip = null;
    }
}

上記にの SMB script ができたら、state ごとにアタッチすると、その state に入る時に設定した audio clip と delay で SE が再生してくれます。 image

image

おわりに

State に依存するロジックは SMB script で実装しやすいし、メンテナンスもしやすくなりますので、結構便利に使えると思います。

明日は UI アニメーションなどによく使われる Tweening についての話になります。担当はです。お楽しみに〜

Docker Composeで開発環境を作る

Docker Composeはいいぞ!

この記事はTech KAYAC Advent Calendar 2016の15日目の記事です。

こんにちは
この6月にKAYACに中途入社し技術部でゲームのサーバーサイドを担当しているひだかです。
新卒でも中途入社でも研修などを終え実務に入った時に一番最初に悲しい思いをするのは開発環境構築の作業ではないでしょうか?
実際に私もあまり整備されていないドキュメントを何度も見て、長い時間をかけやっと構築完了したと思ったらミドルウェアのバージョンが違うとかで序盤からやり直しということがありました。
知らない言語やOSやミドルウェアやフレームワークやモジュール群を前に新人はあまりにも無力です。

そんな時はInfrastructure as Codeです。Dockerを使うのです。
誰でもボタンポチーで開発環境を立てられるDockerを使うのです。
幸いなことにDocker for Mac/Windowsが今年7月に正式リリースされ、つい先日にもDocker for Aws/Azureが発表されローカルでもリモートでもDockerを触れる環境が充実しました。
確実に機運が高まっています。乗りましょうビッグウェーブに。


f:id:mamimumemoomoo:20161214222333p:plain
Uninstall寸前のDockerのクジラもかわいいですね

Docker Compose軽く説明

Dockerをとても簡単に説明すると、設定をコードとして書ける軽い仮想環境(コンテナ)です。
Docker Composeは名前の通り複数のコンテナをまとめて管理や操作ができるものです。

例えば簡単なアプリケーションの環境の開発環境を立てたい時にアプリケーション用コンテナ、DB用コンテナ、KVS用コンテナの3コンテナを用意する必要があり、 起動するだけでもDockerだけの場合はそれぞれのコンテナに対してdocker runしないといけません。
一方、Docker Composeの場合はdocker-compose upというコマンドだけで3コンテナを起動してくれます。
またDocker Composeは簡単な定義のみでコンテナ間の通信ができるようになります。

Docker Composeでしたこと

今回は普段MacのVagrant上で動かしていた開発環境をDocker Composeに置き換えました。
上記の開発環境構築がつらい問題に加え、本番DBはマスタスレーブ構成なのに開発環境では単一のDBのためレプリケーションとトランザクションまわりのCIや確認ができないといった問題もありました。

なので今回作ったDocker Composeは以下の4つのコンテナから構成しています。

  • アプリケーション用コンテナ
  • KVS用コンテナ
  • マスタDB用コンテナ
  • スレーブDB用コンテナ

実装

自由な社風の会社とはいえ、仕事用に作った定義ファイルを公開すると怒られるので上記の構成を最低限にまとめました。

github.com


レプリケーション部分は以下のブログを参考にさせていただきました。
ありがとうございます。

techblog.kayac.com

tkyshm.hatenablog.com

qiita.com

各ファイルに書いていることは基本的なことなので公式を見れば分かるかなという感じです。

Compose ファイル・リファレンス — Docker-docs-ja 1.12.RC ドキュメント

動かしてみる

前準備

Docker for MacやDocker for Windowsをインストールしてdockerコマンドが使えるようにしておいてください。

$ docker -v
Docker version 1.12.3, build 6b644ec

Docker Composeを立ち上げる

// とりあえずclone
$ git clone https://github.com/mamimumemoomoo/docker-compose-base.git
$ cd docker-compose-base/

// コンテナを立ち上げるためのイメージを作る
$ docker-compose build

// コンテナを立ち上げる 標準出力で立ち上げ時のログが流れてるのが分かる
$ docker-compose up

// 正常に立ち上がったらこんな感じ
// docker-compose upの1コマンドで4つのコンテナが立ち上がったのが分かる
$ docker-compose ps
          Name                         Command               State          Ports
-----------------------------------------------------------------------------------------
dockercomposebase_app_1     /bin/bash                        Up      0.0.0.0:8080->80/tcp
dockercomposebase_db_r_1    docker-entrypoint.sh mysqld      Up      3306/tcp
dockercomposebase_db_w_1    docker-entrypoint.sh mysqld      Up      3306/tcp
dockercomposebase_redis_1   docker-entrypoint.sh redis ...   Up      6379/tcp

// コンテナにアクセスするのにCONTAINER IDが必要なのでdocker psコマンド実行
$ docker ps
CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS              PORTS                  NAMES
6949824352e1        app                 "/bin/bash"              3 minutes ago       Up 3 minutes        0.0.0.0:8080->80/tcp   dockercomposebase_app_1
2377ec307496        mysql:slave         "docker-entrypoint.sh"   3 minutes ago       Up 3 minutes        3306/tcp               dockercomposebase_db_r_1
7ba45c25402e        mysql:master        "docker-entrypoint.sh"   3 minutes ago       Up 3 minutes        3306/tcp               dockercomposebase_db_w_1
09022930aca0        redis:3.0           "docker-entrypoint.sh"   3 minutes ago       Up 3 minutes        6379/tcp               dockercomposebase_redis_1

コンテナにアクセスと機能のチェック

// appのコンテナにアクセス ハッシュはpsのCONTAINER IDに応じて
$ docker exec -it 6949824352e1 bash
[root@6949824352e1 /]# 

// コンテナ内でDBを見てみる
// -h db_wでマスタ用DBアクセス、-h db_rでスレーブ用DBアクセスできる
[root@6949824352e1 /]# mysql -u root -h db_w -proot -e "show databases;"
+--------------------+
| Database           |
+--------------------+
| information_schema |
| mysql              |
| performance_schema |
| sys                |
+--------------------+
[root@6949824352e1 /]# mysql -u root -h db_r -proot -e "show databases;"
+--------------------+
| Database           |
+--------------------+
| information_schema |
| mysql              |
| performance_schema |
| sys                |
+--------------------+

// db_wのみにcreate databaseして、db_rにレプリカされてるのを確認
[root@6949824352e1 /]# mysql -u root -h db_w -proot -e "create database docker_compose_saikou;"
[root@6949824352e1 /]# mysql -u root -h db_w -proot -e "show databases;"
+-----------------------+
| Database              |
+-----------------------+
| docker_compose_saikou |
| information_schema    |
| mysql                 |
| performance_schema    |
| sys                   |
+-----------------------+
[root@6949824352e1 /]# mysql -u root -h db_r -proot -e "show databases;"
+-----------------------+
| Database              |
+-----------------------+
| docker_compose_saikou |
| information_schema    |
| mysql                 |
| performance_schema    |
| sys                   |
+-----------------------+

// redisにも接続できることを確認
// -h redisでredis用コンテナに接続
[root@6949824352e1 /]# redis-cli -h redis
redis:6379> set "docker-compose"
(error) ERR wrong number of arguments for 'set' command
redis:6379> set key "docker-compose"
OK
redis:6379> get key
"docker-compose"

// http確認 まだhttpサーバ動かしてないのでFailed connect
[root@6949824352e1 /]# curl localhost
curl: (7) Failed connect to localhost:80; Connection refused
// nginx起動
[root@6949824352e1 /]# nginx
// http確認
[root@6949824352e1 /]# curl localhost
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
... HTML省略 ...

// MacやWindowsなどのホスト側で見られるか確認
// コンテナの80番ポートをホスト側の8080番ポートに転送してる
$ curl localhost:8080
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
... HTML省略 ...

// 起動中のコンテナを落とす
$ docker-compose down

はい、以上で最小構成の動かし方と機能の確認でした。

今後の展望

  • まだまだ使用実績がないので使い倒す
  • 安定したらとりあえずチーム内で布教する
  • CI用サーバもDocker Composeにする(マスタスレーブ構成にしたい)
  • 社内で確認や検証をする際にmirageを使っていて、ここのDockerをDocker Compooseにする(マスタスレーブ構成にしたい)

明日のTech KAYAC Advent Calendar 2016

明日のTech KAYAC Advent Calendar 2016もりごさんです!

「んー、ヒャー 驚きました ヒャー こんなことがあるんですねっていう大逆転ですね ヒャー」という意気込みらしいのでとても楽しみです!

f:id:mamimumemoomoo:20161215102842p:plain