ハツェの真時代傾向璋

興味を持ったことを書いていく鱗片的な場所から先の未来の場

シン・U# 入門 ②

こんばんは。ハツェです。
今回も、Udonスクリプトで制作するための記事を書いていきます。
今回は、Udonの同期関連について話して行けたらなと思っています。
あくまでもU#を扱っているので、この記事はコードを基礎的に書ける方を対象としています。予めご了承ください。
動作環境は、Unity : 2019.4.29f1、VRCSDK : 2021.08.11.15.16、U# : v0.20.2です。

目次


前回

前回は導入と簡単な処理をしました。
hatuxes.hatenablog.jp

今回の概要

今回は同期について紹介します。
変数操作自体は普通のコードとなんら変わりはないですが、同期の方法についてはVRChat独特の概念を持つので、少し混乱するかもしれません。
ですが、しっかりと頭で分けて一つずつ考えれば、内容自体はそこまで難しい話ではないと思います。
また、同期のいろはを書いているため前回よりもかなり長めな記事となっています。あらかじめご了承ください。
では、やっていきましょう!

同期について

SDK2では、ObjectSyncとかTriggerで同期を行っていましたが、Udonにも同期を行う仕組みがあります。
VRC_Trigger等を過去に使用したことが無い状態で初めてUdonを触るという方は、最初に以下の公式動画を見ると良いと思います。
www.youtube.com

基本となる同期は以下の三つです。

  1. ObjectSyncによる同期
  2. 変数単体の同期
  3. 処理の同期

それぞれ説明していきます。

ObjectSyncによる同期

SDK2と同様にObjectSyncを用いると、気軽にオブジェクトの同期が行えます。

f:id:hatuxes:20210319010639p:plain

ここで同期出来るのは、TransformとRigidBodyのみです。
ちなみに、ObjectSyncのOwnerは、最後にオブジェクトを動かした人になるらしいです。
一番下のAllow Ownership Transfer on Collisionは、他のコライダーと接触した時にOwnerを移すかどうかのチェックボックスです。あんまりちゃんと動いてくれないので、オフにしておくのが良いと思います。

また、連続同期設定をしたUdonBehaviorを使用すると、ObjectSyncが同期させるタイプを変更することは出来ます。
Transform同期のスムージングを解除したり、重力やキネマチックの可否を設定することが出来るみたいです。
詳しいことは、公式サイトをご覧ください。
docs.vrchat.com

変数の同期

Udonになって、変数ごとに同期がとれるようになりました。
UdonGraphでいうところのここですね。

f:id:hatuxes:20210319011028p:plain

このSyncはワールド全員に同期するだけでなく、後から来た人にも同期します。
この後から来た人にも同期するというのが、変数同期の一番の強みです。
U#では、変数の属性として[UdonSynced]を指定すると同期変数になります。
ただし、この属性をつけた変数はそのUdonBehaviorのOwnerにしか編集出来なくなるため、変数をいじる際にはSetOwner()等でOwnerを渡す必要が出てきます。

ちなみに、対応している変数の型はbool, char, byte, sbyte, short, ushort, int, uint, long, ulong, float, double, string, Vector2, Vector3, Vector4, Quaternion, VRCUrl, Color, Color32ですが、配列の同期に関しては現在string, VRCUrlのみ同期出来ないので注意が必要ですです。現在は変数か配列か気にせず同様の型が同期可能となっています。

変数の同期方法

変数を同期させる方法は二種類存在します。

  1. Continuous Sync - 連続同期
  2. Manual Sync - 手動同期

どの手法で同期させるかはGameObjectごとに決めることができ、加えて同期方法を指定する方法は、Inspectorから選択する方法とスクリプトから指定する方法の二種類があります。

Inspectorから同期方法を指定する

Inspectorから選択する場合、Synchronization Methodから選択出来ます。

f:id:hatuxes:20210319012805p:plain

欠点としては、複数人で扱うプロジェクトの場合、他人に勝手に変更されてしまう危険性もあるので注意が必要です。

スクリプト内から同期方法を指定する

一方、スクリプトから指定する場合、クラスの属性として指定します。

[UdonBehaviourSyncMode(BehaviourSyncMode.Continuous)]
public class ClassName : UdonSharpBehaviour { }

設定はAny Continuous Manual NoVariableSyncの四つから指定できます。 Any 以外を設定した場合、Inspectorから変更出来ないようにすることが出来ます。
f:id:hatuxes:20210319015657p:plain

また、NoVariableSyncを指定すると、同期変数が存在するコードを記述した場合にコンパイルエラーを出してくれるようになります。

f:id:hatuxes:20210522160729p:plain

便利でもありますが、裏では自動的に連続同期にされるようなので、通信量を少しでも節約したい人はManualを指定しておくと良いと思います。

Continuous Sync- 連続同期

連続同期とは、プレイヤーが更新するタイミングを指定せず、自動的に変数の値を同期させ続ける手法であり、現在はおおよそ0.2~2秒間隔で値を同期させ続ける仕様になっています。
例えば、色情報を常に更新し続けたいみたいな時に使えますね。
常に更新し続けるというのは便利なときもありますが、反対にこの特性から他の通信のために帯域を節約する理由で同期が更新されないこともあるという仕様になっています。つまり、同期の優先度は低めに設定されているということです。そのため、全ての同期設定をこれにするのはあまり良い手法ではないかな思います。

