20210109のUnityに関する記事は7件です。

緯度経度と直交座標を相互変換する

はじめに

緯度経度高さで表される座標と、XYZの三次元直交座標を相互変換します。
Unityで使いたかったため、C#とUnityのタグをつけていますが、特にUnityに制限されるものではありません。

計算方法については、「世界測地系と座標変換」(飛田幹夫著、日本測量協会発行)を参考にしました。
世界測地系と座標変換(Amazon)
この本は日本測量協会に問い合わせれば定価で購入できると思います。

※注意※
著者は本稿の内容について、精度や正確さ正しさを必ずしも保証するものではありません。
著者は本稿の内容を利用したことにより起こった事柄に関して一切の責任を負わないものとします。
実際に使われる場合は、値域の制限などを入れることや、精度保証などのテストを行って、ご自身の責任においてご利用ください。

前提

緯度経度はWGS84に基づいているものとします。変換パラメータを変えれば、他の測地系にも対応できると思いますが、本稿の範囲外とします。
緯度は北側が+、南側に-で、-90~90度。経度は東に+、西に-で、-180~180度とします。
高さは、WGS84に基づく楕円体高でメートルを単位とします。標高ではないのでご注意ください。
XYZ座標は、+X軸が子午線、東向きに+Y軸、北向きに+Z軸とします。こちらも単位はメートルになります。

プログラム

CoordConv.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

using System;

/// <summary>
/// 座標の変換用クラス。なるべくシンプルに必要な変換だけ。
/// 測地系はWGS84を使用。
/// 計算方法は「世界測地系と座標変換 飛田幹夫 日本測量協会」を参考にした。
/// </summary>
public class CoordConv
{
    private const double a = 6378137.0;
    private const double f = 1.0 / 298.257223563;
    private const double e2 = f * (2.0 - f);

    /// <summary>
    /// 緯度経度(WGS84)から直交座標(ECEF)に変換するメソッド。
    /// </summary>
    /// <param name="b">緯度</param>
    /// <param name="l">経度</param>
    /// <param name="h">楕円体高</param>
    /// <returns>直交座標のXYZ。+Zが北極、+Xが子午線、+Yが東経方向</returns>
    public static (double x, double y, double z) BLH2XYZ(double b, double l, double h){
        b = Math.PI * b / 180.0;
        l = Math.PI * l / 180.0;

        double N = a / Math.Sqrt(1.0 - e2 * Math.Pow(Math.Sin(b),2.0));

        return (
            (N + h) * Math.Cos(b) * Math.Cos(l),
            (N + h) * Math.Cos(b) * Math.Sin(l),
            (N * (1.0 - e2) + h) * Math.Sin(b) 
        );
    }

    /// <summary>
    /// 直交座標(ECEF)から緯度経度(WGS84)に変換するメソッド。
    /// </summary>
    /// <param name="x">X</param>
    /// <param name="y">Y</param>
    /// <param name="z">Z</param>
    /// <returns>緯度経度。b緯度、l経度、h楕円体高</returns>
    public static (double b, double l, double h) XYZ2BLH(double x, double y, double z){
        double p = Math.Sqrt(x*x + y*y);
        double r = Math.Sqrt(p*p + z*z);
        double mu = Math.Atan(z / p * ((1.0 - f) + e2 * a/r));

        double B = Math.Atan( (z * (1.0-f) + e2*a*Math.Pow(Math.Sin(mu),3)) / ((1.0-f)*(p-e2*a*Math.Pow(Math.Cos(mu),3))) );
        return (
            180.0 * B / Math.PI,
            180.0 * Math.Atan2(y,x) / Math.PI,
            p * Math.Cos(B) + z*Math.Sin(B) - a*Math.Sqrt(1.0 - e2*Math.Pow(Math.Sin(B),2))
        );
    }
}

注意点

必ず、doubleで値を渡してください。floatでは一般的な用途でも演算の精度が不足する可能性が高いです。得られた値は、なるべく小さな値になるようにdoubleのまま平行移動などしてからfloatに変換し、Unityのオブジェクトの座標(Vector3など)に代入してください。
計算部分では値域のチェックなどもしていません。必要なら追加してください。
変換パラメータがWGS84の改訂などで変わることがあります。高い精度を求める場合は都度信頼できる情報を確認してください。

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

Unityでの2Dジャンプアクションゲームの作成(Stage作成編)

Unityでの2Dアクションゲームの作り方をメモしていきます。

まずはプロジェクトを作っていきます。
Unity Hubから新規プロジェクトを作成を選択。
テンプレートは2Dを選択してプロジェクト名はActionGameとします。
Unityプロジェクト2.png

素材をインポートするためにWindow→Asset Storeを選択してAsset Storeから素材をダウンロードします。今回はFree素材であるPixel Adventure 1をダウンロードしていきます。
rapture_20210109163115.jpg

ダウンロードしてimportを行うと左下にあるProjectに表示されるAssetsフォルダ内にPixel Adventure 1が出来ます。その中に素材が正常にダウンロード出来ているか確認します。
rapture_20210109165730.jpg

次にステージを作成するためにTilemapを使用します。Hierarchyウィンドウの+をクリックして2D Object→Tilemapをクリック。

Unity画面.png

HierarchyウィンドウにGridが作成されクリックするとSceneにグリッドが表示されることを確認します。

