【二日目】Unity でモブプログラミング[C02_NosePush]   

# Unity でペアプロ
2019/05/11、本日は勉強会の参加人数が少ないためチームは２つに分かれ各チーム２人ずつのペアプログラミングによる体制になりました。


# サンプル

【Youtubeの参考動画】（クリックするとYoutubeに移動）
[![IMAGE ALT TEXT HERE](http://img.youtube.com/vi/cIysuNvlamI/0.jpg)](http://www.youtube.com/watch?v=cIysuNvlamI)

4:30くらいからが該当のゲームです

# そして出来上がったゲーム
GIFで飛び飛びですが実際はヌルヌルなカンジです。結構、雰囲気がイイカンジに緩くできました。デザインのことはトヤカク言わない約束です。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/412322/adeae8e4-cd08-ee12-6720-d59ed4110bed.gif" width="50%">

# 込み入った部分の説明

実はゲーム部分だけならスクリプトが１つだけというシンプルな内容です。以下にそのままソースを貼っておきます。
同じ画面にクリア成否のキャラを出すようにしていた名残が残っています。
本来は削除すべきですが、ナレッジ的に残しておきます。人と同じ作業する場合はキレイにして置いた方がイイですね。

```
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement;
public class C02_HandMove : MonoBehaviour {
    float rightMax = 2.0f; //左へ移動可能 (x)最大値
    float leftMax = -2.0f; //右へ移動可能 (x)最大値
    float currentPositionX; //現在の位置(x)保存
    float currentPositionY; //現在の位置(y)保存
    public float sideMoveSpeed = 3.0f; //移動速度+方向

    public float pushSpeed = 10.0f; //打ち出されるスピード

    bool isTouched = false; //タッチされたか
    bool isGameEnd = false; //タッチ後、ゲーム完了したか

    private float stayTime = 0;

    public string nextSceneName = "C02_Result";
    public GameObject successChara;
    public GameObject failureChara;

    List<GameObject> colList = new List<GameObject> ();

    void Start () {
        currentPositionX = transform.position.x;
        currentPositionY = transform.position.y;
    }
    void Update () {
        if (!isTouched) {
            currentPositionX += Time.deltaTime * sideMoveSpeed;
            if (currentPositionX >= rightMax) {
                sideMoveSpeed *= -1;
                currentPositionX = rightMax;
            }
            //現在の位置(x) 右へ移動可能 (x)最大値より大きい、もしくは同じの場合
            //移動速度+方向-1を掛けて反転、現在の位置を右へ移動可能 (x)最大値に設定
            else if (currentPositionX <= leftMax) {
                sideMoveSpeed *= -1;
                currentPositionX = leftMax;
            }
            transform.position = new Vector3 (currentPositionX, currentPositionY, 0);
        }

        TouthPushHand ();

        if (isTouched) {
            stayTime += Time.deltaTime;
            if (stayTime > 1.0f) {
                if (colList.Count == 1) {
                    foreach (GameObject checkObj in colList) {
                        if (checkObj.name == "PushHole") {
                            C02_Result.isGameClear = true;
                        } else {
                            C02_Result.isGameClear = false;
                            //ResultCharaCreate (false);
                        }
                    }
                }
                if (colList.Count > 1) {
                    C02_Result.isGameClear = true;
                    //ResultCharaCreate (true);
                }
                if (colList.Count == 0) {
                    C02_Result.isGameClear = false;
                    //ResultCharaCreate (false);
                }
                SceneManager.LoadScene (nextSceneName);
            }
        }
    }

    //同じ画面にゲームクリア成否のキャラを出すときは以下のメソッドを使う
    void ResultCharaCreate (bool result) {
        if (result && !GameObject.Find (successChara.name)) {
            GameObject sucessObj = Instantiate (
                successChara,
                successChara.transform.position,
                successChara.transform.rotation
            );
            sucessObj.name = successChara.name;
            if (GameObject.Find (failureChara.name)) {
                Destroy (GameObject.Find (failureChara.name));
            }

        } else if (!GameObject.Find (successChara.name) && !GameObject.Find (failureChara.name)) {
            GameObject failerObj = Instantiate (
                failureChara,
                failureChara.transform.position,
                failureChara.transform.rotation
            );
            failerObj.name = failureChara.name;
        }
    }

    void TouthPushHand () {
        Vector3 PushForse = new Vector3 (0, pushSpeed, 0);
        if (Input.GetMouseButtonDown (0)) {
            gameObject.GetComponent<Rigidbody2D> ().AddForce (PushForse, ForceMode2D.Impulse);
            isTouched = true;
        }
    }

    private void OnCollisionEnter2D (Collision2D col) {
        colList.Add (col.gameObject);
    }
}

```

# イケてないところをチェック

いろいろ他のスクリプトを参考にしたりしているので、つぎはぎフランケンなソースになっています。
まずなんかイケてないな～という部分は、左右の移動が「transform.position」で行っているのに対し、上への発射は 「Rigidbody」で行っているところです。
何がイケてないのかというと上への発射も 「transform.position」で行った方が統一されてイイかなと思いました。

## 「transform.position」と「Rigidbody」の混在のリスク
「transform.position」はその都度指定された位置に移動させています。
もし後から「Rigidbody」などで加速させたい場合、「transform.position」で位置が代入されていれば「transform.position」の方が優先され位置が上書きされます。そうじゃねぇ～っていう悲しい動きをすることになります。

## でも、こちらのソース混在されてますよね？
なのでダサいことではありますが、「transform.position」での移動については、タッチ後フラグで動作しないよう無効にしています。その後に「Rigidbody」で上に発射するようにしています。まあ「transform.position」で統一したとしてもフラグで優先する必要があるかもしれません。

## １つのオブジェクトに対して複数の当たり判定

鼻の穴ってどうしてるの？っていう人がいるかもしれません。
とってもカンタンなことなので以下の画像でネタバラシです。

![鼻colider.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/412322/6e25d86c-e4ed-5126-b13b-e2c9a3461cad.png)


画像のうっすい緑色の部分が当たり判定です。上２つが穴に入ったときの当たり判定。下３つに当たった場合はゲーム失敗になります。それじゃあ、両方に当たった場合はどうなんのよ？
これについては別の記事にでもまとめて投稿しようと思います。transform.positionとRigidbodyも基本的なことではあるので別記事にしようかと思いましたが、とりあえずこちらの記事に書いてみました。

# 振り返り

今回は人数が少なかったためペアプロで取り組むことになりました。また、あまり作業を分担せずに結構サクサク書いてしまったため、ペアの相手に考えてもらう時間が少なかったように思われます。

もう一方のチームの素材準備のフォローも行ったためお互いのペアを行き来しながら作業を進めました。通常のペアプロとはかなり違ったカンジになりましたが、差し迫る時間を感じながらも集中した形でお互いのチームでの作業に取り組めたと思います。

実はお昼にNintendo Switchで「1-2-Switch（ワンツースイッチ）」を実習？のようなカンジで遊びたかったのですが全然余裕がなかったです。結局夕方くらいにノンビリした時間に疲れを感じつつフムフム言いながら遊びましたが・・・。