Manual Sync- 手動同期

手動同期とは、任意のタイミングでプレイヤーが変数の値を同期させる手法です。
基本的にこっちの方が確実に値を同期してくれます。
手動で値を更新するので、更新するためには変数の値を変更した後、UdonBehavior.RequestSerialization()を呼んであげると次の通信時に更新されます。
こちらは手動で更新するため帯域を喰う心配はないですが、弱点として唯一挙げるとするとならば、ObjectSyncを扱えないということがあります。

処理の同期

処理同期は、変数同期とは異なります。
例えば銃から弾を発射するというギミックを考えた際、弾が銃口から発射されるという処理が同期していないと完成しないわけです。
これはこれで便利なのですが一つ注意点として、変数同期と異なり現在のインスタンスにいる人には同期されますが、その後に入ってきた人には同期されません。この点は変数同期とかなり異なります。

その処理同期ですが、使うのはSendCustomNetworkEventです。
SendCustomNetworkEventは同期させたい処理を記述している関数の名前を指定してあげることで、処理を同期させることが出来ます。
関数名を指定する手法であるため、現状引数を扱う関数をSendCustomNetworkEventで呼ぶことは出来ません。引数を用いる手法は、要望が多いのでそのうち実装されるかなと...。
因みにUdonGraphにももちろん同じノードがあります。

f:id:hatuxes:20210319014210p:plain

Ownerを渡す

先述しましたが、同期変数はOwnerのみ編集することが出来ます。
そのため、別の人が同期変数に変更を加えるような操作を行う場合、基本的にはその人をOwnerにさせてあげる必要があります。
具体的にOwnerを別の人に移す場合、Network.SetOwnerを呼べば良いです。
Network.SetOwner関数を呼んだ場合、以下の一連の処理が発生します。

  1. (必須) Networking.SetOwner()を呼び、オーナー権限のリクエストをする。
  2. (任意) リクエストが発生した際にはOnOwnershipRequestイベントが呼ばれるので、そのリクエストを許可する場合はtrueを返す。falseを返した場合、Ownerは譲渡されずにこのまま処理が終わる。
  3. (任意) 前述の処理でtrueを返した場合はオーナーが譲渡され、その後OnOwnershipTransferredイベントがインスタンス内のすべてのユーザーのGameObjectで呼び出される。


SDKから移行してきた方へ
特徴的な箇所としては、リクエストの可否が出来るようになった点だと思います。
Udonをアップデートする過程で、旧Udonから移行してきて既存のシステムをそのまま使いたいと思う方も多いと思います。
仮にOnOwnershipRequestを記述しなかった場合、無条件でOwnerを移行する仕様になる(以下のコードを記述した時と同じ処理になる)ので特にコードを変更しなくても対応出来るため、そこまで意識する必要はありません。

public override OnOwnershipRequest(VRCPlayerApi requestingPlayer, VRCPlayerApi requestedOwner)
{
    return true;
}


実例

ここからは、いくつか例になるものを作っていこうと思います。
以下で使用するCanvasとParticleは、全てこのunitypackageに入れてあります。
必要に応じて使用してみてください。(自由に使用して結構です)
drive.google.com

パーティクルを発射する装置を作る

まずは処理同期からやってみましょう。
概要としては、Cubeをインタラクトしたら上からパーティクルが数秒ポロポロと落ちてくるものです。
まず、インタラクトに使用するCubeと以下からダウンロードしたパーティクルをシーンに設置します。
だいたいこんな感じで設置します。

f:id:hatuxes:20200413162524p:plain

そしたらCubeに以下のようなスクリプトをアタッチしましょう。

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

public class PlayParticle : UdonSharpBehaviour
{
    public ParticleSystem Particle;  // 再生するパーティクル本体

    public override void Interact()
    {
        // パーティクルが再生されてなかったら、再生する
        if (!Particle.isPlaying)
        {
            SendCustomNetworkEvent(VRC.Udon.Common.Interfaces.NetworkEventTarget.All, "Emit");
        }
    }

    public void Emit()
    {
        Particle.Play();
    }
}


これで、一応完成となります。少し解説しますね。
気になる所は20行目のここ。

if (!Particle.isPlaying)
{
    SendCustomNetworkEvent(VRC.Udon.Common.Interfaces.NetworkEventTarget.All, "Emit");
}

SendCunstomNetworkEventが処理同期のための関数ということは同期の説明時にお話ししました。
この関数は第一引数に処理を同期させる人の種類、第二引数に同期させる処理を記述した関数の名前を指定します。
第一引数は現状VRC.Udon.Common.Interfaces.NetworkEventTarget.AllVRC.Udon.Common.Interfaces.NetworkEventTarget.Ownerの二つから選べます。
Allの方はインスタンスにいる全員に関数内の処理を実行します。
一方Ownerの方は、オーナーにのみ関数内の処理を実行します。
加えて、ifの中身はパーティクルの重複起動防止のために再生中かどうかの判定を行っています。
ちなみに、SendCunstomNetworkEventで呼ぶ関数はpublicである必要があります。

