土鍋で雑多煮

UnityでXR・ゲーム開発をしています。学んだことや備忘録、趣味の記録などを書いていきます。

XR Interaction toolkit の XR Hands を試す

どうも、土鍋です。

XR Handsが今年の1月にプレスリリースされましたが、触っていなかったので触ってみました。

XR HandsとはXR Interaction toolkitでハンドトラッキングするパッケージです。今までOculus Integrationを使うことが多かったと思いますが、これによりハンドトラッキングできるデバイスであれば、デバイスによらずハンドトラッキングができるので、今後かなり必要になってくると思います。

導入方法

XR HandsはXR Interaction toolkitと組み合わせて使いますので、XR Interaction toolkitとOpenXRのセットアップを先に行ってから導入してください。
XR Interaction toolkitとOpenXRのセットアップはこりんさんの記事が参考になります。(XR Interaction toolkitをいじるときはいつもお世話になってます。)

framesynthesis.jp

Package Managerを開き、Unity Registstryから「XR Hands」を探し、インポートしてください。 HandVisualizerのSampleもインポートしてください。

AndroidでXR Handsを使用する際はOpenXR 1.6.0が必要なので左上のプラスからAdd package by nameでアップデートしてください。

Project SettingsのOpenXRの項目からHand Trackingの項目にチェックしましょう。(PCとAndroid両方)

これで、ハンドトラッキングが動くようになるはずです。

デモシーンを触る

Hand Visualizer

ハンドトラッキングの動作確認だけでしたら「Hand Visualizer」のシーンで良いと思います。

Oculus Linkでも確認できるのでビルドする必要はなさそうです。

指のジョイントを表示する

Draw Meshesのチェックを外し、Debug Draw Jointsのチェックをつけると、指のジョイントが見えます。手のポーズを作っていくときに便利そうですね。

Hands Interaction Demo

ハンドトラッキングを使ったインタラクションを試したい場合はXR Interaction toolkitの「Hands Interaction Demo」がいい感じでした。物を掴む、UI触るなどの動作は問題なくできそうです。

導入はXR Interaction toolkitのSamplesからできます。

動かすとこんな感じ。

※初期設定でHand VisualizerのHand Mesh Materialが外れてて、手が表示されなくなってるので注意

まとめ

Unityデフォルトでハンドトラッキングができるようになったのでは嬉しいですね!
今後、ハンドトラッキングができるデバイスは増えるでしょうし、XR Interaction toolkitとOpenXRのメリットである様々なデバイスに対応できるというのが生きてきそうです。
ただ現状、Oculus Integrationにあった手のポーズとか指の距離とか取るものは無いので、自作する必要がありそうですね。

もう少し色々試したら、また記事を書こうと思います。

2022年の振り返りと2023年の抱負

どうも、土鍋です。
この記事はA-PxL Advent Calendar 2022の25日目の記事です。

2022年が終わり、2023年が来るというので振り返りと抱負と書こうと思います。

2022年の振り返り


1月

UnityThon

初めてのイベント主催。

これについてはブログに書いたので↓

donabenabe.hatenablog.com


2月

プロトスプリントリーグ

これについてもブログに書いたので↓

donabenabe.hatenablog.com


3月

インターン

一年生の時からお世話になってるとこで一ヶ月インターン
今までのUnityのみの開発だけでなく、ハードや通信周りを扱う経験もできました。


4月

A-PxL新入部員集め

A-PxLの新入部員を獲得するために、ポスターやチラシをデザインしたり、XR体験会を開催するなどしました。

UT-virtual 加入

外部のコミュニティに出ていく第一歩として、インカレで地方からも参加できるというので加入しました。


5月

IVRCのチーム立ち上げ&企画書作成

11月まで取り組んだIVRCに関してはブログで詳しく書いたのでこちらをご覧ください。

donabenabe.hatenablog.com

AizuHack運営&勉強会講師

AizuHackの運営とゲームコースやサークルの勉強会の講師をしました。


6月

IVRC書類審査を通過し、開発を開始しました。勉強会講師も継続して行いつつだったので、このころはあんまり開発は進まなかった気がします。


7月

7月もメインは継続してIVRCの開発。ソフト側は8月頭のコミケに出せるようにとりあえず単体で動く状態にしようという目標を掲げ、開発を進めなんとか、動くまでもっていけました。


8月

8月は夏休みに入り、9月頭のSeedStageに向けて開発を加速していきました。ハード側とソフト側を結合しすべて動くようになったのは月末で、かなり時間的にぎりぎりで動いていました。

Iwaken Lab. 加入

Iwaken Lab. はオンラインメインで個人個人の活動中心のコミュニティということで、今まさに自分が欲しているコミュニティだ!ということでイワケンさんに直接DMを送り加入させていただきました。 これは個人的に大きい出来事でその後半年の活動をがんばれた理由の一つです。メンバーの一人一人がそれぞれビジョンを掲げ、それぞれの分野で精力的に活動しており、今も毎日刺激を受けています。


9月

IVRC SeedStage

東京へ行って実際に作品を体験してもらうというのは初めての経験でした。が、そんなに緊張はなく楽しくできたかなーと思います。 ここで見えた反省をもとにLeapStageへ向けブラッシュアップを行いました。

北海道旅行

VR学会に行くついでに日高地方へ行きました。

donabenabe.hatenablog.com


10月

9月はほとんどチームメンバーが実家に帰るやらインターンやらでほとんど開発ができなかったのでIVRCの開発を中心に動きました。が、しかし、授業あるわ学祭あるわでめちゃくちゃ忙しかったです。