次に2D Tilemap Editorを使えるようにするためWindow→Package Managerをクリックします。Package Managerの検索欄にtileと入力すると出てくのでインストールします。
rapture_20210102151322.jpg

インストールするとWindow→2Dの欄に2D tilemap Editorが追加され使用できるようになります。

tilemapを管理するフォルダを作成します。Assets内で右クリックからCreate→Folderを選択してstagesフォルダを作成します。

その後window→2D→Tilemapをクリックすると以下の画面が表示されます。
rapture_20210109160522.jpg

そこでCreate New Palleteをクリックし、新しいパレット(名前は何でもいいですがStagePalleteとにしときます)を先ほど作成したstagesフォルダ内に作ります。

作成したStagePalleteへ"Assets/Pixel Adventure 1/Assets/Terrain"内のTerrain Sliced(16×16)をドラッグアンドドロップします。しばらく処理が走った後に以下の画面のようになりパレットが作成されます。

rapture_20210109162107.jpg

パレットの画面上の筆ボタンを押してSceneにStageを描いていくのですが…何もせず書くととても小さく表示されると思います。

こんな感じ
rapture_20210109162146.jpg

このままでは使えないのでサイズの変更を行います。"Assets/Pixel Adventure 1/Assets/Terrain"内のTerrain Sliced(16×16)を選択したのちにInspectorタブのPixels Per Unitを100から16へ変更してApplyします。
rapture_20210109162237.jpg

そうすると1マス毎にパレットを置けるようになりました。
rapture_20210109164612.jpg

というわけでパレットを適当に置く事でstageが作れるようになりました。
rapture_20210109165337.jpg

次回はプレイヤーキャラを作る方法書いていきます。

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

XRでStylishなUX/UIを実現を可能にするElastic Systemを調べてみた話

空間のオブジェクトを自然に見せるには?

XR技術の中でも拡張現実(AR)、複合現実(MR)では現実空間にオブジェクトを出力することになります。この際、オブジェクトの表示に関する表現方法をより自然に見せたいと思ったことはないですか?
例えば、何かものを動かすシチュエーションで物体を移動させる際に、デジタルコンテンツを操作してもきっちりと操作した分だけ移動することになります。操作方法によってはそのままでも問題ない場合が多いですが、自分の操作に対してオブジェクトの動きに遊びがあるとデジタルコンテンツ独特の唐突感が和らぐ印象があると思います。
また、物体を表示される際にデジタルコンテンツを空間に出力する際も、いきなり表示されるよりも徐々に大きくなるなどの表現を入れる方が自然に見えるといった要素も出てくると思います。
このような動きに遊びを加えようとするとそのための計算や処理が必要になります。特に凝った動きではそれ自体かなり大変な実装になることが多いと思います。
Mixed Reality Toolkit V2.5.0からは上記のような”遊び”を表現することができるElastic Systemが試験的な機能として提供されています。
今回はこれらを利用した実装に関する情報を共有したいと思います。

Elastic Systemとは

Elastic=弾力性という名前の通り、弾性表現を可能にするための一連のシステムになります。現時点では試験的な機能として提供されています。このElastic Systemの特徴は、移動/サイズ調整/回転などのオブジェクトの操作に弾性表現を加えることができます。先ほどの動画でもわかる通り、移動の場合であれば操作時の勢いによって、物体がばねの惰性を受けたような動きを加えることが可能になります。これにより物体の動きに”遊び”を入れることで動きを自然に見せることができます。

Mixed Reality Toolkitで提供されるElastic Systemは大きくは以下の2つの機能が提供されています。

種類 内容
Object Manipulator オブジェクトを操作時にElastic Systemを導入することで動きに弾性表現を加えることができます。
ユーティリティ 現在の状態から弾性表現であらわされる"次の状態"を返すユーティリティ。上記以外の用途で活用したい場合に計算値のみ利用することができます。

いずれの場合も共通してばね定数などの設定があります。

Elastic Systemのパラメータ

弾力性を制御するためにElastic Systemにはいくつかのパラメータを設定することができます。

  • Mass
  • Hand K
  • End K
  • Snap K
  • Drag

Mass

物体の重さ。慣性力に影響します。重い方が動き始めが緩慢になり、操作後は慣性によって勢いがついているため、操作とやめた地点を超えて物体が移動します。その後以下のばね定数に従って減衰していくことになります。

mass0.02.gif mass0.2.gif
Mass : 0.02 Mass : 0.2

Hand K

手の動きに対するばね定数。手の操作に対して物体に力が加わるまでの強さを調整する。数値が小さい方がバネの力が弱く、手の動きに対して力が遅れて徐々にかかるようになります。あまり大きい値を入れると発振したようにブルブル震える感じで不安定になります。

HandK0.1.gif HandK10.gif
Hand K : 0.1 Hand K : 10

End K

Object ManipulatorでUse Boundを利用する際に有効になるばね定数。Use Boundが有効になるとオブジェクトが初期位置から一定の範囲内に固定されます。動画の青枠の立方体がその範囲になります。オブジェクトを動かすことは可能ですが、操作後Boundで指定された範囲の空間に引き戻されます。数値が小さい方がバネの力が弱く、より遠くまで引っ張れます。ばね定数が大きくても勢いよく引っ張ると遠くまで引くことはできますが、戻るときもかなりの勢いになります。

EndK1.gif EndK9.gif
End K : 1 End K : 9

Snap K

