5
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

UdonSharp日記~道の動的生成2~

5
Last updated at Posted at 2020-04-28

Qiitaを初めたら、意外と自分がLGTMを気にしていることに気づきました。
LGTMされていると執筆のやる気が湧きます。UdonSharpの記事を増やす役に立つと思ってLGTMを押してください。

やったこと

前回は床にUdonBehaviorをつけて、床を踏んだら、UdonBehaviorを持つ床をどんどん生成していき、道の動的生成を行う方法をとりました。しかし、その方法だと現環境では失敗してしまうため、方針を変更(インスタンス化した際にUdonが動作しなくなる場合があるため)。
一つのオブジェクトが床を生成し続ける設計としています。

そして今回は十字路を生成することとしました。

やり方

オブジェクトの配置

まず動的生成を管理するmanager(Create Empty)と生成されるfloor(Blenderからインポート)を用意します。
floorはmangerからアクセスしやすいように子として追加します。
(floorの座標を確認するために子にしている)
無題.png

プログラムの設計

決まったサイズの十字路を使用しているため、部屋の移動判定が必要なエリアは等間隔に配置されています。
そしてこのエリアは無限大にあるため、一つ一つ判定するのではなく、数字を加工することで判定します。
無題2.png

絶対値とmod演算(%の演算)を用いることで、部屋の移動判定を少ない条件分岐で表現することが可能です。
今回の場合、X座標とZ座標を部屋の長さ4mで割った余りが比較対象となります。
そうすると下図の緑のエリアだけを判定すれば、部屋の移動が行われたことがわかります。
例1(10, 4) → (2, 0)  部屋移動中
例2(-12, -6) → (0, 2) 部屋移動中
例3(4 , 4) → (0, 0) 部屋の中
無題3.png

部屋の移動がされたことがわかったら、プレイヤーの座標をもとに、上下 / 左右どちらかの部屋を作るか決めるだけです。

無題4.png

設計思想は以上です。他にも座標の調整などがソースコードに反映されていますが、割愛します。

ソースコードのポイント

部屋の移動判定

    //部屋移動判定を行う関数
    private bool isMoveFloor(Vector3 pos)
    {
        float x = Mathf.Abs(pos.x)+ CORRIDER_WIDTH_HALF;
        float z = Mathf.Abs(pos.z)+ CORRIDER_WIDTH_HALF;

        //廊下はxz座標にそれぞれ等間隔で並んでいるためプレイヤー座標を
        //部屋の幅で割った余りから、下記の判定で求める
        x %= (FLOOR_SIZE_HALF*2);
        z %= (FLOOR_SIZE_HALF*2);
        if(FLOOR_SIZE_HALF - CORRIDER_SIZE_HALF < x && x < FLOOR_SIZE_HALF + CORRIDER_SIZE_HALF && z < CORRIDER_SIZE_HALF) return true;
        if(FLOOR_SIZE_HALF - CORRIDER_SIZE_HALF < z && z < FLOOR_SIZE_HALF + CORRIDER_SIZE_HALF && x < CORRIDER_SIZE_HALF) return true;

        return false;
    }

空きスペースのチェック

            bool is_exist = false;//生成座標にすでにFloorがある
            string debug = "";
            foreach (var child in this.GetComponentsInChildren<Transform>())
            {
                //createPos(floor作成座標)とmanagerの子の座標の距離が小さいものがあるか探す
                //距離が小さいということは、もうすでにfloorを生成しているということ
                float distance = Vector3.Distance(createPos, child.transform.position);
                if (distance < FLOOR_SIZE_HALF)
                {
                    is_exist = true;
                    break;
                }
            }

floorの生成

            if(is_exist == false)
            {
                //生成座標にFloorがない場合、インスタンスを設定し、managerの子に設定する。
                debug += "----\n";
                print(debug);
                var fl = VRCInstantiate(floor);
                fl.transform.position = createPos;
                fl.transform.rotation = floor.transform.rotation;
                fl.transform.parent = this.transform;
            }

ソースコード全体


using UdonSharp;
using UnityEngine;
using VRC.SDKBase;
using VRC.Udon;
using UnityEngine.UI;


public class manager : UdonSharpBehaviour
{
    public GameObject floor;

    public Text debugText;
    public Text debugText2;

    private const float FLOOR_SIZE_HALF = 2.0f;//フロア3m 廊下1m
    private const float CORRIDER_SIZE_HALF = 0.5f;//廊下長さ1.0m
    private const float CORRIDER_WIDTH_HALF = 0.25f;//廊下幅0.5m
    private const float FLOOR_HIGHT = 3.0f;//2階の高さ
    private float localPlayerHeight;        //プレイヤーの身長