学園祭

私のチームはVRスカイダイビングを制作しました。新たなライブラリや設計を取り入れるなど技術的にも楽しかったです。


www.youtube.com

Game Client College~設計編~

MVPアーキテクチャを中心とした設計について学ぶプログラムで、これ以降と前で自分の書くコードが大きく変わったと思います。

これに関してはブログを書きます。(宣言してないとやらないので宣言しとく)


11月

IVRC LeapStage 審査委員会特別賞

2022年で一番頑張ったことで嬉しい結果を得ることができて本当に良かったです。IVRC挑戦によって多くの経験もできましたし、その後もIVRCの作品関連で新しいつながりやラジオ出演や新聞の取材など様々な経験に繋がりました。

ラジオ出演

NHK福島の「こでらんに5next」というラジオ番組でサークルのことやIVRCのことを話しました。親や友人など聞いてくれた人たちからはめちゃくちゃちゃんと喋れててびっくりしたと感想を頂いて嬉しかったですね。

www.nhk.jp

駒場祭

UT-virtualではまだチーム開発などをしていなかったので、東大の駒場祭に向けた開発に参加しました。僕のチームは「天空島からの脱出」というゲームを制作しました。

本当はもう少し開発に参加したかったのですが、IVRCや学祭の開発期間ともろ被っていたので、あまり参加できませんでした。機会があればもうちょっとちゃんと開発したいですね。


12月

長期インターン

12月頭からはVR系のベンチャーインターンを始めました。Game Client Collegeで学んだMVPを設計に取り入れリファクタリングしたり、アプリリリースに向けた調査などいろいろ挑戦させていただいています。

コミックマーケット101

12月31日にA-PxLとして初めて参加しました(委託ではC100も参加)。
持っていった23枚すべて売れました!(無料だけど)
Questでしか遊べないのにこれは上々かなーと思います。
来年出すならゲームのクオリティ上げて本数増やして100円とかでも買ってもらえるようにしたいですね。

まとめ

全体的にIVRCや外部のコミュニティの参加など、会津大の外で挑戦しようとした一年でした。会津大外の人との繋がりもできましたし、ある程度それは達成できたのではないでしょうか。 もちろん、影響力や発信力もないですし、まだまだだと思います。 しかし、足がかりはできましたし、外部への発信の癖や外部の人とやり取りをするのにハードルを感じなくなり、自然にできるようになったので、会津大外の人とつながるという目的のための努力をする必要はなくなったかなと思います。

2023年の抱負

2023年の抱負は

「精進」

です。

2022年は外部のコミュニティ・イベントに所属・参加することで、様々な刺激を間近で受けられる環境になりました。そこでやはり感じたのが「差」です。
それは技術力もそうですが、物事への取り組み方や考え方などにも大きな差を感じました。わりと今まで場当たり的に生きてきた、生きてこれちゃったのでなんとかなっていましたが、これより先に行くにはこのままでは絶対に差が埋まらないというのを感じました。

また、私は外部の院への進学を考えています。IVRCインターンなどがあり精神的に余裕がなく、(時間的にもって言おうとしたけど作ろうと思えば作れたはず)年が明けるまでろくに院進の勉強できていないので、年明け以降はそれに全力を振りたいと思います。

と、言いましたが、ずっと行きたかった会社の長期インターンに行くことが叶ったのでそれにも全力で取り組みます。

え、全力って言っておいて「全て」を一つに注いでないじゃないかってツッコミされそうですが、やります。

ひとまず、直近の目標である院進の勉強はここから毎日時間が無くても1秒でもその時間を作ろうと思います。
本当はそれ以外切り捨てて取り組む必要があると思うのですが、僕は場当たり的な人間なので、やりません。どちらにも100%努力します。

ここらへんの将来について友人や先輩に相談したときに、場当たり的に生きるは全然悪くなくて必要なのは100%の努力というのを聞きました。もうこの歳になると生き方を根本から変えることは基本できないので、めちゃくちゃ良い考え方だなと気付かされました。自分が直感でいいなと思ったことには全力で努力。口で言うのは簡単だけど、実際にやるとなると少なくとも最初は意識的にやる必要があると思います。無意識的にできるようになるまでやっていこうと思います。

むしろこんなに場当たり的に生きて今わりと幸せなので、この生き方を否定せず、+αで2023年、それ以降も好きなことに精進しようと思います。

InputActionの個人的ベストな使い方 ~InputActionProperty~

どうも、土鍋です。
この記事はUT-virtual アドベントカレンダー22日目の記事です。

皆さん、Input System使っていますか?

その中でもInput Actionを利用することで複数デバイスに対応しやすくなりますので、最近は積極的に利用しています。

しかし、InputActionの使い方には複数手法があり、初心者にはとっつきづらいと思います。そこで今回は個人的にベストな使い方をご紹介します。

InputActionの使い方

Input Actionについて調べるとPlayer Inputコンポーネントを使用する方法がまず出てきます。

using UnityEngine;
using UnityEngine.InputSystem;

public class Player : MonoBehaviour
{
   Vector3 move;

   public void OnMove(InputAction.CallbackContext context)
   {
       move = context.ReadValue<Vector2>();
   }

   public void OnFire(InputAction.CallbackContext context)
   {
       if (context.phase == InputActionPhase.Performed)
       {
           Debug.Log("Fire");
       }
   }

   void Update()
   {
       const float Speed = 1f;
       transform.Translate(move * Speed * Time.deltaTime);
   }
}


Unityの新入力システム・Input Systemを使おう – Unity for Proから引用