スナップポイントのばね定数。スナップポイントは不可視の座標で物体が磁石で引っ付くように固定される位置になります。このスナップポイントから物体が抜け出す際のばね定数を指定することができます。小さい値を設定するとスナップポイントからすぐに抜け出せますが、ばね定数を大きくすると、スナップポイントから抜け出すためには勢いよく操作するか、かなり引っ張らないと抜け出せなくなります。

SnapK2.gif SnapK9.gif
Snap K : 2 Snap K : 9

Drag

ドラッグ/ダンパー係数。数値が大きい程減衰が早くなる。例えば1を指定すると移動に対する減衰がむしろ抵抗となり移動に対してかなりゆっくりと物体が追従するような動きになる。逆に0.01等小さい値を設定すると、移動に対する勢いが減衰するまで時間が非常に長くかかる。

Drag0.1.gif Drag1.gif
Drag : 0.1 Drag : 1

このように色々な設定を調整することで動きを調整することができます。

実装

次は実際にMixed Reality ToolkitのElastic Systemを利用して実装を行っていきたいと思います。
実装についてはObjectManipulatorで利用する方法と個別に研鑽する方法と2つあるのでそれらを紹介したいと思います。

開発環境

開発環境、利用するソフトウェアは以下の通りです。

  • Windows 10 Pro バージョン1909
  • Visual Studio 2019 バージョン 16.8.3
  • Mixed Reality Toolkit V2.5.3(Elastic SystemはV2.5.0 later)
  • Windows SDK 10.0.18362.0

MRTKの導入と初期設定

MRTKを利用するために導入と初期設定、プロジェクトの細々した設定をおこないます。

パッケージの導入

[プロジェクトフォルダ]\Packages\manifest.jsonに以下の記述を追加して、Unity Package Manager経由でセットアップを行ってください。

