見出し画像

【VRChat】初めてのワールド制作 その3(Udonプログラミングしつつワールド制作)

ワールドが出来ました。良かったら見ていって来てください。

↓ワールドはこちらから!(PC&Quest両対応)

コンセプト

VRの写真展示ってよくあるけど、普通に展示してあるだけなのが多いよね。
普通に移動面倒だし、まだ現実に引っ張られてる感があるなぁ…せや!写真がこればええんや!
と思っていたものの、アルバムはもうやったしなぁ…と思っていたところ、「VOY@GER」を見ていてひらめきました。

途中でパネルが飛んでくるんですが、これじゃん!写真も縦に伸ばせば移動せずに見れる!これや!って感じです。

というわけでイメージ図がこちらです。
円柱の真ん中に操作パネルがあってパネルを操作するイメージ。アセット探しもモデリングも面倒大変なので極力労力をかけない形にしています。

10分で作った美麗スライド

制作

アセットはほとんど使わず、Udonを書いて進めていきました。
台はBlenderでモデリング、または四角形や円柱など標準で用意されている物を使っています。
写真で重くなるだろうという予想と、写真以外は脇役にしたいので、なるべく簡素にしました。決してテクスチャやモデルを探したりするのが面倒だからではありません。
そこまで複雑なギミックでもないので、回転や移動の制御を一つのcsファイルでまとめ、それぞれのボタンから参照させて呼び出させる形にしています。

コードはここに書くと長くなるので末尾に。UdonはListが使えないらしいので配列で頑張っていく方針で書いていました。

また、写真はpngを使いました。最初jpegでやっていたんですが、近くで見るとかなりボケていたのでpngに。見え方として大きくは変わらないんですが、多少はっきりした…かな???って感じです。
(Unity側で指定している最大テクスチャサイズが初期値のままなのであんまり変化ない気はしている。上げると容量がーってなりそう。単純計算100倍なので。)

操作盤は自作です。そんなに難しいことはできないのでなるべく単純図形になるようにしました。それぞれの3Dモデルは結合せずそのままUnityにもっていって、オブジェクトとして使いました。

困った事

作りながら思ったことを羅列していきます。

「使う」ってどうやるの

Interact()を使うのです。コードの例は一番最後に記述しました。

Interact()の関数は一つのスクリプトにつき一つ?

これが地味に困りました。
ボタンのように押したら反応するのがInteractionなのですが、こちらが複数使えないので、一つのボタンにつき一つスクリプトを作って、Interaction記述するという形をとっています。地味に面倒です。
オブジェクト名を取って処理を変えるとかは考えたんですが、それもそれで…っていう感じでやめました。
もう少し良いやり方があれば教えて下さい。

Interactが機能しない

これはコライダーが無いのが原因でした。初歩的だけど原因は大体これ。忘れずにつけましょう。
試してた感じメッシュコライダーでも良さそう。

カーソル持っていったときに「Use」以外にする

時々カーソル充てると???って書いてあったりするじゃないですか、あれやりたいんですよね。
Interactionを設定すると出てくるInteraction Textって欄に書くと良いっぽい。

Quest対応ってどうやんの?

容量制限がありますが、基本的にプラットフォームをAndroidにすれば対応できるようです。自分のは70MB行かないぐらいだったのでついでにQuestにもアップロードしておきました。なぜかPCより容量が10MB増えました。
見え方としてはそんなに変わらないのですが、アニメーションで写真が回っているときにちらつきが少し気になるかな、という程度の変化です。
(プロジェクトの設定はデフォルトのまま、図形もほぼプリミティブの物、ライティングもほぼ触らずなのでそれはそう)

公開

もう一度貼っておきます。単純ですけど写真が並んでるのを見ると「おお!」ってなるのでぜひ見てみてください。

おまけ:コードなどのざっくりした仕組み

写真の列の切り替えとか諸々をつかさどるコード。
管理用に「AnimationManager」というのがあり、外部からここを参照して色々制御してます。最初は可変性を持たせられるようにしていたんですけどphotoGroupが10枚組×10で100枚、3メートル間隔での配置に決まったので途中で力尽きてます。

写真の塊は「photoGroup」prefabを作成しました。10枚で1グループ、写真の整列はアニメーションでやっています。
ちなみに写真はコンポーネントに「SpriteRenderer」というのがあったのでそれを使いました。RawImageとかだと撮影時に写らないことがありそうだったので。(検証してないですがclusterで昔作ったワールドは映らなかった思い出)

photoGroupのprefab。写真が無いので寂しい感じです。

アニメーションでやっているのは回転と並べ替え。

この辺の物を↓で一括制御しています。
ちなみに「整列」は英語でLineUpらしくとても紛らわしい感じに…

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

public class AnimationManager : UdonSharpBehaviour
{
    public GameObject[] photoGroup;
    public GameObject rootObject;
    public GameObject info;
    public float baseHeight = 1.0f;
    public int maxHeight = 16;
    int length;
    int maxNumber;
    bool animationStop = false;
    bool isRockingUpDown;