実行

実行画面は以下の通りです。(三人称視点の映像)

f:id:hatuxes:20200413165038g:plain

押した人は別の人なのに、ちゃんと自分の画面にもパーティクルが降り注いでますね。
これで処理同期が確認出来たことになりました。

カウントアップする装置を作る

次は、カウントアップ装置を作りながら変数同期の例を作成していきます。
まず始めに、上記のprefabの名前とかを少し変更して、以下のように三つのCanvasを作成しました。全く同じ感じでも良いですし、好きにアレンジしても構いません。
内容は左から、ローカル版、Owner経由版、Owner変更版として使って行く予定です。

f:id:hatuxes:20210519184305p:plain

まずはローカルのカウントアップ装置を作る

最初に、ベースとなるカウントアップ装置を作っていきます。ここは同期とは一切関係ありません。
概要としては、Cubeをインタラクトしたら数字が1ずつ増えていくものを作ります。
インタラクトさせるCubeにUdonBehaviorを追加して、以下のスクリプトをアタッチします。

using UdonSharp;
using UnityEngine.UI;

public class Local_Countup_System : UdonSharpBehaviour
{
    public Text DisplayDataText;  // データを表示するためのText

    private int _countData;           // データ本体

    void Update()
    {
        // 常時データをTextに出力する
        DisplayDataText.text = _countData.ToString();
    }

    // Cubeをインタラクトすることで、値を+1する処理
    public override void Interact()
    {
        _countData++;
    }
}


仕組みは単純で、インタラクトしたらint型の変数_countDataを1ずつ増やしていくだけです。

実行

実行画面は以下の通りです。

f:id:hatuxes:20210520144716g:plain

今度は、これを同期させてみましょう。

Ownerに加算処理をさせることで同期変数を操る

同期の説明時にもお話ししましたが、同期変数はそのUdonBehaviorのOwnerしか変更出来ません。
最初の手法では、Ownerに処理を任せることで同期変数を扱ってみようというパターンを紹介します。
ローカル版と同様、Cubeに以下のスクリプトをアタッチします。

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

[UdonBehaviourSyncMode(BehaviourSyncMode.Manual)]
public class ViaOwner_Countup_System : UdonSharpBehaviour
{
    [UdonSynced(UdonSyncMode.None)] private int _countData;        // データ本体

    public Text DisplayDataText;            // データを表示するText
    public Text OptionText;                 // 誰がOwnerかを表示するText



    private void Start()
    {
        // Onwerかどうかを表示
        SetOptionalText(Networking.LocalPlayer);
    }



    // Cubeをインタラクトしたときに呼ばれる
    public override void Interact()
    {
        var player = Networking.LocalPlayer;

        if (player.IsOwner(this.gameObject))
        {
            // Ownerが押したら、純粋にカウントアップする
            CountUp();
        }
        else
        {
            // Owner以外が押したら、Ownerにカウントアップさせるように命令する
            SendCustomNetworkEvent(VRC.Udon.Common.Interfaces.NetworkEventTarget.Owner, nameof(CountUp));
        }
    }

    // Owner以外のデータ表示処理
    public override void OnDeserialization()
    {
        DisplayDataText.text = _countData.ToString();
    }



    // Ownerが値を+1する処理
    public void CountUp()
    {
        _countData++;                                   // データ更新
        RequestSerialization();                     // 同期更新
        DisplayDataText.text = _countData.ToString();   // データ表示更新
    }

    // プレイヤーがOwnerかどうかをテキストで表示させる処理
    public void SetOptionalText(VRCPlayerApi player)
    {
        if (player.IsOwner(this.gameObject))
        {
            // Owner側の処理
            OptionText.text = $"<color=red>{player.displayName} is Owner!</color>";
        }
        else
        {
            // Owner以外の処理
            OptionText.text = $"{player.displayName} isn't Owner";
        }
    }
}


少し情報が多いので、一つずつ紹介していきましょう。

同期変数の書き方

まずは、後から来た人にも同様な値を見せたい変数に[UdonSynced]属性をつけます。

[UdonSynced(UdonSyncMode.None)] private int _countData;

UdonSyncModeは同期の仕方を設定するオプションです。
設定はNone Linear Smooth NotSyncedの四つから指定できます。
それぞれただの同期 リニア補間同期 スムーズ補間同期 同期しないです。
Linear Smoothについては、連続同期でのみ使用出来ます。手動同期では使えないので注意が必要です。
それぞれ動かしてみた感じとしては以下の通りで、体感としては下図のような感じになります。

f:id:hatuxes:20200413161827p:plain

上記動画を見てもわかる通り、IntやFloatなら問題ないですが、Quaternion等のLinearSmoothが正常に同期されていないため、素直にNoneを使用するのがオススメです。
また、今回は同期変数を使用し、かつ手動同期を用いるため、クラスの属性に[UdonBehaviourSyncMode(BehaviourSyncMode.Manual)]を付けました。(クラス属性を付けずにInspectorからManualSyncを選択しても構いません)