manifest.json(抜粋)
{
  "scopedRegistries": [
    {
      "name": "Microsoft Mixed Reality",
      "url": "https://pkgs.dev.azure.com/aipmr/MixedReality-Unity-Packages/_packaging/Unity-packages/npm/registry/",
      "scopes": [
        "com.microsoft.mixedreality",
        "com.microsoft.spatialaudio"
      ]
    }
  ],
  "dependencies": {
    "com.microsoft.mixedreality.toolkit.examples": "2.5.3",
    "com.microsoft.mixedreality.toolkit.foundation": "2.5.3",
    "com.microsoft.mixedreality.toolkit.tools": "2.5.3",
 ...

また、unitypackageも提供されています。URLを載せておきます。ここからダウンロードしてカスタムインポートでも同じことが可能です。

manifest.jsonを変更して保存するとMRTKがインポートされます。
インポート後はBuild SettingsでHoloLensにデプロイするための設定を行っておきます。
今回は軽いサンプルなので[Sample.scene]をbuildシーンとして追加しておきます。それ以外は以下のように設定。設定後「Switch Platform」で確定します。

設定
Platform Universal Windows Platform
Target Device HoloLens
Architecture ARM
Build Type D3D Project
Target SDK Version 10.0.18362.0
minimum Platform Version 10.0.10240.0
Visual Studio Version Lasted installed(Visual Studio 20919)

image.png

最後にメニューから[Mixed Reality Toolkit]-[Utilities]-[Configure Unity Project]を選択し表示されたダイアログで[Apply]を押下します。
ダイアログ上にボタンが表示されていない場合はすでに正しい設定が完了しているのでそのまま閉じてください。

image.png

シーンの設定

次にMRTKの設定を有効にするためにメニューから[Mixed Reality Toolkit]-[Add Scene And Configure...]を選択しシーンにMRTKに必要な設定を取り込みます。

image.png

実行すると上記のようにオブジェクトが設定されます。

次にMRTKの設定を行います。MRTKではProfileで設定を切り替える形になっています。この際SDKをいくつか選択できるのですが、レガシーな手段とXRSDKを使うパターンです。
レガシーな方法は将来のUnityのバージョンで廃止になる予定の機能です。Unity 2019では、いずれのパターンでも問題なく使うことができます。

XRSDKを使う方法

[MixedRealityToolkit]オブジェクトを選択しProfileに[DefaultXRSDKConfigrationProfile]を設定します。
次に追加で以下のパッケージをインストールします。

  • Windows XR Plugin
  • XR Plugin Management

image.png

メニューから[Edit]-[Project Settings...]を開きます。サブメニューから[XR Plug-in Management]を選択しWindowsのplug-in Providerとして[Windows Mixed Reality]を設定します。
image.png

windwos Mixed Realityの設定はデフォルトのままでOKです。
image.png

legacyな方法(Player Settingsから行う以前からの方法)

[MixedRealityToolkit]オブジェクトを選択しProfileに[DefaultHoloLens2ConfigrationProfile]を設定します。

メニューから[Edit]-[Project Settings...]を開きます。サブメニューから[Player]を選択し[XR Settings]を設定します。
[Virtual Reality Supported]をチェックしSDKに[Windows Mixed Reality]を追加します。

image.png

Object ManipulatorでElastic Systemを利用する

次にObject ManipulatorでElastic Systemを利用するための実装をいくつかしたいと思います。最初に対象のオブジェクトにコンポーネントを設定します。

Hierarchyに操作対象となるGameObjectを生成します。
image.png

GameObjectを選択しInspector内の[Add Component]を押下して以下の3つのコンポーネントを追加します。

  • ObjectManipulator
  • NearInteractionGrabbable
  • ElasticManager

追加後に[ObjectManipulator]内のパラメータ[Elastics]-[Elastics Manager]にこのGameObjectを割り当てます。

image.png

コンポーネントの追加は以上で完了です。
次にElastic Systemに必要なパラメータを定義するための設定を用意します。
Projectパネルの任意の場所で右クリックを押し[Create]-[Mixed Reality Toolkit]-[Experimental]-[Elastic]-[Elastic Configuration]を選択すると[ElasticConfiguration]が追加されます。この設定を利用することでElastic Systemに必要な設定を共通で行うことができます。
image.png

実際の操作に対して適用する際には[ElasticManager]の[Manipulation types using elastic feedback]パラメータに適用したい操作を割り当てます。例えば、移動時に弾性表現を加えたい場合は[Move]を選択します。設定は排他ではないので複数の操作を同時に設定可能です。Object Manipulator扱えるMove/Rotate/Scaleの3種類に対してElastic Systemを利用することが可能です。
image.png

上記の設定を行えば弾性表現をObject Manipulatorに対して導入することが可能になります。さらに、Elastic SystemではObject Manipulatorの操作に対して以下のパターンを構成することが可能です。

  • 制約なし
  • 範囲固定
  • スナップ動作

(注意)現時点のElastic Managerを設定する際に以下の3つのパラメータの設定については一度値を変更しないとNullReference例外が発生しObjectr Manipulatorが無効になります。Elastic Managerの設定変更を行った際には以下の値の中にあるばね定数等を一度変更して元に戻す or 共通の設定を割り当てるなどしてオブジェクトがインスタンス化されている状況を作るようにしてください。

  • Transition Elastic
  • Rotation Elastic
  • Scale Elastic

制約なし

すべての操作に適用できる設定です。

mass0.02.gif
通常のObject Manipulatorの操作に弾性表現が入る形になります。この設定を有効にするためには[Manipulation types using elastic feedback]で有効にしたい操作(Move/Rotation/Scale)を設定するのみで利用できます。設定としてはExtentの設定をすべて行っていない状態で利用する形になります。
image.png

範囲固定

MoveおよびScale操作に適用できる設定です。

EndK1.gif
Object Manipulatorに対して制約を与えます。一定の範囲でオブジェクトを固定します。
例えばmoveの場合は指定の座標周辺でオブジェクトを固定することができ、操作しようとしても引き戻されるような効果を加えることができます。
この設定を有効にする場合はExtentの[Stretch Bounds]の値と[Use Bounds]を有効にする必要があります。

パラメータ 説明
Stretch Bounds - Center オブジェクトを固定する空間の中心座標を設定します。World座標で設定します。
Stretch Bounds - Extent オブジェクトを固定する空間の範囲を指定します。この範囲からはみ出たオブジェクトは引き戻されこの空間内に戻ってくるようになります。オブジェクト自体はこの設定値の範囲の中に納まるように動きます。
Use Bounds 範囲固定の有効/無効を設定します。有効にすると上記の設定値の範囲にオブジェクトがバネで固定された可能ような動きになります。

スナップ動作

SnapK2.gif
Object Manipulatorに対して制約を与えます。操作を連続的ではなく、一定の範囲で刻んだスナップポイントに従って動作します。スナップポイントは空間の座標に対して一定間隔で用意できるポイントでオブジェクトの操作時にポイント間で操作を行うことが可能になります。上記の動画(Snap K)のように格子状の点上を伝って動くような表現が可能になります。

この設定を有効にする場合はExtentの[Snap Points]と[Repeat Snap Points]および[Snap Radius]を設定する必要があります。

パラメータ 説明
Snap Points スナップポイントとして指定する座標を設定します。スナップポイントは複数設定することができます。例えば、時計の文字盤状にポイントを構成すれば、文字盤の数字間を移動するオブジェクトなどが作成可能になります。
Repeat Snap Points 設定したスナップポイントを繰返しで設定するかを設定します。スナップポイントの1点を使い、上記のような格子点として空間上に展開することができます。複数のスナップポイントが設定された状態でも利用可能ですが、スナップポイントの展開が複雑になるため意図した形にはなりにくいです。Snap Pointsを1点だけ登録し、それをリピートして格子状での利用するか、リピートなしで複数のSnap Pointsを割り当てる形のいずれかにしましょう。
Snap Radius これはスナップポイントを中心にこの設定値を半径として、オブジェクトが吸い付くようになります。この半径はスナップポイント同士で重ならないように調整します。例えば、スナップポイント間が1となる場合はこの値を0.5にすることで操作時にどちらかのポイントに吸い付くようになります。

設定例1.範囲固定(Move)

BoundsSamples.gif

範囲固定の場合は以下のパラメータを設定します。この例ではワールド座標(0,0,1)を中心とした10cm四方の立方体の範囲内でオブジェクトを固定する設定になります。

  • Stretch Bounds : (0,0,1)
  • Extent : (0.1,0.1,0.1)
  • Use Bounds : true

image.png

設定例2.スナップ動作(Move) - 三角形の頂点移動

TriangleSamples.gif

スナップ動作として三角形の頂点についつくような動きをする設定例です。この例では三角形の頂点にあたる座標をSnap Pointsとして登録しています。繰返しは無しで、スナップの半径は0.2と指定します。頂点を中心に0.2の範囲に入るとオブジェクトが吸い込まれて所定の位置に固定されます。

  • Snap Points : (0,0.2,1), (0.2,-0.2,1), (-0.2,-0.2,1)
  • Repeat Snap Points : false
  • Snap Radius : 0.2

image.png

設定例3.スナップ動作(Move) - 格子状の頂点移動

RepeatSnapSamples.gif

スナップ動作として格子状に動く設定例です。0.1間隔でスナップポイントが設定されまる。スナップの半径は0.1です。格子状の点を辿っていきながら動くような操作を実現できます。

  • Snap Points : (0.1,0.1,0.1)
  • Repeat Snap Points : true
  • Snap Radius : 0.1

image.png

移動時の例を紹介しましたが、ほかの操作でも同じように設定することが可能です。

Elastic Systemの計算値のみ利用する。

Object Manipulatorに利用するElastic Managerも内部ではばね定数の情報と現在のオブジェクトの状態を基に次のフレームでの状態を計算しています。この計算を行うためのユーティリティを利用することでObject Manipulator以外の独自の処理に弾性表現を加えることが可能です。

Samples.gif

ユーティリティでは以下の3つのデータ型を扱うことが可能です。

データ型 使用するクラス 概要
float LinearElasticSystem float値を用いて設定されたばね定数に従って現在の値から次のfloat値を計算して返します。
Vector3 VolumeElasticSystem Vector3 値を用いて設定されたばね定数に従って現在の値から次のVector3 値を計算して返します。
Quaternion QuaternionElasticSystem Quaternion 値を用いて設定されたばね定数に従って現在の値から次のQuaternion 値を計算して返します。

今回はこの中でオブジェクトの移動と表示にElastic Systemを導入して表現を変えてみたいと思います。

オブジェクトの移動にElastic Systemを導入する

基本オブジェクトの移動につ入れてはTransformに加算していくだけになります。今回はシンプルに左から右に移動する物体にElastic Systemを入れてみたいと思います。
1方向だけの移動であれば、LinearElasticSystem でもいいですし、VolumeElasticSystem でやってもいいと思います。今回はVolumeElasticSystem を使ってみましょう。

オブジェクトを用意する

Move.gif

まず、Elastic Systemを使う場合と使わない場合でどういう動きになるかを比較できる形にしたいと思います。まず任意の空オブジェクトを作成します。その子要素として、空間に任意のオブジェクト(cube等)を2つ配置します。座標は(-0.5,0,0.5), (-0.5,0.2,0.5)にしましょう。

image.png

次にProjectパネルで新しいスクリプト[ElasticTransformSample.cs]を作成します。作成したら、最初に作った空オブジェクト(上図のGameObject)にドラッグ&ドロップでコンポーネントとしてセットします。
次に[ElasticTransformSample.cs]の実装を行いたいと思います。今回は作成した2つのオブジェクトを5秒おきに左右に移動させ一方にElasticSystemを導入して動きの違いを視覚的に確認します。まずコンポーネントに必要なフィールドを作成します。

ElasticTransformSample.cs
    [SerializeField]
    private GameObject linearMoveObject;

    [SerializeField]
    private GameObject elasticMoveObject;

    // The elastic properties of our springs.
    private ElasticProperties elasticProperties = new ElasticProperties
    {
        Mass = 0.02f,
        HandK = 4.0f,
        EndK = 3.0f,
        SnapK = 1.0f,
        Drag = 0.2f
    };

    private VolumeElasticSystem elasticSystem;
    private Vector3 initialEPos;
    private Vector3 initialLPos;
    private Vector3 elasticGoal;
    private Vector3 linearGoal;

    private float timer;

コンポーネントのパラメータとしては移動対象となるGameObject2つをInspectorで設定できるようにします。
Elastic SystemのパラメータについてはObject Manipulatorと同様です。VolumeElasticExtentクラスでスナップポイント周りの設定を行い、ElasticPropertiesクラスでばね定数を定義します。
次に開始時の初期設定を行いましょう。初期設定では各オブジェクトの始点(初期位置)と終点と計算します。今回は1m間隔を取ります。
また、Elastic SystemはVolumeEralticsSystemを利用します。

ElasticTransformSample.cs
private void Start()
{
    linearGoal = linearMoveObject.transform.position + new Vector3(1f, 0f, 0f);
    initialLPos = linearMoveObject.transform.position;
    elasticGoal = elasticMoveObject.transform.position + new Vector3(1f, 0f, 0f);
    initialEPos = elasticMoveObject.transform.position;

    elasticSystem = new VolumeElasticSystem(elasticMoveObject.transform.position, Vector3.zero, volumeExtent,
        elasticProperties);
}

VloumeElasticSystemクラスはコンストラクタでいくつかの引数を持っています。
バネの基点はこのコンストラクタで設定された値になります。

elasticSystem = new VolumeElasticSystem([初期位置], [初期速度], [ElasticProperties設定したスナップポイント], [ElasticPropertiesで設定したばね定数]);

後はオブジェクトを移動する実装を行います。ElasticSystemでは最終状態と経過時間を与えることで次の状態を取得できます。これをUpdateメソッド内で実装します。

elasticMoveObject.transform.localScale = elasticSystem.ComputeIteration([終点], [経過時間(Time.deltaTime)]);

完全なコードは以下の通りです。

ElasticScaleSample.cs
// Copyright (c) 2021 Takahiro Miyaura
// Released under the MIT license
// http://opensource.org/licenses/mit-license.php

using Microsoft.MixedReality.Toolkit.Experimental.Physics;
using UnityEngine;

public class ElasticTransformSample : MonoBehaviour
{

    [SerializeField]
    private GameObject linearMoveObject;

    [SerializeField]
    private GameObject elasticMoveObject;

    // The elastic properties of our springs.
    private ElasticProperties elasticProperties = new ElasticProperties
    {
        Mass = 0.02f,
        HandK = 4.0f,
        EndK = 3.0f,
        SnapK = 1.0f,
        Drag = 0.2f
    };

    private VolumeElasticSystem elasticSystem;
    private Vector3 initialEPos;
    private Vector3 initialLPos;
    private Vector3 elasticGoal;
    private Vector3 linearGoal;

    private float timer;

    // The elastic extent for our backplate scaling.
    private VolumeElasticExtent volumeExtent = new VolumeElasticExtent
    {
        SnapPoints = new Vector3[] { },
        RepeatSnapPoints = false,
        SnapRadius = 0
    };


    // Start is called before the first frame update
    private void Start()
    {
        linearGoal = linearMoveObject.transform.position + new Vector3(1f, 0f, 0f);
        initialLPos = linearMoveObject.transform.position;
        elasticGoal = elasticMoveObject.transform.position + new Vector3(1f, 0f, 0f);
        initialEPos = elasticMoveObject.transform.position;

        elasticSystem = new VolumeElasticSystem(elasticMoveObject.transform.position, Vector3.zero, volumeExtent,
            elasticProperties);
    }

    // Update is called once per frame
    private void Update()
    {
        if (timer < 5)
        {
            elasticMoveObject.transform.position = elasticSystem.ComputeIteration(elasticGoal, Time.deltaTime);
            linearMoveObject.transform.position = linearGoal;
        }
        else if (timer < 10)
        {
            elasticMoveObject.transform.position = elasticSystem.ComputeIteration(initialEPos, Time.deltaTime);
            linearMoveObject.transform.position = initialLPos;
        }
        else
        {
            timer = 0;
        }

        timer += Time.deltaTime;
    }
}

最後にUnity Editorで[ElasticScaleSample]コンポーネントに、2つのオブジェクトを[linearMoveObject], [elasticMoveObject]に割り当てます。
後は再生ボタンを押すと、上記のようなオブジェクトが左右に動きます。

オブジェクトの表示にElastic Systemを導入する

enable_disable.gif

オブジェクトの表示に利用する際も基本的には移動の場合と変わりません。positionの値を変更していた代わりに、localScaleの値をVolumeElasticSystemを使って変更することになります。実装方法は上記と変わりません。空オブジェクトの作成、Scale変更を行う2つのオブジェクトを追加します。スクリプトとして[ElasticScaleSample.cs]を追加し、以下の通り実装します。

ElasticScaleSample.cs
// Copyright (c) 2021 Takahiro Miyaura
// Released under the MIT license
// http://opensource.org/licenses/mit-license.php

using Microsoft.MixedReality.Toolkit.Experimental.Physics;
using UnityEngine;

public class ElasticScaleSample : MonoBehaviour
{
    private Vector3 elasticGoal;

    // The elastic properties of our springs.
    private ElasticProperties elasticProperties = new ElasticProperties
    {
        Mass = 0.02f,
        HandK = 4.0f,
        EndK = 3.0f,
        SnapK = 1.0f,
        Drag = 0.2f
    };

    [SerializeField]
    private GameObject elasticScaleObject;

    private VolumeElasticSystem elasticSystem;

    private Vector3 linearGoal;

    [SerializeField]
    private GameObject LinearScaleObject;

    private float timer;

    // The elastic extent for our backplate scaling.
    private VolumeElasticExtent volumeExtent = new VolumeElasticExtent
    {
        SnapPoints = new Vector3[] { },
        RepeatSnapPoints = false,
        SnapRadius = 0
    };


    // Start is called before the first frame update
    private void Start()
    {
        linearGoal = LinearScaleObject.transform.localScale * 3;
        elasticGoal = elasticScaleObject.transform.localScale * 3;

        elasticSystem = new VolumeElasticSystem(elasticScaleObject.transform.position, Vector3.zero, volumeExtent,
            elasticProperties);
    }


    // Update is called once per frame
    private void Update()
    {
        if (timer < 5)
        {
            elasticScaleObject.transform.localScale = elasticSystem.ComputeIteration(elasticGoal, Time.deltaTime);
            LinearScaleObject.transform.localScale = linearGoal;
        }
        else if (timer < 10)
        {
            elasticScaleObject.transform.localScale = elasticSystem.ComputeIteration(Vector3.zero, Time.deltaTime);
            LinearScaleObject.transform.localScale = Vector3.zero;
        }
        else
        {
            timer = 0;
        }

        timer += Time.deltaTime;
    }
}   

最後にUnity Editorで[ElasticScaleSample]コンポーネントに、2つのオブジェクトを[linearScaleObject], [elasticScaleObject]に割り当てます。
後は再生ボタンを押すと、上記のようなオブジェクトが表示/非表示(サイズ変更)が交互に実行されます。その違いを確認してみてください。

まとめ

Elastic Systemどうですか?いくつかサンプルや動きを見せましたが、派手に使うのではなくさりげないところにこういうギミックを仕込むことでコンテンツの与える印象が大きく変わると思います。特に、オブジェクトの出現のさせ方など、徐々に出したい場合などの実装が格段に楽なるのがわかるかと思います。他にもいろいろな用途が考えられるので一度活用してみてください。

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

[Unity]NullReferenceException: Object reference not set to an instance of an object xxx. の解決例

エラーの様子

スクリーンショット 2021-01-09 11.00.36.png

原因

今回はC#のファイル内に間違いはなく、inspector内の変数の設定のし忘れがあるようです。
ちなみに今回の筆者の場合は下記の図のimageの部分の設定し忘れでした。

スクリーンショット 2021-01-09 11.02.08.png

解決策

inspector内の変数の設定のし忘れを見つけて、設定することで修正できます。

同じエラーの記事

C#ファイルの中でGetComponentしてなかったときの記事:
https://qiita.com/KONTA2019/items/c964af275419dc425d3e

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

スクリプト1つでシーンの暗転切り替え

概要

UniTaskがversion2からDOTweenのawaitにも対応したと聞いたので、作ってみました。
締め切りまで時間がない!そんな時にスクリプト1つ付けるだけでひとまず暗転遷移ができます。

必要なライブラリ

  • UniTask
  • DOTween

ただし、動作させるためには次の設定が必要だそうです。

OpenUPMからDOTweenを導入する、またはScripting Define Symbolsに「UNITASK_DOTWEEN_SUPPORT」を定義する
assembly definition「UniTask.DOTweeen」への参照を追加する

引用:https://qiita.com/toRisouP/items/8f66fd952eaffeaf3107

今回は以下の3つをasmdefに登録しました。
- UniTask
- UniTask.DOTween
- DOTween.Modules
image.png

実装

using Cysharp.Threading.Tasks;
using DG.Tweening;
using UnityEngine;
using UnityEngine.SceneManagement;
using UnityEngine.UI;

public class SimpleSceneTransition : MonoBehaviour
{
    [SerializeField] private float fadeTime = 1;
    [SerializeField] private Color transitionColor = Color.black;
    [SerializeField] private string nextScene;

    void Update()
    {
        if (Input.GetKeyDown(KeyCode.Space)) StartTransition();
    }

    async void StartTransition()
    {
        var g = new GameObject();
        DontDestroyOnLoad(g);
        var canvas = g.AddComponent<Canvas>();
        canvas.renderMode = RenderMode.ScreenSpaceOverlay;
        var image = g.AddComponent<Image>();
        transitionColor.a = 0;
        image.color = transitionColor;
        await image.DOFade(1, fadeTime);
        await SceneManager.LoadSceneAsync(nextScene);
        await image.DOFade(0, fadeTime);
        Destroy(g);
    }
}

UniTaskもDOTweenもわからんのじゃ!という人のための実装

ライブラリを使わずに一応書いてみましたがめちゃくちゃ長くなりました。
やはり定番どころのライブラリは勉強して使いこなせるようになったほうがよさそうですね。

using System.Collections;
using UnityEngine;
using UnityEngine.SceneManagement;
using UnityEngine.UI;

public class SimpleSceneTransition : MonoBehaviour
{
    [SerializeField] private float fadeTime = 1;
    [SerializeField] private Color transitionColor = Color.black;
    [SerializeField] private string nextScene;

    private Image image;

    public static SimpleSceneTransition Instance { get; private set; }

    private void Start()
    {
        if (Instance == null)
        {
            Instance = this;
            DontDestroyOnLoad(gameObject);
        }
        else
        {
            Destroy(gameObject);
        }

        var canvas = gameObject.AddComponent<Canvas>();
        canvas.renderMode = RenderMode.ScreenSpaceOverlay;
        image = gameObject.AddComponent<Image>();
        transitionColor.a = 0;
        image.color = transitionColor;
    }

    void Update()
    {
        if (Input.GetKeyDown(KeyCode.Space))
            StartCoroutine(StartTransition());
    }

    IEnumerator StartTransition()
    {
        float t = 0;
        while (true)
        {
            t = Mathf.Min(t + Time.deltaTime, fadeTime);
            transitionColor.a = t / fadeTime;
            image.color = transitionColor;
            if (t >= fadeTime) break;
            yield return null;
        }

        yield return SceneManager.LoadSceneAsync(nextScene);

        while (true)
        {
            t = Mathf.Max(t - Time.deltaTime, 0);
            transitionColor.a = t / fadeTime;
            image.color = transitionColor;
            if (t <= 0) break;
            yield return null;
        }
    }
}
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[Unity] UniRx の Object Pool を使わないでシンプルにしてみる

某所で ObjectPool の話題が出ていたが、大体GameObjectでのpoolになっているが、
クラス指定できないと毎度GetComponent する羽目になる。

イケてない

UniRx の Object Pool 使えばよくね

プール用クラス書くのめんどくさくね

という事で書いてみた。

要件

  • ObjectPool
  • 型指定できる
  • 一応、UniRx使わない方向で(UniRx.tools のプール使えばいいので)

本体

コピペでOK

SimpleObjectPool.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class SimpleObjectPool<T> where T : MonoBehaviour
{
    // Public Static
    public static SimpleObjectPool<T> Instance;

    // Private 
    T prefabBase;
    int ObjectCnt = 0;
    Transform poolBehaviour;
    Queue<T> pool = new Queue<T>();

    int createCount = 0;
    public SimpleObjectPool(T prefab, int objectCount = 100)
    {
        Instance = this;

        prefabBase = prefab;
        ObjectCnt = objectCount;

        var go = new GameObject("SimpleObjectPool");
        GameObject.DontDestroyOnLoad(go);
        poolBehaviour = go.transform;
    }

    public T Rent()
    {
        if (pool.Count == 0)
        {
            CreateNewObject();
        }
        var obj = pool.Dequeue();
        obj.gameObject.SetActive(true);
        return obj;
    }

    public void Return(T obj)
    {
        obj.gameObject.SetActive(false);
        pool.Enqueue(obj);
    }

    public void Update()
    {
        if (createCount < Instance.ObjectCnt)
        {
            CreateNewObject();
        }
    }

    void CreateNewObject()
    {
        var newObj = GameObject.Instantiate(Instance.prefabBase) as T;
        newObj.transform.SetParent(Instance.poolBehaviour);
        newObj.name = newObj.name + $"{createCount}";
        newObj.gameObject.SetActive(false);

        Instance.pool.Enqueue(newObj);
        createCount++;
    }
}

使い方

Inspector

シーンの最初からあるGameObjectとかに、
エディタ上からプールに指定したいプレハブなどを設定しておく

    public DummyEnemy enemyPrefab = default;

初期化

SimpleObjectPool クラスを型指定で新規作成。
以降は、シングルトンなので、SimpleObjectPool.Instance でどこからでもアクセスできる。

    void Start()
    {
        var pool = new SimpleObjectPool<DummyEnemy>(enemyPrefab);
    }

オブジェクト生成

一度にまとめて作成してもいいが、フレームごとに1つづつ作ったほうがお行儀が良いので。

    void Update()
    {
        SimpleObjectPool<DummyEnemy>.Instance.Update();
    }

借りて、返す

        // 借りる
        var obj = SimpleObjectPool<DummyEnemy>.Instance.Rent();
        // 返す
        SimpleObjectPool<DummyEnemy>.Instance.Return(obj);

実際どうなの?

車輪の再開発とはまさにこのこと。多分自分用。
Updateの部分を observable にすれば、初期化だけで綺麗にUpdateできるね!

それだと最初からUniRxツール使えよという話。
ゴミですね。
供養(-人-)チーン

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

[Unity] UniRx の Object Pool を使わないで Object Pool してみる

某所で ObjectPool の話題が出ていたが、大体GameObjectでのpoolになっているが、
クラス指定できないと毎度GetComponent する羽目になる。

イケてない

UniRx の Object Pool 使えばよくね

プール用クラス書くのめんどくさくね

という事で書いてみた。

要件

  • ObjectPool
  • 型指定できる
  • 一応、UniRx使わない方向で(UniRx.tools のプール使えばいいので)

本体

コピペでOK

SimpleObjectPool.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class SimpleObjectPool<T> where T : MonoBehaviour
{
    // Public Static
    public static SimpleObjectPool<T> Instance;

    // Private 
    T prefabBase;
    int ObjectCnt = 0;
    Transform poolBehaviour;
    Queue<T> pool = new Queue<T>();

    int createCount = 0;
    public SimpleObjectPool(T prefab, int objectCount = 100)
    {
        Instance = this;

        prefabBase = prefab;
        ObjectCnt = objectCount;

        var go = new GameObject("SimpleObjectPool");
        GameObject.DontDestroyOnLoad(go);
        poolBehaviour = go.transform;
    }

    public T Rent()
    {
        if (pool.Count == 0)
        {
            CreateNewObject();
        }
        var obj = pool.Dequeue();
        obj.gameObject.SetActive(true);
        return obj;
    }

    public void Return(T obj)
    {
        obj.gameObject.SetActive(false);
        pool.Enqueue(obj);
    }

    public void Update()
    {
        if (createCount < Instance.ObjectCnt)
        {
            CreateNewObject();
        }
    }

    void CreateNewObject()
    {
        var newObj = GameObject.Instantiate(Instance.prefabBase) as T;
        newObj.transform.SetParent(Instance.poolBehaviour);
        newObj.name = newObj.name + $"{createCount}";
        newObj.gameObject.SetActive(false);

        Instance.pool.Enqueue(newObj);
        createCount++;
    }
}

使い方

Inspector

シーンの最初からあるGameObjectとかに、
エディタ上からプールに指定したいプレハブなどを設定しておく

    public DummyEnemy enemyPrefab = default;

初期化

SimpleObjectPool クラスを型指定で新規作成。
以降は、シングルトンなので、SimpleObjectPool.Instance でどこからでもアクセスできる。

    void Start()
    {
        var pool = new SimpleObjectPool<DummyEnemy>(enemyPrefab);
    }

オブジェクト生成

一度にまとめて作成してもいいが、フレームごとに1つづつ作ったほうがお行儀が良いので。

    void Update()
    {
        SimpleObjectPool<DummyEnemy>.Instance.Update();
    }

借りて、返す

        // 借りる
        var obj = SimpleObjectPool<DummyEnemy>.Instance.Rent();
        // 返す
        SimpleObjectPool<DummyEnemy>.Instance.Return(obj);

実際どうなの?

車輪の再開発とはまさにこのこと。多分自分用。
Updateの部分を observable にすれば、初期化だけで綺麗にUpdateできるね!

それだと最初からUniRxツール使えよという話。
ゴミですね。
供養(-人-)チーン

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