この方法はたしかにインスペクターから設定でき、初心者でもとっつきやすいですが、拡張性が低く、ある程度コードを書けるようになってくると使いづらいと思います。

そこで、InputActionアセットからクラスを生成する方法もあります。

using UnityEngine;

public class Player : MonoBehaviour
{
   TestInputActions testInputActions;

   void Awake()
   {
       testInputActions = new TestInputActions();
       testInputActions.Enable();
       testInputActions.Player.Fire.performed += context => Debug.Log("Fire");
   }

   void Update()
   {
       const float Speed = 1f;

       var direction = testInputActions.Player.Move.ReadValue<Vector2>();
       transform.Translate(direction * Speed * Time.deltaTime);
   }
}

Unityの新入力システム・Input Systemを使おう – Unity for Proから引用

この方法ではスクリプトだけで完結しますが、ActionごとにC#スクリプトを生成する必要がありますし、必要なActionの使用の書き方がなかなか面倒だと思います(個人的感想)

InputActionPropertyを使用する方法

なんか、どの方法も使い勝手悪いな~と思っていたのですが...

XR Interaction toolkitのInput Actionの使用方法がかなり使い勝手が良かったので、最近は真似して書いています。

その方法とはInputActionPropertyを使用する方法です。

[SerializeField]
private InputActionProperty playerMoveInput;

private void Awake()
{
    playerMoveInput.action.Enable();
}

void Update()
{
    move = playerMoveInput.action.ReadValue<float>()
    transform.Translate(move * Speed * Time.deltaTime);
}

このように書くとインスペクターからこのように設定できます。

だいぶ直感的ですね。

この方法であれば、特定のActionだけをインスペクターからセットできますし、コード的にもかなり簡潔に理解しやすく書くことができます。

調べたときになかなかこの方法にたどり着かなかったので、今回記事を書きました。どなたかの参考になれば嬉しいです。

XR Interaction Toolkit & OpenXR Plugin & Ouest2 & VIVEトラッカーでVR空間を歩けるようにする!

どうも、土鍋です。
この記事はIwakenLab Advent Calendar 2022の19日目の記事です。(遅刻してすいません)

今回はIVRCでも使用したXR Interaction Toolkit & OpenXR Plugin & Ouest2 & VIVEトラッカーで現実で足踏みをするとVR空間を歩けるようにするシステムを制作したので解説します。

IVRCについてはこちらに書きました。よろしければ御覧ください。

donabenabe.hatenablog.com

Quest2とVIVEトラッカーの併用(Oculus Link使用)

まずはQuest2とVIVEトラッカーを併用する必要があります。

こちらの記事を参考にさせていただきました。

kohavrog.com

XR Interaction Toolkit & OpenXR PluginでVIVEトラッカーを使用する

XR Interaction Toolkit & OpenXR PluginでVIVEトラッカーを使用するまではこちらの記事を参考にさせていただきました。

framesynthesis.jp

最近対応したばかりなので、記事も少なく苦労しました...(こりんさんの記事には毎回お世話になっています)

VR空間を歩けるようにする

それでは歩けるようにしましょう。

仕様

  • 足をその場で上下させることで移動
  • 歩いているかの判断はy座標の差が一定以上になったとき
  • 一定時間足を揃えると停止
  • 一定時間片足立ちでも停止

UniTaskとUniRxを使用しています。

ContinuousMoveと挙動を一致させたいのでContinuousMoveProviderBaseを継承しています。 またReadInputの部分やInputActionの取得部分は基本的にContinuousMoveと同じような作りにしました。

コードの解説

void Start()
{
    _isGround.Value = true;

    _isGround
        .Subscribe(isGround =>
        {
            if (isGround)
            {
                _cancellationTokenSourceOneLeg.Cancel();
                _cancellationTokenSourceStanding = new CancellationTokenSource();
                Standing(_cancellationTokenSourceStanding.Token).Forget();
            }
            else
            {
                _cancellationTokenSourceStanding.Cancel();
                _cancellationTokenSourceOneLeg = new CancellationTokenSource();
                Walking(_cancellationTokenSourceOneLeg.Token).Forget();
            }
        }).AddTo(this);
}

足が地面についているか否かのReactivePropertyを用いて値に変更があったら歩いているか立っているかを切り替えています。

StandingメソッドとWalkingメソッド内で時間計測し、一定時間片足立ちor両足が揃っていると歩く処理をしないようにしています。

cancellationTokenSource.Cancel()メソッドで中断処理を行っています。

protected override Vector2 ReadInput()
{
    var leftFootValue = _LeftFootMoveAction.action?.ReadValue<Vector3>() ?? Vector3.zero;
    var rightFootValue = _RightFootMoveAction.action?.ReadValue<Vector3>() ?? Vector3.zero;

    Vector2 footValueVector2 = new Vector2();

    _isGround.Value = (Math.Abs(leftFootValue.y - rightFootValue.y) < threshold) ? true : false;
    walking = (!_isStanding && !_isOneLeg);

    footValueVector2 = walking ? Vector2.up : Vector2.zero;

    return footValueVector2;
}

ContinuousMoveProviderBaseのReadInput()をoverrideしています。
_isGroundは両足のy座標の差が閾値を超えているか否かで足が地面から離れているかを判断しています。
歩いているか否かは立ち止まっておらず片足立ちでない場合、歩いていると判断しています。
歩いていた場合、Vector2.upをreturnしています。

現状は歩いてる、歩いてないのみなので、歩く速度の変化とかはないですね。

以下全体のソースコード(一部抜粋)です。 正直冗長な部分多いです。お恥ずかしい。

