【Unity-Chan】HeartCatch【音楽アクション】

ニコニコ自作ゲームフェス2016に応募してみた。
http://www.nicovideo.jp/watch/sm28018281

![image](https://qiita-image-store.s3.amazonaws.com/0/96808/6d920a34-5940-bba4-602e-ce157028ca99.png)

#[音楽アクション]
年末くらいから作成開始して
音楽に合わせて何かすると楽しいようなものを目指していて、
ジャムセッションにあるような、交互にソロをまわすような構成で
音楽に合わせて操作して、操作音が演奏になったら楽しいのではと思って作ってみたものだったりします。

![image](https://qiita-image-store.s3.amazonaws.com/0/96808/61dfc18b-81e6-eeff-b087-a323fb5d35e2.png)

#[きっかけと経緯]
もともとは年末のMusic2Gameのあとの改善例として
http://www.nicovideo.jp/watch/sm27912479
タイミングだけでもと交互にして操作をつけて、ついでにいろいろメカニムとかでの2DスプライトとかUIとかUnityChanとかいろいろいじっていたらこうなった。

![image](https://qiita-image-store.s3.amazonaws.com/0/96808/7f9ef8f9-139e-e7d0-b773-a14578a64bd6.png)

#[交互に操作する]
作っていて気がついたけどパラッパラッパーの構成だった。交代する対象がもっと多いとどうなるかと4人にふやしてみた。
ルールはなるべくシンプルにハートをあつめるだけ。

![image](https://qiita-image-store.s3.amazonaws.com/0/96808/70504331-d8f7-d994-839a-e9d675811d3f.png)


#[メカニム2D]
アニメーションの制御をUnityChan2Dをみて見よう見まね。

![image](https://qiita-image-store.s3.amazonaws.com/0/96808/027aeb92-afc7-46e9-b74e-b27da3ced21a.png)

![image](https://qiita-image-store.s3.amazonaws.com/0/96808/4b2dfe24-b288-7b66-db6b-7b42bac0076d.png)

操作した方向に合わせてキャラ絵の差分を用意して、スプライトが変化するようにメカニムやアニメーションを設定して、
スクリプトからTriggerしてステートの遷移をしている。まだまだわからないところ多い。

``` a.cs
 public void OnLeft()
    {
        if (moveFlag)
        {
            if (buttons.Count > 0)
            {
                buttons [0].animator.SetTrigger(buttons [0].animationTriggers.pressedTrigger);
            }
            Move(new Vector2(-2f, 0.1f));

            m_animator.SetTrigger("GohstL");　//メカニムへトリガ

            audioSource.clip = clipList [0];
            Music.QuantizePlay(audioSource, 11 + baseKey + (miniorFlag ? -1 : 0));   //  第三音はマイナーかどうか も加味
            //  方向によって音を変更
        }
    }
```

![image](https://qiita-image-store.s3.amazonaws.com/0/96808/33dc1889-68f3-5fee-d615-9836255a0fb3.png)

#[ステージ管理]
![image](https://qiita-image-store.s3.amazonaws.com/0/96808/56f2a992-7e18-1ccc-4e89-79ec1f880c20.png)
シーンごとにステージを分けていて、StageDataが曲ごとの譜面データの役割をになっている。
これで、曲のビートや拍、セクションごとの展開が行われるようになる。

![image](https://qiita-image-store.s3.amazonaws.com/0/96808/4f939aa1-9341-edd5-97fc-1992254d89a1.png)


![image](https://qiita-image-store.s3.amazonaws.com/0/96808/89038717-c91b-5f1a-91dc-b357244d885d.png)

#[Vocaloid with Unity]
リアルタイムで合成できるので軽い実況的に、チームの勝ち負けをブリッジでしゃべらせている。
スコアの読み上げとか、歌わせるまでは至っていない。
あと、iOS/Macでのビルドは調べたけど、WindowsとかAndroidビルドはためせていない。

![image](https://qiita-image-store.s3.amazonaws.com/0/96808/71532e51-d350-bc0a-60cd-7a16b770d359.png)

![image](https://qiita-image-store.s3.amazonaws.com/0/96808/e7e72dcb-30df-bfaf-3abd-1df2564db88c.png)

#[MusicEngine]
ゲームの進行と音楽同期は[MusicEngine](https://github.com/geekdrums/MusicEngine)をベースにしていて、
セクションの命名規則からステージ毎にイントロ、操作プレーヤーのきりかえタイミング、ブリッジ、エンディングが制御されている。
進行状況のバー表示のビート同期演出とか、カメラ移動とかいろいろ利用してみた。
まれにIsJust系が呼ばれていなかったり、クオンタイズ同時発音で音が鳴っていないものとかまだ動作でわからないところもある。

MusicEngineの改良としては、スクリプトからのセクション定義指定、次のセクションまでのビート数取得とか、次のセクションへのスキップ機能とか。セクション内での何ビート目とかのステータスのとりまわしの拡張がなかなか難しかった。
（次回作はセクション毎に相対値ビート数でコードとかスケール指示用のタグとかでの定義を入れたいところ）

``` a.cs
    void Update()
    {
        if (Music.IsPlaying)
        {

            if (Music.IsJustChangedSection())
            {
                if (introductionPlate)
                {
                    if (Music.CurrentSection.Name == ("Intro") || Music.CurrentSection.Name.StartsWith("Bridge"))
                    {
                        introductionPlate.SetActive(true);
                    } else
                    {
                        introductionPlate.SetActive(false);
                    }
                }

                int count = 0;
                foreach (GameObject tmpPanel in playerPanels)
                {
//                tmpPanel.SetActive(false);
                    PanelDeactive(tmpPanel, playerManager.players [count].playerColor);    //  ボタンをPlayerカラーに
                    count++;
                }

//            Debug.Log("CurrentPanel " + playerManager.currentPlayerId.ToString());

                if (playerManager.currentPlayerId == 0)
                {   
                    sectionText.text = Music.CurrentSection.Name;
                } else
                {
                    PanelActive(playerPanels [playerManager.currentPlayerId - 1], playerManager.players [playerManager.currentPlayerId - 1].playerColor);    //  ボタンをPlayerカラーに

                    sectionText.text = "";
                }
            }

            if (Music.IsJustChangedBar())
            {   
                if (Music.CurrentSectionIndex != Music.SectionCount - 1 && Music.GetSection(Music.CurrentSectionIndex + 1) != null)
                {
                    //  sliderBarの現在値を得る
                    float sliderValue = (float)(Music.Just.Bar + 1 - Music.CurrentSection.StartBar) /
                                        (float)(Music.GetSection(Music.CurrentSectionIndex + 1).StartBar - Music.GetSection(Music.CurrentSectionIndex).StartBar);

                    StartCoroutine("MoveBarPos", sliderValue);
                }

                if (playerManager.currentPlayerId == 0)
                {
                    {
                        Image[] images = sliderSection.GetComponentsInChildren<Image>();
                        images [0].color = new Color(0.0f, 0.0f, 0.0f, 0.5f);
                        images [1].color = new Color(0.9f, 0.9f, 0.9f);
                    }

                    {
                        Image[] images = sliderBeat.GetComponentsInChildren<Image>();
                        images [0].color = new Color(0.0f, 0.0f, 0.0f, 0.5f);
                        images [1].color = new Color(0.9f, 0.9f, 0.9f);
                    }
                } else
                {
                    {
                        Image[] images = sliderSection.GetComponentsInChildren<Image>();
                        images [0].color = new Color(0.2f, 0.2f, 0.2f, 0.3f) * playerManager.players [playerManager.currentPlayerId - 1].playerColor;
                        images [1].color = playerManager.players [playerManager.currentPlayerId - 1].playerColor;    //  バーをPlayerカラーに
                    }
                    {
                        Image[] images = sliderBeat.GetComponentsInChildren<Image>();
                        images [0].color = new Color(0.1f, 0.1f, 0.1f, 0.3f);
                        images [1].color = playerManager.players [playerManager.currentPlayerId - 1].playerColor;    //  バーをPlayerカラーに
                    }
                }
            }

            if (Music.IsJustChangedBeat())
            {   
                StartCoroutine("BounceBar");
                StartCoroutine("BouncePanel");


                barCountDownText.text = "";
                //  もしセクション最後の小節だった場合
                if(Music.CurrentSectionIndex != Music.Current_.Sections.Count -1 && Music.Current_.Sections[Music.CurrentSectionIndex+1].StartBar-1 == (int)Music.MusicalTimeBar){
                 
                    //  sliderBarの現在値を得る
                    float sliderBeatValue = (float)(Music.Just.Beat + 1) /
                                            (float)(Music.GetSection(Music.CurrentSectionIndex).UnitPerBeat);
                    //  カレントのビート数
                    //  カレントのビートtotal数
                
                    StartCoroutine("MoveBeatPos", sliderBeatValue);
                }
            }
        }
    }
```

![image](https://qiita-image-store.s3.amazonaws.com/0/96808/73ee8108-b770-ab56-25b1-1c47efa6f202.png)


#[ゲーム内容について]
ハートを集めるのが目的で、単に操作が不自由な感もある。
曲の長さに対して単調なので、構成とかで、操作楽器やフレーズ、スケールなど変化が欲しいところ。

![image](https://qiita-image-store.s3.amazonaws.com/0/96808/44f05dba-37ad-13de-53c2-eb13b43f1a90.png)

#[ばね面白い]
今回作って思いの外よかったのは、放置していてもそれなりに動作する「ばね」の存在。
マリオメーカーの全自動マリオにはまったときに「ばね」
面白いなと。
今は縦のばねしかないけど、横ばねとか、ばね自体も動き回るとかでカオスになりそう。

![image](https://qiita-image-store.s3.amazonaws.com/0/96808/080586ea-6857-a7bc-f4a4-39be29d02ad7.png)

``` a.cs
public class JumpStand : MonoBehaviour
{
    private Animator m_animator;

    void Awake()
    {
        m_animator = GetComponent<Animator>();
    }

    void OnCollisionEnter2D(Collision2D collision)
    {
        if (collision.gameObject.tag == "Player" || collision.gameObject.tag == "UnityChan"|| collision.gameObject.tag == "heart"|| collision.gameObject.tag == "Star")
        {
            m_animator.SetTrigger("Jump");

            StartCoroutine(PlayerDamageMove(collision));

            if (collision.gameObject.tag == "Player")
            {
                PlayerController pc = collision.gameObject.GetComponent<PlayerController>();
                pc.OnSpring();
            }
        }
    }

    IEnumerator PlayerDamageMove(Collision2D collision)
    {
        // 弾かれる
        Vector2 relativeVelocity = collision.relativeVelocity;

        relativeVelocity.y = Mathf.Clamp(relativeVelocity.y,0.5f,0.8f);   //  この範囲で
        relativeVelocity.x = Mathf.Clamp(relativeVelocity.x*-0.2f,-0.8f,0.8f);

        relativeVelocity*= 10f;

        int count = 0;
        while (true)
        {
            if(collision != null && collision.gameObject != null){
                collision.gameObject.GetComponent<Rigidbody2D>().velocity = relativeVelocity;
            }
            yield return null;
            count++;
            if (count == 1)
                break;
        }
    }
}
```

音符ブロックとかもいいな。曲のスケールに合わせた音が鳴るとか、マリオメーカーのエディット時みたいに、動作中にメロディーがボコーダー風になぞられるような音オフのもいいかも。

![image](https://qiita-image-store.s3.amazonaws.com/0/96808/58c9470b-3f67-1da2-3cc7-952507251abb.png)

#[楽曲について]
曲はPDになったジャズスタンダードとUnityChanライセンスの曲だけど、こちらもバリエーションとかの方向もあり。
増やすにはもう少し楽に追加できるシステムが欲しいところで、
マインクラフト的な自動生成、ビブリボン的な自動解析、自動割り当て的なところが必要かも。

* [I got rhythm](https://ja.wikipedia.org/wiki/%E3%82%A2%E3%82%A4%E3%83%BB%E3%82%AC%E3%83%83%E3%83%88%E3%83%BB%E3%83%AA%E3%82%BA%E3%83%A0) (編曲:tatmos) 3:08,
* [ornithology](https://en.wikipedia.org/wiki/Ornithology_(composition)) (編曲:tatmos) 4:14,
* [unity in the sky](http://unity-chan.com/) (編曲:tatmos) 4:12

#[これからの課題として]
これはプロトタイプで、とりあえず完成ということにして、

つくっていて新たな欲望としては

・曲の構成、コード進行、音楽的要素を取り入れたい
　今は曲のキーとメジャー／マイナーまで対応
　メロディーをユニゾンするとか、キメとかリフがあるとか、
　前の人の演奏の操作に似せるとか似せないとかでボーナスとか。
・UnityChanのしゃべれないボイスの対応
　今は「じゃ」とか一部リアルタイムで認識しないひらがながあるみたい。実況歌詞生成とか。
・星とか取得できると何かおこるとか
・味方に体当たりした時のエフェクトとか（BeamManPさんのとか入れたい）

#[おまけ：タイトルから曲のフェードアウト処理]
[動画](http://www.nicovideo.jp/watch/sm28018281)の0分49秒あたりですが、フェードアウトにExp関数使ってみた。
すこし「しっとり」としたフェードアウトになります。

関数f(x)の描画 - 高精度計算サイト
https://keisan.casio.jp/exec/system/1180917567

関数グラフGeoGebra
https://www.geogebra.org/graphing?lang=ja

![image](https://qiita-image-store.s3.amazonaws.com/0/96808/79722b99-7d71-390b-ed6e-5bea88375f69.png)


``` a.cs
    IEnumerator GoScene(string sceneName)
    {
        if (vocAudioManager == null)
        {
            GameObject go = GameObject.Find("VocaloidManager");
            vocAudioManager = go.GetComponent<VocAudioManager>();
        }

        int count = 0;

        faderImage.gameObject.SetActive(true);
        faderImage.color = new Color(0,0,0,0);

        while (true)
        {
            Music.SetVolume(0f-( 1.0f - Mathf.Exp(-5f*(float)count/10f)-1f));

            faderImage.color = new Color(1,1,1,1.0f - Mathf.Exp(-3f*(float)count/10f));
            
            yield return new WaitForSeconds(0.1f);

            count++;
            if (count == 11)
                break;
        }

        faderImage.color = new Color(1,1,1,1);
        vocAudioManager.Stop();
        SceneManager.LoadScene(sceneName);
    }
```
#[おまけ２：ダッキングつかってみた]
![image](https://qiita-image-store.s3.amazonaws.com/0/96808/58ad5911-ce0d-4ff6-a51f-b016bd5c0c7f.png)
ものすごくうっすらとですがVocaloid発話中はBGMが気持ち若干さがります。
これ効果が過敏に反応するので使いどころ難しい。
おそらくもっと頻度の低い特別な音に使うと印象的になるはず。