20191102のUnityに関する記事は6件です。

急カーブにも強いNavMeshAgentを作る

NavMeshAgentは簡単にオブジェクトを自動で移動させることができる便利な機能ですが、使っていてある問題が発生したので、その解決策を載せます。

githubに全ソースコードを載せています。
https://github.com/AtshshiMori/NavMeshMovement

問題

以下のようにスピードがある程度遅ければ良いが、早くすると急に曲がるときに通り過ぎてしまう。

Speed=3のときは問題なく曲がれる。
speed3.gif

Speed=5 のときは行き過ぎてしまい上手く曲がれない。
speed5.gif

ソースコード

サンプルシーンのソースコードです。
Unityちゃんに以下のスクリプトを追加します。
targetにはインスペクターからUnityちゃんが向かう先であるカプセル型のオブジェクトを指定しておきます。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Player : MonoBehaviour
{
    [SerializeField] GameObject target; // ゴールとなるターゲットオブジェクト
    UnityEngine.AI.NavMeshAgent agent;
    Animator animator;

    void Start()
    {
        agent = GetComponent<UnityEngine.AI.NavMeshAgent>();
        animator = GetComponentInChildren<Animator>();
    }

    void Update()
    {
        agent.destination = target.transform.position; // ターゲットの設定
        animator.SetFloat("Speed", agent.velocity.sqrMagnitude); // Unityちゃんをアニメーションさせるためのパラメータ
    }
}

解決策1 等速で動かす

とりあえず等速で動くようにすることで解決ができます。
Update関数内に以下を追加します。

agent.velocity = (agent.steeringTarget - transform.position).normalized * agent.speed;
transform.forward = agent.steeringTarget - transform.position;

agent.velocityでNavMeshAgentのスピードを指定できます。

NavMeshAgentは常にゴールまでのパスを保持しており、
agent.steeringTargetはパスの中継地点のうち、現在向かっている地点となります。

steeringTarget.png

したがって、
(agent.steeringTarget - transform.position)
により方向を取得し、agent.speedをかけることで常にインスペクターでNavMeshAgentに設定したスピードで動くことになります。

結果がこちら
fixedSpeed.gif

上手く曲がれるようになりましたが、動きに滑らかさはなくなってしまいます。表現したいものによっては使えると思います。

解決策2 曲がり角でスピードを落とす

曲がり角が近くなったらスピードを落とすようにします。
以下のようにスクリプトに追加します。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Player : MonoBehaviour
{
    [SerializeField] GameObject target; // ゴールとなるターゲットオブジェクト

    UnityEngine.AI.NavMeshAgent agent;
    Animator animator;

    private float speed = 0.0f; // 追加:NavMeshAgentのspeedを保持するための変数

    void Start()
    {
        agent = GetComponent<UnityEngine.AI.NavMeshAgent>();
        animator = GetComponentInChildren<Animator>();
        speed = agent.speed;            // 追加:初期値の保持
    }

    void Update()
    {
        agent.destination = target.transform.position; // ターゲットの設定
        animator.SetFloat("Speed", agent.velocity.sqrMagnitude); // Unityちゃんをアニメーションさせるためのパラメータ

        // 以下を追加
        if (Vector3.Distance(agent.steeringTarget, transform.position) < 1.0f)
        {
            agent.speed = 1.0f;
        }
        else
        {
            agent.speed = speed;
        }
    }
}

先ほどの steeringTarget で曲がり角の位置を取得し、距離が近くなったときに speed を 1.0f に変更しています。

その結果がこちら
slowSpeed.gif

スムーズに曲がれるようになりました。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Unityで使える便利関数(拡張メソッド)達

はじめに

約一年半「Unity」を使ってきて、よく使う機能やよく繰り返す機能を
たくさん作ってきたので、それらの一部分をいくつかのクラスにまとめて公表したいと思います!

そのまま使って頂いても構いませんし、
これらのアイデアを元に、更にいいものを作り上げて欲しいとも思っています
(それが出来上がったら是非コピp、、、もとい、参考にさせていただきたいです)

公表場所

以下のリポジトリにあります。
※C#7.0以上が必要です。
https://github.com/0Nome0/NerScript

機能紹介

ここでは内容されている機能の一部分を説明したいと思います。

機能は全て、namespace NerScriptにまとめてありますので、
using NerScript;

を忘れずにお書きください

ComponentExtend

アタッチされているRigidBodyやColliderの取得

gameObject.GetRigidBody();
gameObject.GetRigidBody2D();
gameObject.GetCollider();
gameObject.GetCollider2D();

GameObjectExtend

ありそうで無い便利なやつ

gameObject.Destroy();
gameObject.DontDestroyOnLoad();
gameObject.AddChild([オブジェクト]);

一番近いObjectを返してくれる

gameObject.GetClosestObjectInList([Objectのリスト]);

RigidBodyExtend

RigidBodyのFreeze操作

一時期とてもお世話になった

rigidbody.UnFreezeAll(); //Freeze全解除
rigidbody.FreezePosY(); //PositionYのみ凍結
rigidbody.UnFreezeRotation(); //Rotationの凍結解除
rigidbody //もちろんRigidbodyConstraintsで設定可能
.Freeze(RigidbodyConstraints.FreezePositionX | RigidbodyConstraints.FreezePositionY);