[SerializeField] 
private float threshold = 0.1f;

[SerializeField]
InputActionProperty _LeftFootMoveAction;

public InputActionProperty leftFootMoveAction
{
    get => _LeftFootMoveAction;
    set => SetInputActionProperty(ref _LeftFootMoveAction, value);
}

[SerializeField]
InputActionProperty _RightFootMoveAction;

public InputActionProperty rightFootMoveAction
{
    get => _RightFootMoveAction;
    set => SetInputActionProperty(ref _RightFootMoveAction, value);
}

[SerializeField]
public UnityEvent OnGround; 
[SerializeField]
public UnityEvent OnIdentifyTracker;

[SerializeField] 
private float stopJadgeTime = 1.5f;

[SerializeField] 
private ReactiveProperty<bool> _isGround;
[SerializeField]
private bool _isOneLeg = false;
[SerializeField]
private bool _isStanding = false;

[SerializeField] 
private bool walking = false;

private CancellationTokenSource _cancellationTokenSourceStanding = new CancellationTokenSource();
private CancellationTokenSource _cancellationTokenSourceOneLeg = new CancellationTokenSource();

void Start()
{
    _isGround.Value = true;

    _isGround
        .Subscribe(isGround =>
        {
            if (isGround)
            {
                _cancellationTokenSourceOneLeg.Cancel();
                _cancellationTokenSourceStanding = new CancellationTokenSource();
                Standing(_cancellationTokenSourceStanding.Token).Forget();
            }
            else
            {
                _cancellationTokenSourceStanding.Cancel();
                _cancellationTokenSourceOneLeg = new CancellationTokenSource();
                Walking(_cancellationTokenSourceOneLeg.Token).Forget();
            }
        }).AddTo(this);
}

protected override Vector2 ReadInput()
{
    var leftFootValue = _LeftFootMoveAction.action?.ReadValue<Vector3>() ?? Vector3.zero;
    var rightFootValue = _RightFootMoveAction.action?.ReadValue<Vector3>() ?? Vector3.zero;

    Vector2 footValueVector2 = new Vector2();

    _isGround.Value = (Math.Abs(leftFootValue.y - rightFootValue.y) < threshold) ? true : false;
    walking = (!_isStanding && !_isOneLeg);

    footValueVector2 = walking ? Vector2.up : Vector2.zero;

    return footValueVector2;
}

async UniTask Standing(CancellationToken cancellationToken = default(CancellationToken))
{
    cancellationToken.ThrowIfCancellationRequested();
    _isOneLeg = false;
    OnGround.Invoke();
    Debug.Log("ground");
    await UniTask.Delay(TimeSpan.FromSeconds(stopJadgeTime),cancellationToken: cancellationToken);
    if (!cancellationToken.IsCancellationRequested)
    {
        _isStanding = true;
        Debug.Log("standing");
    }
}

async UniTask Walking(CancellationToken cancellationToken = default(CancellationToken))
{
    cancellationToken.ThrowIfCancellationRequested();
    _isStanding = false;
    Debug.Log("walking");
    await UniTask.Delay(TimeSpan.FromSeconds(stopJadgeTime),cancellationToken: cancellationToken);
    if (!cancellationToken.IsCancellationRequested)
    {
        _isOneLeg = true;
        Debug.Log("one leg");
    }
}

インスペクターはこんな感じ

IVRCを振り返る

どうも、土鍋です。
この記事はAizu Advent Calendar 2022の14日目の記事です。

私はIVRC2022で「自宅でも遭難がしたい!」という作品を制作しました。その制作過程や経験、体験について書きたいと思います。 今後、IVRCに挑戦する人などの参考になればうれしいです。

youtu.be

protopedia.net

挑戦までの経緯

1年生の時からIVRCには出たいなと思っていました。VRやるために大学に入った(VRサークルとVR関連の研究室があったことが入学を決めた最大の理由)ので、VR学会が主催するIVRCに出ることが技術を極める上で一番良い目標だと感じたからです。しかし、大学生からプログラミングを始めたので、そもそも勉強するので精一杯だったし、技術に対するモチベが今よりかなり低かった気がします。

2年生の4月にもIVRCに出たいという気持ちはありましたが、やはりまだ知識が足りない&周りに一緒に出てくれる人がいなさそうだと感じて諦めました。

ですが、もう来年には絶対挑戦しなければならないと思い、そのために何が必要か考えました。そこでUnityだけではなくマイコンの勉強が必要だと思い、ちょうど大学で開催されたAizuHackというイベントのIoTコースに参加しました。そこから会津大内のZliという技術者コミュニティの人々と関わるようになり、大きな衝撃を受けました。

AizuHackをやり遂げたあと(8月以降)はサークル運営やイベント主催、インターンなどで忙しくなり、自分のスキルアップにかけられる時間が減ってしまったが、その代わりマネジメントの経験は積めた気はします。

そうこうしているうちに4月になり3年生になってしまいました。

4年生は卒論や院試で余裕が無くなるのは目に見えていたので、ラストチャンスの気持ちで全力でIVRCに挑戦しようと思いました。

正直、4月から動きたかったが、サークルの新入生勧誘やイベント運営、勉強会講師などの仕事が舞い込んで来てしまいそれどころではありませんでした。一通り一段落したところ(5月頭)でようやくIVRCに向けて動けるようになりました。

プロジェクト立ち上げと企画書制作

メンバー集め

5月のゴールデンウィーク明けから所属するVRサークル「A-PxL」中心に自分の仲の良い人に声かけまくりました。とりあえずどんなイベントか話して少しでも興味を持ったらガンガンDiscordにぶち込んで、雑多にどんなアイデアがあるか聞いて集め始めました。