    void Start()
    {
        isRockingUpDown = false;
        length = photoGroup.Length;
        ChangeLine("reset");
    }
    public void StopAnimation()
    {
        float speed;
        animationStop = !animationStop;
        if (animationStop)
        {
            speed = 0;
        }
        else
        {
            speed = 1.0f;
        }
        foreach (var a in photoGroup)
        {
            a.GetComponent<Animator>().SetFloat("MovigSpeed", speed);
        }
    }

    public void ChangeHeitht(string type)
    {
        float height = 0;

        switch (type)
        {
            case "up":
                height = rootObject.transform.position.y + 0.1f;
                break;
            case "down":
                height = rootObject.transform.position.y - 0.1f;
                break;
            case "reset":
                height = baseHeight;
                break;
        }

        rootObject.transform.position = new Vector3(0, height, 0);
    }
    public void ChangeLine(string type)
    {
        if (isRockingUpDown) return;
        switch (type)
        {
            case "up":
                maxNumber--;
                if (maxNumber < -length)
                {
                    maxNumber = -length;
                }
                break;
            case "down":
                maxNumber++;
                if (maxNumber > length)
                {
                    maxNumber = length;
                }
                break;

            case "reset":
                maxNumber = 0;
                break;
        }
        Debug.Log(maxNumber + " maxNumber");
        int height = 0;
        for (int i = 0; i < length; i++)
        {
            height = (i + maxNumber) * 3;
            if (height > maxHeight)
            {
                height -= 30;
                if (height > maxHeight)
                {
                    height -= 30;
                }
            }
            if (height < -12)
            {
                height += 30;
            }

            photoGroup[i].transform.localPosition = new Vector3(0, height, 0);

            if (i % 2 == 0)
            {
                photoGroup[i].GetComponent<Animator>().SetBool("reverse", true);
            }
            else
            {
                photoGroup[i].GetComponent<Animator>().SetBool("reverse", false);
            }
        }
    }

    public void LineUp()
    {
        isRockingUpDown = !isRockingUpDown;


        int num;
        if (maxNumber <= 0)
        {
            num = -maxNumber;
        }
        else
        {
            num = length - maxNumber;
        }

        if (isRockingUpDown)
        {
            for (int i = 0; i < photoGroup.Length; i++)
            {
                if (i == num)
                {
                    photoGroup[i].GetComponent<Animator>().SetTrigger("lineup");
                }
                else
                {
                    photoGroup[i].GetComponent<Animator>().SetFloat("MovigSpeed", 0);
                }
            }
        }
        else
        {
            for (int i = 0; i < photoGroup.Length; i++)
            {
                if (i == num)
                {
                    photoGroup[i].GetComponent<Animator>().SetTrigger("lineup_end");
                }
                else
                {
                    photoGroup[i].GetComponent<Animator>().SetFloat("MovigSpeed", 1);
                }
            }
        }

    }

    public void LineUpEnd()
    {
        isRockingUpDown = false;
        int num;
        if (maxNumber <= 0)
        {
            num = -maxNumber;
        }
        else
        {
            num = length - maxNumber;
        }
        for (int i = 0; i < photoGroup.Length; i++)
        {
            if (i == num)
            {
                photoGroup[i].GetComponent<Animator>().SetTrigger("lineup_end");
            }
            else
            {
                photoGroup[i].GetComponent<Animator>().SetFloat("MovigSpeed", 1);
            }
        }
    }
    public void ShowInfomation()
    {
        if (isRockingUpDown)
        {
            int num;
            if (maxNumber <= 0)
            {
                num = -maxNumber;
            }
            else
            {
                num = length - maxNumber;
            }
            var active = photoGroup[num].transform.GetChild(0).GetChild(0).gameObject.activeSelf;
            foreach (Transform child in photoGroup[num].transform)
            {
                if (active)
                {
                    child.gameObject.GetComponent<SpriteRenderer>().color = new Color32(255, 255, 255, 255);
                    child.GetChild(0).gameObject.SetActive(false);
                }
                else
                {
                    child.gameObject.GetComponent<SpriteRenderer>().color = new Color32(255, 255, 255, 120);
                    child.GetChild(0).gameObject.SetActive(true);
                }
            }
        }
        else
        {
            info.SetActive(!info.activeSelf);
        }
    }
}

Interactの記述はこんな感じ。これがたくさんあります。
ウインドウ上で↑にあるAnimationManagerを入れていく形。

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

public class HeightDown : UdonSharpBehaviour
{
    public AnimationManager animationManager;

    public override void Interact()
    {
        animationManager.ChangeHeitht("down");

    }
}

以上です。ずっと抱えてたのが終わってすっきりしました。
また何か思いついたら作ろう…

今までの記事

調査編

環境構築編



この記事が気に入ったらサポートをしてみませんか?