【Unity(C#)】unity1weekで共同開発に初挑戦した振り返り

##unity1week

不定期で行われているunity1weekというゲームジャムに参加しました。

>Unityを使って1週間でゲームを作るイベントです。
UnityがインストールされたPCとインターネット環境があればどなたでも参加可能です。
月曜0時にお題が発表されます。日曜20時までにゲームを作って投稿しましょう。
お題については多少こじつけでも大丈夫。ゲーム作りを楽しむことを心がけてください。

【引用リンク】：[unityroom](https://unityroom.com/unity1weeks)

##共同開発

私自身参加は6回目なのですが、今回の参加においては共同開発に挑戦しました。
こんな感じのノリで始まりました。
<img width="200" alt="GWunity.PNG" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/244071/269f42e7-b31a-88aa-231a-ccea54cd58f8.png">

[この友人](https://twitter.com/MShakariki)のお仕事は、プログラムを書いてお金をもらうお仕事でもないですし、
理系でコンピューターサイエンスを専攻してたとかでもない、"ただただ興味がある"
という状態だったのでまずは一緒に何か動かしてみることにしました。

##Day0
unity1week開催前日にDiscordで通話して画面共有を行いながら
同じ手順を踏んでもらう形で**ボールをキー入力で動かす**というゲーム？を作りました。

![Mitsu0.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/244071/4d672b8c-161a-0000-c1dc-233c843ef73b.gif)


```C#
using UnityEngine;

/// <summary>
/// Inspectorで動かしたいSphereにAdd Component
/// </summary>
public class BallMove : MonoBehaviour
{
    //ボールを動かす力を調整
    //[SerializeField]でInspectorに表示
    //[Range(最小値,最大値)]でInspectorにスライダーを表示
    [SerializeField, Range(0, 100)] private float power = 10;

    //Rigidbody：物理挙動に関する設定を簡単に扱える便利Component
    //Rigidbodyの入れ物を用意する
    private Rigidbody rb;

    //最初にStart関数内に書いた処理が実行される
    private void Start()
    {
        //先程用意した入れ物にRigidbodyを入れる
        
        //この.ゲームオブジェクトから.コンポーネントを取得<Rigidbodyを指定>
        //this.gameObject.GetComponent<Rigidbody>();
        rb = this.gameObject.GetComponent<Rigidbody>();
    }

    //Start関数内に書いた処理が実行されたあと、Update関数内に書いた処理が実行される
    //Updateは毎フレーム呼ばれる
    //1秒間に60回フレームが更新されるなら→60fps
    private void Update()
    {
        //Rigidbodyクラスに定義されているAddForceという関数を使う

        //もし、右矢印キーを押していたら、、、
        if (Input.GetKey(KeyCode.UpArrow))
        {
            //ワールド空間の指定した方向に任意の力を加える
            rb.AddForce(Vector3.forward * power);
        }

        //もし、下矢印キーを押していたら、、、
        if (Input.GetKey(KeyCode.DownArrow))
        {
            //ワールド空間の指定した方向に任意の力を加える
            rb.AddForce(Vector3.back * power);
        }

        //もし、右矢印キーを押していたら、、、
        if (Input.GetKey(KeyCode.RightArrow))
        {
            //ワールド空間の指定した方向に任意の力を加える
            rb.AddForce(Vector3.right * power);
        }

        //もし、左矢印キーを押していたら、、、
        if (Input.GetKey(KeyCode.LeftArrow))
        {
            //ワールド空間の指定した方向に任意の力を加える
            rb.AddForce(Vector3.left * power);
        }
    
}

```
友人に教えている最中に
**あまりにも書いてあることがわからなさすぎて技術書を噛みちぎろうとした日**を思い出しました。
(まあまあの値段がしたので踏みとどまりました)

**何もわからない状態からなら、丁寧すぎて逆に理解しづらいかな？という懸念は一旦捨てて良い**
と仮説を立てて"くどい"くらいにコメントを書きました。

##Day1
unity1weekは、開始と同時にお題が発表され、
そのお題に関連性のあるゲームを開発するというルールがあります。

今回のお題は「密」でした。

Day1はどんなゲームを作るかアイデア出しを行いました。

アイデア出しの進め方としては、「密　熟語」で検索して
アイデアのベースになりそうなワードを列挙しました。

そして、そのワードからイメージを派生し、ゲーム性、実現可能性について検討しました。

---
★実際のメモ
<img width="317" alt="Day1メモ.PNG" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/244071/2565fd63-70ab-16cb-fc26-67257c221802.png">
---

アイデア出しに関しては[unity1week online共有会 #1](https://www.youtube.com/watch?v=-qWwYVWgczA&feature=youtu.be&fbclid=IwAR2X_qscngSmSgSB0v0z2izSRgFYJMk5f07JIBZgcOQLILnlGivHJZS0u6k)(24:10～)
でお話してるので参考にしてみてください。


実際にできたアイデアのデザインモックがこちらです。
[Getting Over It With Bennett Foddy](https://store.steampowered.com/app/240720/Getting_Over_It_with_Bennett_Foddy/?l=japanese)　×　[モンスト](https://www.monster-strike.com/)
をイメージしたゲーム性です。

<img width="250" alt="キャプチャ.PNG" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/244071/b78daa4a-7e7f-27b7-6da7-d407f8327dea.png">



##Day2
下記リンクを参考に**Unity Collaborate**の環境構築を完了させました。

【参考リンク】：[【Unity】Unity Collaborateでリアルタイム通信コンテンツの制作フローを効率化](https://qiita.com/OKsaiyowa/items/4c314b33f583c9d1f798)

いきなりのGitはハードルがさすがに高いと思ったので試みましたが、
大正解でした。本当に簡単にプロジェクトの共同編集が可能なので、超おススメです。

##Day3
この日からはひたすら実装です。
先ほどのデザインモックの段階でゲーム性の関してはある程度固まっていましたが、
**"何を"飛ばすか**がまだ決まっていませんでした。

<img width="250" alt="キャプチャ.PNG" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/244071/423c4ad9-f0c9-f0ac-0736-fb66da568b5e.png">

ただ、引っ付いた際、発射する際の**アニメーションを自分で作るのが面倒**だったので
**手足の無い無機質な生命体**の方が楽できそうだなと
実装可能性の観点からこの時点でアイデアは絞れていました。

**手足の無い無機質な生命体**で思いついたのはスライムです。
壁に密着するというアイデアにシナジーも生まれます。

スライムでアセットストアで検索した際に欲しかった形の
**無機質な生命体**を発見したので採用しました。
<img width="615" alt="SlimePBR.PNG" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/244071/b0ae5dfa-a388-c42c-65a4-4c6d2ebe1d08.png">

ただ、**スライムの引っ付いたり弾んだりする質感を実装で再現するのが面倒**だったので
ここでもう一段階、実装を楽にするための設定を考えました。

ズバリ、**磁力**です。

この設定の付与で実装のイメージが一気に固まりました。

この日はキャラクターを"飛ばす"、それに伴う"補助線"の実装で終了しました。

![Mitsu1.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/244071/f21ed77e-d8cc-2d9c-41fe-fa824482fb69.gif)

![Mitsu2.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/244071/5932e051-ff2a-ce47-0af4-7a1291d34037.gif)


##Day4

磁力の実装に着手しました。

![Mitsu3.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/244071/0ad94fc2-3b98-9d7d-d2ba-e097b7782120.gif)

磁力の実装には万有引力の公式を利用しています。

---
**F[N]**：質量m₁[kg]と質量m₂[kg]の２つの物体の間に働く万有引力の大きさ
**r[m]**：物体間の距離
**G**：万有引力定数(G=6.673×10-11N・m2/kg2) ※今回はここを係数にして力を調整可能に

```math
F = G\frac{m₁m₂}{r^2}
```

---

ただ、このまま利用すると**m₁とm₂の物体間距離が遠くなればなるほど
力が弱くなる**という性質になり、なかなか気持ち良い挙動にはなりませんでした。

なので、**m₁とm₂の物体距離が遠くなればなるほど力が強くなる**式に変更して利用してみました。

```math
F = Gm₁m₂r^2
```

望み通りの挙動になったのですが、
質量は今回関与する必要はないので下記が最終的な計算式です。

```math
F = Gr^2
```

私がほしかったのはただの指数関数だったわけですね。

###コード


```C#:磁力として扱うオブジェクトにアタッチ
/// <summary>
/// 磁力の表現　万有引力の計算式を利用
/// S,Nでそれぞれベクトルが逆になる
/// 磁力として扱うオブジェクトにアタッチ
/// </summary>
public class MagnetFunction : MonoBehaviour
{
    private enum MagnetState
    {
        S,
        N
    }

    [SerializeField] private float _accelerationScale;
    [SerializeField] private MagnetState _magnetState;

    private Rigidbody _collisionObjRigidbody;
    private Vector3 _direction;

    private float _distance;
    private float _magnetPower;

    private void OnTriggerEnter(Collider other)
    {
        //Start関数内で名指しで取得する実装でも良いかも
        _collisionObjRigidbody = other.gameObject.transform.root.GetComponent<Rigidbody>();
    }

    private void OnTriggerStay(Collider other)
    {
        //衝突したキャラがSかNの判定を入れた方がよさそう
            switch (_magnetState)
            {
                case MagnetState.S:
                    // 星に向かう向きの取得
                    _direction = this.gameObject.transform.position - other.transform.position;
                    // 星までの距離の２乗を取得
                    _distance = _direction.magnitude;
                    _distance *= _distance;
                    // 万有引力計算
                    _magnetPower = _accelerationScale * _distance;
                    // 力を与える
                    _collisionObjRigidbody.AddForce(_magnetPower * _direction.normalized, ForceMode.Force);
                    break;

                case MagnetState.N:
                    // 星に向かう向きの取得
                    _direction =  other.transform.position - this.gameObject.transform.position;
                    // 星までの距離の２乗を取得
                    _distance = _direction.magnitude;
                    _distance *= _distance;
                    // 万有引力計算
                    _magnetPower = _accelerationScale * _distance;
                    // 力を与える
                    _collisionObjRigidbody.AddForce(_magnetPower * _direction.normalized, ForceMode.Force);
                    break;
            }
    }
}
```

InspectorでS極N極それぞれの役割を変更できるようにしました。
S,Nそれぞれの機能でクラスを用意しても良いかと思います。

磁力としてBox Colliderのみを保持するオブジェクトを用意し、
その子に磁石として扱うオブジェクトを配置します。

磁力のコライダーの範囲内に留まっている間は
磁力が働く仕組みです。

<img width="164" alt="NCube.PNG" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/244071/d941c37e-4785-a8c2-c752-d84f577060da.png">

##Day5
友人から稼働時間を確保できると連絡がきたので
タイトルシーンを作ってもらうことにしました。

簡単なデザインモックを渡した後、
適当に調べてもらって分からないことがあれば
逐一、Slackで聞いてもらう形を取りました。

<img width="383" alt="TitleMock.PNG" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/244071/99d17951-ba6c-db71-7457-7b37260de522.png">

すぐ作ってくれたので助かりました。
友人もUnityもすごい。
![MitsuMasuo1.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/244071/31f3e481-19c5-5343-69c5-519b78a94905.gif)

私は 磁力の調整、および発射パワー調整を行いました。

##Day6

下記を頑張りました。

・ステージの実装(チュートリアルっぽいやつ)
・音探して編集して入れたり
・パワーの入力タイミングに制限を加えた
・パワー入力タイミングに関して画面に出す画像を用意してプレイヤーに伝えられるようにした
・フェードインアウト実装

![Mitsu5.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/244071/919ec2d5-3ec8-e189-7e05-72e01128d2c7.gif)


###フェードインアウト

DoTween(無料版)を使って実装しました。

実際に使ったコードはもっと乱雑でしたが、
次回からはこのようなUtilityを用意しておいて最小限の労力で臨もうかと思ってます。

```C#
using DG.Tweening;
using UnityEngine;
using UnityEngine.EventSystems;

/// <summary>
/// 任意のUIを表示、非表示に関するユーティリティークラス
/// アニメーション中はUIいじれない
/// </summary>
public static class UIAppearTweenAnimeUtility
{
    /// <summary>
    /// UI表示
    /// </summary>
    /// <returns>シーケンス</returns>
    public static Sequence AppearUI(RectTransform uiRectTransform)
    {
        EventSystem eventSystem = EventSystem.current;

        float appearDuration = 0.5f;
        
        return DOTween.Sequence()
            .OnStart(()=>eventSystem.enabled = false)
            .Append(uiRectTransform.DOScale(Vector3.one, appearDuration))
            .OnComplete(()=>eventSystem.enabled = true);
    }
        
    /// <summary>
    /// UI非表示
    /// </summary>
    /// <returns>シーケンス</returns>
    public static Sequence DisappearUI(RectTransform uiRectTransform)
    {
        EventSystem eventSystem = EventSystem.current;

        float disappearDuration = 0.5f;
        
        return DOTween.Sequence()
            .OnStart(()=>eventSystem.enabled = false)
            .Append(uiRectTransform.DOScale(Vector3.zero, disappearDuration))
            .OnComplete(()=>eventSystem.enabled = true);
    }
}
```

ポイントとしては
**・メソッドがSequence(シーケンス)を返す
・EventSystemのenableを操作するヤンキーコング[^1]**
の二点です。

###メソッドがSequenceを返す

DoTweenの**Sequenceはいろいろな処理を任意の順番、タイミングで実行**することができます。
また、**SequenceとSequenceを繋いで実行**することもできます。

呼び出し側でUIアニメーションの処理に追加して
新しい処理を行わせることが可能になります。

```C#:UIのアニメーション処理の終了後に音を再生
AppearUI(uiRectTransform)
    .AppendCallback(()=>_fadeOpenAudioSource.Play())
    .Play();
```

アセットのインポート直後の初期設定ではSequenceが自動で実行される設定になっているかと思うので、
ツールバーから設定を変更しておくとScriptから実行できます。
<img width="369" alt="DoTweenPanel.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/244071/e7849ec0-744e-aef6-7aa3-de059fd3bfe8.png">

<img width="346" alt="TweenAutoPlay.PNG" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/244071/911ba15a-4b1b-fb44-2129-d61cd82d0bef.png">

###EventSystemのenableを操作するヤンキーコング

**UIアニメーションの処理を行っている間は入力を受け付けないようにしたかった**ので
EventSystemごとenableをオフにしています。
そのため、**一時的にすべてのEventSystem関連の入力が受け取れない状態**になります。
(Unityゲーム開発者ギルドで教えてくださった方ありがとうございます)

```C#
EventSystem eventSystem = EventSystem.current;
eventSystem.enabled = true
```

##Day7
結果的に私は**毎度のことのように〆切に間に合わなかった**のですが、
この日は**リトライ処理**の実装や**ステージ解放**の実装を行っていました。

【参考リンク】：[【Unity(C#)】覚えゲーのリトライポイント実装](https://qiita.com/OKsaiyowa/items/4cc468a301bd6e4a707a)
【参考リンク】：[【Unity(C#)】PlayerPrefsを使用したステージ解放の実装](https://qiita.com/OKsaiyowa/items/62aabe276e0d78a57ede)


![Mitsu6.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/244071/1f25dfa8-5cf8-8d04-deca-d43bc969b1b8.gif)


友人にはステージを作ってもらっていました。
**ゲーム性を与えるセンス抜群＆ツールを使いこなす速度がバケモノ**で助かりました。
<img width="312" alt="MMGameSS.PNG" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/244071/ea30e25b-65bc-559e-9701-bbfd041679aa.png">

##振り返り

評価はなかなかシビアな感じでした。
<img width="184" alt="MMReview.PNG" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/244071/ed7e0135-e06d-40a8-5b1b-c3bfe9c38fce.png">

###操作性
特に操作性の部分では**Getting Over It With Bennett Foddy**を意識した難易度設計から、
**難しい→操作しづらい**　という
印象の変換が無意識下で行われてしまったのではないか、という分析をしています。

**Getting Over It With Bennett Foddyは確実に名作ですが、
操作をしやすいか質問して、"はい"という回答は得られにくい**かと思います。

**Getting Over It With Bennett Foddyは
操作しづらいことで大きなゲーム性が生まれています。**

つまり何が言いたいかというと、
**評価項目の何かしらが欠損していることによってゲーム性が拡張されるゲーム**は
unity1weekで上位を狙いにくいということです。

文字起こししてみると当たり前ですが、
意外と見落としがちな要素かと思います。

操作性を振り返っての結論としては、
**unity1weekで上位に食い込めなかったからといって
おもしろくないゲームであるという解釈にはならない**ということです。(暴論)

##最後に
自分への慰めも含めて声を大にして言いますが、
みなさん**胸を張って自分の作ったゲームに誇りを持ちましょう。**

あと、運営、開発、実況等、本当にみなさんお疲れさまでした。
合わせて、お礼の言葉で締めさせていただきます。ありがとうございました。

(技術的な誤り、誤植等ありましたらお気軽にコメントください)

##参考リンク
[[Unity] 惑星に向かって物体が落ちるようにする](http://ftvoid.com/blog/post/738)
[Unity で EventSystem.current が null になってる問題](http://kyoto-games.hatenablog.jp/entry/2017/06/25/221311)
[[Unity] DOTweenのSequenceを使ってアニメーションを結合する](https://qiita.com/lycoris102/items/6a9e1e39bfa69880eaba)

[^1]: ヤンキーコングはヤンキーコーディングを略した造語です。

【作ったゲーム】：[MAGNET MONSTER](https://unityroom.com/games/kento_magnet)