ManualSyncで変数を同期させる

同期の説明時にもお話ししましたが、UdonBehavior.RequestSerialization()を呼んだフレームから次の通信(正確には次のネットワークティック)で他の人にも同期されます。
難しいことを言っているように聞こえますが、結局RequestSerialization()関数を呼べば同期してくれるわけです。
実際に、変数を同期させている箇所はCountup()関数内で行っていました。

// Ownerが値を+1する処理
public void CountUp()
{
    _countData++;                                   // データ更新
    RequestSerialization();                     // 同期更新
    DisplayDataText.text = _countData.ToString();   // データ表示更新
}

まず、同期変数の値を+1し、その値を他の人にも同期させ、最後に自分の表示を更新するという流れです。
この関数はOwnerのみが実行されるように作っているため、問題はありません。

処理をOwnerに渡す

処理をOwnerに渡す方法は、SendCustomNetworkEventの第一引数をOwnerにするだけです。ちなみに、第二引数の関数名にはnameof()関数を用いると、楽にコードが書けるのでオススメです。

SendCustomNetworkEvent(VRC.Udon.Common.Interfaces.NetworkEventTarget.Owner, "関数の名前");

今回は、インタラクトしたプレイヤーがOwnerかどうかで処理を分けました。

public override void Interact()
{
    var player = Networking.LocalPlayer;

    if (player.IsOwner(this.gameObject))
    {
        // Ownerが押したら、純粋にカウントアップする
        CountUp();
    }
    else
    {
        // Owner以外が押したら、Ownerにカウントアップさせるように命令する
        SendCustomNetworkEvent(VRC.Udon.Common.Interfaces.NetworkEventTarget.Owner, nameof(CountUp));
    }
}

Ownerの場合は普通に処理を実行し、そうでない場合はOwnerに同じ処理を渡しています。
今回は説明のために丁寧に作っていますが、結局処理がOwnerに渡る意味では条件分岐なんかせずに、SendCustomNetworkEventだけInteract()関数内に書いても問題ないと思います。

Owner以外に同期データを反映させる

これはテクニックですが、OnDeserialization()内に同期変数を扱ったデータに関する処理を書くのが良いです。
Update()じゃないの?と思う方もいると思いますが、OnDeserialization()自体が同期変数を受け取った時に呼ばれる関数になっています。そのため、データの確実性的にOnDeserialization()内に書いた方が良いという訳です。
今回は、Owner以外の人に向けて同期変数_countDataの表示処理をOnDeserialization()内に書いています。

// Owner以外のデータ表示処理
public override void OnDeserialization()
{
    DisplayDataText.text = _countData.ToString();
}


追記
現在では、同期変数を扱う処理はOnDeserializationよりOnValueChangedを用いる方が良いとされています。
理由は、OnDeserializationよりも更新速度が速いため、OnDeserializationを用いた場合に同期ズレが発生していたような処理でもOnValueChangedを用いた場合は素直に同期処理出来るからです。
OnValueChangedについては、C#でいうプロパティの仕組みを用いるため、当記事では取り上げていません。
当記事を読んで同期の仕組みが理解できた上で気になる方は、以下の別記事を見てみることをオススメします。
hatuxes.hatenablog.jp

実行

実行画面は以下の通りです。(三人称視点の映像)
他人が押しても、自分が押しても問題なく動いています。英文が間違ってるのはミスです...

f:id:hatuxes:20210520153828g:plain

Ownerを移して自分で同期変数を操る

今度はもう一つの方法として、自分がOwnerになって同期変数を操作するパターンを紹介します。
ローカル版と同様、Cubeに以下のスクリプトをアタッチします。

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

[UdonBehaviourSyncMode(BehaviourSyncMode.Manual)]
public class OwnerTransfer_Countup_System : UdonSharpBehaviour
{
    [UdonSynced(UdonSyncMode.None)] private int _countData;      // データ本体

    public Text DisplayDataText;          // データを表示するText
    public Text OptionText;               // 誰がOwnerかを表示するText



    private void Start()
    {
        // Onwerかどうかを表示
        SetOptionalText(Networking.LocalPlayer);
    }



    // Cubeをインタラクトした時に呼ばれる
    // 新にOwnerになって、データを自身で更新する
    public override void Interact()
    {
        var player = Networking.LocalPlayer;
        Networking.SetOwner(player, this.gameObject);

        if (player.IsOwner(this.gameObject))
        {
            CountUp();
        }
    }

    // Owner以外のデータ表示処理
    public override void OnDeserialization()
    {
        DisplayDataText.text = _countData.ToString();
    }

    // SetOwnerのリクエストが飛んだ際に呼ばれる関数
    // 譲渡の可否を返す必要がある
    // trueを返すだけなので、書かなくても良い
    public override bool OnOwnershipRequest(VRCPlayerApi requestingPlayer, VRCPlayerApi requestedOwner)
    {
        return true;  // 譲渡を許可
    }

    // Ownerが移行した際の処理
    // このイベントはインスタンスにいる全員に発行されるので、Network.LocalPlayerを用いる
    // 引数のplayerは新たなオーナーを指す
    public override void OnOwnershipTransferred(VRCPlayerApi player)
    {
        SetOptionalText(Networking.LocalPlayer);
    }