内部実装を久しぶりに見たらString で1と0を管理しててビビった。
今ならビット使おうぜってなる代物。修正はしない。

StringExtend

文字列を数値に変換

int num = "12345".Int(); //Int(12345)

文字列を反転

"あいうえお".Reverse(); //おえういあ

UnityExtend

デバックがすこし捗る

int num = 5000.DebugLog(); //num:5000
string str = "ほげ".DebugLog(); //str;"ほげ"

代入するついでにLog表示してくれます。
こんなこともできちゃいます。

int num = 5000.DebugLog() + 300;
float length = new Vector3(12, 34, 56).DebugLog().magnitude;

この場合もしっかりと値が入ります。

Colors

大量の色が定義されています。

これに関しては自分で作ったものではなく、他人のものなのですが、
どこにあったか見つけ次第リンクを張っておきます。

追記:見つけました。こちらを参考にさせていただきました。
http://baba-s.hatenablog.com/entry/2017/12/28/145900

EnumLib

int->Enum変換

MyEnum enum = 3.ToEnumName<MyEnum>();

面倒くさい処理(「()」が多すぎる)を一発で出来ます。

GizmosLib

これに関しても他者のものを自分なりに拡張したものです。

参考にしたもの
https://github.com/code-beans/GizmoExtensions

ListLib

よく使うものたちです。
速度?気にしたら負けです。
人間様が使いやすいので問題ないです。

要素数を変更

list.Cut(5); //max = 5
list.Fill(10); // min = 10
list.SetCount(7); //count = 7

最後の要素を削除

list.RemoveFirst();

配列/リストに変換

int num = 10;
int[] nums = num.ShiftArray();
//nums : {10}
int[] nums2 = num.ShiftArray(30,40,700);
//nums2 : {10,30,40,700}

MathLib

intのClamp

this ref により、そのモノの変更が可能

int num = 10;
num.Clamp(0, 5);
//num : 5

num = num.Clamp(0,5);にする必要がない!!

RandomLib

ここには様々なRandomがあります

Colorで色,PointInCircleで円の中の一点など

ReflectionLib

使う人は使うSystem.Reflection

これが必要になるほどの人はきっと説明がなくても使いこなせるでしょう。

RectLib

AddやSet,Addedなど

Addや、Addedがありますが、
Add,SetはRectそのものを操作します。
対して、Added,Setedは操作された、別物を返します。

rect
.AddedPosX(10) //x += 10
.SetedPosY(-20.5f) // y = -20.5
.SetedSize(100, 120);size = 100,120

よくこんな使い方をしています。
メソッドチェーン使いやすい。

ちなみに前述のListLibもチェーンできるように設計されています。

TransformExtend

Positionなどを変更

transform.AddPosX(10); //x += 10
transform.SetLclPos(0, -1, 20); /LocalPosition = 0,-1,20
transform.SetSclY(4); //LocalScale = 4,4,4
transform.AddPosX(10).SetSclY(4).SetLclPos(0, -1, 20); 

これも割と使いやすいです。
速度?メモリ?知らん。

VectorLib

変換

Vector2 v2 = new Vector2(2,3);
Vector3 v3 = v2.ToVec3().OXY; 
//v3 : 0,2,3

並び替え

v3 = v3.SortVec3().YZX;
//v3 : 2,3,0

v3 = v3.SortVec3().OYY;
//v3 : 0,3,3

応用

//v3 : 1,2,3
v3 = v3
.AddedX(3) //4,2,3
.SetedZ(-2) //4,2,-2
.AddedAxis(Axis.Y, 5) //4,7,-2
.Clamp(Vector3.zero, new Vector3(5, 5, 5)) // 4,5,0
.MergeVec3(new Vector3(22, 33, 44))["xXy"]; //4,22,5

終わりに

これからも追加されていくとおもうので
参考がてらに覗いて見ていってください

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Unity】Crunch圧縮って結局どうなの?

はじめに

UnityにはCrunch圧縮という機能がありTextureのサイズを劇的に減らすことができます。
crunch.png

ビルドサイズを削減するという点では非常に有効だと思うのですがロード時間の短縮と言う点ではどうなのでしょうか?
ビルドサイズが小さくなれば当然読み込み時間は短くなるでしょうが圧縮しているため解凍する必要があります。IOの高速化により短縮できる時間と解凍にかかる時間のどちらの方が大きいのかを測定してみました。
結論から言うとCrunch圧縮した方がいいぜって話です。

環境

OS:Windows10 64bit
ビルドターゲット:Windows(スタンドアロン) LZ4圧縮は無し
Unityバージョン:2019.2.0f1
プロセッサ:Corei3-6100U
CrystalDiskMark:
crystaldiskmark.png

測定方法

こんな感じのシーンを作りました。
scene.png

フルHDから4Kまでのテクスチャが存在しておりメモリ上には1.3GBのテクスチャが載っています。

profiler.png

このシーンをAssetBundleに格納し、ロードします。

以下のスクリプトでロード時間を測定します。

StartTimer.cs
using UnityEngine;
using UnityEngine.SceneManagement;
using System.IO;
using System.Collections;

