ちょっとした回路シュミレーターを作るお話

#はじめに
この記事は１４日目の[福島高専アドベントカレンダー](https://qiita.com/advent-calendar/2020/nitfc)となっております。
Unity 2019.2.15f1を使って回路シュミレーターを作る始めたことを軽く記事にまとめてみました。
#ちょっとした回路シュミレーターを作るお話
プログラミング練習をTwitterに晒し始めて、重なった黒歴史約半年…そろそろなんかプログラマーぽいことをしてみたい季節になりました。
プログラマーっぽいことって何だろうと考えて、回路シュミレーターを作ることにしました。
…ということで、回路シュミレーターを作ります。
![回路をつなぐ.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/859689/c475f31a-e1ca-ec0c-4f1f-184587f63d78.png)
#回路シュミレーター作成計画
さて、どうやって回路をシュミレーションしようか。まず、電池が必要で、抵抗が必要で、導線が必要で、分岐用の接点が必要です。
下にシュミレーションで必要なものと役割をまとめます。

| 必要なもの | 役割 |
|:-:|:-:|
| 抵抗  |  電圧降下 |
| 電池  |  電圧供給 |
| 節点  |  電圧の大きさと電流の分配 |
| 導線  |  電流の伝達 |

#節点同士を導線でつなぐ
まずは節点と節点を結んでみます。
節点を結ぶためには、接続先のオブジェクトを探すこと、そして接続先のどの面につなぐのかを取得すること、最後に、接続点と接続点をちゃんとつなぐことの３つが必要になります。
それぞれを一つづつ解決していきます。
##接続先のオブジェクトを探す
接続先のオブジェクトを探すには、Physics.Raycast(ray, out hit)という、関数を使いました。これは、ある点から、ray..つまりレーザーを出して、ぶつかった当たり判定のあるオブジェクトのデータを取得するという関数です。
引用のサイトを利用して書かせていただきました。

>【Unity】RayCastを使いこなせ!判定や表示に使ってみよう | 侍エンジニア塾ブログ（Samurai Blog） - プログラミング入門者向けサイト
>https://www.sejuku.net/blog/83620

##接続先のどの面につなぐのかを取得する
電気回路のそれぞれの部品は、ただつながっていればよいというわけではなく、つないだ後に、電圧降下、分流、などつながり方によって、どのように、つながっているかが重要になっています。
そのため、各接続点に専用のゲームオブジェクトを作成し、以下の形にしてみました。
![節点のオブジェクト.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/859689/0ac1eedd-77b8-c6f7-fa41-e9f483ba443d.png)
画像では、sitennの一つ一つの、面に対して、up,down,front,back,right,leftと専用のゲームオブジェクトが用意されてるのがわかります。さらに、それぞれのオブジェクトに、当たり判定を与えることで、Physics.Raycast(ray, out hit)で、つながる点を識別できるようにしました。
ただこのままだと、同じオブジェクトに対して複数の接続がされたり、同じ接続点のセットに対して複数の導線が付いたりするので、いろいろと、問題がありますが、詳しく書くと長くなるので、別記事にそのうちまとめます。

##接続点と接続点をちゃんとつなぐ
次に接続点と接続点を結ぶための導線部分の作成したいと思います。導線ですが、やることが主に３つあります。
・導線のオブジェクトを二つの接続する点の中点に置く。
・接続点と接続点の距離の長さまで、オブジェクトをのばす。
・接続点同士をつなげるように回転させる。
以上三つの点を解決する必要があります。
上の三つすべて、またまたPhysics.Raycast(ray, out hit)によって入手した、オブジェクトのデータを使えば、解決できそうです。
ただ、三つめは、解決するのにちょっと手間取ったので、メモ書き程度に図と解説を載せます。
![回転.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/859689/ea0ec2ba-fb6d-3cc7-1b1f-dc7832506e01.png)
三つ目の回転の解決のためには、三次元の回転をどう扱うかによります。私の場合、二つの軸に対して回転させることで解決しました。角度は位置関係からAcosを利用して、角度を求めました。それでその肝心な角度ですが、例えば図のようにY軸とZ軸で回転させるとします。
Y軸の回転はAcos(X軸の距離/XZ平面でのでの距離)で求められます。
~~Z軸の回転もAcos(X軸の距離/YZ平面でのでの距離)で求められます。~~
Z軸の回転はY軸の回転の影響を受けるので、面倒ですがAcos(XZ平面での距離/三次元での距離)で求められます。
以上のことを踏まえまして、ちょっと手直ししたものが以下のコードになります。

```C#:mid.cs
public class mid : MonoBehaviour
{
    public GameObject trget1 = null;//接続点のオブジェクト
    public GameObject trget2 = null;//接続点のオブジェクト
    [SerializeField] private float syoki=0.1f;//導線の太さ
    void Update()
    {
        Vector3 memovec;//二つのオブジェクトの距離を表すベクトル
        Vector3 noma;//cosを使うので回転軸の＋－が逆になったときに補正するための各要素が＋－１になるように設定されたベクトル
        Vector3 memoangl;//一時的に角度を入れておくためのベクトル
        Vector2 memox,memoy,memoz;//X軸を含まないベクトル、Y軸を含まないベクトル、Z軸を含まないベクトル
            
        if (trget1 != null && trget2 != null)//接続点がどちらか片方が存在しない場合つなげないから、実行しない
        {
            transform.position = (trget1.transform.position + trget2.transform.position) / 2;//中点にオブジェクトを置く
            memovec = trget2.transform.position - trget1.transform.position;//位置の差を求める
            transform.localScale = new Vector3(memovec.magnitude, syoki, syoki);//導線の長さを決める
            //各平面を代入
            memox = new Vector2(memovec.y, memovec.z);
            memoy = new Vector2(memovec.x, memovec.z);
            memoz = new Vector2(memovec.x, memovec.y);
            //補正用のベクトルの作成

            noma = new Vector3(
                memovec.x/Mathf.Abs(memovec.x),
                memovec.y / Mathf.Abs(memovec.y),
                memovec.z / Mathf.Abs(memovec.z));
            memoangl = new Vector3(
                0,
                -noma.z * Mathf.Acos(memovec.x/memoy.magnitude) / Mathf.PI * 180,//Y軸の回転
                noma.y*Mathf.Acos(memoy.magnitude / memovec.magnitude) / Mathf.PI * 180);//Z軸の回転
            transform.rotation = Quaternion.Euler(memoangl);//回転の適用
            //もしも同じオブジェクトをつなぐ設定だった場合は、即座にオブジェクトを消去する。
            if (trget1 == trget2)
            {
                Destroy(this.gameObject);
            }
        }
    }
}
```
なんでコードの名前がmidなのかは聞かないでください…

続いて上のクラスmidの適用されたプレハブを利用して、作成した接続点側のプログラムを示します。

```C#:sartch.cs
public class sartch : MonoBehaviour
{
    [SerializeField] private Vector3 dire =new Vector3(0,0,0);//オブジェクトを探すレーザーの方向
    [SerializeField] private GameObject linepref;//導線のプレハブ
    [SerializeField] private GameObject kijunn;//このコネクタの出すレーザーの合わせる回転をするゲームオブジェクト
    [SerializeField] private int  myindex=0;//同じオブジェクトにつながないようにする管理のために必要な番号（今回は説明しない）
    private GameObject myline;//作成した導線を管理するための変数

    void Update()
    {
        Quaternion memo = kijunn.transform.rotation
            * new Quaternion(dire.x, dire.y, dire.z, 0)
            * Quaternion.Inverse(kijunn.transform.rotation);//レーザーにオブジェクトの回転を適用させる（参考サイト下に書いておきます）
        Vector3 di = new Vector3(memo.x, memo.y, memo.z);//回転後のレーザーの向き

        Ray ray = new Ray(transform.position, di);//レーザーの作成
        RaycastHit hit;//レーザーの当たったオブジェクトを入れてもらうための変数


       // Debug.DrawRay(transform.position, di/50, Color.red);//デバッグの時に正しくレーザーがでてるか確認に使ったやつ
        if (Physics.Raycast(ray, out hit)) //レーザー発射
        {
            if (linepref != null)//導線のプレハブをちゃんと設定したか確認する。
            {
                if (hit.collider.gameObject.GetComponent<mid>()==null)//当たったオブジェクトが導線じゃないか確認する。（一応）
                {
                    if (hit.collider.GetComponent<sartch>() != null)//当たったオブジェクトがコネクタであることを確認する。
                    {
                        if (transform.parent.GetComponent<setuzokumane>().ch_conect(hit.collider.gameObject))//当たったオブジェクトがまだ接続されてないかを確認するための、関数（今回は説明しない）
                        {
                            //まだ導線を持ってないなら作る
                            if (myline == null)
                            {
                                myline = Instantiate(linepref);
                            }
                            transform.parent.GetComponent<setuzokumane>().conectreset(hit.collider.transform.parent.gameObject, myindex);//同じオブジェクトに接続しないように自身以外のオブジェクトに接続しているものを削除する（今回は説明しない）

                            GameObject conecter = hit.collider.gameObject;//対象のコネクタを入手（今回は説明しない）

                            transform.parent.GetComponent<setuzokumane>().conect(hit.collider.transform.parent.gameObject, myindex);//自身の接続記録する（今回は説明しない）

                            myline.GetComponent<mid>().trget1 = this.gameObject;//自身を接続点に設定する
                            myline.GetComponent<mid>().trget2 = conecter;//対象を接続点に設定する
                            conecter.GetComponent<sartch>().setline(transform.parent.gameObject, myline);//接続相手とコネクタを共有（今回は説明しない）

                        }
                    }
                }
            }
        }
        //何らかの原因で導線が削除されたとき管理できるようにするための処理（今回は説明しない）
        if (myline == null)
        {
            transform.parent.GetComponent<setuzokumane>().disconect(myindex);//何らかの理由で一方的に接続を削除されたときに自分のオブジェクトの接続記録を削除する
        }
    }
}
```
コメントに大量の（今回は説明しない）があります。申し訳ないです。m(__)m
そのうち ~~すぐとはいってない~~（今回は説明しない）のところを書いた記事を上げたいと思います。

>Quaternion memo = kijunn.transform.rotation
            * new Quaternion(dire.x, dire.y, dire.z, 0)
            * Quaternion.Inverse(kijunn.transform.rotation);
>の部分の参考ページです。
>Quaternion を完全に理解した | VirtualCast Blog
>https://virtualcast.jp/blog/2019/11/quaternion/

これにより出来上がったものがこちらになります。

<blockquote class="twitter-tweet"><p lang="ja" dir="ltr">プログラミング練習151日目<br>できた（ごり押し）Unity最高 <a href="https://t.co/1CqlH0OU8B">pic.twitter.com/1CqlH0OU8B</a></p>&mdash; 鍵田　春代 (@Kagita_Haruyo) <a href="https://twitter.com/Kagita_Haruyo/status/1333769555223605248?ref_src=twsrc%5Etfw">December 1, 2020</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

#謝罪
今回の記事で回路シュミレーターの作成の全記録をまとめる、またはリンクを張りたかったのですが、思っていた以上に作業がはかどらず、完成までは至れませんでした。たいへん申し訳ございませんm(__)m
#宣伝
回路シュミレーターは完成させるつもりではあります。また、最初のほうに記述した通り、Twitterにて、日々の愚痴とともに進捗を毎日晒していきますので、続きが気になる、他に何をしてるのか見たい、という方は以下のリンクのほうで確認できるので、できるので、どうぞ。
>鍵田　春代 (@Kagita_Haruyo)
>https://twitter.com/Kagita_Haruyo

#最後に
長々とした記事を最後までおつきあいいただき、ありがとうございます。回路シュミレーターの作成記録の続編はそのうち投稿すると思うので、気長にお待ちください。

#引用ページまとめ
>Physics.Raycast(ray, out hit)の参考ページです。
>【Unity】RayCastを使いこなせ!判定や表示に使ってみよう | 侍エンジニア塾ブログ（Samurai Blog） - プログラミング入門者向けサイト
>https://www.sejuku.net/blog/83620
>Quaternion memo = kijunn.transform.rotation
            * new Quaternion(dire.x, dire.y, dire.z, 0)
            * Quaternion.Inverse(kijunn.transform.rotation);
>の部分の参考ページです。
>Quaternion を完全に理解した | VirtualCast Blog
>https://virtualcast.jp/blog/2019/11/quaternion/