    // Ownerが値を+1する処理
    public void CountUp()
    {
        _countData++; // データ更新

        // Owner変更後に即時更新すると、OnDeserializationに追いつかないことがあるため。少し遅延させている
        SendCustomEventDelayedSeconds(nameof(SerializeData), 0.4f);


        DisplayDataText.text = _countData.ToString();   // データ表示
    }

    // プレイヤーがOwnerかどうかをテキストで表示させる処理
    public void SetOptionalText(VRCPlayerApi player)
    {
        if (player.IsOwner(this.gameObject))
        {
            // Owner側の処理
            OptionText.text = $"<color=red>{player.displayName} is Owner!</color>";
        }
        else
        {
            // Owner以外の処理
            OptionText.text = $"{player.displayName} isn't Owner";
        }
    }

    public void SerializeData()
    {
        RequestSerialization();       // 同期更新
    }
}


仕組みは先程のOwnerに処理を渡す方法と同じです。そのため、異なっている箇所のみ説明を挟もうと思います。

Ownerを渡す流れ

同期の説明時にもお話ししましたが、SetOwner()をするとOnOwnershipRequest, OnOwnershipTransferredと順に処理が発生します。
今回の例で同じ箇所を追ってみましょう。
今回は、まずインタラクト時にSetOwner()を呼びました。

// Cubeをインタラクトした時に呼ばれる
// 新にOwnerになって、データを自身で更新する
public override void Interact()
{
    var player = Networking.LocalPlayer;
    Networking.SetOwner(player, this.gameObject);

    if (player.IsOwner(this.gameObject))
    {
        CountUp();
    }
}

その後、OnOwnershipRequest内でOwnerの譲渡を無条件で許可するように記述しました。

// SetOwnerのリクエストが飛んだ際に呼ばれる関数
// 譲渡の可否を返す必要がある
// trueを返すだけなので、書かなくても良い
public override bool OnOwnershipRequest(VRCPlayerApi requestingPlayer, VRCPlayerApi requestedOwner)
{
    return true;  // 譲渡を許可
}

そして、Ownerが受け渡された時に自身がOwnerかどうかのテキスト表記を更新するという書き方をしました。

// Ownerが移行した際の処理
// このイベントはインスタンスにいる全員に発行されるので、Network.LocalPlayerを用いる
// 引数のplayerは新たなオーナーを指す
public override void OnOwnershipTransferred(VRCPlayerApi player)
{
    SetOptionalText(Networking.LocalPlayer);
}

ちなみに、OnOwnershipTransferredの引数のplayerは新しくOwnerになったプレイヤーを指し、Network.LocalPlayerは単純にローカルプレイヤーを指します。
OnOwnershipTransferredは、Ownerだけとかではなくインスタンスの全員に発行されるので、この違いには注意が必要です。

実行

実行画面は以下の通りです。(三人称視点の映像)
他人が押しても動作しており、自分が押した際にもしっかりとOwnerが移った状態で変更されていることが分かります。英文が間違ってるのはミスです...

f:id:hatuxes:20210520155120g:plain


以上です!お疲れ様でした。
今回も、サンプルプロジェクトはGithubに置いておきます。
github.com

次回

次回は、UIでUdonを使う方法について触れていきます。
hatuxes.hatenablog.jp

2020年もお疲れ様でした

こんばんは。ハツェです。
2020年は煌びやかな年になるかなと個人的には思っていましたが、現実はとても忙しい一年だったかなと思っています。
という訳で(どういう訳)今年一年私が何をしていたかを振り返っていけたらと思います。

目次


今年の創作等の振り返り

年明け~三月

一月

年明け大一発は、シェーダーの動画を上げている方の動画の内容を再現していたりしましたね。
この時はまだシェーダーを書くことが好きでしたが、これ以降は特にシェーダーについて新たなことを学ぶということはしなくなりました。


あとは、VFXGraphを触り始めたりもしていましたね。懐かしいです。
PointCacheの機能を試していた時に、偶然Alcedoちゃんが時空間移動してそうな見た目になったのは今でも覚えていますね。


コレ関連で言うと、HDRPを触ってみようかなと思って続かなかったことを思い出しました。良い思い出です。
HDRPは綺麗な絵が作れるのかなと思って触っていたんですが、蓋を開けてみたら現実のパラメータを参照することが前提の仕様だったので割とすぐに諦めましたね。
フォトリアルを作るときに役立つかなと言った気持ちでした。


因みに、このタイトル画面を作って見ようの時に理解した内容を記事として投稿していたりします。
hatuxes.hatenablog.jp
他にはしっかりとBlenderレンダリングしてみたかったので、入門動画を真似たりしていました。
意外とBlenderだけでも楽しめるというか本来の機能をしっかりと使ってみたみたいな印象でしたね。純粋に楽しかったです。