しかし集まったメンバーの中でハードウェアのできる人があまりいませんでした。そこでREMsというロボットサークルの人に声をかけメンバーになってもらいました。

数度に渡るMTGを重ねれば、来るメンバーもだいたい固定になってくるし、やる気のあるなしも見えてくるので、最大コミットしてくれそうな人を正式メンバーとしました。

結果的に3年生5人、2年生3人の計8人チームと少し多めになってしまいましたが、ソフト・ハード分けるし部分部分分けて開発できるだろうということで、正式メンバーを決定しました。

イデア出し

イデア出しはMiroやHackMDを活用しました。特にMiroのマインドマップはいろいろ派生してアイデアが思いつくのでかなり良かったです。

50個程度のアイデアから一人3つ選んで一人でも選んだ人がいれば候補の1つにして、その後、候補内から3つに絞り、ひとつひとつについて何が面白いか技術的に可能か議論し、アイデアを決定しました。

企画書

企画書自体は一人で書きました。(文章の書き方は統一したいのでこれは正解な気がする)
メンバーだけではなく教授や友人など多くの人に校閲してもらいました。

企画書を書く際は以下の点に注意しました。

  • 図や写真は説得力向上に必須。
  • 新規性は必ず欲しいので、IVRCの過去作品や先行研究を調査。
  • 実現可能性も重視されるので、なるべく具体的に書いて技術的に可能であることを明記する。

余談

チーム名は「(s)遭難(s)したい(d)土鍋の団」で「SSD団」としました。(涼宮ハルヒの憂鬱SOS団のパロディ)
作品名「自宅でも遭難したい!」は「中二病でも恋がしたい!」のパロディです。

Seed Stage

6月上旬、書類審査を通過し、本格的に開発を始めました。
ハード班、ソフト班にメンバーを分けそれぞれで開発を開始しました。

ハード側は最初はとりあえず買って、作って、検証、の繰り返しでした。なかなか望むような体験を作り出せず、結局本番の形がまとまりだしたのは8月中旬ぐらいでした。そこからも修正を重ねなんとか全体が動くようになったのは8月も後半の方だったと思います。

ソフト側は体験の大きな範囲を構築する視覚を司るので、早くから動き出し8月前半時点で最初から最後まで通しで遊べるようにしました。しかし、そこからは地道なクオリティアップです。山としてのリアリティの追求、今回の作品の要である疲労度の増加の調整などなど…

そして案の定というか、結局締め切りぎりぎりまで開発が終わらず、宅配便で荷物を輸送するのも間に合わないので、自分たちの車で前日に輸送しました。

プロジェクトマネジメント

8人チームというのもあってここは気を使わねばならないなと思って取り組んだ。
とりあえず、ソフト班とハード班に分けてそれぞれで活動した。

  • 定例ミーティング
    • 毎週一回は必ず実施
    • 全体は週一だが各班はそれ以外も集まって活動
    • 内容&目的
      • 全体の進捗を確認するため
      • 次の週なにをするか決めるため
      • 後述するがモチベを落とさないため
  • チームメンバーのモチベーション
    • 常に組織・プロジェクトが動いている感を見せる
    • 結果がついてきていることを見せる
    • リーダーが一番頑張っているところを見せる
    • チームに参加して良かったと思えるようにする
      • 就活で話せるネタになる
      • 未経験の技術に触る機会
      • 技術力向上
  • タスク管理
    • Notionを利用
    • タスクをパッと見て把握できるようにする
      • 確認がめんどくさいと見なくなる
    • 何が重要で何が重要でないかの判断
    • どのようなスケジュールで動くのか判断しやすくする

審査当日(9/3)

身内以外に体験してもらうのは初なので、様々な問題が発生しました。特に私達の作品は体に装着するタイプなので、装着時間や準備時間がかなりかかってしまう問題や、老若男女誰でもどんな服装でもフィットできないという問題が発覚しました。

逆に言うと、明確にLeapStageへの課題が見えたと思います。特にLeapStageは一般の方も体験に来るので、上記の点は必ず解決する必要がありました。

Leap Stage

まさか進出できるとは思ってませんでした。正直、SeedStage当日、他の展示作品に圧倒されてしまい、これは難しいかなぁとすこし諦めかけていました。しかし、決まったからにはやり遂げると決意を固めました。

開発はSeedStageの時は夏休みだったのでかなり確保できましたが、今度は授業と並行して開発を進める必要があり、思うように進みませんでした。しかし、当初の課題だった装着・準備時間の短縮や老若男女誰でも体験しやすくしたいという点をある程度解決することができました。

また、直前まで良さそうなアイデアを思いついたら作り、ぎりぎりまでクオリティアップを目指しました。

展示会前日まで準備中に動かなくなったり普通に忘れ物したり(これは本当にやらかしましたごめんなさい)トラブルは続出しましたが、なんとか当日までに仕上げることができました。

実際に登山に行く

LeapStageまで行くのであれば、実際に登山を経験せねばならないと思い、会津から1時間半ほどの距離の一切経山に登山へ行きました。

が、しかしこの天気である。
霧で視界がかなり悪く、とても寒かったです。しかし、遭難体験を作っている身としては逆に良かったかもしれません。

山頂付近は一層霧が強く、風もめちゃくちゃ強かったです。本来ならきれいな湖が見えるはずなんですが…

紅葉シーズンというのもあり人はこんな天気でもそこそこいました。きれいでしたが、やっぱりもう少し天気が良いときに来たかったな~