    void Start()
    {
        //位置の高さ調整のために初回身長を計測する。
        var player = Networking.LocalPlayer;
        localPlayerHeight = player.GetTrackingData(VRCPlayerApi.TrackingDataType.Head).position.y;
    }
    void Update()
    {

        var player = Networking.LocalPlayer;
        if (player != null)//Unityの再生ボタンで実行すると変数がnullになるらしいよ
        {
            Vector3 pos = player.GetTrackingData(VRCPlayerApi.TrackingDataType.Head).position;
            if (isMoveFloor(pos))
            {
                makeFloor(pos);
            }
        }
        dispChild();
    }

    //部屋移動判定を行う関数
    private bool isMoveFloor(Vector3 pos)
    {
        float x = Mathf.Abs(pos.x)+ CORRIDER_WIDTH_HALF;
        float z = Mathf.Abs(pos.z)+ CORRIDER_WIDTH_HALF;

        //廊下はxz座標にそれぞれ等間隔で並んでいるためプレイヤー座標の余りから
        //下記の判定で求める
        x %= (FLOOR_SIZE_HALF*2);
        z %= (FLOOR_SIZE_HALF*2);
        if(FLOOR_SIZE_HALF - CORRIDER_SIZE_HALF < x && x < FLOOR_SIZE_HALF + CORRIDER_SIZE_HALF && z < CORRIDER_SIZE_HALF) return true;
        if(FLOOR_SIZE_HALF - CORRIDER_SIZE_HALF < z && z < FLOOR_SIZE_HALF + CORRIDER_SIZE_HALF && x < CORRIDER_SIZE_HALF) return true;

        return false;
    }

    //Floorを生成する関数
    //1.プレイヤーの位置をもとに、2つの生成候補の座標を作成する
    //2.子オブジェクトの座標を確認し、生成候補の座標が被らなければ、そこにFloorを生成する。
    private void makeFloor(Vector3 pos)
    {
        //プレイヤーの座標をFLOOR_SIZE_HALFの精度にまるめる
        float x = Mathf.Round(pos.x / FLOOR_SIZE_HALF)* FLOOR_SIZE_HALF;
        float z = Mathf.Round(pos.z / FLOOR_SIZE_HALF)* FLOOR_SIZE_HALF;
        float y = Mathf.Round((pos.y - localPlayerHeight) / FLOOR_HIGHT) * FLOOR_HIGHT;
        //上下・左右どちらの廊下を通ったか判定し、結果により生成候補座標を2つ作る。
        Vector3[] spawnPos = new Vector3[2];
        bool is_updown = (Mathf.Abs(z) % (FLOOR_SIZE_HALF*2) > CORRIDER_SIZE_HALF);
        if (is_updown)
        {
            //上下移動の場合
            spawnPos[0] = new Vector3(x, y, z + FLOOR_SIZE_HALF);
            spawnPos[1] = new Vector3(x, y, z - FLOOR_SIZE_HALF);
        }
        else
        {
            //左右移動の場合
            spawnPos[0] = new Vector3(x + FLOOR_SIZE_HALF, y, z);
            spawnPos[1] = new Vector3(x - FLOOR_SIZE_HALF, y, z);
        }

        foreach(Vector3 createPos in spawnPos){
            bool is_exist = false;//生成座標にすでにFloorがある
            string debug = "";
            foreach (var child in this.GetComponentsInChildren<Transform>())
            {
                //child is your child transform
                float distance = Vector3.Distance(createPos, child.transform.position);
                debug += string.Format("create{0}, child{1}, dist {2}, cnt {3}\n", createPos.ToString(), child.transform.position.ToString(), distance);
                if (distance < FLOOR_SIZE_HALF)
                {
                    is_exist = true;
                    break;
                }
            }
            if(is_exist == false)
            {
                //生成座標にFloorがない場合、インスタンスを設定し、managerの子に設定する。
                debug += "----\n";
                print(debug);
                var fl = VRCInstantiate(floor);
                fl.transform.position = createPos;
                fl.transform.rotation = floor.transform.rotation;
                fl.transform.parent = this.transform;
            }
        }
    }

    private void print(string mes)
    {
        if(debugText.text.Length > 500)
        {
            debugText.text = "";
        }
        debugText.text += mes;
    }
    private void dispChild()
    {
        debugText2.text = "";
        foreach (var child in this.GetComponentsInChildren<Transform>())
        {
            debugText2.text += string.Format("{0},{1}\n",child.name, child.transform.position.ToString());
        }
    }
}

今回は実験だったので、生成したfloorのデザインやサイズ感がちょっとひどいですが、おいおいはしっかりしたもので作成したいですね。

5
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?