あと個人的には、成人式に行ったことですかね。二日前に熱を出したときは行けないかなと諦めていましたが、自分なりに根気で治した記憶があります。まだ、世界が今みたいになる前だったので直前に体調を崩していても出席出来てた時代。
余談ですがこの頃、スマブラのストーリーをクリアしたりFFXIVを始めてみたんですが、FFはあんまりハマらなかったので今は遊んでなかったりします。


二月

二月はあんまり色々はしてない気がします。
この頃からECSについて勉強し始めていました。

「たくさんもの出しても重くならないの!?すごーい!」ぐらいの気持ちで始めたら、予想以上に難易度高くてびっくりしたのが思い出ですね。

色々遊んでたらツイートの幾つかが伸びたりしてびっくりしましたね。

あとは、この時に初めてAMOKAの皆さんにお会いしたりしました。当時はVケットにあったポスターを見て「なんだろう?」と検索したら音楽アーティストだったと分かって気になっていたタイミングだったので、個人的にはこの時に会えたのは結構嬉しかったですね。

あとは、かなりAPEXしてました...w


三月

三月は恒例の誕生日イベント🎉
いつもこの返信をしているときは気分がとてもルンルンしています。


他には、初めてゲームを作り切るという経験をして見たくなったので、小さい規模ではありますがリリースするところまでやってみたりしていました。
しっかりとゲーム作るなら生半可な覚悟でやっても続かないなと、このことから思うようになりましたね。ゲーム作りはとても大変。でも面白いとハマり始めたら絶対楽しいと思います。当時作っててそんな気持ちを実感しました。
github.com
加えて、三月からメインで使うアバターを右近ちゃんからCygnetちゃんに変えました。完全に一目惚れです。とてもかわいい。
booth.pm booth.pm
他には当時防振りにハマっていたので、その中の演出の真似事をしたりとか。

UnrealEngine頑張って入門してみたけど、結局続かなかったりとか。
当時バーチャルレンズが出始めたころで、めっちゃ撮ってもらった写真を見て「良~👍」とか言ったり。
早朝に開くバーに入り浸ったりしていました。

四月~六月

四月

まずは、バチャコレVRoidStageやってましたね。実はこの回から中のUnityProject大改造計画を実行していました。Unityの長期Git管理を考えるのはとても大変ですね。今後スムーズに動かせるようにするため、グチャグチャだった中身を私一人で整頓する作業を三月ぐらいから進めていて、四月になって一気に演出とその操作盤を作るといういつも通りのハードスケジュールでやっていました。これのおかげで半年後のMen'sStageは比較的制作側は楽でした。
www.youtube.com


あとは、この頃OpenBetaになっていたUdonの機能を触っていたりしました。まだ本リリース前だったので、当時から触っている人はほとんどいなかった感じがします。
それで、触っているうちに「これ初見で扱う人リファレンスほぼほぼ無くて路頭に迷うかもしれない」と思って、我先に入門記事を書くことを決断しました。それでこの記事を投稿した訳です。今でも見てくださる方が多くてビックリしていると共に、作ってよかったなぁと思っています。
hatuxes.hatenablog.jp
他には、つぶやきGLSLというハッシュタグTwitterに出現し始めたので、その流れに乗っかったりしてました。今もそのハッシュタグを使っている人がいて文化が続いているのは良いなーという気持ちになっています。

余談ですが、この頃VRChatの規約が変わってたりしましたね。


五月

五月はホームワールドに使う3Dモデルを自作していました。いわゆるデジタル空間での自室ってやつです。1月に入門していたモデリング経験を何かに生かしたいなと思って皮だけでもフルスクラッチで自室を作ろうかなと思ったことと、別にもう一つ前からやりたかったことがあったので、両方の理由から自室ワールド制作を始めましたね。自分で作ったおかげかかなり自分の中で気に入っていて、今でも使っています。


あとは、当時たまたま見つけたResonarkというゲームワールドにハマっていたりしました。とにかくUdonで凄いの作る人現れたな~って思ってましたね。今もこの気持ちは変わってないです。

他には、VSCodeにLiveShareっていう同時に複数人で同じコードを触れる機能が出たので、それでライブコーディングとか言って遊んでたりしましたね。懐かしいです。今となってはそんな大したものは作れないですが、こう遊び感覚でたまにやってしまう心境。なんなんでしょうね。

後、この月にはリンドの夜探しっていうYoutubeライブ番組に出演したりもしていました。私自身生放送に出ることとかが好きなので、「面白い登場演出がしたいな~」と思って直前にリンドさんに無茶ぶりしてしまったのを今でも覚えています。リンドさんがとても優しい方だったので応じていただけましたけど、やっぱり無茶ぶりは良くないですよね...w
youtu.be
あとは、当時memexのイベントがとても面白かったり。

Vケット回ったり。
APEXしたり、飛んできたブルーインパルスに感動したりしていました。


六月

六月からは夏の準備とかを水面下でやっていたので、表立った公表はあんまりしてなかったと思います。「VRAAどうしようねー」みたいなことを言っていたような気もする。
まずは、私のワールド「No time.」をUnity2018に対応させたりしていました。それと合わせてポスターを配布し始めたりもしていましたね。懐かしい。良かったら飾っててください!