これはふもと付近ですが、今回の作品のイメージに一番近いものが撮影できたのでとても参考になりました。

足元は岩がたくさんあり歩きづらいとかどこから寒くなるか、木々の生え方、どう疲れるか、ストックを持ったほうが登山感が出るのではないかなどなど

実際に登山を行ったことでよりリアルにするにはどこを改良すべきかが見えので、下山後ミーティングを行い早速取り組みました。

当日(11/5,6)

展示会当日は、とてつもなく忙しかったです。休む暇が本当に0でした。説明して、装着させて、体験させて、メンテナンスして…

特に二日目は装置にも限界が来て、どんどん故障していきました。ポンプ4台中2台壊れるわ、コードは引きちぎれるわ、ショートするわ...

ただやり遂げたときの達成感はとてつもないものがありました。

最後に

結果として私達のチームは審査委員会特別賞をいただいました。この結果はひとえにチームメンバーのおかげです。チームメンバーがいなかったらこんな結果は得られませんでした。特に3年生はこの時期インターンなどもあり忙しかったと思います。それなのに一緒に開発に臨んでくれて本当に感謝の言葉が尽きません。2年生も貴重な体験ができたと思います。今後の開発にもこの経験を活かしてほしいです。(良ければIVRCにまた挑戦して欲しいな)

私個人としてはIVRCでとても成長できたと思います。技術的な部分もですが、特に長期間にわたってなにかに取り組むという点でこれほどの規模のものは初めてだったので、とても良い体験ができました。 また、1度作品を作ると他のとこで見せることができるのでいいですね。この作品はXR Creative Award一次審査通過、ヒーローズリーグ特別賞を獲得することができました。

この記事が今後、IVRCに挑戦する人の役に立てば嬉しいです。

ありがとうございました。

---12/25追記--- またハード班による詳しい解説はこちら。

hackmd.io

Extenject(旧Zenject)を触ってみた ~Inputの切り替えを例に~

はじめに

どうも、土鍋です。
この記事はIwaken Lab.の開発合宿ブログリレー11日目の記事です。
9月に行われた開発合宿で初めて触ったExtenjectについて書きました!

Extenjectとは?

DI(依存性の注入)フレームワークで、もともとZenjectという名前でしたが、開発者の方が会社とごたごたがあって名前が変わったそうです。 役割としては、「疎結合やテストのことを考えて設計したときに発生してしまう問題を解決してくれる」といった感じです。

Extenjectを使うには、疎結合な設計になっていないとその効力を発揮しない。

疎結合な設計

そこでまず、疎結合な設計について見ていきます。 例えば、複数デバイスの入力に対応し、切り替えを行う場合、どうしてもコードがごちゃっとしたり、デバッグが大変だったりします。

→ そこでInterface等を使って疎結合にしていく!!

例としてVRとキーボードで入力切り替えることを考えて設計していきましょう。

まずこれが密結合の状態です。

これでは操作方法が増えるたびにPlayerMoveを書き換える必要が出てきてしまいます。これでは手間ですし、チーム開発においては様々な問題に繋がりかねません。

これをinterfaceを使うことで、疎結合にしていきます。

これによりPlayerMoveは書き換えることなく、様々な操作方法に対応できます。

具体的なスクリプト

この例ではスカイダイビングゲームを想定して書きました。

PlayerMove

Playerの動き用スクリプト

public class PlayerMove : MonoBehaviour
{
    [SerializeField] private float moveSpeed = 2;
    [SerializeField] private float fallSpeedMagnification = 3;
    
    private IInputProvider _inputProvider;

    private Rigidbody _rigidbody;
    private void Start()
    {
        _rigidbody = GetComponent<Rigidbody>();
    }

    private void Update()
    {
        _rigidbody.AddRelativeForce(_inputProvider.GetMove() * moveSpeed);
        _rigidbody.drag = _inputProvider.FallSpeed() * fallSpeedMagnification;
    }
}

うまーいこと疎結合にしているとInputが増えてもここを書き換える必要はない。

IInputProvider

今回作ったInput用のインターフェイス。使用する動きを定義。

public interface IInputProvider
{
    Vector2 GetMove();
    float FallSpeed();
}

VRInputProvider

IInputProviderを実装しています。

public class VRInputProvider : IInputProvider
{    
    public Vector2 GetMove()
    {
        処理
    }

    public float FallSpeed()
    {
        処理
    }
}

KeyboardInputProviderVRInputProvider同様に実装する。

Extenjectの利用

ただ、疎結合にするとクラスの利用者的には不便な場面が多いです。

例えば 「利用するインスタンスの決定をどうやって行うのか?」 「どのタイミングで呼び出すのか?」 といった問題が発生します。

そこでExtenjectで依存性を注入する!!

導入方法

assetstore.unity.com

従来のアセット通り、AssetStoreからインストールできます。

今回の実装

主な構成はこんな感じ。

1. Installerを書いていく

DI Container1に対して「IInputProviderが要求されたら、VRInputProviderを注入する」ということを設定するVRInputInstallerを書く。

VRInputInstaller

public class VRInputInstaller : Installer<VRInputInstaller>
{
    public override void InstallBindings()
    {
        Container
            .Bind<IInputProvider>()
            .To<VRInputProvider>()
            .AsCached();
    }
}

KeyboardInputInstaller

同様にしてKeyboardInputInstallerを記述。

public class KeyboardInputInstaller : Installer<KeyboardInputInstaller>
{
    public override void InstallBindings()
    {
        Container
            .Bind<IInputProvider>()
            .To<KeyboardInputProvider>()
            .AsCached();
    }
}