public class StartTimer : MonoBehaviour
{
    private void Update()
    {
        if (Input.GetButtonDown("Jump"))
        {
            StartCoroutine(Load());
        }
    }


    IEnumerator Load()
    {
        File.AppendAllText(Application.streamingAssetsPath + "/result.txt", "LoadStart..." + Time.time+"\n");
        var result = AssetBundle.LoadFromFileAsync(Application.streamingAssetsPath + "/sphere");

        yield return new WaitWhile(() => {
            return !result.isDone;
        });

        var assetbundle = result.assetBundle;

        File.AppendAllText(Application.streamingAssetsPath + "/result.txt", "ABLoadComplete..." + Time.time+"\n");
        string sceneName = Path.GetFileNameWithoutExtension(assetbundle.GetAllScenePaths()[0]);
        SceneManager.LoadScene(sceneName);
    }
}

CompleteTimer.cs
using UnityEngine;
using System.IO;

public class CompleteTimer : MonoBehaviour
{
    void Start()
    {
        File.AppendAllText(Application.streamingAssetsPath + "/result.txt", "LoadComplete..." + Time.time+"\n");
    }
}

(AssetBundleからのシーン読み込みの方法はこちらのブログを参考にさせていただきました。http://tsubakit1.hateblo.jp/entry/2016/08/23/233604)

StartTimer.csを開始時に読み込まれるシーンのGameObjectにアタッチし、CompleteTimer.csは先ほどのシーンのGameObjectにアタッチしておきます。

この測定をCrunch圧縮なし/ありでそれぞれ行いロード時間を比較したいと思います。
なおCrunch圧縮のクオリティは50でやっていきます。

結果

Crunsh圧縮無し

result.txt
LoadStart...0.9617942
ABLoadComplete...32.18606
LoadComplete...32.20223

アセットバンドルサイズ : 267,533KB(267.5MB)

アセットバンドルのロードにかかった時間 : 31.2242658秒
アセットバンドルロード完了後にシーンの読み込みにかかった時間 : 0.01617秒

Crunch圧縮あり

result.txt
LoadStart...10.97006
ABLoadComplete...21.08436
LoadComplete...21.10166

アセットバンドルサイズ : 105,891KB(105.9MB)

アセットバンドルのロードにかかった時間 : 10.1143秒
アセットバンドルロード完了後にシーンの読み込みにかかった時間 : 0.0173秒

結果

Crunch圧縮によりアセットバンドルのサイズは半分以下に、読み込み時間は3分の1ほどになりました。
シーンの読み込みにかかった時間はCrunch圧縮ありの方がほんのわずかに遅かったですが誤差レベル。人間には知覚できないでしょう。

AssetBundle無し

それではアセットバンドルをビルドせずにシーンを直接組み込んだ場合はどうでしょうか。
環境及び計測方法は大体同じです。

Crunch圧縮無し

result.txt
LoadStart...1.388524
LoadComplete...1.41218

ビルドサイズ:652 MB
シーンのロード時間:0.023656

Crunch圧縮あり

result.txt
LoadStart...1.386678
LoadComplete...1.408058

ビルドサイズ:236 MB
シーンのロード時間:0.02138

結果

ビルドサイズは3分の1ほどになりましたがシーンのロード時間はほぼ変わりませんでした。
またゲームの起動時間は測定方法が分からなかったので測定できませんでしたが体感には全く差は感じられませんでした。

まとめ

Crunch圧縮をすることでビルドサイズが劇的に小さくなった上に解凍によるオーバーヘッドは全く感じられませんでした。特にアセットバンドルからのシーンロードの際にはCrunch圧縮した方が圧倒的にロードが速かったです。すべてのデータを毎度ネットワークからロードしてくるWebGLなどではさらに大きく差が開くことでしょうし、モバイルアプリにおいてもダウンロードサイズを抑えるための有効な手段になるはずです。
環境によっても変わってくるでしょうが基本的にはCrunch圧縮はしていく方向でいいのではないでしょうか。
ただしテクスチャの各辺の長さが4の倍数でなければ圧縮できませんし、Crunchは不可逆圧縮なので画像が劣化する可能性がある点は注意が必要です。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Unity】Crunch圧縮でロード時間は速くなるのか?

はじめに

UnityにはCrunch圧縮という機能がありTextureのサイズを劇的に減らすことができます。
crunch.png

ビルドサイズを削減するという点では非常に有効だと思うのですがロード時間の短縮と言う点ではどうなのでしょうか?
ビルドサイズが小さくなれば当然読み込み時間は短くなるでしょうが圧縮しているため解凍する必要があります。IOの高速化により短縮できる時間と解凍にかかる時間のどちらの方が大きいのかを測定してみました。
結論から言うとCrunch圧縮した方がいいぜって話です。

環境

OS:Windows10 64bit
ビルドターゲット:Windows(スタンドアロン) LZ4圧縮は無し
Unityバージョン:2019.2.0f1
プロセッサ:Corei3-6100U
CrystalDiskMark:
crystaldiskmark.png

測定方法

こんな感じのシーンを作りました。
scene.png

フルHDから4Kまでのテクスチャが存在しておりメモリ上には1.3GBのテクスチャが載っています。

profiler.png

このシーンをAssetBundleに格納し、ロードします。

以下のスクリプトでロード時間を測定します。

StartTimer.cs
using UnityEngine;
using UnityEngine.SceneManagement;
using System.IO;
using System.Collections;

public class StartTimer : MonoBehaviour
{
    private void Update()
    {
        if (Input.GetButtonDown("Jump"))
        {
            StartCoroutine(Load());
        }
    }


    IEnumerator Load()
    {
        File.AppendAllText(Application.streamingAssetsPath + "/result.txt", "LoadStart..." + Time.time+"\n");
        var result = AssetBundle.LoadFromFileAsync(Application.streamingAssetsPath + "/sphere");

        yield return new WaitWhile(() => {
            return !result.isDone;
        });

        var assetbundle = result.assetBundle;

        File.AppendAllText(Application.streamingAssetsPath + "/result.txt", "ABLoadComplete..." + Time.time+"\n");
        string sceneName = Path.GetFileNameWithoutExtension(assetbundle.GetAllScenePaths()[0]);
        SceneManager.LoadScene(sceneName);
    }
}

CompleteTimer.cs
using UnityEngine;
using System.IO;

public class CompleteTimer : MonoBehaviour
{
    void Start()
    {
        File.AppendAllText(Application.streamingAssetsPath + "/result.txt", "LoadComplete..." + Time.time+"\n");
    }
}

(AssetBundleからのシーン読み込みの方法はこちらのブログを参考にさせていただきました。http://tsubakit1.hateblo.jp/entry/2016/08/23/233604)

開始時に読み込まれるシーンのGameObjectにStartTimer.csをアタッチし、CompleteTimer.csは先ほどのテクスチャいっぱいのシーンのGameObjectにアタッチしておきます。

この測定をCrunch圧縮なし/ありでそれぞれ行いロード時間を比較したいと思います。
なおCrunch圧縮のクオリティは50でやっていきます。

結果

Crunch圧縮無し

result.txt
LoadStart...0.9617942
ABLoadComplete...32.18606
LoadComplete...32.20223

アセットバンドルサイズ : 267,533KB(267.5MB)

アセットバンドルのロードにかかった時間 : 31.2242658秒
アセットバンドルロード完了後にシーンの読み込みにかかった時間 : 0.01617秒

Crunch圧縮あり

result.txt
LoadStart...10.97006
ABLoadComplete...21.08436
LoadComplete...21.10166

アセットバンドルサイズ : 105,891KB(105.9MB)

アセットバンドルのロードにかかった時間 : 10.1143秒
アセットバンドルロード完了後にシーンの読み込みにかかった時間 : 0.0173秒

Crunch圧縮によりアセットバンドルのサイズは半分以下に、読み込み時間は3分の1ほどになりました。
シーンの読み込みにかかった時間はCrunch圧縮ありの方がほんのわずかに遅かったですが誤差レベル。人間には知覚できないでしょう。

AssetBundle無し

それではアセットバンドルをビルドせずにシーンを直接組み込んだ場合はどうでしょうか。
環境及び計測方法は大体同じです。

Crunch圧縮無し

result.txt
LoadStart...1.388524
LoadComplete...1.41218

ビルドサイズ:652 MB
シーンのロード時間:0.023656

Crunch圧縮あり

result.txt
LoadStart...1.386678
LoadComplete...1.408058

ビルドサイズ:236 MB
シーンのロード時間:0.02138

ビルドサイズは3分の1ほどになりましたがシーンのロード時間はほぼ変わりませんでした。
またゲームの起動時間は測定方法が分からなかったので測定できませんでしたが体感には全く差は感じられませんでした。

まとめ

Crunch圧縮をすることでビルドサイズが劇的に小さくなった上に解凍によるオーバーヘッドは全く感じられませんでした。特にアセットバンドルからのシーンロードの際にはCrunch圧縮した方が圧倒的にロードが速かったです。すべてのデータを毎度ネットワークからロードしてくるWebGLなどではさらに大きく差が開くことでしょうし、モバイルアプリにおいてもダウンロードサイズを抑えるための有効な手段になるはずです。
環境によっても変わってくるでしょうが基本的にはCrunch圧縮はしていく方向でいいのではないでしょうか。
ただしテクスチャの各辺の長さが4の倍数でなければ圧縮できませんし、Crunchは不可逆圧縮なので画像が劣化する可能性がある点は注意が必要です。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

学部2年生のVRゲーム開発日誌

はじめに

本記事はタイトル通り理系大学の情報学科に通う学部二年の私が大学の学祭で展示するVRゲームを開発したときの(8月下旬から10月末までの)日誌になっております。
完成してからこの記事を書いていますので、ところどころ何を考えていたのか覚えていないところもありますがそこはご了承ください。

さて、タグにもある通りHTCViveとUnityを使用してVRゲームを開発したわけですが、残念ながら時間が足りず、肝心の体を動かした入力や、コントローラーのリアルな振動など、VRゲームの魅力と言っても差し支えない部分については私は何も開発していないです...
UnityのAssetStoreで公開されているSteamVRのLongbowのモデルをそのまま利用して、ゲームのルールや一通りの流れを作っただけです。
そのため、VRの入力のとり方わかんねぇって人や、すでにVRコントローラーを使ったゲームを自作できる人向けの記事ではないと思います、ごめんなさい。
一応勉強はしたんですけどね、使いこなせなかったです。
その勉強の際に使用したURLは下記しておきます(全部英語ですけど頑張って翻訳してください。GIFとかでも説明してくれるので読めなくても多分なんとかなります。)
Viveコントローラーの入力のとり方や、ものを掴んで投げる、ワープする、くらいまではここのサイトの知識で十分できるようになります。
HTCVive Tutorial for Unity

というわけで、技術の指南というよりは、開発時にここで詰まって、どのように解決したかや、実装方法の試行錯誤などを感じてもらえればと思います。

また、最終的に使った方法を見出しの後ろにつけておこうと思うので、そちらの技術に関しては少しお話できるかもしれません。

開発に至ったきっかけ

元も子もないことを言ってしまうと予算が下りないからです()
私は大学公認のサークルに加入しており、主な活動内容は学祭での自作ゲーム展示に向けてゲーム制作をすることです。
そして、今年はVRゲーム用の機材を大学の予算で買ったため、作らないと翌年の予算が大幅減額されてしまうのです。(予算8000円の悪夢)
まあ、流石にこれは冗談として、せっかく部にVR機器があるのだから、開発しないほうが損じゃないかということで自分から志願しました。

最初は流鏑馬作ってみたいなぁで開発していたのですが、馬のモデルやアニメーション制作が難しいし、何より酔いそうだったので、トロッコにのって流鏑馬やる感じのゲームになりました。
それでもテストプレイした人たちからは酔いそうと言われたのでだめだったみたいですが。

開発環境

Unity 2019.1.1f1
HTC Vive 77H02568-15M Rev.B

どんなゲームにするか考える

時期としては8月下旬から9月初旬にかけて。
このあたりはVR機器のinput周りを勉強してました。色々迷ったものの結局流鏑馬に決めて開発を始めることにしました。(VRマ○オカートも考えたけど入力を取るのが難しすぎて断念)

SteamVRのLongbowと的を利用するのが良さそう(基本的にValve社は自作宣言しなければ第三者の使用に寛容っぽい)となったので、流鏑馬に必要な部分はほぼここで完結。(参考:Valve Third Party Legal Notice)

あとはValve社の作った処理にちょっと手を加えれば完成だな!とこの時は思っていました...

SendMessageを使わないように(ExecuteEvent.Execute)

時期としては9月中旬から10月中旬まで。
的に当てて得点を手に入れていき、ハイスコアで競う形のゲームにしようと思っていました。
矢を放って、的にあたったらひっつくという処理を既にValve社が作ってくれています。
(詳しい実装はSteamVR/InteractionSystem/Longbow/Scriptsにあるarrow.cs(矢)とArcheryTarget(的)を見てください)

実装をすごい雑に説明すると、矢を放つために矢のモデルをInstantiateして、打つ。
矢の速度が一定以上で当たった物体のmaterialが刺さってもよいmaterialなら矢がひっつくという処理をしていました。

新たに追加したい機能として
1,矢を放った回数をカウントする(命中率を計算するため)
2,矢が当たった位置と的の中心位置から距離を求めて、距離に応じた点数を加算する
の二つがあったので、自分の作ったクラス内にこれらを計算する機能をつけて終わりだと思ったのですが...

なぜか矢の方(Arrow.cs)から自分の作ったクラス(今回はGamemaster.csという名前)が見つかりません。
ファイル構成的には
Asset--myfolder--Gamemaster.cs
          |
          --SteamVR--InteractionSytstem--Longbow--Scripts--Arrow.cs

雑に記述するとこんな感じだったのですが、Gamemasterのインスタンスを生成しようとしてもクラスを認識していないみたいで、できませんでした。
色々探したところUnity自体のコンパイル順が関係しているのではないかということはわかりました。(参考:Unity公式)

まあ当たり前ですがArrow.csの隣にGamemaster.csを置けば認識してくれたので、それで解決はできるのですが、自作のクラスが散らばるとあとから見にくいことこの上ない...ということで、SendMessageなるものを使ってゴリ押しで解決しました。
これはgameobjectにくっついてるコンポーネントの関数の中から引数で受け取ったString型と同じ名前の関数を実行するという機能です。

まあ機能の説明聞いただけで良くない部分がわかりますよね。
gameobjectに本当にその名前の関数があるかもわからないし、渡せる引数も上限があります。
更には関数名をString型で渡すので補完が効かず、タイプミスもしやすい。
関数呼び出しを検索することもできません。

さて、ここで導入したのがExecuteEvents.Executeです。
これはSendMessageの改良版で、文字列をキーとするのではなく、インターフェースをキーにして関数を実行してくれます。
何気にC#でインターフェースを使ったのが初めてだったので(機能として知ってはいましたが)、そこには少し詰まりました。
実装の際にはこの方のブログを大いに参考にさせていただきました。この場で感謝申し上げます。
http://tsubakit1.hateblo.jp/entry/2015/04/13/010645

自作クラスが認識されない問題さえ解決すれば、あとは簡単です。
矢が的にひっつくときにはPhysics.raycastを利用してgameobjectにひっつけるかどうかを判定していたので、それを利用して的の命中位置を出しました。
矢の中心位置が矢羽根の位置になっていたせいで、的の中心との距離が変なことになっていたのには気がつくのに1日費やしました...

あとは矢をリリースする処理をしている関数内に、同じように撃った矢の本数を1足す関数を実装することで、もう半分くらいゲームはできたと言えるでしょう。
この時点で的に当てると点数が増えて、的の中心に近いほうが点数が高い、そしてスコアの合計を出せるくらいならできました。あとは自機を動かせば完成!

ルートに沿って自機を移動させる(NavmeshAgent)

やっていた時期は10月中旬ごろ。
一年生のゲーム制作の際にも使っていて、まだ覚えていたのでこれを使用。
慣れもあって一週間足らずで実装できました。
Animationとも迷ったのですが、細かい調整が効きにくそうという理由でNavMeshAgentを活用することにしました。(今更ながら試すだけならやってみるべきだったとも思います)
こいつにターゲットの位置を与えてあげることで、NavMeshAgentをアタッチしたオブジェクトがターゲットに向かって移動してくれます。
(参考:公式リファレンス)
これを応用して、進路上にターゲットを並べて、追跡する対象を次々変えていくことで指定した順番で、順路を決めて動かすことができます。
NavMeshAgent.speedを変更してあげれば移動速度の変更もできるので、一周させる速度の調整なども簡単。
まあ追跡対象を並べていくのが正直めんどくさいのと、曲がるときに結構急カーブになってしまったので、そこは改良の余地ありですかね。
ここに関してはもっと良い方法があるだろうと思っているので、なにかいい方法があればコメントでお願いします。

的を動かす(Animation)

10月中旬ごろに実装。
ただ移動して動かない的を撃つだけではつまらないと思ったので、動く的を作ろうと思ったのですが、上下と左右に移動するだけくらいにしておいたほうがいいかなと(難しすぎなくていいかな)と思ったので、すぐできるAnimationを使って動かしました。
的のTransform.positionをただキーフレーム打って指定してあげるだけの、公式リファレンスでやっていることくらいしかやっていないので特に解説することがないです...
(公式:https://docs.unity3d.com/ja/2017.4/Manual/AnimationEditorGuide.html)

カウントダウン・リザルト表示の実装(Timeline)

10月下旬。最後の壁になった実装でした。
初期位置で弓を持つ→的を撃つとカウントダウンが始まる→ゲームスタート→一周する間的を撃って得点を稼ぐ→ゴール→リザルト表示
といった流れのゲームにしようと決まったのですが、カウントダウンはフレームをまたぐ処理だ→コルーチン使うか、という流れでひとまず作ってみたのですが、カウントダウンのSEが無限に再生されてしまう状態を解消できず...
その時期にTimelineなるものの存在を知ったために、最終的にはTimelineで実装しました。
とりあえず公式の情報としては下記の感じ。
https://docs.unity3d.com/ja/2017.4/Manual/TimelineSection.html

名前の通りタイムラインに沿ってオブジェクトの表示・非表示やアニメーションの再生、挙句の果てにはスクリプトの実行までできます。どういう動きをするかのテスト再生もできるので、すごいわかりやすかったです。
このあたりは映像編集をやったことがある人にはわかりやすいUIだと思いました。
ただし、スクリプトの実行にはExposedReferenceというのが出てきて、実装には苦労しました。
実装の際には下記の方のお世話になりました。
https://bardaxel.jp/archives/643

正直作るだけならできるようになりましたが、おまじない感が否めない...

カウントダウンはテキストを弄るスクリプトを実行させながら、SEを流すことで実装。
カウントが0になったときにゲームスタートの処理をするようにもしました。
カウントダウンタイムライン.PNG

リザルト画面の表示はカウントダウンよりは複雑で、
終了のブザーを鳴らす→パネル・矢を撃った回数・的にヒットした回数・命中率・命中率によるボーナス倍率・ボーナスを加味した最終得点
の順番で表示しています。
ランキングも実装したため、スコアが表示されると同時にランキングに表示されるようにしました。
ランキングタイムライン.PNG

最後に

ここまで読んでいただいた方にはどんなゲームになったか見ていただきたいのですが、画面録画できる環境がなく、スクリーンショットもうまくいかなかったので、写真はプロジェクト中のものだけですが公開します...申し訳ないです。
録画やスクショが取れるようになったら追加しておこうと思います。

ただし、このゲームは2019年の11/2(土)~4(月)まで、中央大学後楽園キャンパス6418教室にて展示しておりますので、興味があればお越しいただけると幸いです。
一年生が制作したゲームの展示・配布も行っていますので、そちらもよければどうぞ。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

学部2年生のVRゲーム開発日誌(11/3更新)

はじめに

本記事はタイトル通り理系大学の情報学科に通う学部二年の私が大学の学祭で展示するVRゲームを開発したときの(8月下旬から10月末までの)日誌になっております。
完成してからこの記事を書いていますので、ところどころ何を考えていたのか覚えていないところもありますがそこはご了承ください。

さて、タグにもある通りHTCViveとUnityを使用してVRゲームを開発したわけですが、残念ながら時間が足りず、肝心の体を動かした入力や、コントローラーのリアルな振動など、VRゲームの魅力と言っても差し支えない部分については私は何も開発していないです...
UnityのAssetStoreで公開されているSteamVRのLongbowのモデルをそのまま利用して、ゲームのルールや一通りの流れを作っただけです。
そのため、VRの入力のとり方わかんねぇって人や、すでにVRコントローラーを使ったゲームを自作できる人向けの記事ではないと思います、ごめんなさい。
一応勉強はしたんですけどね、使いこなせなかったです。
その勉強の際に使用したURLは下記しておきます(全部英語ですけど頑張って翻訳してください。GIFとかでも説明してくれるので読めなくても多分なんとかなります。)
Viveコントローラーの入力のとり方や、ものを掴んで投げる、ワープする、くらいまではここのサイトの知識で十分できるようになります。
HTCVive Tutorial for Unity

というわけで、技術の指南というよりは、開発時にここで詰まって、どのように解決したかや、実装方法の試行錯誤などを感じてもらえればと思います。

また、最終的に使った方法を見出しの後ろにつけておこうと思うので、そちらの技術に関しては少しお話できるかもしれません。

開発に至ったきっかけ

元も子もないことを言ってしまうと予算が下りないからです()
私は大学公認のサークルに加入しており、主な活動内容は学祭での自作ゲーム展示に向けてゲーム制作をすることです。
そして、今年はVRゲーム用の機材を大学の予算で買ったため、作らないと翌年の予算が大幅減額されてしまうのです。(予算8000円の悪夢)
まあ、流石にこれは冗談として、せっかく部にVR機器があるのだから、開発しないほうが損じゃないかということで自分から志願しました。

最初は流鏑馬作ってみたいなぁで開発していたのですが、馬のモデルやアニメーション制作が難しいし、何より酔いそうだったので、トロッコにのって流鏑馬やる感じのゲームになりました。
それでもテストプレイした人たちからは酔いそうと言われたのでだめだったみたいですが。

開発環境

Unity 2019.1.1f1
HTC Vive 77H02568-15M Rev.B

どんなゲームにするか考える

時期としては8月下旬から9月初旬にかけて。
このあたりはVR機器のinput周りを勉強してました。色々迷ったものの結局流鏑馬に決めて開発を始めることにしました。(VRマ○オカートも考えたけど入力を取るのが難しすぎて断念)

SteamVRのLongbowと的を利用するのが良さそう(基本的にValve社は自作宣言しなければ第三者の使用に寛容っぽい)となったので、流鏑馬に必要な部分はほぼここで完結。(参考:Valve Third Party Legal Notice)

あとはValve社の作った処理にちょっと手を加えれば完成だな!とこの時は思っていました...

SendMessageを使わないように(ExecuteEvent.Execute)

時期としては9月中旬から10月中旬まで。
的に当てて得点を手に入れていき、ハイスコアで競う形のゲームにしようと思っていました。
矢を放って、的にあたったらひっつくという処理を既にValve社が作ってくれています。
(詳しい実装はSteamVR/InteractionSystem/Longbow/Scriptsにあるarrow.cs(矢)とArcheryTarget(的)を見てください)

実装をすごい雑に説明すると、矢を放つために矢のモデルをInstantiateして、打つ。
矢の速度が一定以上で当たった物体のmaterialが刺さってもよいmaterialなら矢がひっつくという処理をしていました。

新たに追加したい機能として
1,矢を放った回数をカウントする(命中率を計算するため)
2,矢が当たった位置と的の中心位置から距離を求めて、距離に応じた点数を加算する
の二つがあったので、自分の作ったクラス内にこれらを計算する機能をつけて終わりだと思ったのですが...

なぜか矢の方(Arrow.cs)から自分の作ったクラス(今回はGamemaster.csという名前)が見つかりません。
ファイル構成的には
Asset--myfolder--Gamemaster.cs
          |
          --SteamVR--InteractionSytstem--Longbow--Scripts--Arrow.cs

雑に記述するとこんな感じだったのですが、Gamemasterのインスタンスを生成しようとしてもクラスを認識していないみたいで、できませんでした。
色々探したところUnity自体のコンパイル順が関係しているのではないかということはわかりました。(参考:Unity公式)

まあ当たり前ですがArrow.csの隣にGamemaster.csを置けば認識してくれたので、それで解決はできるのですが、自作のクラスが散らばるとあとから見にくいことこの上ない...ということで、SendMessageなるものを使ってゴリ押しで解決しました。
これはgameobjectにくっついてるコンポーネントの関数の中から引数で受け取ったString型と同じ名前の関数を実行するという機能です。

まあ機能の説明聞いただけで良くない部分がわかりますよね。
gameobjectに本当にその名前の関数があるかもわからないし、渡せる引数も上限があります。
更には関数名をString型で渡すので補完が効かず、タイプミスもしやすい。
関数呼び出しを検索することもできません。

さて、ここで導入したのがExecuteEvents.Executeです。
これはSendMessageの改良版で、文字列をキーとするのではなく、インターフェースをキーにして関数を実行してくれます。
何気にC#でインターフェースを使ったのが初めてだったので(機能として知ってはいましたが)、そこには少し詰まりました。
実装の際にはこの方のブログを大いに参考にさせていただきました。この場で感謝申し上げます。
http://tsubakit1.hateblo.jp/entry/2015/04/13/010645

自作クラスが認識されない問題さえ解決すれば、あとは簡単です。
矢が的にひっつくときにはPhysics.raycastを利用してgameobjectにひっつけるかどうかを判定していたので、それを利用して的の命中位置を出しました。
矢の中心位置が矢羽根の位置になっていたせいで、的の中心との距離が変なことになっていたのには気がつくのに1日費やしました...

あとは矢をリリースする処理をしている関数内に、同じように撃った矢の本数を1足す関数を実装することで、もう半分くらいゲームはできたと言えるでしょう。
この時点で的に当てると点数が増えて、的の中心に近いほうが点数が高い、そしてスコアの合計を出せるくらいならできました。あとは自機を動かせば完成!

ルートに沿って自機を移動させる(NavmeshAgent)

やっていた時期は10月中旬ごろ。
一年生のゲーム制作の際にも使っていて、まだ覚えていたのでこれを使用。
慣れもあって一週間足らずで実装できました。
Animationとも迷ったのですが、細かい調整が効きにくそうという理由でNavMeshAgentを活用することにしました。(今更ながら試すだけならやってみるべきだったとも思います)
こいつにターゲットの位置を与えてあげることで、NavMeshAgentをアタッチしたオブジェクトがターゲットに向かって移動してくれます。
(参考:公式リファレンス)
これを応用して、進路上にターゲットを並べて、追跡する対象を次々変えていくことで指定した順番で、順路を決めて動かすことができます。
NavMeshAgent.speedを変更してあげれば移動速度の変更もできるので、一周させる速度の調整なども簡単。
まあ追跡対象を並べていくのが正直めんどくさいのと、曲がるときに結構急カーブになってしまったので、そこは改良の余地ありですかね。
ここに関してはもっと良い方法があるだろうと思っているので、なにかいい方法があればコメントでお願いします。

的を動かす(Animation)

10月中旬ごろに実装。
ただ移動して動かない的を撃つだけではつまらないと思ったので、動く的を作ろうと思ったのですが、上下と左右に移動するだけくらいにしておいたほうがいいかなと(難しすぎなくていいかな)と思ったので、すぐできるAnimationを使って動かしました。
的のTransform.positionをただキーフレーム打って指定してあげるだけの、公式リファレンスでやっていることくらいしかやっていないので特に解説することがないです...
(公式:https://docs.unity3d.com/ja/2017.4/Manual/AnimationEditorGuide.html)

カウントダウン・リザルト表示の実装(Timeline)

10月下旬。最後の壁になった実装でした。
初期位置で弓を持つ→的を撃つとカウントダウンが始まる→ゲームスタート→一周する間的を撃って得点を稼ぐ→ゴール→リザルト表示
といった流れのゲームにしようと決まったのですが、カウントダウンはフレームをまたぐ処理だ→コルーチン使うか、という流れでひとまず作ってみたのですが、カウントダウンのSEが無限に再生されてしまう状態を解消できず...
その時期にTimelineなるものの存在を知ったために、最終的にはTimelineで実装しました。
とりあえず公式の情報としては下記の感じ。
https://docs.unity3d.com/ja/2017.4/Manual/TimelineSection.html

名前の通りタイムラインに沿ってオブジェクトの表示・非表示やアニメーションの再生、挙句の果てにはスクリプトの実行までできます。どういう動きをするかのテスト再生もできるので、すごいわかりやすかったです。
このあたりは映像編集をやったことがある人にはわかりやすいUIだと思いました。
ただし、スクリプトの実行にはExposedReferenceというのが出てきて、実装には苦労しました。
実装の際には下記の方のお世話になりました。
https://bardaxel.jp/archives/643

正直作るだけならできるようになりましたが、おまじない感が否めない...

カウントダウンはテキストを弄るスクリプトを実行させながら、SEを流すことで実装。
カウントが0になったときにゲームスタートの処理をするようにもしました。
カウントダウンタイムライン.PNG

リザルト画面の表示はカウントダウンよりは複雑で、
終了のブザーを鳴らす→パネル・矢を撃った回数・的にヒットした回数・命中率・命中率によるボーナス倍率・ボーナスを加味した最終得点
の順番で表示しています。
ランキングも実装したため、スコアが表示されると同時にランキングに表示されるようにしました。
ランキングタイムライン.PNG

最後に

ここまで読んでいただいた方にはどんなゲームになったか見ていただきたいのですが、画面録画できる環境がなく、スクリーンショットもうまくいかなかったので、写真はプロジェクト中のものだけですが公開します...申し訳ないです。
録画やスクショが取れるようになったら追加しておこうと思います。

(11/3追記)
youtubeにてテストプレイ動画を公開しました。
ただ画面録画しただけですが、どんな雰囲気のゲームになったかはわかるかと思います。
https://www.youtube.com/watch?v=Wbs4zibV_dc&feature=youtu.be
(追記終わり)

このゲームは2019年の11/2(土)~4(月)まで、中央大学 後楽園キャンパス6418教室にて展示しておりますので、興味があればお越しいただけると幸いです。
一年生が制作したゲームの展示・配布も行っていますので、そちらもよければどうぞ。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む