t.co
創作はこれぐらいでして、思い出としては古のVRChatのロード画面の動画を投稿したらやっぱりリアクションが多かったり。
一年ぶりぐらいにNeosVRに行ってきたり。

Cygnetちゃん集会に参加したり。

(ご好意で)ResonarkChampionShipの決勝ステージで遊ばせてもらったりしていました。

七月~九月

七月

七月はVRAAのワールドを公開しました。名前は「On Instances.」です。結果は惨敗でしたけど、この時の目標が「Udonで何かしらを作る」ぐらいの気持ちだったのであんまり入選する気はなかったんですよね。まぁ後から言えばどうとでもっていうやつですけど。多分ここの隠しメッセージを読んでいる人はほとんどいないんじゃないかな。そういうのも面白いですけどね。歴史的発見!みたいな感じがして。ちなみに、このワールド作った感想は書くつもりが無くなりました。


あとはなんか私のGithubのプロジェクトが永久凍土されたりAPEXしたりしていましたね。水面下で夏の準備していましたが、七月後半とかは倒れそうになっていたと思います。


八月

八月前半は盛夏音祭'20の準備とかでずっとしんどかったです。めっちゃ大変だったもんこのイベント。規模はデカいし、制作期間が以上に短かったりでてんてこ舞いでした。それでも、いろんな人が各部署で頑張ってくれていて、誰か一人でも欠けたら成立できないイベントだったかなと終わった今は思いますね。
www.youtube.com


あとはVFXGraphで何か作りたいなーと思ったので、入門動画を参考に花火とか作ってました。しかしこれ、慣れるのに結構時間がいるなーって触っていて思いましたね。やっぱりParticleSystem(Shuriken)とは扱いがかなり違う感じでしたからね。Particleの中に少し演算システムを組み込めるようなものみたいなイメージを持った方が扱いやすいかもしれないですね。まだまだ探検し甲斐がありそうです。

あとは、Avatar3.0を探求したり。

夏祭りに参加していたり。

海を泳いだりAPEXしたりしていました。


九月

9月は半年たったECSで何か作れないかなと思って、再度ミニゲームを制作していました。やっぱり弾のあるゲームを作りたいですよね。作ってて楽しい。


あとはなんだろ。UdonにVideoPlayerが追加されるからそれについて探求していましたね。今ではMerlinさんのUSharpVideoPlayerがあれば問題ないので私は何もしていませんけどね。軽い解説記事は投稿しました。
hatuxes.hatenablog.jp
思い出としては、肖像画のような写真がえこちんさんのワールド「Gallery Lens」に飾られたり。
逆再生のGiFを作って遊んでいたりしました。
あとはAPEXしてました。

十月~十二月

十月

10月からはいよいよ何も創作してないです。この時期からは就活の準備をし始めていたので、あんまり創作活動にコミットできなくなりました、 一つは、UnitBarrageという弾幕ミニゲームをリリースしたことですね。攻めてECSを使ってゲームを作ったことと、ネットワーク通信に挑戦したりしたので、自分の中ではかなり頑張ったつもりですね。苦手なJavascriptも使わなきゃいけなかったので、ストレスがまぁまぁあったかな...w

github.com
二つ目は、あのResonarkChampionShipの第二回のスタッフをしたことですね。
面白いなーと思って遊んでいたゲームの大会に携われるっていうのは、結構嬉しいもんですよね。にしても主催のseptemさんは凄い方だなぁと。企画力があるし、モノづくり力もあるし、マネジメント力もあるし、広報力もあるしで凄いですよね。尊敬するなぁと同じ大会を運営していて思いました。
youtu.be
三つめは、Udonの仕様が変わって壊れていたOn Instances.を直したりしていました。ついでにポスターも作ったりしまして。こういうポスター作りの経験は今までなかったのですが、手探りながらもいい感じのポスターを作っています。良かったらこちらも飾っていただけると嬉しいです。 hatuxes.booth.pm
10月の思い出としてはUdonSwordが出たので、この時仲良くさせてもらっていた方々と巡ったりしていました。

十一月

バチャコレしました!Men'sStage!!四月に内部変更しておいたおかげで、この時はスムーズにプロジェクトを回せたなと思います。ただ、この時から新体制になったことから運用が手探りになっていたこともあり、本番のミスが目立ってしまったなぁというのが課題点ですね。今後頑張っていきたい。 www.youtube.com


あとは、いつか使いそうなプレゼンシステムを作ってました。去年は技術系のイベントで登壇したりとかしていましたが、今年はあんまり参加してなかったので使うことはなかったです。けど、動画を出すときとかには使えるかなと思ったので、作ったんですよ。
思い出としてはカスタム背景で遊んだり。
APEXってホラーゲームかなと思ったりしました。

十二月

12月はバーチャル学会2020の開催に携わってました。と言いたいんですが、バーチャル学会実は4月ぐらいからずっと裏で準備していたので、一年のほとんどをこのタスクに注いでいました。率直に疲れましたねー。確実にイベントを進めていくというのはこうも難しいのかとエンタメイベントとの違いに気づかされました。これを知れたのはとても良い経験だったかな。私は最悪半年以内で完結するイベントに関っていくほうが得意だなと思いました。