InputManager

さらにVRかキーボードかを判定するInputManagerを書く。

public class InputManager : MonoInstaller
{

~~中略~~
    
    public override void InstallBindings()
    {
        if (XRGeneralSettings.Instance && XRGeneralSettings.Instance.Manager.activeLoader != null)
        {
            Debug.Log("VR Mode");
            VRInputInstaller.Install(Container);
        }
        else
        {
            Debug.Log("Keyboard Mode");
            KeyboardInputInstaller.Install(Container);
        }
    }
}

2. Contextを設定する

配置されたシーンにのみInstallerの影響範囲を設定するScene Contextをシーンに配置する。

Scene ContextにInputManagerを登録します。

3. DI Containerがオブジェクトを注入する先を設定

変数に[Inject]属性をつけることでContainerがオブジェクトを注入してくれます。

いやー簡単ですね...

今回であれば、PlayerMoveIInputProviderの宣言の部分に[Inject]属性を追記。

[Inject]
private IInputProvider _inputProvider;

これで実行時にIInputProviderVR or キーボードのInputProviderが注入されます。

まとめ

今までまともに設計したことがなかったのですが(設計しても結局うまくいかなかった)、Extenjectを学ぶにあたりインターフェイス周りの勉強にもなりました。

UniRx/UniTask等と組み合わせることで、さらにチーム開発においてかなり効力を発揮しそうですねー。今度はそこら辺取り組んで記事にしようかなと思います。

参考

以下の記事を参考にさせていただきました!ありがとうございました!

Zenject入門その1 疎結合とDI Container - Qiita

Unity開発で使える設計の話+Zenjectの紹介

