Unityでルービックキューブ

##はじめに
思い付きでルービックキューブを作ってみたのでその記録として残す。
実際に動く画面がこちら
<blockquote class="twitter-tweet"><p lang="ja" dir="ltr">ルービックキューブ作ってみた　改善点は多い <a href="https://t.co/qhP8xPnuFj">pic.twitter.com/qhP8xPnuFj</a></p>&mdash; momantai (@momantai_jp) <a href="https://twitter.com/momantai_jp/status/1467235361726562305?ref_src=twsrc%5Etfw">December 4, 2021</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>
めんどくさくて平面での操作にしたがちゃんと3Dのモデルは作っている。
どんな流れで思考したかの記録。

プロジェクトファイルはここ

https://github.com/kanpy-momantai/rubiccube
##スワイプ機能
まずスマホでタッチして操作できるようにしようと思い実装。
こちらを参考にした
[【Unity】ドラッグ(スワイプ)でオブジェクトを移動させる](https://ymgsapo.com/2021/08/27/%E3%80%90unity%E3%80%91%E3%83%89%E3%83%A9%E3%83%83%E3%82%B0%E3%82%B9%E3%83%AF%E3%82%A4%E3%83%97%E3%81%A7%E3%82%AA%E3%83%96%E3%82%B8%E3%82%A7%E3%82%AF%E3%83%88%E3%82%92%E7%A7%BB%E5%8B%95%E3%81%95/)

#ルービックキューブの作成
blenderを使ってCubeを26個(見える部分)、それぞれの色をPlaneで張り付けた。
Unityにimportする際に中央にCubeを用意し、rubic1~27の名前を付けている。
今回のやり方では別にUnityの方でCubeとPlaneで用意してもいいと思う。

![rubicunity.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2353543/8a8a4b92-0a40-042d-33f7-e53235d97c8b.png)
##回転の実装
回転させたいCubeを選択し、縦横を選択して動かせるように考えたのが以下の方法。
1. 3×3×3のstring型の3次元配列を用意し、rubic1~27をそれぞれ入れる
2. タッチしたCubeの名前を取得し、配列内を検索し、添字を求める。
3. 回転するCubeを取得し、その内1つのCubeを親としそれ以外を子にする。
4. 回転中心を回転面の真ん中のCubeにして90度回転させる。
5. 親子関係を解消させる
6. 3次元配列の中身を回転に応じて書き換える。

回転させるCubeの取得はRayを飛ばしてやっている。

長くなるので左右回転のみ記述

```C#
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class ScreenInput : MonoBehaviour
{
    GameObject target;
    [SerializeField] float mouse_sensitivity = 0.2f;
    [SerializeField] float touch_sensitivity = 0.001f;

    GameObject[] childobjects= new GameObject[9]; //回転させるCubeを格納する配列

    string[,,] rubic= new string[3,3,3]; //ルービックキューブを表す配列
    string[,,] crubic= new string[3,3,3]; //回転させた後書き換える用の配列

    int i,j,k,ti,tj,tk,n=1;

    void Start()
    {
        //1. 3×3×3のstring型の3次元配列を用意し、rubic1~27をそれぞれ入れる
        for(i=0;i<3;i++){
            for(j=0;j<3;j++){
                for(k=0;k<3;k++){
                    rubic[i,j,k] = "rubic"+n;
                    crubic[i,j,k] = "rubic"+n;
                    n++;
                }
            }
        }
    }
 
    void Update()
    {
        //2.タッチしたオブジェクトを取得する
        if(Input.GetMouseButtonDown(0)){
            target = null;

            Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
            RaycastHit hit = new RaycastHit();

            if(Physics.Raycast(ray, out hit)){
                target = hit.collider.gameObject;

                //2.オブジェクトがどこにあるか探索
                for(i=0;i<3;i++){
                    for(j=0;j<3;j++){
                        for(k=0;k<3;k++){
                            if(target.name ==  rubic[i,j,k]){
                                ti=i;
                                tj=j;
                                tk=k;  
                            }
                        }
                    }
                }
            }
        }

        //ドラッグ中もしくはスワイプ中
        if (Input.GetMouseButtonUp(0))
        {
            float dx,dy;
            Vector3 point;
            
            //マウスの場合
            dx = Input.GetAxis("Mouse X") * mouse_sensitivity;
            dy = Input.GetAxis("Mouse Y") * mouse_sensitivity;
 
            //タッチの場合
            if (Input.touchCount > 0)
            {
                dx = Input.touches[0].deltaPosition.x * touch_sensitivity;
                dy = Input.touches[0].deltaPosition.y * touch_sensitivity;
            }

            if(dx > 0.001f || dx < -0.001f){//左or右回転
                //3.回転させるrubic[i,j,k]を取得する iはrayで取得したもので固定
                i=0;
                for(j=0;j<3;j++){
                    for(k=0;k<3;k++){
                        childobjects[i] = GameObject.Find(rubic[ti,j,k]);
                        i++;
                    }
                }
                //4.実際に回転させる
                point = childobjects[4].transform.position;
                for(i=1;i<9;i++){
                        childobjects[i].transform.SetParent(childobjects[0].transform);
                }

                if(dx < -0.001f){//右回転
                    childobjects[0].transform.RotateAround(point,Vector3.up,90);
                    for(j=0;j<3;j++){
                        for(k=0;k<3;k++){
                            rubic[ti,j,k] =crubic[ti,3-k-1,j];
                        }
                    }
                }
                else if(dx > 0.001f){//左回転
                    childobjects[0].transform.RotateAround(point,Vector3.down,90);
                    for(j=0;j<3;j++){
                        for(k=0;k<3;k++){
                             rubic[ti,j,k] =crubic[ti,k,3-1-j];
                        }
                    }
                }
                //5. 親子関係を解消させる
                for(i=1;i<9;i++){
                    childobjects[i].transform.parent= null;
                }

                //6. 3次元配列を書き換える 
                for(i=0;i<3;i++){
                    for(j=0;j<3;j++){
                        for(k=0;k<3;k++){
                            crubic[i,j,k] = rubic[i,j,k];
                        }
                     }
                }
            }
        }
    }
}
```
##現在ある問題点
- 縦横の操作の区別が難しい。<br>→xy方向にベクトル量で詳しく設定するとかしないとダメかな～という所。3Dに見えるような視点で操作させるならさらに色々必要？
- 最初の動画のように白面を正面にしている所からのスタートになる。<br> 本来のルービックキューブを考えるとランダムに回してからのスタートや別の面を正面にした時の操作も可能にした方が理想。回転の実装方法から変えた方が良いかも。<br>思いついたのはカメラをそれぞれの面で切り替えらえるようにして、その都度3次元配列を書き変える方法。
- 回転するとCubeがずれて見える。これはモデリングが下手なのが原因。


あと継ぎ足しで長くなったのでソースコードちゃんと分けたい。