あと、12月といえばアドベントカレンダーの季節。去年は一個だけ書いたんですけど、今年は流れに乗ってしまって二つ記事を書くことになり、結構大変でした💦
一つ目はョョョねこアドベントカレンダーですね。題材はECSについて書きました。 hatuxes.hatenablog.jp 二つ目はVRChatアドベントカレンダーです。題材は一時期話題になっていた移動床について書きました。 hatuxes.hatenablog.jp どっちも今年の私を象徴するような記事になったので、うまいことまとめられたかなと思います。
あとは自室を少し冬仕様にしたりしました。思い出を飾るっていうのは結構心にじんわり来ますよね。大切だと思います。このまとめ記事も同じように。
思い出としては、安土城に行ったり。

VRChatに課金したり。

第二回Cygnetちゃん集会にお邪魔したり。
U#の公式ページに私の記事のリンクが載ってたり。
Vケット見に行ったりしていました。

今年の総括

今年もずっとVRChatのイベントに携わっていたような気がします。それもこれが私の使命なのかっていうぐらいの重量で。私なりによく頑張ったなぁと思います。けど、ちょっと頑張りすぎたかなと振り返って思いますね。率直に無茶ぶりに答え過ぎた感じです。もう少し先を読んでスケジューリングするべきかなと思いました。そこは反省点ですね。というかいくつかのイベントを掛け持ちするのは素直にやめた方が良かった
あとは、去年に掲げた抱負を達成出来たかどうか振り返ってみますか。
まずは「何かしらソフトウェアを出す」という抱負。これは達成したといっていいかな。ゲーム二本をビルドして遊べる状態にしてリリースしたので達成できたかなと思います。
次は「私個人の名刺を新調する」という抱負。これは未達成ですね。正直今年の情勢的に必要なくなったので新調しなかったんだと思います。
最後は「自分は周りの方々にどのようなことをしていけばよいのかについて考える」という抱負。これは達成できたと聞かれると自分の中では就活の準備をしていて決めたことがあるので達成できたかなと思います。率直に言うと、自分は周りの方に向けてエンターテイメントを作っていけるような人になりたいなと思っています。方法は色々あると思いますが、どんな形であれ参加する方を喜ばせられるようなものを作って発信していけたらいいなと決めました。
といったような感じですね。抱負を総括するなら、おおむね達成できたかなと私は思います。抱負立ててたことはすっかり忘れてましたけど、それでもだいたい達成できるような行動をしていたのは面白いですね。私も今こうして振り返っていてびっくりしています。
まぁ、今年全体の振り返りをまとめると頑張りすぎたと言うに他ならないかな😅
今年の情勢的に家から出ることなくオンラインでほとんど完結できるようになったからか、作業量が増したように思えます。ただ、このまま行くと私がボロボロになるので考えモノです。こういう意味では自粛したいですね。

未来の話

来年の抱負についてです。以下の三つを掲げたいなと思います。

  1. 内定先を獲得する
  2. 自分の身体を第一に考えて行動する
  3. ゲーム作りに専念する

一つ目の「内定先を獲得する」。これは私が来年就活しなければならない時期になるからです。怖いですね就活。自分の思いがしっかりと企業先に伝わり、それが内定に繋がるかどうかが心配で怖くて。毎晩寝る度に不安に苛まれる日々を送っています...。無事に就活を終えれたらいいなと心からそう思います。
二つ目の「自分の身体を第一に考えて行動する」。これは私が年末に身体を崩してしまった時に思ったことです。多少熱があった時の夜に、少し呼吸が乱れて死ぬかなと思う時がありました。初めてですよ。本当の意味で死にそうになったのは。あの瞬間は色々考えてしまいました。その時に「一人になるのは怖いな」という恐怖と落ち着いたときに「色々な人に支えられて私は人として生活出来ているんだな」という思いを感じました。言っても風邪ですが、油断すると風邪でも人は死にます。今年のように。なのでまずは、自分の体調をダメにしないような生活をすべきだなと思いました。毎日自分の欲望を大事にして体調を疎かにしてこうやって数日間何もできない時期を過ごすのか、毎日ちょっとずつ健康に気づかって無駄な時間を作らないようにするかで考えたらやっぱり後者かなと。私は今まで軽率に自分の健康より他人に迷惑をかけないように進捗をなるべく出そうと生きてきたのがここにきて裏目に出たなぁと。来年からはしっかりと自分の身体と相談しながら生きていこうと思います。
三つ目の「ゲーム作りに専念する」。これこそしっかりとした目標かな。今年ミニゲームっぽいものを二つリリースしましたが、まだまだ未熟な作品だと私は思っています。なので、自分が今までに作ってきた作品はこれだ!と胸を張って言えるようなものを作りたいなと思っています。なので、VRChatの創作よりもゲームの創作に重きを置けるような活動をしていけたらいいなぁと私は思っています。少し誘われているゲーム作りの話があるので、それを本格化させたいなと企んでいたり。ここら辺は追々決めていこうかなと思います。




それではみなさん!今年もありがとうございました👍良いお年を!!


来年もみなさんと愛を共有できますように........


ハツェ