【Unity(C#)】Extenject(Zenject)使って入力機能を切り離したものを管理してみた - Qiita


  1. DI Containerは簡単に言えば「環境にあらかじめ依存関係を設定しておくと、それに応じて自動的にインスタンスを設定してまわってくれる便利な存在」です。Zenject入門その1 疎結合とDI Container - Qiitaより引用

馬産地と日高本線廃線巡りの旅

どうも、土鍋です。

VR学会でIVRCのOSと表彰式に出るために北海道に行くことになったのでついでに日高地方を旅行をしてきました。

(研究室の教授に頼み込んだら飛行機代出していただいた、うれし~~~~)

旅行計画

さてさて、行き先をどうしようかな~と考えたのですが、

「以前から行きたかった日高地方に馬と廃線を見に行こう」

と決めました。

馬はウマ娘きっかけで競馬を見るようになり、気づいたらYouTubeで馬の動画を見るレベルでハマっていて、前々から実際に見に行きたいな~~となっていました。
日高本線廃線は当初はついでで数カ所有名なとこだけ見るつもりだったんですが、目次見ても分かる通りほぼ全駅を巡りました。自分でもびっくりです。

Notionでざっくり計画立てたのですが、

うーんハードスケジュール...

2日間で馬も廃線も見てしかも移動距離が北海道スケールなので移動時間も考える必要がありました…

ですがやり遂げたら絶対楽しい!

気合い入れて、全力で楽しむぞ!

一日目

寝坊から始まる

やらかしましたね。さっそく。

8:30の電車で札幌出発のはずが、9:30に起きました。 移動時間的には間に合いますが、日高本線の本数が少なく、一本乗り過ごすと次が2時間後なので、レンタカーの開始時間には確実に遅れることになってしまいました。

バスなら間に合うが、日高本線現存区間には乗りたかったので、レンタカー屋に一報入れて、なんとかなりました。

苫小牧駅

日高本線との乗り継ぎ時間がかなりあったのでここで昼食。
どうやら苫小牧はほっきが名物らしく、改札出てすぐの「苫小牧いぶりカレー」さんで「ほっきカレー」を注文。

まだ時間が余ったのでいい感じの雰囲気の「純喫茶ドンドン」に入店し、時間を潰した。

日高本線

待望の日高本線に乗車。車窓はやはり海側を確保。車内はボックス席に一人ずつ入ってちょうど埋まるくらいの乗車率でした。

車窓はたいたい荒涼とした草原か工業地帯と言った感じでした。かなり大きい港もありましたね

そして、早速北海道を感じました。 さすが北海道。鹿が当たり前に群れをなして線路沿いにいるとは。

そしてこちらは浜田浦駅 うーん。雰囲気がいいですね~。駅舎も周りの風景も。
(使う人いるのかな...)

鵡川駅

現在の終着駅鵡川駅に到着。

ここから先、線路は続いているが、列車が走ることは二度と無い。

駅舎

すでに一時間遅れているので、急いでレンタカー屋へ行く。

レンタカーを借り、出発!

道中も馬がたくさんいて、もううっきうきで運転してました

ヴェルサイユリゾートファーム

まずやってきたのはヴェルサイユリゾートファーム

営業時間が16時まで(カフェは15時)なので一番最初に訪問

カフェでは馬を見ながらコーヒーを飲める。 受付や人参売り場もここ

タニノギムレット

ダービー馬でウオッカの父。最近ウマ娘にもなりましたね。

門別競馬場

お次は門別競馬場 すぐにレースが始まりそうだったので初物理馬券は「ヤマノススメ」を絶対もじったであろう「ヤマノムスメ」を名前だけで購入。

ゴール手前で先頭に立ったときは、これはまさか来るのでは!?と思ったけどギリギリで差されての二着。でも、直感で選んだ割に好成績で嬉しい。

次のレースはパドックを見て決めようと思ったので、いざパドックへ。 なんかめちゃめちゃ元気な子とめちゃめちゃおっとりしてる子がいたので、その二頭を購入。 落ち着いてる方の名前は「ドンドン」で、苫小牧で寄った喫茶店の名前と同じだったというのも決め手。 たださっき単勝で負けたのでひよって複勝に。

このレースはスタート地点が客席側だったので間近でゲートインを鑑賞。

結果は... めちゃめちゃ元気だった方が勝ちました!

あー単勝買っておけば、、、っていうのは競馬あるあるでしょうね

初めて生で競馬を見ましたが、300円だけ(勝ったので実質150円)でかなり楽しめましたね。
競馬見ながらジンギスカン食べれる店とかあったので、ぜひまた行きたい。

もっといたかったが、今夜の宿まで二時間かかるので出発。

うらかわ優駿ビレッジAERU

車で二時間ずっと運転してやっと到着。 ただ、高速道路よりも景色に代わり映えがあるので全然疲れなかった。 ここに泊まるために二日間快活に泊まりました…

そしてお楽しみの夕食

部屋の入口に蹄鉄がついてておしゃれ~ 一週間前とかに予約したので、一人部屋は埋まっておりツインしかなかった。

二日目

放牧地へ

厩舎で人参を購入できます。

にんじんをあげる

ウイニングチケットスズカフェニックスは高齢馬なので人参はあげられません。

ウイニングチケット

さすがの人気で朝一番に行きましたが、すでに5人くらいいましたね。

スズカフェニックス

ごろんごろんかわいい

様似駅

廃線前の終点

終点

ここから先えりも岬まで行く計画もあったとか

エンルム岬

AERUから様似駅に向かう途中で変な地形を見かけて気になったので寄ってみた

函館のちっちゃい版みたいな地形(陸繋島かな)

西様似駅

北海道の駅あるある。車掌車とか貨物車の改造駅

鵜苫駅

日高幌別駅

東町駅

住宅地入ったところにひっそりあった

ちらっと見える海が良い

浦河駅

様似駅以来の大きな駅 立派な跨線橋

向別川橋梁

絵笛駅

牧場のど真ん中にある駅

馬が周りにいる駅なんてなかなかないと思うので、うーん廃止前に行きたかった...

荻伏駅

本桐駅

蓬栄駅

トイレ併設

日高三石駅

目の前のセイコーマートで北海道でしか食べれない商品を買う

ポテトが大量に入って160円だった

さすが北海道

日高東別駅

春立駅

草が多すぎてまったくホームが見えない

東静内駅

まさに撤去の最中でした

静内川橋梁

きれいに残ってる

今にも列車が来そうだけど二度と来ないんだなあ

静内駅

日高本線で一番でかい駅かも?

今でもバスターミナルとして活躍していました

いい雰囲気の駅そばがまだ営業してて嬉しい

競走馬のふるさと案内所

この辺は特に馬産地が集中している

ということで競走馬のふるさと案内所へやってきた 牧場を訪問したいときは一度ここによって最新情報を聞くべき(というか迷惑になる可能性がある)と聞いたので訪問

見たい馬の名前を言えば、注意事項等を聞けます。

ビッグレッドファーム

さて、リアルゴルシを見たいということで来ました

放牧地は一頭もいなかった

どうやらご飯の時間だったようで全頭厩舎にいました

ゴルシ全然顔出さない

ずーっと食べてた

食べ終わっても入れ物かじってずっとこちらには後ろ向き

何人も見に来ていたが、彼らも撮れていないようでした

ウインブライト

ゴルシと違って全然ずっと顔だしてた気がする

かわいい

ゴールドシップ

散々待ってやっと顔を出した

と思ったら10秒くらいで戻ってしまった

さすがゴルシ

優駿記念館

オグリキャップ

ポニー

行ける部分の草はほとんど剥げてた

優勝レイ

オグリのお墓

新冠駅

かなりきれいに残ってた

節婦駅

後ろに建設中の高速道路が後ろに見える

台風被害がなくても、どちらにせよあの高速が完成していたら廃線の運命だったと思う

大狩部駅

駅への入り口

国道の下をくぐる

ネズミいた

東京にいるでかいドブネズミとかよりもちっちゃくてかわいい

台風被害が一番近くで見れるポイント

こんなに海が近いところを走っていたと思うと驚き

車窓はさぞ良かっただろうなあ

厚賀駅

からっぽ

清畠駅

美しい

この荒涼とした感じがめっちゃ好き

豊郷駅

ここも美しい

日高門別駅

今までで一番キレイにホームと線路が残ってた

色々展示してありましたが私がついたときには閉まっていました

残りの二駅

富川駅汐見駅にも行きたかったのですが、行っていたら飛行機に乗れる最後の列車に遅れてしまう!ということで本当に残念ながら諦めて鵡川駅のニコニコレンタカーへ直行しました。

ぜひリベンジしたい…

鵡川駅

レンタカー屋についた頃には列車の時間まで15分…!

レンタカー屋さんにギリギリですみません!!と誤りながら車を返した。

列車に飛び乗ってそのまま新千歳へ

東京へ

その日のうちに東京へ

家についたのは深夜1時でした

感想

最初は廃線巡りこんなちゃんとやる予定はなかったんですが、気づいたらほぼ全駅回ってましたw

あと、ウマカワイイデス

また、行きたいなあ。(行けなかった駅のリベンジもしたいし)

こんな旅は一人旅じゃないとできないですよね。過酷だし趣味全振りなので。
それに北海道は車も少なくて景色もきれいなのでドライブがめっちゃ楽しかったですね。
免許取ってよかった~~