20201218のUnityに関する記事は16件です。

HoloLens2はじめの一歩~buttonを出そう~

キャプチャ.PNG
こんにちは。
こちらはHoloLens Advent Calender 2020の18日目の記事です。

12月11日我が家にピカピカのHoloLens2✨がやって来ました。

ここ1週間くらいはチュートリアルをしたりして、遊んでいましたが
開発にもチャレンジしたいと思います(^^)

基本的なことになりますが、
prefabのpressable buttonをbuildするところまでを記事にします。

HoloLens2でbuttonをbuildしよう

◇環境
Unity 2019.4.0f

Unityの3Dprojectを作成します。
作成したら、File>Build SettingsからUniversal Windows Platformにswitch platformします。
次に
GitHubからMRTKをダウンロードします。
Microsoft.MixedReality.Toolkit.Unity.Foundation.〇.unitypackageをダウンロードしてください。
他のMRTKもダウンロードする場合には、同じバージョンのものにしないとエラーになります。
設定を続けます。

次に
画面上部のUnityメニューでMixedRealityToolKit>Add to Scene and Configure..の順に選択して
MRTKを現在のシーンの追加します。
HierarcheでMixedRealityToolkitをタッチして、InspectorのMicedRealityToolkit構成プロファイルを
DefaltHoloLens2ConfigurationProfileに変更します。
hololens2.PNG
ProjectSettingsで、Player>XR Settingsの順に選択し、+をクリックします。
Windows Mixed realityを選択します。これを忘れると、
buildしたときにHoloLensにprojectがブラウザの形式で
表示されるようになってしまいます。(私これやった)

Assetから PressableButtonHoloLens2を選択して、Sceneにドラッグ&ドロップします。
「Pressable」で検索するとみつけやすいです。
再生するといらっしゃいます。
キャプチャ.PNG
見つからないときは、SキーやWキーで動いて、探してみてください。

build作業に移ります。
buildは公式のこちらの説明を参考にしてみてください。

buttonを自分の目の前に出現させることができました。
buttonを押したら、紙吹雪がSetActiveTrueになって自分をお祝いしたかったのですが
どこで発動しているのかわからず!
玄関でbuildしたときは、zを2にしてみたのですが、壁の奥にいました。
位置設定の感覚をこれからつかんでいきたいです。

<追伸>
Holo2で録画した動画、画質が良すぎてそのままTwitterにあがりませんでした、、!
動画アップに30分もかかってしまった。

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

NeosVRに見出した可能性と未来について:「4つの世界」は「7つの世界」に

この記事は「NeosVR reso Advent Calendar 2020」、および「NeosVR Advent Calendar 2020」の18日目です

昨日は、@garyumaru さんの「はじめてのLogiX」でした


NeosVRから現実世界に干渉し、生活や仕事をより良いものへと改変していくコミュニティ「NeosVR reso」のオーガナイザー、piacereです
ご覧いただいて、ありがとうございます :bow:

約7ヶ月前、「NeosVR」という、VR内でプログラミング可能なVRプラットフォームに出会って以来、私の生活やビジネス、そして世界認識は、完全に書き換わり、人生観や夢/野望にも、大きな軌道修正が訪れました

このコラムでは、そんなNeosVRに見出した可能性や未来について、まとめてみたいと思います

はじめに:NeosVRとの出会い

NeosVRとはじめて出会ったのは、NeosVRのスター的存在、Orangeさん(@mikan3134)の下記「投げ銭できるVR」というNeosVR紹介ツイートでした(Twitterを始めて以来、VR関連のネタは、それとなく位しか拾って無かったので、ホントたまたま誰かがRTしてただけだと思います)


VR内で仮想通貨が使えることも凄いと思いましたが、ソレ以上に、NeosVRのウインドウや背景の未来感に、一瞬で魅了されました

見た直後、SteamとNeosVRをインストールしましたが、その体験は、VRヘッドセット(HMD)が無くても、充分に現実世界とは異なる、新たな別の世界でした


そして翌日、フリックさん(@FRICK)にNeosVRの「初心者案内デー」へと連れ出してもらい、私のNeosVRの冒険が始まったのです


「初心者案内デー」から戻った後、「あれは夢じゃ無かったんだ」と、ちょっとした異世界体験をした訳です


ちなみにVR自体は、23年前の「VRML」という、テキストファイルだけで、3Dポリゴンの頂点やテクスチャ、光沢などを定義でき、下図のようにWebブラウザで動かせるマークアップを作ったり、ソレ以降のVRムーブメント(セカンドライフ、Cluster、VRChatなど)も何となく触れてはいましたが、HMDを2台も買ってしまうほど、本気でがっつりVRにハマったのはNeosVRがはじめてでした
image

NeosVRで鏡の中の世界へ…

大ファンである映画「マトリックス」で、モーフィアスがネオに

「赤い薬を飲めば、君は不思議の国にとどまり、私がウサギの穴の奥底を見せてあげよう」
image

と言ったように、この現実とは全く別な「鏡の中の世界」へと誘われて以来、私が触れたり、いじる世界は、

- 現実
- プログラミングとデジタル
- オンライン
- 宇宙/ブラックホール/重力制御物理/数学

の「4つの世界」から、

- 現実
- プログラミングとデジタル
- オンライン
- 宇宙/ブラックホール/重力制御物理/数学
- NeosVR

の「5つの世界」へと一変しました

①NeosVRに見た「未来のシステム開発」

最初に、私がNeosVRの中に見た未来は、「システム開発のあるべき姿」でした

18年前と、国内で注目されるより10年は早い段階から手掛けているアジャイル開発(eXtreme Programming)でよく行う「ホワイトボードの前でのモブ設計/実装」の理想形が、NeosVRには既にありました


さらにNeosVR自体が、「VRの中にいたまま、VRプログラミングできる」という衝撃の事実を知り、これはまさに「ブートストラップ」もしくは「自己書き換えコード(自己改変)」という、厨二病には堪らない世界観でした


ちなみに、ここまで、NeosVRをインストールした初日の出来事です

その後、いてもたってもいられなくなり、HMD無しでもNeosVRのプログラミング(「LogiX」と言います)を始めてみたのですが、HMD無しの「デスクトップモード」に関するドキュメントは、ほぼゼロだったので、四苦八苦しながら、なんとかプログラミングできるところまで、ほぼ情熱だけで突っ走りました




この頃の苦労があったので、他の方が、もっと気軽にHMD無しでもNeosVRプログラミングに触れられるよう、下記のようなイベントも開催するようになりました:information_desk_person_tone1:

VRヘッドセットが無い方/使わない方限定のNeosVRプログラミング入門
https://neosvr.connpass.com/event/196608/
image
image

このイベントでレクチャーする内容をコラム化したものも書きました

VRヘッドセット(HMD)持っていなくてもNeosVRプログラミング:3D豆腐をブン回す
https://qiita.com/piacerex/items/ec6db710a2bc42f764f1
image

このコラムは、NeosVR内の「skillshareworld」にも採用いただきました


そして、NeosVR内に自分だけの「開発工房」も作りました


このように、NeosVRには「未来のシステム開発」の姿が見えました

これは、システム開発だけに限らず、全ての複数人数のコラボレーションの未来でもあると思っていて、アフターコロナ/withコロナにおける1つの希望でもあると感じました

②NeosVRから現実に干渉する

NeosVRをはじめて1ヶ月くらい経った頃、NeosVRがHTTP GET/POSTをプログラミングできるということが分かり、もともとWebで社会インフラ構築や、スマホ標準の認証・課金基盤を仕事として手掛けてきた私は、

「自分が38年間、現実世界で実現してきた全てのシステム開発領域が、NeosVRに接続できる…」

という、プログラミングを始めた8歳の頃から脳内妄想してた着想に、ワクワクの歯止めが効かなくなりました


早速、Elixirでもよく例題に出す「Qiita API」をNeosVRから叩いてみたら、ちゃんと返ってきた … 感動:sob:


これに味をしめて、Webだけじゃ飽き足らず、遠隔地にあるIoTデバイスをぶっ叩く実験を @kikuyuta さんの所属する高知Elixirコミュニティ「kochi.ex」の方々とコラボして試したところ、これまた成功しました


NeosVRからWebやIoTを通じて、現実へと干渉する第一歩となりました

スマホやIoT、各種センサー等の現実世界に置かれるエッジデバイスが、社会に浸透/蔓延すればするほど、NeosVRが現実世界に干渉/侵食する割合は増え、現実改変の機会は増えていきます

そして、これまでオンライン上でサービスやアーキテクチャを作ってきたノウハウの延長上で、現実干渉/侵食/改変を通じて、「社会そのものを変え得るチカラ」を手に入れていると言っても過言では無いと私は思っています

③NeosVRの世界をARで現実世界に投影する

OrangeさんがNeosVRで作った、この「手からメニューを出現させる」という機能が、3つ目の大きなターニングポイントになりました


このツイートを、XR友達のsgidonさんに紹介したら、こんなAR実装をしてくれました


これを見た瞬間、私の脳内回路が、パパパーっと繋がり、

「この素晴らしいNeosVRの世界を、現実世界へと現界させたい」

という着想(厨二病?)に駆られ、1ヶ月後くらいには、「VR |> AR投影」の原型のようなアイデアが生まれました

その後、ComputeShader以外、ほぼ触ったこと無かったUnityのARプログラミングを1~2日くらいでキャッチアップし、Unity 3Dプログラミングも続く3日後には動くものを作り始め、NeosVRのHTTP POSTで連携されたVRオブジェクトをスマホカメラに映る現実とミックスする「VR |> AR投影アプリ」を作っていました

ちなみに、NeosVRとVR |> AR投影アプリの中継には、Elixirが使われています

この「NeosVR |> AR投影」アプリの制作を通じて、任意の緯度・経度に、VRオブジェクトを現界させることが可能になったので、

「NeosVRの世界を、いつでも、どこにでも、テレポーテーションさせられる」

という現実には無い概念が、私の中に生まれました

そして、「NeosVRから現実世界を改変する」ことが、私の新たな夢となりました(そして今は、それが仕事にもなり始めています)


④NeosVRの世界で現実を塗り潰す:「固有結界」の実現

通常であれば、3Dオブジェクトの内側にテクスチャは貼らない(計算を簡素化するため)のですが、Unityを調べていくと、3Dオブジェクトの内側にもテクスチャが貼れることが分かり、現実空間をまるっと覆う「固有結界」を作れることに気付きました


そして実際に作ってみて、近所の公園で、AR投影されたNeosVRのビルへと入っていったところ、私の中の「現実」という概念が、まぁまぁ崩れました … スマホの中と言う小さな画面にも関わらず、そこには、目に映る現実とは、明らかに異なるリアルが現界していたのです


その後、「宇宙球」を、近所の公園に浮かべ、徒歩で踏み込んでいったところ、それは地上での体験だったハズが、全く地上にいる気がしませんでした … パラレルワールドというか、異世界というか、そういう領域に踏み込んでしまった感覚でした


スマホですら、こんな感覚なのだから、もしこれが、ARグラスやARコンタクトだったら、一体、どんな現実改変体験になるのだろう … と妄想したら、これまた、いてもたってもいられなくなり、海外のARコンタクトベンダーとの事業提携を結ぶ活動を、翌月には開始していました


これらの活動を、取引しているお客様の数社に共有したら、宇宙に関係するVR構築と、街1つをまるまる使ったVR |> AR投影のビジネスの話へと繋がってしまい、私の仕事も、じょじょに現実改変されていったのです

これにより、「5つの世界」は、

- 現実
- プログラミングとデジタル
- オンライン
- 宇宙/ブラックホール/重力制御物理/数学
- NeosVR
- NeosVRがAR投影された複合現実

の「6つの世界」へと大変革を迎えてしまいました(その間、約3ヶ月間です):laughing:

⑤宇宙に行かなくてもNeosVRで「人類の次のセンス」を入手

ここまでは、テクノロジーによって現実を書き換えるという厨二病感満載の内容でしたが、最後は、テクノロジーからやや離れた、厨二病ポエムで締め括ります

HMDを入手した後、フリックさんにHMD利用のガイドをしてもらったり、アバター調整をしてもらってる最中、フッと「子供の頃、裏の川で友達と遊んでた」のと似たような感覚が去来し、なぜだか良く分からないけど、とてつもなく幸せな気持ちになりました


この平和・平穏をもたらす感覚こそが、7年くらい前から自らのテクノロジー課題として置いてきた、下記ツイートのようなことへの回答だと直観しました



これらアイデアの元は、「2100年の科学ライフ」という、私が7年前から大好きで、ビジネスやシステム開発における未来予測にも役立てている本からです

2100年の科学ライフ
image

地球資源に依存する惑星文明タイプ0から、労働や食料問題、エネルギー問題から真に開放される、核融合やグラフェンの量子揺らぎ電力のような無限エネルギーの開発をしても、それを奪い合う人類の野蛮さがある限り、人類同士は争い、その結果、地球資源に依存しない「真の自由」が実現される惑星文明タイプ1に到達すること無く、滅亡する … というシナリオが、この本で紹介されています(ご興味ある方は「8章:人類の未来 - 惑星文明」をご覧ください)

この解決には、人類は、宇宙に進出し、地球という「ゆりかご」を巣立ち、スペースコロニーに住むことで得られるセンスを獲得する必要があるだろう … ということを、コンスタンチン・ツィオルコフスキー、もしくはガンダム(笑)で学んだ訳ですが、一方で、宇宙進出やスペースコロニー建造には、莫大なエネルギーが必要で、地球に埋蔵するエネルギーで、それが賄えるかには、疑問がある訳です

しかし、NeosVRで感じたこの感覚があれば、宇宙に出なくても、アクシズを落として粛清しなくても(笑)、人類は、野蛮さを捨て、平和の実現ができる(そして結果的に宇宙や銀河へと旅立てる) … そう直感しました

このアイデアを、フリックさんにNeosVR内のお絵描きペンで共有したときから、私は「人類の次のセンス」が得られるNeosVRがもたらす未来に賭けようと決めたのです(下記ツイートは、そのネタを書いて無いですが、お話したときの会のキャプチャです)


これにより、「6つの世界」は、

- 現実
- プログラミングとデジタル
- オンライン
- 宇宙/ブラックホール/重力制御物理/数学
- NeosVR
- NeosVRがAR投影された複合現実
- 宇宙へ行かなくても人類がNeosVRで次のセンスを見つける世界線

の「7つの世界」へと変わり、私の未来も、NeosVRによって改変されたのです:tada:

余談:NeosVRと現実世界を繋ぐにあたっての課題

  • エンジニアの多くは、「メシが食えるWeb開発」に引き篭もっている
  • XRプレイヤーの多くは、SIerがやる基幹システム繋ぎ込みを経験しない

これらの詳細については、今後のコラムで触れていく予定ですが、どちらの領域も経験する私が、その実現をミッションと感じ、どちらの領域に対しても、NeosVRの新しい世界とプログラミングパラダイムに触れる機会を増やすこともやはり私のミッションです

最後に

このコラムを読んで、NeosVRに触れてみたいという方が1人でも増えたら幸いです:laughing:

私と、下記マトリックスそっくりのNeosVRワールドや、その他ステキなNeosVRの世界をご一緒してくれる方を、いつでも歓迎しています


どうぞ下記の私のTwitter DMまで、お気軽にご連絡ください(NeosVRの導入も、ワールドご案内も、プログラミング入門も、なんだってウェルカムです)

piacere の Twitter DM(リンク先の赤枠部分をクリックしてください)
https://twitter.com/piacere_ex
image.png

また、先月発足したばかりのコミュニティ「NeosVR reso」では、上述したNeoVRプログラミングイベントやNeosVRワールド制作会、そして「NeosVRからの配信」などを通じて、NeosVRからNeosVRの外の「現実」に干渉するべく、NeosVRとAI・ML/IoTを連動させたり、NeosVRオブジェクトをAR化することで現実改変することを通して、「VRだけでも、現実だけでも、実現しない共振」を起こすような活動を行っていきます

現在、NeosVR resoでは、下記Advent CalendarでNeosVRコラムを毎日アップしているので、ぜひご覧ください(応援もいただけたら嬉しい)
https://qiita.com/advent-calendar/2020/neosvr_reso
image.png

p.s.このコラムが、面白かったり、役に立ったら…

image.pngimage.png にて、どうぞ応援よろしくお願いします:bow:


明日の記事は、@sirojake さんの「FizzBuzzを作った話」です

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

真っ新なMacBookAirにUnityをインストールしてスマホ向けアプリの動作確認をするまで

自分の好みの快適な開発環境にする方法と動作確認までメモ

環境

構築する開発環境

  • MacBook Air 2019 (Intelチップ)
    • Mac OS Catalina 10.15.7
  • Unity 2019.4.16f1 (LTS)
    • Android Build Support
    • iOS Build Support
  • Visual Studio Code version 1.5.2

作ったアプリの動作確認環境

  • Mac : MacBook Air 2019 (Intelチップ)
    • Mac OS Catalina 10.15.7
  • Android : Pixel 3a
    • Android OS Version 11
  • iPhone : iPhone 8
    • iPhone OS xxx

ステップ

  1. Unityのインストール
  2. Visual Studio Codeのインストール
  3. Unityの起動と設定
  4. Visual Studio Codeの起動と設定
  5. MacBookAir向けにビルドして動作確認
  6. Android向けにビルドして動作確認
  7. iPhone向けにビルドして動作確認

1.Unityのダウンロードとインストール

  1. 以下からUnity Hubをダウンロードし、UnityHubSetup.dmgを実行してインストール
  2. UnityHubを起動してアカウントの設定(Unityアカウントを持っていなければ作成)
  3. UnityHubからUnity2019.4.16f1をインストール
    1. Visual Studio for Mac をアンチェック
    2. Android Build Supportにチェック
    3. iOS Build Supportにチェック
    4. ドキュメンテーションをアンチェック
    5. 日本語をチェック

2.Visual Studio Codeのインストール

  • 以下からMac向けのVisual Studio Codeをダウンロードし、VSCode-darwin.dmjを実行してインストール
    https://code.visualstudio.com/download

  • 以下の拡張機能を入れる(その他の自分好みの設定や拡張については別途)

    • C#
    • Debugger For Unity
    • Mono Debug

3.Unityの起動と設定

Visual Studio Codeをエディタに設定する

  1. 適当な名前でプロジェクトを作成
  2. Unity > Preferences… > External Tools から External Script EditorにVisual Studio Codeを選択
  3. Genetate .csproj files forの下にあるチェックボックスをすべてチェック

Android NDKエラーが出ている場合

Unity > Preferences… > External Tools から External ToolsからAndroid NDK installed with Unityで警告が出ていたので確認
表示されているパスを確認するとなぜかNDKが空っぽなのでUnity Hubから一度Android Build Supportを削除して、もう一度インストールするとちゃんと入っていた
理由は不明(UnityHubにフォルダアクセス権限がなかった?でも他のものは生成できているので謎)

4. Visual Studio Codeの起動と設定

  • 適当にC#スクリプトを作成
  • Unityの上のメニューバーからAssets > Open C# Projectから起動 ←重要

インテリセンス(補完機能)が効かない場合

  • Home Brewが入っていない場合はターミナルから下のコマンドでインストール
/bin/bash -c "$(curl -fsSL 
https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
  • monoをターミナルからHome brewコマンドでインストール
brew install mono
  • Visual Studio CodeのSetting.jsonに以下を追記(コマンドパレットから検索で開ける
"omnisharp.useGlobalMono": "always",
"omnisharp.monoPath": "/usr/local/Cellar/mono/6.8.0.105"

これでインテリセンスが聞くようになった

5. MacBookAir向けにビルドして動作確認

  • UnityでFile > Build Setting
    • PlatformをPC, Mac & Linux Standaloneが選択された状態にする
    • Build and Run

6. Android向けにビルドして動作確認

  • UnityでFile > Build Setting
    • PlatformをAndroidが選択された状態にする
    • まだしていなければAndroid端末を開発者モードにする(ビルド番号を7回タップ)
    • USBケーブルでMacとAndroid端末をつなぐ
    • Build And Run

7. iPhone向けにビルドして動作確認

まだ
iPhone持っていないので、家族に借りてこんどやってみる

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

UnityのDebug機能を便利にしたい!紹介編

現状のUnityEngine.Debugの問題

皆さんUnityのDebugクラスよく使いますよね? Debug.Log()とか。
でもたまに不便だな~って思う時ありません?

配列を表示したい時とか

        Debug.Log(new string[] {"a", "b", "c"});

image.png
↑配列なことぐらいわかってるわ!要素が欲しいんじゃ!
こういう場合はforeachなどで回すか、LINQでゴニョゴニョするとかしか無いんですよね、、、

非常にめんどくさい!!

というわけで作りました。その名もDebugExtentions
まだ制作途中で、機能が豊富とは言えませんが、一部をgithubに公開いたしましたので今回はそれをご紹介したいと思います。

DebugExtensions機能一覧
・普通のDebugクラスの機能全部(もしかしたら実装漏れがあるかも)
・命名の関係上ClearDeveloperConsole()ClearConsoleError()になっています。
・多少制限はあるけど、structも表示できます。
・Colorクラスの表示が見やすくなった
Array, List, HashSet, Dictionaryの要素表示(Format系は対応してないですごめんなさい、、)
・ログのテキスト保存
・コンソール全クリア
・リッチテキストを簡単に使えるぞ

1. Array, List, HashSet, Dictionaryの要素表示

個人的に一番最初に実装したかった機能。
こんな感じで表示されます。

Array・List・HashSet

    void Start() {
        string[] wordArray = new[] {
            "apple",
            "blue",
            "cactus",
            "default",
            "emission"
        };

        DebugEx.Log(wordArray);
    }

image.png

Dictionary

    void Start() {
        Dictionary<string, int> dict1 = new Dictionary<string, int>() {
            {"normalSword", 5},
            {"superSword", 2},
            {"apple", 100},
            {"water", 1}
        };

        DebugEx.Log(dict1);
    }

image.png

Dictionaryの冒頭2行は、KeyとValueの型情報を表示してくれています。
突っ込むだけで、簡単に要素を表示してくれるのでかなり便利になりました。

しかし、筆者の表示形式のセンスが無いので、あまり統一感がありません。
この表示形式は、これからのアップデートで改善していく予定です。

2. ログのテキスト保存

DebugEx.RecordStart();
処理
DebugEx.RecordStop();

image.png

RecordStart()RecordStop()の間で、ロギングが行われた場合、情報が保存されます。
RecordStop()が実行されたときに保存されるので注意。
デフォルトではDebugExtensions/Logsに保存されますが、設定で変えることができます。

3. コンソール全クリア

DebugEx.ClearConsoleAll();

その名の通り、まとめてログを全削除します。

4. リッチテキストを簡単に使えるぞ

"Alpha".Color(Color.red) //色の変更
"Bravo".Bold() //太字にする
"Charlie".Italic() //イタリック体にする
"Delta".Size(12) //文字のサイズを変更する
"Echo".RemoveRichText(); //リッチテキストを取り除く

Consoleではリッチテキストを使うことができるので、string型の変数に上記のようなメソッドを連結することで、リッチテキストに変換することができます。
コンソールの文字をちょっといじりたいときなどに便利です。

5. その他設定

image.png
LogSavePath: ログファイルを保存するパス
SaveStackTrace: ログの下の部分にくっついている、呼び出し元のトレース情報をログファイルに書き込むか
Dictionary Key,Value: Dictionaryをコンソールに表示するときの色

最後に

結構頑張ればもっと使いやすくなると思うので、これからブラッシュアップしていけたらなと思います。
リリースできるようになれば、アセットストアなどに出す予定です。

!!告知!!
現在、チームでShoutousというFPSゲームを開発しています!

~Soutousとは?~
◉大規模な現代戦を題材にした
◉どんな遊び方でも皆活躍できる
◉今のFPSの良いとこ取りをした
◉e-Sports味の全くない、貴方の為の
◉意識低い系高画質お祭りFPSゲーム!

公式ツイッターのフォローよろしくお願いいたします!
Twitter: https://twitter.com/ShoutousJP

3Dデザイナーや、Unityエンジニアなども募集しているので、興味があればぜひTwitterID:@harumar0nにDMいただけるとありがたいです!

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

なんでボールは勝手に転がるの?

はじめに

こんにちは たこといいます。
LifeisTech!TokaiAdventCalenderの12/18用に書きます。

今回はUnityをやっていて、疑問に思っていたけど理由までははっきり分からなかったことを、こういう機会がないとやらないので、自分で調べてみようと思いました。

そのテーマが、RigidbodyのConstraintsとtransform.position、AddForceについてです。

きっかけは下のGIFのように、ボールを矢印キーで動かし、落ちたらスタート地点に戻るという際に、矢印キーを離しているのに勝手に転がってしまうのはなんでなんだろうと思ったからです。
example.gif

これの解決策自体はRigidbodyのConstraintsのFreeze Rotation xとFreeze Rotation zにチェックを入れることで解決するのですが、理由はいまいち分からりませんでした。

そこで今回は、Sphereを以下のような条件(A~F)で場合分けをして調べました。

実験

・床とSphereを用意
・Sphereを矢印キーで前後左右に動かすスクリプトを用意
・Sphereに、Rigidbodyをつける
・Sphereは最初(0,3,0)の位置にあり再生すると床まで落下し静止

Sphereの動かし方 Physic Material
A transform.position なし
B transform.position あり  動摩擦力・静止摩擦力1
C transform.position あり  動摩擦力・静止摩擦力0
D AddForce   なし
E AddForce   あり  動摩擦力・静止摩擦力1
F AddForce   あり  動摩擦力・静止摩擦力0

そして、各A~Fの条件の下で、RigidbodyのConstraintsの選択を下の表の32パターンにし、Sphereの動きを調べました。(Position yにチェックを入れると始めに落下しないため除外)

Constraints チェック 入れた 項目
何もなし Pos x Pos z Pos x,z Rot x Rot y Rot z
Rot x,y Rot x,z Rot y,z Rot x,y,z Pos x Rot x Pos x Rot y Pos x Rot z
Pos x Rot x,y Pos x Rot x,z Pos x Rot y,z Pos x Rot x,y,z Pos z Rot x Pos z Rot y Pos z Rot z
Pos z Rot x,y Pos z Rot x,z Pos z Rot y,z Pos z Rot x,y,z Pos x,z Rot x Pos x,z Rot y Pos x,z Rot z
Pos x,z Rot x,y Pos x,z Rot x,z Pos x,z Rot y,z Pos x,z Rot x,y,z

結果

ボールを矢印キーでしっかりコントロールでき、落下後もスタート地点で静止させるのに必要な条件はこのようになりました。

・条件A~C(Sphereをtransform.positionで動かす場合)では、少なくともFreeze Rotation x とFreeze Rotation zの二つのチェックを入れる

・条件D,E(SphereをAddForceで動かす場合)の下でうまくいく条件は存在しない

・条件F(SphereをAddForceで動かす場合)の下で少なくともFreeze Rotation x と Freeze Rotaion zの二つにチェックを入れる必要

他に分かったこと

・条件D~Fで、Freeze Positionにチェックを入れると矢印キーを押しても動かないが、条件A~Cで、Freeze Position にチェックを入れると矢印キーを押したら動く

・条件D,EでFreeze Rotation x と Freeze Rotation zの二つにチェックを入れて再生すると、すごくゆっくり動く

・条件A~Cでは、Physic Materialの有無で結果は変わらなかった

・条件A~Cでは、1のようにぬるっと落ちない場合と、始めのうちはスタート地点に戻った後も原点で停止する
 しかし、2のように落ちるとスタート地点に戻った後も勝手に転がる

1 ぬるっと落ちない

notnuru2.gif

2 ぬるっと落ちる

nuru2.gif

考えたこと

Sphereでは、軸が回転することによる力がボールにかかっているため、その力により勝手に動き出すのではないかということを考えました。

・条件A~Cでは、positionを直接動かすので、落ち方によってはあまりSphereが回転せず落下するのでスタート地点に戻った後も静止する 

・条件D~Fでは、力をSphereに加えることで動かすため、左右キーを押すとPositionが左右に動くだけでなく、z軸を回転軸とし回転する力も加わるため左右キーを離しても回転し、力はかかり続ける

そのため、RotationxとRotationzにチェックを入れることで、Sphereは回転せず動きます。つまり、回転による力がかからなくなり、矢印キーを離しても動くことはなくなります。

また今回の実験では、SphereをCubeに変えて同じことをしました。
CubeはSphereほど、勝手に転がる現象は見られませんでしたが、条件D~Fの際に、すごくゆっくり動くかまったく動かない状況になり、なぜすべての項目でスムーズに動かなかったのかは新しく疑問に思いました。これはまた考えてみようと思います。

感想

調べる項目が多くて大変だったけど、少しもやもやしていた所がすっきりしたのでやって良かったと思いました。

ゲームのことを考えると、ボールが矢印キーを離した後も勝手に転がってしまうから落ちないようにコントロールすること自体は面白いと思います。でも、落下してスタート地点に戻ってきてもう一度やろうとしたら、すごいスピードで転がってしまっていては自分でボールを操作できないので、そうしたい場合はリロードしたりすればいいのかなと思いました。

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

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

Unityアセット管理に関して、あう可能性のある問題(その壱)

キーワード

AssetBundle
アセット制作 texture \ mesh \ material \ Shader \ audio \ animation
Lightmap


一、AssetBundle相関

Q1: UnityのSerializedFileはどのように生成しましたか? Unload(false)でクリアできますか?Bundleにある内容は読み込んだ後、他のオブジェクトに付値されました。そして、全ての画像をBundleにパッケージして読み込んだら、画像のサイズはこのSerializedFileのサイズを超えるはずでしょう?
1.jpg

SerializedFileは、AssetBundleがロード時に生成したシリアル化された情報です。通常、LoadFromCacheOrDownload、LoadFromFile、およびNewWWWがローカルAssetBundleファイルをロードすることによって発生します。AssetBundle内のアセットがすでにロードされていて、AssetBundleに依存するアセットがその後ロードされない場合は、Unload(false)でそれらを削除できます。SerializedFileに記録されているのは、含まれたアセットの内容ではなく、AssetBundleのシリアル化情報でありため、そのサイズは、リソースの実際のメモリよりも小さいか、はるかに小さくなります。


Q2:今、2つのアセット管理方法を採用していますが、メモリ占用量の問題を理解したいです。
方式A:AssetBundleがロードされた後、メモリに保持されます。オブジェクトを作成する必要がある場合は、Instantiateによって作成します。
方法B:AssetBundleがロードされた後、すぐに一つのオブジェクトをインスタンスかして、このAssetBundleをUnload(false)します。必要な場合、cloneを介してオブジェクトを作成します。
方法Aの場合、関連するAssetBundleファイルがProfilerのWebStreamに存在し、このメモリは必要のないものと認めますから、方式Bを採用しましたが、方式Bはアセットのアンロードはクリーンではない状況に遭いました。一つ問題を確認したいのですが、方法Aを採用した場合:xxx.assetBundleの元のファイルサイズは1MBで、Webstreamメモリに解凍すると2MBになりますが、最終的に占用する実際のメモリは2MBまたは3MBですか?

方法Aを採用した場合:最終的な実際のメモリ使用量は3MBではなく2MBであり、WebStreamには元のAssetBundleのデータがすでに含まれています。依存関係のないAssetBundleファイルに対して、不要なメモリ占用を避けるために、方法BでAssetBundleをアンロードすることをお勧めします。この方法でロードされたアセットについては、Resources.UnloadAssetおよびResources.UnloadUnusedAssetsを使用してアンロードできます。アンロードできない場合は、このアセットは絶対キャッシュされています。開発チームが自分のバッファープール/Constainerをチェックして検出できます。同時に、Unity 5.3以降の場合は、MemoryProfilerでさらに確認することができます。


Q3:パッケージ化中にAssetBundleのmd5は常に変更します(パッケージ化されているものは変更されていません)、どうすれば解決できますか?ある噂はDeterministicAssetBundleを追加したら大丈夫ですが、試したけどmd5は依然として変更します。

この方法は確かに役に立ちません。Unity 4.xバージョンのAssetBundleファイルに対して、確かにこのmd5値はある場合に前後不一致になります(パッケージ化されている内容は全く同じである場合でも)。このシリーズバージョンに対しては、開発チームに配置ファイルを生成してAssetBundleを管理することのみ提案できます。
但し、Unity 5.xバージョンの場合、パッケージ化時にAppendHashToAssetBundleNameオプションをオンにすることをお勧めします。これで、Unityエンジンは各AssetBundleファイルの後に唯一のHashIDを生成し(ファイル名の後に表示)、開発チームはこのIDで対応するAssetBundleファイルが変更されたかどうかを判断できます。


Q4:NGUIなどの依存パッケージを使用する場合、アトラスAは依存されるパッケージとして使用され、インターフェイス1、2、3は独立パッケージとして別々に依存パッケージします。アトラスAを再パッケージ化する場合、インターフェイス1、2、3も再パッケージ化する必要がありますか?現在は、インターフェイスパッケージ化時にすべてのアトラス情報をクリアしてからパッケージ化し、クライアントにロードした後に動的に割り当て直します。これにより、アトラスを独立に更新できますが、ローディングパフォーマンスが犠牲になります。 より良い解決策はありますか?

「アトラスAを再パッケージ化する場合、インターフェイス1、2、3も再パッケージ化する必要がありますか?」これは必要ありません。Unity 4.xの依存関係パッケージの制限は、再パッケージ化するときに、依存する全てのパッケージを再パッケージ化する必要があることですが、それに依存せれるパッケージを再パッケージ化する必要はありません。


Q5:内蔵Shaderのパッケージ化する方法は?内蔵シェーダーを使用しましたが、Shaderだけでなく、この時、下図のように、Profilerに見られるローディング結果は複数あります。
2.png

一般的に、二つ方法で内蔵Shaderをパッケージ化できます。
1、Graphics SettingsのAlways Included Shadersに添付します。この時、添付された内蔵ShaderはAssetBundleにバンドルさせません。
2、http://unity3d.com/cn/get-unity/download/archive で内蔵Shaderをダウンロードし、プロジェクトにインポートして、内蔵ではないシェーダーに置き換えます。そして、スクリプトからパッケージ化の方法を直接にコントロールできます。


Q6:パーティクルエフェクトのシェーダーは依存関係パッケージを使用できませんか?シェーダーモデルと特殊効果に依存関係パッケージ化をしており、実行するとモデルの表示は正常ですが、パーティクル特殊効果が使用するシェーダーは正常に動作せず、特殊効果が異常に表示されます。エディターでは、Material中のShaderが存在していると確認できます。このとき、同じシェーダーを再度手動でマテリアルに割り当てると、パーティクルエフェクトが正常に表示されますが、原因は何ですか?

一部のShaderがAndroidバージョンのAssetbundleにパッケージされた後に、プラットフォームの非互換性のために正しく表示されません。これは、パッケージされた後のShaderコードはターゲットプラットフォームのプリコンパイルされたコードのみを保持し、Editorで実行できるかどうかもわかりませんから、これは正常です。しかし、実機でこのような問題は発生しないため、依存関係パッケージ化には影響しません。


Q7:ResourceシーンにはScene1.unityとScene2.unityの2つのシーンがあります。 これらのファイルをパッケージ化したら、下記のようなファイルが取得できます。

Scene1.assetbundle
Scene1.assetbundle.meta
Scene2.assetbundle
Scene2.assetbundle.meta

同じアセットがある場合、理論的には2つのパッケージのそれぞれにコピーが保存されるため、パッケージ本体が大きくなりすぎます。したがって、各シーンが大きくなりすぎないために、共有アセットを依存対象として別々にパッケージ化する方法はありますか?

同じなアセットのAssetBundle名をしっかりと設定すれば、パッケージ化する時に自動的に抽出されます。


Q8:現在、AssetBundleを生成すると、各ファイルに一つのManifestファイルが生成します。このファイルもAssetBundleと一緒にアップロードする必要がありますか?アセットがロードする時に具体的にどうせれば良いですか?

各ファイルによって生成されたManifestファイルをアップロードする必要はありません。その機能は、開発者がAssetBundle内の依存関係やその他の情報を確認できにさせることです。ただし、各ファイルが生成したManifest 以外に、ルートディレクトリの下にルートディレクトリと同じ名前のAssetBundleファイルとManifest ファイルもあります。実行時にこのAssetBundleをロードすると、AssetBundleManifestオブジェクトを取得でき、このオブジェクトからAssetBundleの直接依存関係を取得できます。

これ以上の情報は、http://docs.unity3d.com/ScriptReference/AssetBundleManifest.html で参考してください。


Q9:一つのPrefabがあります、そのDependenciesがResourcesフォルダーにある場合、AssetBundleをパッケージ化する時に、このPrefabのみをパッケージ化したら(BuildAssetBundleOptions.CompleteAssetsおよびBuildAssetBundleOptionsCollectDependenciesを指定しません)、このPrefabを正しくインスタンス化できますか?

AssetBundle内のアセットとResourceフォルダー内のアセットは依存関係を確立しないため、これを正しくインスタンス化することはできません(スクリプトを除き、BuildAssetBundleOptionsCollectDependenciesがオンになっている場合、スクリプトは依然としてAssetBundleにパッケージ化されません)。そのため、メッシュ、マテリアルなどが失われます。


Q10:AssetBundleを介してShaderをプリロードした後、AssetBundleをアンロードしませんでしたが、後でロードされたObjectが正しくShaderに引用されていないことがわかりました。原因は何でしょうか。

プロジェクト内のAssetBundleの依存関係が正しくパッケージ化されていない可能性があります。後続のすべてのロードされたAssetBundleは、ShaderのAssetBundleファイルに依存する必要があります。これにより、Unityエンジンは後続のAssetBundleをロードするときにShaderを関連付けます。


Q11:私のゲームには繰り返し特殊効果がたくさんあります。それらのいくつかは同じパターンですが、色のパラメーターが変更されています。それらをすべて独立AssetBundleにパッケージすると、メモリに複数のTextureがあります。そのようなパッケージには一般的に何が推奨案ありませんか?

内容が同じで全体の色のみが異なる場合は、プロジェクト内に初期テクスチャアセットのみを保留し、Shaderを介して実行中に全体的な色を変更し、さまざまな効果を実現することをお勧めします。但し、局部カラーが異なる場合、初期テクスチャに基づいて1つ又は1つ以上のMaskテクスチャを追加して、カラーの適応調整を行い、そしてShaderを介してさまざまな表示効果を実現できます。


Q12:すみません。2つのプリセットに3番目のAssetBundleのテクスチャを引用していますが、このテクスチャが二枚存在したくない場合、必ずこの2つのプリセットがロードされた後に、テクスチャのAssetBundleをアンロードできませんか?

そうです。しかし、「このテクスチャが二枚存在したくない」という理由ではなく、テクスチャのAssetBundleを最初にアンインストールすると、後で2つのプリセットをロードしたときに依存関係を失われます、つまり、テクスチャが見つからなくなります。スクリプトでこの状況をチェックし、再びテクスチャのAssetBundleをロードすると、現時点に「このテクスチャが二枚存在」問題を導きます。


Q13:下図の図1は、ゲームに入りばかり時に取得した情報で、図2は同じUIインターフェイスを数回切り替えた後に取得したものです。2つの画像を比較すると、重複するテクスチャが複数あることがわかりました。これはどうしてですか?ローディング方式はUIがAssetBundleを介してロードされ、ロード後にAssetBundleが解放されます。その後に再びUIをロードするとテクスチャアセットの冗長性が発生します。
6.jpg
7.jpg

ゲームに入りばかり時に取得した、図にある「重複」アセットは冗長ではない可能性があります。Atlasの Groupにはサイズの同じPage(つまりテクスチャ)が複数含まれている可能性があり、これらのPageがメモリにある名前は同じです。

ただし、同じUIインターフェイスを複数回開いた後、同じアセットがメモリにさらに出現する場合は、UI管理方法に特定の問題があることを示しています。頻繁に使用するUIに対して、ロード後にバッファプールを介してキャッシュし、後で使用する時にバッファプールを介して直接取得することをお勧めします。毎回AssetBundleを介してロードする必要はありません。この方法は、より高いCPU占用を導き、アセットの冗長性の可能性も高いです。

同時に、複数回開かれたUIインターフェイスは異なる、メモリ内の同一種類のアセットの増加を導く場合、AssetBundleがパッケージ化されたときにUIが冗長性を生成する可能性は高いです(この状況は現在のUGUIシステムによく見られます)。これに対して、開発チームがUGUIを使用する場合、同じAtlas UIインターフェイスを可能な限りAssetBundleファイルにパックすることをお勧めします。そうしないと、リソースの冗長性が発生します。


Q14:プロジェクトがリリースされたとき、Player Settingにチェックされたオプション(Optimize Mesh Data)は、パッケージ化されたおよびStreaming Assetsに配置されたAssetBundleファイルに効果ありませんか? モデルアセットにOptimize Meshがありますが、同じ効果を達成できますか?

理論的には、Optimize Mesh DataはBuild Player 又はBundle 時にのみ有効になります。ですから、この前に作成したBundleは無効になさせます。さらに、2つのオプションの効果は異なります。後者は、パッチの順序を調整することです。


二、アセットの使用

テクスチャ相関

Q1:ETC2形式の同じパッケージと同じアトラスがあり、Redmi Note1でのメモリはCoolpadでのメモリの4倍になります。これの原因は何ですか? OpenGL 3.0をサポートしていない場合、このような大きな影響が発生しますか?

理論的に、ETC2形式はOpenGL ES 3.0デバイスでのみサポートされ、サポートされていないデバイスでは、内部でRGBA32 / ARGB32形式に自動的に変換されます。これは、RGBA Compressed ETC2 8bitsテクスチャの4倍です。 したがって、OpenGL ES 2.0デバイスで透明な素材を圧縮する場合は、Alphaチャネルを分離し、2つのETC1を使用して圧縮することができます。


Q2:StreamingAssetsのテクスチャを動的にロードします。コードは次とおりです。
9.png
この方法のメモリコストは高いことがわかりました。一枚の512×512のテクスチャは2MBを占有します。公式の解析によると、メモリもビデオメモリも一部の占用が必要です。 テクスチャを動的にロードするための推奨される方法を知りたいですか?

一般的に、bytesストリームではなく、AssetBundleを介してアセットを動的にロードすることをお勧めします。 プロジェクトでこの方法を使用してテクスチャをロードしている場合は、戦略的に変更することを提案します。 現在から見ると、bytesストリームを介してアセットを生成する原因のほとんどは、アセットを解読しにくいように暗号化するためです。 しかし実際には、この暗号化方法はあまり役に立ちません。なぜなら、私たちの知る限り、Mali Graphics Debugger、Qualcomn Profilerなど、基盤となるグラフィックスレイヤーを介してさまざまなテクスチャやメッシュアセットを直接表示できるツールがたくさんあるからです。 したがって、暗号化の観点からbytesストリームを介してアセットを生成する場合は、AssetBundleの直接メソッドを介してロードすることをお勧めします。


Q3:iOSプラットフォームでは、アトラスのRGBチャネルとAlphaチャネルを分離する必要がありますか? 同じサイズの画像(正方形)で、RGB Compressed PVRTC 4bitsとRGBA Compressed PVRTC 4bitsの2つの形式が同じメモリを占用し、画像が2つに分割されている場合、iOSプラットフォームは2倍のメモリを占有ませんか ? 透明なチャネルがある場合、そのアトラスをより適切に処理するにはどうすればよいですか?

iOSのPVRTC形式はAlphaチャネルをサポートしているため、通常、iOSではチャネル分離を行う必要はありません。ただし、iOSでのチャネル分離は歪みを減らし、視覚効果をある程度改善するのに役立つというチームからのフィードバックもあるため、比較を試みることもできます。

占有されているメモリが同じであることが確認したら、元の画像はRGBです。 iOSでチャネル分離を行うと、メモリは確かに2倍になります。 UIテクスチャは、iOSでデフォルトのCompressを直接に選択でき、Atlasをパッケージする時にPVRTCに自動的に処理されます。開発チームは、Sprite packerウィンドウからAtlasの圧縮形式を確認できます。


Q4:ゲームの特殊効果で使用されるテクスチャはたくさんありますが、それらを管理する良い方法はありますか?アトラスを合併しないと、何千枚の小さな透明テクスチャがあり、アトラスを合併するとメモリコストは高すぎです。

アトラスに合併することができます。一般的に、頻繁に同時出現するテクスチャは、過剰なメモリを発生させないように、可能な限りアトラスに合成されます。 これについて、前に推奨したプラグインMesh Bakerを参照することはできます。


UWA Technologyは、モバイル/VRなど様々なゲーム開発者向け、パフォーマンス分析最適化ソリューション及びコンサルティングサービスを提供している会社でございます。

UWA公式サイト:https://jp.uwa4d.com
UWA公式ブログ:https://blog.jp.uwa4d.com

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

WebGLプロジェクトを開発するには、これらを知る必要があります

WebGL

Q1:現在、H5ゲームに変換する必要があるSpineを使用した2Dプロジェクトがありますが、Unityを使用してWebGLプロジェクトを直接に開発する場合、パフォーマンスに関する注意事項を教えてください。

問題主はモバイルデバイスでこのプロジェクトを実行したいようです。私たちのプロジェクト(レース類)は、直接にUnity 5を使用してページゲームバージョンに変換しようとしましたが、PCプラットフォームで実行していました。モバイルプラットフォームでは、他の人のプロジェクトを簡単に了解しました。当時に出た結論は、モバイルプラットフォームはハイレベルおよびミドルレベルのパフォーマンスに耐えません。これは自分でテストしていません。

私たちのプロジェクトがUnity5からPCページツアーへの変更過程に踏んだピットをリストして挙げます、参照してください。

1)WebGLはマルチスレッドとC#ネットワークをサポートしていません。代わりに、CoroutineとWebsocketを使用できます。

2)一部のCoroutineの使用方法に問題があります。While(1)ような関数を使用して、アセットの読み込みが完了する前に待ちます。それは、他のプラットフォームでは問題ありませんが、WebGLでは、おそらくWebGLで無限ループが発生します。WebGLがマルチスレッドをサポートしていないことは原因かもしれません。

3)ゲームがアセットをロードする過程中に、占有メモリが増えると、時々にメモリがオーバーフローすることがあります。これは主に、アセットが最適化されていませんから。成熟なページゲームチームと連絡した時、相手はページゲームのメモリが256MBを超えてはならず、アセットのロードとリリースを注意深く制御する必要があると提案しました。

4)LZ4圧縮形式を使用することをお勧めします。WebGLはマルチスレッドをサポートしていませんから、LZMA圧縮されたBundleはメインスレッドが解凍中の遅い状況を引き起こします。

5)単一アセットのサイズは大きすぎないようにする必要があります。1MB未満に制御し、アセットをできるだけパッケージ化することをお勧めします。そうしないと、ロード時間が長すぎて体験に影響します。

5)単一アセットのサイズは大きすぎないようにする必要があります。1MB未満に制御し、アセットをできるだけパッケージ化することをお勧めします。そうしないと、ロード時間が長すぎて体験に影響します。

6)WebGLは中国語の入力をサポートしていません。ネイティブな方法で中国語の入力を実現しました。

7)PC端末で一部のブラウザはサポートしていません。テストしたサポートされているブラウザは次のとおりです。
●Google Chrome 9+
●Mozilla Firefox 4+
●Safari 5.1+(Mac OS Xシステムのみ、Windowsを除く)
●Opera 12 Alpha+
●IE9 +、ただしIEはWebGLをサポートしていませんが、プラグインIEWebGLをダウンロードしてインストールできます

8)アセットがインスタンス化されて使用されていない場合、エディターでクラッシュすることはありませんが、PCパッケージとエクスポートされたWebGLバージョンの両方がクラッシュします。


Monoメモリ

Q2:理論的には、Monoメモリは可能な限り小さく(UWAは40MB以内で制御することをお勧めします)、割り当て頻度は可能な限り低くします。では、一回のより大きなメモリ割り当てと頻度の高い(例えば毎フレーム)小さなメモリ割り当て、どちらがパフォーマンスへの影響は大きいですか?一回GCの時間コストは、全体的なMonoサイズに影響を受けますか、それともMono内引用されたオブジェクトの数量に影響を受けますか?簡単に言うと、フレームごとに割り当てられる小さなメモリと一回のより大きなメモリ割り当て、どちらの最適化作業を優先する方がいいですか?GCがマシンの発熱への影響は大きいですか?

フレームごとに割り当てられた小さなメモリの最適化を優先します。これはGCに大きな影響を与えます。 頻繁なGCは、実行のスムーズさにより大きな影響を与えます。全体から言うと、GCがパフォーマンスへの影響は大きくなく、一般的なオーバーヘッド(レンダリングモジュール、UIモジュールなど)よりもはるかに小さくなります。
一回の大きいMonoメモリ割り当てに対しては、2MB以上の割り当てを注意する必要があります。合理性を確認し、出来る限りに8MB以上の割り当てを避けます。最もめんどくさい問題はMonoメモリのピーク値を伸ばし、下がることはできないことです。

Monoメモリのピークは高く、最大の影響は、Monoメモリの占有量を上がり、Monoがシステムに申し込んだメモリは増加するだけであるということです。率直に言って、ゲームのメモリ使用量は増加しており、メモリの少ないデバイスではクラッシュしやすいというリスクがあります。

GC時間コストは必ず影響を受けます。現在、IL2CPPであろうとMonoであろうと、GCは世代別ではありません。つまり、リサイクル時にすべてのノードがトラバースされます。


アセット管理

Q3:コードをどのように分離しますか? 複数の部門が一緒にゲームプロジェクトにアクセスする必要があるから、ソースコードを保護するために、技術以外の部門がゲームプロジェクトで暗号化されたDLLファイルのみを取得できることが望まれます。

最初の考えは、各部門が同じゲームプロジェクトを取り、その中、技術部門がソースコードを直接取得し、他の部門がDLLファイルを受け取り、技術部門がプログラムを書いて、DLLを生成して、他の部門に更新させるというものでした。ただし、UnityのスクリプトコンポーネントはGUID + FileIDの形式であるため、ソースコードを取る同僚が異なるスクリプトを持つなら異なるGUIDを得ますが、DLLを取る同僚は全員同じGUIDを手に入れます。その結果、異なる部門が同じプロジェクトで制作することはできなくなりました。
現在のやり方は、一つのコンパイルプロジェクトを別に作成して各プラットフォームに対応するDLLファイルを生成しますが、これはより面倒です。

PS:モジュールが分離されている場合でも、DLLをインポートすることはいくつかのcsファイルを更新することより時間をかかります。何かいい方法ありませんか?

私たちの会社では、これは部門の要件次第です。
1)アート部門は、常にUnityで効果をデバッグする必要があるため、この部門に暗号化されたDLLプロジェクトのみを提供し、ディレクトリを指定して対応するアセットを提出します。ツール操作のヒントも作成します。
2)企画部門に対して、Unityで効果を調整することは役割ではありません(これも会社次第です)から、ローカルLuaコードをロードできるexeパッケージを提供できます。運営部門に配置コードを提供させ、この部分のコードを企画さんに開放します、それともツールでexeにパッケージします。
一時的にこれだけ考えられます、参照してください。

「モジュールが分離されている場合でも、DLLをインポートすることはいくつかのcsファイルを更新することより時間をかかります。」これは多分、問題主はAssemblyInfoを付いていません。UnityがAPIをスキャンすることを導きます。このドキュメントを参照できます。
https://github.com/pangweiwei/slua/blob/master/Assets/Slua/Editor/LuaCodeGen.cs#L544
みんな全員が同じUnityバージョンを使ったら、スピードは速くなります。

もう一度PS、現在、一部のコードのためにそうなん面倒な方法を使うことは本当に低効率です。

ビジネスロジックが全てLuaにある場合は、Luaのbytecodeを暗号化することを選択できます。
ビジネスロジックもC#で書いた場合は、暗号化しないことを選択できます。
実には、今の時代に、コードはそれほど価値がありません。 DLLを使用すると、他の部門の使用に面倒なところを増加させます。


アセット管理

Q4:複数の異なるシェーダーが同じcgincファイルを引用する場合、実際に何回コンパイルされますか?

私の理解では、cgincが含まれています。これは、コンパイラの前処理をコピーしてShaderファイルに貼り付けるのと同じで、cgincファイルは存在しないと認められます。だから、このcgincを含むShaderファイルがいくつかあり、何度かコンパイルします。

Cgincは単なるコード欠片です。Shaderが特定のcgincを使用すると、内部に対応する関数を抽出し、precompileの時に作業を完了します。だから、異なるシェーダーが同じcgincファイルを引用する場合、コンパイルでは、cgincファイル全体がコンパイルに参加するのではなく、シェーダーで使用されるコードの一部がコンパイルされます。


アニメーション

Q5:一つのアニメーションイベントに別のアニメーションオブジェクトをインスタンス化したところ(Animatorを使用)、インスタンス化されたオブジェクトが直接アニメーションを開始しなく、先にデフォルト姿態を示して、次のフレーム後からアニメーションを始めました。 これにどう対処するか?

アニメーションイベントは、アニメーションシステムが当該フレームのアニメーション計算(Animation Evaluation)を行った後にアクティベーションします。アニメーションイベントにアニメーションオブジェクトをインスタンス化する場合、このオブジェクトのanimatorは当該フレームでアニメーション計算を行いません、次のフレームまで待機します。アニメーションシステムがアニメーション計算を実行します。解決策は、アニメーションオブジェクトをインスタンス化した後にAnimator.Update(0)をコールすることです。


UWA Technologyは、モバイル/VRなど様々なゲーム開発者向け、パフォーマンス分析最適化ソリューション及びコンサルティングサービスを提供している会社でございます。

UWA公式サイト:https://jp.uwa4d.com
UWA公式ブログ:https://blog.jp.uwa4d.com

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

Unityでの開発において、車を扱うアプリで指定したルート上でイベント処理を行う-Vol.2

プログラマーの大角です。Unityで車を扱ったOculus Rift用のVRアプリ開発を行った際の実装の一部をご説明させて頂きます。

下記記事の続きとなります
https://qiita.com/NestVisual/items/dbb54a920ecf2e5707a7

概要

今回は決められたルートを走る車が指定したポイントに来た時に、STATEを変化させて次のSTATEで処理を行う部分についての実装方法を説明させて頂きます。

前回の記事でWayPointProgressTrackerAuxiliaryForMainCar.csで定義したProgressNumForMainCarという変数を使用します。

実装工程(1)

指定したルートのポイントにProgressNumForMainCarが到達した時にSTATEに連動させるメソッドが下記になります。
UniRxで実装しています。

        /// <summary>
        /// 指定したWayPoint以上で指定したStateに変更
        /// </summary>
        /// <param name="state">変更したいState</param>
        /// <param name="progressNum">WaypointProgressTrackerのprogressNum</param>
        protected void StateChangeInSpecifiedWaypoint (STATE state, int progressNum) {
            this.UpdateAsObservable ()
                .Select (x => WayPointProgressTrackerAuxiliaryForMainCar.ProgressNumForMainCar)
                .DistinctUntilChanged ()
                .Subscribe (_ => {
                    if (CurrentStateAndDifferenceExists (state) &&
                        WayPointProgressTrackerAuxiliaryForMainCar.ProgressNumForMainCar >= progressNum) {
                            State = state;
                    }
                });
        }

        /// <summary>
        /// 変更したいSteteと現在のStateとの差分確認
        /// </summary>
        /// <param name="setState">変更したいState</param>
        /// <returns></returns>
        bool CurrentStateAndDifferenceExists (STATE setState) {
            int setStateIntType = (int)setState;
            int currentStateIntType = (int)State;
            return setStateIntType > currentStateIntType;
        }

実装工程(2)

メソッドを使う側は下記のように記述します。
STATE.ONEなど今回は数字で定義してますが、実際にはそのSTATEで行う内容で定義しています。

switch (State) {
                case STATE.ONE:

                    // MEMO:場合によっては何らかの処理の後に下記メソッドを呼び出す等組み合わせていく
                    StateChangeInSpecifiedWaypoint (STATE.TWO, 63);
                    break;

                case STATE.TWO:

                    // TODO:ここに指定したルートのポイントで行いたい処理を記述する
                    break;

                default:
                    break;
}

あとがき

UniRxは便利なので良く使わせて頂いてます。
最後まで見て頂き有難う御座いました。

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

1時間でできる,VRChat ワールド制作手順書.with Revit

はじめに

このページは,Unityを知らない人のための,RevitからVRChatにワールドをアップロードするまでの手順を示したものです.
1時間で終わらせるため,影響が少ない手順は省いています.
すでに3Dモデルを持っている方は,手順2から作業してください.

注意事項

VRChatUnityのアカウントが必要です.
VRChatには,信用スコア(トラストランクと呼ばれている)システムがあります.
VRChatで数日遊ぶ 又は課金しないと,ワールドをアップロードできません.
Oculus Quest単体用のワールドには対応していません.
VRChatへのアップロードは,データの転載となります.権利・ライセンスの確認をしておきましょう.
この記事で生じた事故・事件・その他トラブルには,著者は一切責任を負いません.

前提条件

  • Windowsの基本操作ができる
  • Revit(2016以降)をインストールしている(既にfbxや.daeなどの3Dファイルを持っている場合は必要なし)
  • Revit(.rvt)ファイルを持っている(既にfbxや.daeなどのファイルを持っている場合は必要なし)

後々必要になるもの (有料)

Bakery - GPU Lightmapper
影や光の反射をラッピングするツール.
値段は$55.半額セールを狙って買ってもいいでしょう.
Bakery

目次

  1. 環境構築
  2. Revitからモデルを出力
  3. Unityへインポート
  4. マテリアル(表面の模様・壁紙)をつける
  5. Skybox(空や背景)をつける
  6. コライダー(当たり判定)を設定する
  7. ライトを設置する
    1. 基本設定
    2. Mesh Renderer
    3. UV Map
    4. ライト設置
    5. ライトの明るさなどの設定
  8. ライトベイク(照明の反射を計算)する
  9. アップロードする

環境構築

Unityセットアップ

Unity Hubをインストール

Download Unity Hub をクリック
https://unity3d.com/get-unity/download
unity hub download.PNG
UnityHubSetup.exeを実行
Run Unity Hubにチェックが入っていることを確認して,Finishを押す.
Unity Hub Install.PNG
自動的にUnity Hubが起動します.
Unity Hub.png
右上のアイコンをクリックして,Sign inをクリックします.
Unity Login 2.PNG
Unity IDにサインインします.
Unity IDを持っていない場合は,create oneからアカウントを作成します.

https://docs.vrchat.com/docs/current-unity-version にアクセスします.
Click here to install the current version of Unity via Unity Hub. をクリックして,Unity Hubを開きます.(ブラウザがUnity Hubを開くかを聞いてくる)
docs.PNG
Unity Hubが開きます.
INSTALL を押して,Unityをインストールしましょう.
Unity 2018.4.20f1がインストールされます.(2020年12月時点)
Unity Install.PNG
インストールしている間に,ライセンス認証を済ませましょう.
license.gif

Revitセットアップ (3Dファイルを持っている場合は次の項目へ)

Lumion® LiveSync® for Autodesk® Revit® をダウンロードします.
https://apps.autodesk.com/RVT/en/Detail/Index?id=1257010617153832891&appLang=en&os=Win64

Revitが起動していないことを確認して,ダウンロードした Act-3DBVLumionLiveSyncforAutodeskRevit_〇〇.msiを実行します.
image.png

アドオンの警告が出ます.
Always Loadをクリックして,Lumion® LiveSync®を常に読み込むように設定しましょう.
lumion.PNG

Revitからモデルを出力

VRChatにアップロードしたい.rvtファイルを開きます.
Lumionタブ > Expotをクリック.
Surface smoothing を low に設定して,Exportを押す.
Lumion Ex.gif

Unityへインポート

Unity Hubを開きます.
NEWをクリックして,Templateが3Dになっていることを確認し,任意のProject NameとLocation(ファイルの場所)を設定して,Createをクリック.
UnityOpen.gif

出力した.daeファイルを,UnityのProjectウィンドウ(下の方)にドラッグアンドドロップします.
読み込んだ.daeファイルをSenceウィンドウ(中央)にドラッグアンドドロップします.(一軒家以上の大きさだと,引いて見ないと全体が見えません.)
ezgif-7-caed3d798619.gif

マテリアル(表面の模様・壁紙)をつける

Projectウィンドウ上で右クリックし,Create>Materialを選択.
ここで,マテリアルの名前が変更できます.(後からでも変更可能)
Ctrl + D で,マテリアルをコピペできます.なるべく少なめにしましょう.マテリアルが多すぎると,VRChat上での処理が重くなります.
mat.gif

マテリアルは,Senceウィンドウの建物にドラッグすると,その面に適用されます.
matmove.gif

木目の画像や壁紙の画像をダウンロードしてきましょう.
おすすめのサイトは,Texture HavenCC0 Textures

textureフォルダを作って,ダウンロードしてきた画像をフォルダごとドラッグアンドドロップします.
tex.gif

先程 壁にドラッグしたマテリアルを選択します.
Inspectorウィンドウにマテリアルの設定画面が出てきます.
Albedo,Metallic,Smoothnessに先程ダウンロードした画像を適用します.
画像が多すぎて見つけられない場合はSelect Textureウィンドウから検索できます.

[画像名と適用先の"例"]
画像名_Color → Albedo
画像名_Normal → Normal Map
画像名_AmbientOcclusion / 画像名_AO → Occlusion

AlbedoとNormal Map以外は用意されてない場合があります.必須項目ではないのでない場合は適用しなくて構いません.

texstet.gif

Albedo:表面の色・模様
Metallic:表面がどれくらい”金属的”かを表す
Smoothness:表面の滑らかさを表す
Normal Map:凹凸,傷,溝などのディテールを表す.

[ MetallicSmoothnessの解説 ]
0 から 1 の Metallic 値の範囲
1に近づくほど金属的な反射をする.
Metallic
0 から 1 の Smoothness 値の範囲
1に近づくほど表面がツルツルになる.
Smoothness

ガラスのマテリアルを作る

InspectorウィンドウのRendering ModeをFaldeに設定し,MetalicとSmoothnessを1にします.
image.png
Albedoの右の白い長方形を選択し,Colorウィンドウの一番下のAを0に近い値にします.画像では14に設定しています.
image.png
作ったガラスマテリアルを,ガラス部分にドラックすればガラス窓の出来上がりです.

全部の面にマテリアルを付けるとこうなります.好みのテクスチャとマテリアル設定にしてください.
mat off.PNG

簡易的に地面をつける(地面がない場合)

GameObject > 3D Object > Cube
InsoectorウィンドウのScaleのXとZの値を変更し,大きさを調節する.
yuka.gif

Skybox(空や背景)をつける

背景を追加してみましょう.
HDRI Haven から,好きな背景をダウンロードしましょう.

今回は,Noon Grassを選びました.

ダウンロードした〇〇.hdrをProhectウィンドウにドラックし,InspectorウィンドウのTexture ShapeをCubeに変更し,Apllyを押す.

hdri.gif
Projetcウィンドウ内で右クリックし,Create > Material を選択.
InsoectorウィンドウのShaderをStanderdからSkybox > Cubemapに変更.
Cubemapに〇〇.hdrを適用する.
出来上がったMaterialをSenceウィンドウにドラックする.
skybox.gif

コライダー(当たり判定)を設定する

コライダーを付けたいパーツを選択し,Inspectorウィンドウ内のAdd Comportnentを押し,Mesh Coliderを適用する.
colider.gif

ライトを設置する

Bakery - GPU Lightmapperのライトを使用します.Unity標準のライトは使用しません.(処理が遅すぎる,扱いにくいなどなどの理由)

基本設定

Hierarchyウィンドウ内のDirectional Lightを消去します.Deleteで消えます.
エラーが出る場合は,水色の立方体アイコンを右クリックしてUnpack Prefab Completelyします.これで消せるようになります.
Screenshot (6).png
次に,window > Rendering > Lighting Settingを開き,一番下にあるAuto generateをオフにする.
Screenshot (7).png
Capture.PNG

Mesh Renderer

オブジェクトを全て選択して(ライトなどは選択しないように注意),Inspectorウィンドウ内の
LightmapStaticをオン
Cast Shadowsをオン
Receive Shadowsをオン
Scale in Lightmapを4に設定.
bakeryopen.gif

UV Map

Hierarchyウィンドウの3Dファイル(.fbxとか)を選択し,Inspectorウィンドウの中のGenerate Lightmap UVsにチェックを入れる.

ライト設置

Bakery > Create から置きたいライトの種類を選んで設置していきます.

  • Directional Light:太陽光.すべてを均一に照らす.
  • Skylight:環境光.全体を明るくしたい場合は,これのIntensityの値を大きくする.
  • Point Light:電球のような光.周囲を照らす.距離の2乗離れると暗くなる.
  • Area Light(Example):片面が光る板
  • Spotlight:スポットライト.車のライトのようなもの.

ライトの明るさなどの設定

Directional Lightの場合

image.png

  • Intensity:ライトの明るさです。大きくすれば明るく、小さくすれば暗くなります
  • Shadow spred:値を大きくすると影がぼやけます.
  • Shadow samples:影を計算するサンプル数.ベイク結果がザラザラしている場合は,この値を大きくする.
  • Indirect Multiplier:間接光.1未満では、跳ね返るごとに暗くなります.1より大きい値では跳ね返るごとに明るくなります.

Skylightの場合

image.png
- Intensity:ライトの明るさです。大きくすれば明るく、小さくすれば暗くなります
- Samples:光を計算するサンプル数.ベイク結果がザラザラしている場合は,この値を大きくする.
- Indirect Multiplier:間接光.1未満では、跳ね返るごとに暗くなります.1より大きい値では跳ね返るごとに明るくなります.

Point Lightの場合

image.png

  • Intensity:ライトの明るさです。大きくすれば明るく、小さくすれば暗くなります
  • Shadow spred:値を大きくすると影がぼやけます.
  • Range:光の到達距離.大きいほど明るい.小さい空間だと影響を受けやすい.Intensityとのバランスが大事.
  • Samples:光を計算するサンプル数.ベイク結果がザラザラしている場合は,この値を大きくする.
  • Indirect Multiplier:間接光.1未満では、跳ね返るごとに暗くなります.1より大きい値では跳ね返るごとに明るくなります.

Area Light(Example)の場合

image.png

  • Intensity:ライトの明るさです。大きくすれば明るく、小さくすれば暗くなります
  • Cutoff:光が到達する距離.値が大きいと光が遠くまで届く.
  • Samples (Near/Far):光を計算するサンプル数.ベイク結果がザラザラしている場合は,この値を大きくする.

Spotlightの場合

image.png

  • Intensity:ライトの明るさです。大きくすれば明るく、小さくすれば暗くなります
  • Shadow spred:値を大きくすると影がぼやけます.
  • Projection mask:Coneはスポットライトのように照らし,Omniは全方向を照らします. spot.PNG

ライトベイク(照明の反射を計算)する

有料のBakery - GPU Lightmapperを使用します.
Asset Storeタブ > My Asset からBakery - GPU Lightmapperを探して Downloadを押した後,Importを押します.(画像はDownloadした後の画面)
aaaaa.PNG

Bakery > Render Lightmap を開く
Renderを押す.
Screenshot (5).png
dfadas.PNG

もっと綺麗にしたい場合.

SettingをSimpleからExperimentalに変更.
画像と同じ設定にしてください.
PCのGPUがRTXシリーズ以外の場合は,RTX modeのチェックを外して構いません.
bakery1.PNG
画像と同じ設定したら,一番下のRenderを押します.
終わったら Crtl + S で上書き保存しておきましょう.

アップロードする

VRChatのアカウントが必要です.
https://vrchat.com/home/download からVRCHAT SDK3 -World をダウンロード
sdk.PNG
VRCSDK3-WORLD-〇〇〇.unitypackage をUnityのProjectウィンドウにドラッグ.
sdkimport.gif
インポートするか聞かれるので,全てにチェックが入っていることを確認してAplyを押す.
VRChat SDK > Controlを押す.
VRChatにログインする.
login.gif

Allowed to publish worlds と書いてあればOK.アカウントにワールドをアップロードする権限があります.
うまく行かない場合は,一度保存してからUnityを再起動しましょう.

[Not yet allowed to publish worlds が表示された人へ]
アカウントを作って間もないため,VRChat内の信用スコアが足りていません.数日遊んでフレンドを増やすと,スコアが上がりワールドをアップロードする権限が得られます.課金してスコアを得る方法もあります.
VRChat Safety and Trust System
VRchat 日本wiki トラスト(Trust)

VRChat SDK > Show Control Panel を押す.
Balderタブを選択し,Setup layers for VRChat 及び Set Collision Matrix を押す.
Bild & Publish for Windows を押す.
sdkset.gif

以下を入力します.
World Name:ワールドの名前
Player Capacity:同じワールドに存在できる人間の数
Description:ワールドの説明文(必須ではない)
name.gif
The abroad information is~ にチェックを入れ,Publish to Community Labs にもチェックを入れる.

ワールドのサムネイル画像を作る

左上のSenceを押して,カメラ(VRCCam)を選択します.
右下のCamera Previewを見ながら,サムネイル画像にしたい位置にカメラを移動します.
Inspectorウィンドウ内のFiled of Viewで,カメラをズームできます.
終わったら,右上のGameタブを押しましょう.
右下のUploadを押すと,アップロードが開始します.(数分かかります)
samune.gif
アップロードが終わると,このウィンドウが出ます.
scusess.PNG

VRChat(デスクトップモード)で見る

Steamをダウンロード

https://store.steampowered.com/about/

VRChatをダウンロード

https://store.steampowered.com/app/438100/VRChat/
VRChatを起動してログインします.
VRヘッドセットの場合は,上のLanch VRChat in Steam VR Modeを選択.
PCのモニターで見る場合は,下のLanch in Desktop (Non-VR) modeを選択.ここでは このモードで説明します.
image.png
起動したら,Escキーを押して右上のWorldを選択.
Mineタブに移動し,アップロードした自分のワールドを選択.
Goを押す.
done.gif

完成です.よくがんばりました.えらい

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

借金の利息がバーチャルペットになって画面を散歩するアプリを作ります

この記事は

「クソアプリ2 Advent Calendar 2020」の19日目の記事となります。

はじめに

親の教えとして「何事もポジティブに」と教育を受けてきました。
おかげか大体のことはポジティブに考えることがきました。

しかし、世の中には無理じゃね?というものもあります。

       借 金

どうポジティブにとらえたらいいんだ。。。
負のものしか含まれていないぞ。
ポケ〇ンをやりながら考えていました。

モ、モンスターにおきかえてみるか。

コンセプト

借金をするほど強くなる(ポジティブに見える)アプリをつくる。

開発ツール

Unity

生まれたもの

7.gif

タイトル通り散歩します、借金が。

5千兆円借金してみる

7.gif

う、うわああああああああああ。

色々とバグってますがあれです。
クソアプリなんで許して。

最低限の借金返済アプリの体裁は一応整えていたりします

モンスターをタッチすると借金返済できます。
ギリギリ使えるレベルにするのもクソアプリ製作の楽しいところですよね。
8.gif

実装

カレンダーの実装は下サイトから頂戴しました
Unityのカレンダーアセット作った!-alberttecの日記
今回のクソアプリで見た目部分も着手できたのはカレンダー配布がめちゃ大きいです。神配布感謝です。

キャラクターはアセットストアで購入(7$)
Pixel Mobs-UnityeAssetStore

キャラクターの移動
SetNextPosition()で移動先を指定してLerpで移動。
移動が完了したらRecenter()で移動先を再指定です。

mob.cs
void Update()
{
    float interval = 5;
    elapsedTime += Time.deltaTime / interval ;

    Vector2 tmpPos = Vector2.Lerp(beforePosition, nextPosition, elapsedTime);
    rectTransform.localPosition = new Vector3(tmpPos.x, tmpPos.y, 0);

    if (IsMoved)
    {
        Init();
    }
}

private void Init()
{
    SetNextPosition();
    elapsedTime = 0;
}

public void Recenter()
{
    beforePosition = new Vector2(rectTransform.localPosition.x, rectTransform.localPosition.y);
    elapsedTime = 0;
}

private void SetNextPosition()
{
    beforePosition = new Vector2(rectTransform.localPosition.x, rectTransform.localPosition.y);
    int randX = Random.Range(-MaxX, MaxX);
    int randY = Random.Range(-MaxY, MaxY);
    nextPosition = new Vector2(randX, randY);
}

借金の計算式系
コア部分を抜粋したものです。
LoanPrincipalListには元金
LoanAnnualInterestListには利率
です。
最初、doubleではなくfloatで型指定した結果、1000万以上借金をすると100円になる素敵なバグが発生しました。

LoanCalculation.cs
// 元金
public double GetPrincipal(int index)
{
    return LoanPrincipalList.Count == 0 ? 0 : LoanPrincipalList[index];
}

// 1日あたりの利息
public double GetOneDayDebt(int index)
{
    double pricipal = LoanPrincipalList.Count != 0 ? LoanPrincipalList[index] : 0;
    double annualInterest = LoanAnnualInterestList.Count != 0 ? LoanAnnualInterestList[index] : 0;
    double oneYearDebt = pricipal * (annualInterest / 100);// 100は百分率になおしている
    return Math.Round(oneYearDebt / OneYear);// 365は1年
}

public double GetTotalMoney(int index)
{
    double pricipal = LoanPrincipalList.Count == 0 ? 0 : LoanPrincipalList[index];
    double annualInterest = LoanAnnualInterestList.Count == 0 ? 0 : LoanAnnualInterestList[index];

    double totalDebt = GetTotalDebt(index);
    return pricipal + totalDebt;
}

// 利息の合計
public double GetTotalDebt(int index)
{
    double diffTotalDays = GetDifferenceTotalDays(index);// 現在日から支払日までの差
    double oneDayPricipal = GetOneDayDebt(index);// 一日あたりの年利
    return Math.Ceiling(diffTotalDays * oneDayPricipal);// 年利で発生した金額
}

// 現在日から支払日の差
private double GetDifferenceTotalDays(int index)
{
    DateTime today = DateTime.Today;
    DateTime loanLastRepaymentHistory = (LoanLastRepaymentHistory.Count != 0 ? LoanLastRepaymentHistory[index] : today);

    TimeSpan diffDay = (today - loanLastRepaymentHistory);
    return diffDay.TotalDays;
}

余談

せっかくつくったのでAppleStoreに申請しようとたところキーワードという欄があるそうです。
アプリ検索用に100字以内で検索ワードを記述できるようです。

実際に記述したもの

ギャンブル,サラ金,デビット,プロミス,モビット,リボ,リボモン,リボルビング,レイク,ローン,借入,借金,債務,元金,利子,利息,残高,消費者金融,計算,負債,返済,金利

この世の地獄みたいなキーワードランキングがあったら上位に食い込めそうです。

最後に

借金が増えるほどモンスターがいっぱいになっていきます。
なんか楽しい気持ちになってきた気がします。
これで少しは借金をポジティブに考えられるようになった気がします。
わーい。
めでたしめでたし。

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

Unity iOSのデバッグビルドでのunsafeコードの速度低下とその対策

この記事はDeNA Advent Calendar 2020の20日目の記事です。
大竹悠人(@Trapezoid)です。 DeNAではゲーム用のライブラリ/SDK開発やセキュリティ対策、開発効率化などを横断的に行いつつ、トラブルシューターとして活動しています。
今日はUnityエンジニア向けに、死ぬほどニッチな小ネタを書こうと思います。

TL;DR

  • IL2CPPでは、高負荷なロジックをunsafeにしたらiOSのデバッグビルドで激烈に遅くなることがある
  • IL2CPPがinline化を前提とした癖のあるコードを生成するが、それがデバッグビルドで最適化が無効になることでinline化が阻害されるのが原因
  • 一般的な最適解としてはそもそも最適化を有効にするか、Burstやネイティブプラグイン化で対策した方が望ましい
  • 様々な事情により、局所解として手動inline化とも言える力技を行って解決した

経緯

パフォーマンス的にナイーブな計算量の多い場面で、C#のunsafeを使った最適化を行うことは稀にあるかと思います。
僕も特に暗号化やハッシュ化などのために稀によく頻繁に使うのですが、ある時利用者から iOSでの実行時だけ該当ロジックが異常に重いので調査してほしい という報告を受けました。
いくつか原因切り分けをしてもらったところ、 (Xcode上の)DebugビルドでiOSビルドを行った場合のみ この現象が起こることが分かってきました。
Debug設定時のみとはいえ、続行を諦めたくなるほど遅くなっていたので、速やかな対処が必要です。

発生していた問題

Debugビルドのみで起こるというと何かしら最適化と相性の悪い何かがあるのかな...とは思いつつ、再現させてプロファイリングを行ったところ、unsafeコードとして記述している区間がボトルネックになっていることが分かりました。

問題になったのは、stackallocで固定長の小さなバッファを用意して、そのバッファをひたすらこねるといった、暗号化などでよくあるワークロードでした。

起こっていたことを、次のようなコードを使って説明します。
(今回は問題を単純化するために、ただ引数をstackallocしたバッファに添字指定でコピーするだけのmemcpyすれば?って感じの意味のないコードにしています。実際はバッファへのコピーも単純なコピーではないです)

public unsafe void Test1(byte* x)
{
    var buffer = stackalloc int[16];
    buffer[0] = x[0];
    buffer[1] = x[1];
    buffer[2] = x[2];
    // ...
    buffer[15] = x[15];
    //bufferをこね回す重い処理が以降に入る
}

このコードにIL2CPPをかけると、次のようなC++コードに変換されます。

// var buffer = stackalloc int[16];
int8_t* L_0 = (int8_t*) alloca((((uintptr_t)((int32_t)64))));
memset(L_0, 0, (((uintptr_t)((int32_t)64))));
// buffer[0] = x[0];
int8_t* L_1 = (int8_t*)(L_0);
int32_t* L_2 = ___x0;
int32_t L_3 = *((int32_t*)L_2);
*((int32_t*)L_1) = (int32_t)L_3;
// buffer[1] = x[1];
int8_t* L_4 = (int8_t*)L_1;
int32_t* L_5 = ___x0;
int32_t L_6 = *((int32_t*)((int32_t*)il2cpp_codegen_add((intptr_t)L_5, (int32_t)4)));
*((int32_t*)((int8_t*)il2cpp_codegen_add((intptr_t)L_4, (int32_t)4))) = (int32_t)L_6;
// buffer[2] = x[2];
int8_t* L_7 = (int8_t*)L_4;
int32_t* L_8 = ___x0;
int32_t L_9 = *((int32_t*)((int32_t*)il2cpp_codegen_add((intptr_t)L_8, (intptr_t)((intptr_t)il2cpp_codegen_multiply((intptr_t)(((intptr_t)2)), (int32_t)4)))));
*((int32_t*)((int8_t*)il2cpp_codegen_add((intptr_t)L_7, (intptr_t)((intptr_t)il2cpp_codegen_multiply((intptr_t)(((intptr_t)2)), (int32_t)4))))) = (int32_t)L_9;
// buffer[3] = x[3];
int8_t* L_10 = (int8_t*)L_7;
int32_t* L_11 = ___x0;
int32_t L_12 = *((int32_t*)((int32_t*)il2cpp_codegen_add((intptr_t)L_11, (intptr_t)((intptr_t)il2cpp_codegen_multiply((intptr_t)(((intptr_t)3)), (int32_t)4)))));
*((int32_t*)((int8_t*)il2cpp_codegen_add((intptr_t)L_10, (intptr_t)((intptr_t)il2cpp_codegen_multiply((intptr_t)(((intptr_t)3)), (int32_t)4))))) = (int32_t)L_12;
//...

単なる代入だけの単純な処理だったはずが、大量の il2cpp_codegen_add 及び il2cpp_codegen_multiplyの呼び出しが発生しており、
IL2CPPを通ったunsafeなメモリアクセスが、intptr_tにキャストした上でアドレスを計算しなおすように変換されていることが分かります。

il2cpp_codegen_add 及び il2cpp_codegen_multiplyの定義を見てみると、次のようなinline指定されたtemplateになっています。

template<typename T, typename U>
inline typename pick_bigger<T, U>::type il2cpp_codegen_multiply(T left, U right)
{
    return left * right;
}
template<typename T, typename U>
inline typename pick_bigger<T, U>::type il2cpp_codegen_add(T left, U right)
{
    return left + right;
}

inlineキーワードのみのinline関数は-O0指定時はインライン展開されないため、これは-O0で最適化が無効化された場合にはunsafeコード中でのアドレス演算の度に複数回の関数呼び出しが行われるということを意味します。unsafeコード中のアドレス演算が手計算される関係で、この回数は想像よりも更に多くなります

今回の問題の原因はこのように、XcodeでBuild ConfigurationをDebugにしてビルドした場合、-O0で最適化が無効化されるようになっているため、呼出回数の多いポインタへのインデックスアクセスのコストが爆増したことにありました。

また、そもそもプリミティブ型の四則演算に関しては、unsafeでない場合にも概ね同じルールで変換されるため、unsafeに限らず純粋なプリミティブ型の演算を大量に行う場合はこの問題にあたることが多くなりそうです。

対策

対策は複数考えられます。

Debugビルド時の最適化レベルを-O2以上にする

-O2以上であればinline関数は概ねinline化される為、関数呼出によるオーバーヘッドはなくなり、根本的な対策にはなります。

また、問題になるような負荷の高いロジック以外でも、四則演算全般の速度がある程度向上すると思われます。

ただし、デバッガビリティはある程度犠牲になります。今回は共通基盤となるライブラリのコード内での問題でしたので、これを利用者側への強制するのは流石に避けたいという思いがありました。また、元々パフォーマンスに振り切ったかなりナイーブな実装をメソッドに閉じてしていた為、ある程度ナイーブな対策を追加で行っても相対的には問題ないという判断をしました。

Burst / ネイティブプラグインで書く

そもそも負荷の高いナイーブな処理であれば、Burstを使ってIL2CPPを避けて最適化されたLLVM Bitcodeを出力させたり、直接C/C++でネイティブプラグインとして書く、というのも手です。

そもそもの最適化という意味では非常に良い選択肢ですし、そもそも問題が起こり得る領域を考えると大抵の場合の最適解となり得ると思っています。

ですが、今回はUnityに依存しない、.NET Coreからも使われるコードベースであった為、Burst化やネイティブプラグイン化はUnity特化として追加する形で行う必要がありました。このため、検討はしつつも今回の解決策としては見送りました。

アドレス演算の回数を少なくする

問題となったコードでは、stackallocした領域にかなり回数アクセスする一方で、アクセスするアドレスの範囲は非常に限られており、数も固定されていました。

このため、今回はアクセスする全てのアドレスのポインタをそれぞれ1つだけスタック上に確保しておき、それらのポインタを経由して値を読み書きするという死ぬほど泥臭い対応を行うことで解決しました。

サンプルとして出した例で書き直すなら、以下のような形になります。

public unsafe void Test1(int* x)
{
    var buffer = stackalloc int[16];
    var bp0 = &buffer[0];
    var bp1 = &buffer[1];
    var bp2 = &buffer[2];
    //...
    var bp15 = &buffer[15];
    *bp0  = x[0];
    *bp1  = x[1];
    *bp2  = x[2];
    //...
    *bp15 = x[15];
    //bp~経由でbufferをこね回す重い処理が以降に入る
}

IL2CPPを通してみると、以下のようなコードになります。

int32_t* V_0 = NULL;
int32_t* V_1 = NULL;
int32_t* V_2 = NULL;
int32_t* V_3 = NULL;
//...
{
    // var buffer = stackalloc int[16];
    int8_t* L_0 = (int8_t*) alloca((((uintptr_t)((int32_t)64))));
    memset(L_0, 0, (((uintptr_t)((int32_t)64))));
    // var bp0 = &buffer[0] ;
    int8_t* L_1 = (int8_t*)(L_0);
    V_0 = (int32_t*)(((uintptr_t)L_1));
    // var bp1 = &buffer[1] ;
    int8_t* L_2 = (int8_t*)L_1;
    V_1 = (int32_t*)(((uintptr_t)((int8_t*)il2cpp_codegen_add((intptr_t)L_2, (int32_t)4))));
    // var bp2 = &buffer[2] ;
    int8_t* L_3 = (int8_t*)L_2;
    V_2 = (int32_t*)(((uintptr_t)((int8_t*)il2cpp_codegen_add((intptr_t)L_3, (intptr_t)((intptr_t)il2cpp_codegen_multiply((intptr_t)(((intptr_t)2)), (int32_t)4))))));
    // var bp3 = &buffer[3] ;
    int8_t* L_4 = (int8_t*)L_3;
    V_3 = (int32_t*)(((uintptr_t)((int8_t*)il2cpp_codegen_add((intptr_t)L_4, (intptr_t)((intptr_t)il2cpp_codegen_multiply((intptr_t)(((intptr_t)3)), (int32_t)4))))));
    //...
    // *bp0  = x[0];
    int32_t* L_17 = V_0;
    int32_t* L_18 = ___x0;
    int32_t L_19 = *((int32_t*)L_18);
    *((int32_t*)L_17) = (int32_t)L_19;
    // *bp1  = x[1];
    int32_t* L_20 = V_1;
    int32_t* L_21 = ___x0;
    int32_t L_22 = *((int32_t*)((int32_t*)il2cpp_codegen_add((intptr_t)L_21, (int32_t)4)));
    *((int32_t*)L_20) = (int32_t)L_22;
    // *bp2  = x[2];
    int32_t* L_23 = V_2;
    int32_t* L_24 = ___x0;
    int32_t L_25 = *((int32_t*)((int32_t*)il2cpp_codegen_add((intptr_t)L_24, (intptr_t)((intptr_t)il2cpp_codegen_multiply((intptr_t)(((intptr_t)2)), (int32_t)4)))));
    *((int32_t*)L_23) = (int32_t)L_25;
    // *bp3  = x[3];
    int32_t* L_26 = V_3;
    int32_t* L_27 = ___x0;
    int32_t L_28 = *((int32_t*)((int32_t*)il2cpp_codegen_add((intptr_t)L_27, (intptr_t)((intptr_t)il2cpp_codegen_multiply((intptr_t)(((intptr_t)3)), (int32_t)4)))));
    *((int32_t*)L_26) = (int32_t)L_28;
    //...
}

stackallocした領域のポインタが全てスタック上に確保されて、使い回されていることがわかります。

これはもちろん、sizeof(IntPtr) * 要素数byteのスタックを追加で消費することになるので、要素数が少ないときにしか使えない、場面が非常に限定される対策ではあります。

ですが、暗号学的なアルゴリズムでは比較的サイズが固定的で小さいステートに対して流し込むデータのサイズに比例する回数の操作を行うというものが多くありますし、それらを実装する場面(どちらにせよニッチですが...)では同様に使えることもあるのではないか...と思います。

最後に

ここまで読んでくれた皆様、ありがとうございました。
ニッチな事例のための特殊な対応を紹介していきましたが、いかがでしたでしょうか。
僕はBurst最高だと思います。皆さんBurstを使いましょう。

DeNA 公式 Twitter アカウント @DeNAxTech では、Blog記事だけでなく色々な勉強会での登壇資料も発信しています。
この記事でDeNAの技術的な取り組みに興味を持った方は、是非フォローお願いします。

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

Unity Timelineで待ち受け処理っぽい事を実装する

初期化処理の完了待ちを行ったり、途中のロード完了を待ち受けたい時に
タイムラインでどうやって実装したら良いかわからなかったので調査してみました。

参考URL:
https://blogs.unity3d.com/jp/2018/04/05/creative-scripting-for-timeline/
https://github.com/UnityTechnologies/ATerribleKingdom

■前提
テスト用にUpdate文の中で数字を加算するだけの簡単な処理を作りました。
数値が1000を超えるとFLGがTrueになり、Trueになるとタイムライン側のループをやめて
次のクリップの処理に移行するようにしました。
(Update文内の処理はかなり雑)

■処理
数値が1000を超えるまではCubeが表示され、それ以降はSphereが表示されるという簡単なものです。

■Playble Assetが参照しているオブジェクトにあたっちされているソース

FlgChange.cs
using UnityEngine;

public class FlgChange : MonoBehaviour
{
    public bool IsEnd = false;
    int count = 0;
    // Start is called before the first frame update
    void Start()
    {
        IsEnd = false;
        count = 0;

    }

    // Update is called once per frame
    void Update()
    {
        count++;
        Debug.Log("count=" + count);
        if(count > 1000)
        {
            Debug.Log("IsEnd=" + IsEnd);
            IsEnd = true;
        }
    }
}

■Playble Asset

TestWaitAsset.cs
using UnityEngine;
using UnityEngine.Playables;

[System.Serializable]
public class TestWaitAsset : PlayableAsset
{
    public ExposedReference<FlgChange> FlgChangeValue;

    // Factory method that generates a playable based on this asset
    public override Playable CreatePlayable(PlayableGraph graph, GameObject go)
    {
        Debug.LogError("CreatePlayable");
        var testPlayable = ScriptPlayable<TestWaitBehaviour>.Create(graph);
        var testBehaviour = testPlayable.GetBehaviour();

        testBehaviour.TestFlgChange = FlgChangeValue.Resolve(graph.GetResolver());

        return testPlayable;
    }
}

下記の時間を巻き戻す処理でタイムラインの初めまで戻します、
数値指定すればそこに戻るはず。

おそらくフレーム単位の形でも時間単位の形でも戻すことも可能なはず(調べてません。)

// 時間を巻き戻す
(playable.GetGraph().GetResolver() as PlayableDirector).time = 0.00d;

■Playable Behaviour

TestWaitBehaviour.cs
using UnityEngine;
using UnityEngine.Playables;

// A behaviour that is attached to a playable
public class TestWaitBehaviour : PlayableBehaviour
{
    public FlgChange TestFlgChange;

    private bool clipStart = false;

    // Called when the owning graph starts playing
    public override void OnGraphStart(Playable playable)
    {
        clipStart = false;
    }

    // Called when the owning graph stops playing
    public override void OnGraphStop(Playable playable)
    {

    }

    // Called when the state of the playable is set to Play
    public override void OnBehaviourPlay(Playable playable, FrameData info)
    {
        clipStart = true;
    }

    // Called when the state of the playable is set to Paused
    public override void OnBehaviourPause(Playable playable, FrameData info)
    {
        if(playable.IsNull())
        {
            return;
        }
        Debug.LogError("IsEndLoop=" + TestFlgChange.IsEnd);
        Debug.LogError("clipStart=" + clipStart);
        if(clipStart)
        {
            if(!TestFlgChange.IsEnd)
            {
                // 時間を巻き戻す
                (playable.GetGraph().GetResolver() as PlayableDirector).time = 0.00d;
            }
        }
    }

    // Called each frame while the state is set to Play
    public override void PrepareFrame(Playable playable, FrameData info)
    {
    }
}

■タイムラインの設定(Playable Assetのセットされている区間を繰り返す)
Timelineビューの設定.png

■PlayableAssetの設定
PlayableAssetの設定.png

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

【Unity】HingeJointって便利なんよ…野球盤みたいにバット振り回してみた

概要

こちらの記事ではHingeJointについて紹介していきます。
サンプルコードをコピペすれば誰でも簡単に実装することができるので、初心者の方でも最悪理解しなくても使えるようにします!
できるだけ解説も入れていきますが、個人的な解釈が多いので参考程度にお願いします☺️

動作環境

MacOS : Catalina 10.15.7
Unity : 2019.4.16f1

HingeJointで野球盤のバットを作ってみる

下準備

バッティングセンターをイメージして作っていきます。
バッター(ただ棒)、ピッチャー(ボールを生成するスポナー)を用意しましょう。

スクリーンショット 2020-12-18 2.17.41.png
今回はCubeで棒を作成し、Create Emptyでボールを生成するオブジェクトを用意しました(実態を持たないので画像の矢印がスポナーです)。

HingeJoint使っていくよ

それでは早速HingeJointを入れていきます。
HingeJointはComponentなので、棒のオブジェクトにあるAdd Componentから、「Physics=>Hinge Joint」を選んでHingeJointを付与しましょう!
スクリーンショット 2020-12-18 2.38.41.png

HingeJointを無事付与できたら次は設定を行なっていきます。
Use Springのチェックをつけます。これにチェックをつけないとスイングしてくれません。
スクリーンショット 2020-12-18 2.52.28.png

続いてAnchorとAxisの説明をします。
こちらはスイングする回転軸を設定することができます。
HingeJointをつけたオブジェクトに近づいて確認してみると、実は新しい矢印が一つ小さく追加されています(以下の画像の茶色いやつ)。
スクリーンショット 2020-12-18 2.59.52.png

この茶色い矢印を軸に回転するのでこのままだと棒はバットを振るようなスイングにはなりません。
ので、まずはAxisをいじって回転軸の向きを変えます。
今回はxy平面に対してスイングさせていくので、Axisのx,yには0を、zには1を入れます(おそらく奥を向いてくれてるはず)。

続いて、このままだと棒の真ん中を支点に回転してしまうので、その支点位置を変更します。
Anchorの値をいじって棒の左側に矢印がくるように調整しましょう。
以下の画像のように矢印が移動して向きが変わっていればOKです。
(自分の場合、棒の左端に寄せています。)
スクリーンショット 2020-12-18 3.08.11.png

早速実装していきます!

ピッチャーの方のコードも載せますが、今回はHingeJointの説明をしたいので割愛させていただきます?
以下ボールを生成するスクリプトになります。スポナーにアタッチしましょう。

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

public class PicherScript : MonoBehaviour
{
    float lancherTimer;
    public GameObject ball;

    void Update()
    {
        lancherTimer += Time.deltaTime;
        BallLancher();
    }

    void BallLancher()
    {
        if (lancherTimer > 3)
        {
            Instantiate(ball, this.gameObject.transform.position, Quaternion.identity);
            lancherTimer = 0;
        }
    }
}

今回のballは、Sphereをそのまま用いています。SphereをPrefabにしてそれを生成元にしましょう。
一応仕様を話すと、lancherTimerが0になったらBallを生成するようになっています。
ので、lancherTimerの数字をいじれば生成頻度をいじることができます?
また、Ballには以下のスクリプトをアタッチしておきましょう。

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

public class BallMoveScript : MonoBehaviour
{
    [SerializeField] float speed = 5.0f;

    private Rigidbody rb;

    void Start()
    {
        rb = this.gameObject.GetComponent<Rigidbody>();
    }

    void Update()
    {
        Move();
        Destroy(this.gameObject, 4.0f);
        Debug.Log(rb.velocity);
    }

    void Move()
    {
        rb.velocity += new Vector3(0, -speed * Time.deltaTime, 0);
    }
}

速度を与えてバットに向かって飛んでくるようになっています。
speedをいじって飛んでくる速さを調整しましょう!
最後に本命のバットです。コードを書いて棒のオブジェクトにアタッチしてください。

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

public class SwingBatScript : MonoBehaviour
{

    public float spring = 40000;
    public float damper = 1000;
    public float openAngle = 75;
    public float closeAngle = -90;

    HingeJoint hj;

    JointSpring spr;

    void Start()
    {
        hj = GetComponent<HingeJoint>();
        spr = hj.spring;
        spr.targetPosition = closeAngle;
    }

    void Update()
    {
        if (Input.GetMouseButtonDown(0))
        {
            openBat();
        }

        if (Input.GetMouseButtonUp(0))
        {
            closeBat();
        }
    }

    public void openBat()
    {
        spr.spring = spring;
        spr.damper = damper;
        spr.targetPosition = openAngle;
        hj.spring = spr;
    }

    public void closeBat()
    {
        spr.spring = spring;
        spr.damper = damper;
        spr.targetPosition = closeAngle;
        hj.spring = spr;
    }
}

コードの解説

SwingScriptの解説を行なっていきます。
Update()内からいきます。

SwingScript.cs
    public float spring = 40000;
    public float damper = 1000;
    public float openAngle = 75;
    public float closeAngle = -90;

    HingeJoint hj;

    JointSpring spr;

HingeJointはtargetPositionという角度が存在し、その角度に向かって先ほどの茶色い矢印を軸にオブジェクトを回転してくれます。
なのでtargetPositionがどの角度なのか、どれくらいの速度でそのtargetPositionに向かってくれるのか、を設定する必要があるので、上記のような変数宣言を行っています。

「spring」=>この値を大きくすればするほどtargetPositionに向かう速さが速くなる。
「damper」=>この値を大きくするほどtargetPostionに向かう速さが遅くなる(正しくは角速度の値を調整しています)。
「openAngle」,「closeAngle」=>それぞれバットを振った後の角度、振るまえの角度を想定して設定しています。

続いてStart()のなかをみていきます。

SwingScript.cs
    void Start()
    {
        hj = GetComponent<HingeJoint>();
        spr = hj.spring;
        spr.targetPosition = closeAngle;
    }

変数宣言のところでHingeJointの情報をいれるための変数「hj」を用意したのでまずGetComponentで取得します。
springの中にtargetPositionと呼ばれる目標角度を初期設定するために最初に予めcloseAngleを代入しています。

続いてUpdate()内をみていきます。

SwingScript
void Update()
    {
        if (Input.GetMouseButtonDown(0))
        {
            openBat();
        }

        if (Input.GetMouseButtonUp(0))
        {
            closeBat();
        }
    }

左クリックを押したらバットを振り、クリックの指を離したらバットを戻すようにトリガーの設定をしています。

続いてopneBat()とcloseBat()の中身を見ていきます。

SwingScript
public void openBat()
    {
        spr.spring = spring;
        spr.damper = damper;
        spr.targetPosition = openAngle;
        hj.spring = spr;
    }

    public void closeBat()
    {
        spr.spring = spring;
        spr.damper = damper;
        spr.targetPosition = closeAngle;
        hj.spring = spr;
    }

先ほどの変数宣言で用意した値を代入しています。

プレビューして確認してみよう!

実際にクリックするとopenAngle(75度)に向かって棒が回転したり、クリックを離すとcloseAngle(-90度)に向かって棒が戻っているのが確認できましたか?
実際にボールを打てるとめっちゃ気持ちいですよね!
名称未設定.gif

以上で終わります!ここまでお疲れ様でしたー!
またどこかで〜?

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

ShaderGraph レシピ #5 HEX柄(ハニカム模様)

HEX柄(ハニカム模様)

スクリーンショット 2020-12-18 1.07.28.png

六角形の有機的な柄です。
これを作れると、バリア表現など様々な演出で重宝します。

作り方

構造を説明するのが難しいので、こういうものだと覚えましょう。
また自分で作れなくてもUnity公式が配布しているので困ったらそれをつかいましょう。

1.タイルを作ります

前半部分の全体像
スクリーンショット 2020-12-18 1.14.37.png

まずストライプを作って、タイリングした1つ辺りのサイズの大きさに切り出します。

スクリーンショット 2020-12-18 1.36.53.png

スクリーンショット 2020-12-18 1.41.24.png
これのことです。今はTiling=10で作ってるので、だいたい10/1くらいの部分にストライブが出ています。

これと、一番最初にMultiplyした結果をVector2のxとy成分にします。
スクリーンショット 2020-12-18 1.46.58.png
この部分ですね。
そうすると実際には下のような画像がつくられます
スクリーンショット 2020-12-18 1.45.44.png

それをModuloノードを使って1,1で割った余りを出すときれいにタイリングされます。理屈はよくわかりません。
スクリーンショット 2020-12-18 1.43.44.png

この結果から0.5を引いて、負数の部分を作った後、absoluteノードで絶対値を取り出すとHEXのもとになるタイルが作れます。

スクリーンショット 2020-12-18 1.50.59.png

2.ヘックスに仕上げます

後半部分の全体像

スクリーンショット 2020-12-18 1.18.06.png

1ノードずつ追いかけていくと、なんとなく過程が分かるのですが、要するに前半戦で作ったタイルの各成分を一度取り出して、増幅して、合成して、重なりが薄い部分(結果的に値が0に近い部分)でHEXの線を作ります。

スクリーンショット 2020-12-18 1.59.33.png

この部分ですね。
あとは値を倍にしてメリハリつけた後、Smoothstepノードを使って2値化して完成です。

スクリーンショット 2020-12-18 1.59.59.png

SmoothstepノードEdge2の値の閾値を大きくするとHEXの線が太くなります。

スクリーンショット 2020-12-18 2.03.22.png

スクリーンショット 2020-12-18 2.03.41.png

完成!

スクリーンショット 2020-12-18 1.07.28.png

その他のレシピはShaderGraphレシピ一覧にまとまっています

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

【Unity】Rayってめっちゃ便利なんよ…簡単なミニゲーム風シューティングゲーム作ってみた

概要

こちらの記事ではRayCastについて紹介していきます。
こちらはとても便利な機能で、Rayを自由に操ることができればゲーム開発に幅が広がること間違いなしです!?
今回はRayCastが具体的にどういうもので、どんなことができるのかを簡単に紹介できればと思ってますので、よかったら最後までお付き合いください。
サンプルコードをコピペすれば誰でも簡単に実装することができるので、初心者の方でも最悪理解しなくても使えるようにします!
できるだけ解説も入れていきますが、個人的な解釈が多いので参考程度にお願いします☺️

動作環境

MacOS : Catalina 10.15.7
Unity : 2019.4.16f1

RayCastとは

その名の通り、RayをCastするので「光を投げる」です。
光を投げるってよくわからない人は、簡単にいえば光を飛ばしていると思ってくれればOKです。
Rayを飛ばした時に、そのRayが何かに衝突した時に何かを処理するコードを書いたりいろいろできます。

チュートリアル

実際Unityにはネットにたくさんの記事が転がっていますので、Rayの使い方の紹介などはもう腐るほどあります。
こちらの記事が個人的にとても参考になるし、簡単で解説付きで分かりやすかったので
初めて使うよって人は、チュートリアルと称してまずそちらでいろいろ遊んでみてください!

Rayを飛ばして簡単なシューティングゲームを作ってみた

チュートリアルを終えた方は、早速ちょっと応用してみましょう!
CameraからRayを飛ばして画面に写っている敵をクリックして破壊するちょっとしたミニゲームを再現してみます!

以下のコードを書いてCameraにスクリプトをアタッチしてください!

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

public class RazerScript : MonoBehaviour
{
    void Update()
    {
        if (Input.GetMouseButtonDown(0))
        {
            RayCast();
        }
    }

    void RayCast()
    {
        //↓Rayの作成↓       
        Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
        RaycastHit hit;

        Debug.DrawRay(ray.origin, ray.direction * 10, Color.red, 5);

        if (Physics.Raycast(ray, out hit, 10.0f))
        {
            Destroy(hit.collider.gameObject);
        }
    }
}

コードの解説

Update()内からいきます。

RazerScript.cs
    void Update()
    {
        if (Input.GetMouseButtonDown(0))
        {
            RayCast();
        }
    }

if文の条件は「マウスの左クリックを押した瞬間」で、処理したい内容は
下の方に書いた「RayCast()を処理して!」→「光を飛ばして!」ってことになります。

続いてRayCast()の中身になります。

RazerScript.cs
    void RayCast()
    {
        //↓Rayの作成↓       
        Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);

最初にRayを作成しています。

RazerScript.cs
        RaycastHit hit;

そのすぐ下で「RaycastHit」という型の「hit」という変数を宣言します。
これは衝突するオブジェクトの情報を入れるための箱になります。

        Debug.DrawRay(ray.origin, ray.direction * 10, Color.red, 5);

こちらがRayを可視化するための一文です。
Debug.Logに出だしが似てるのでなんとなく効果も同じ感じがしますね。
色も指定できるので、今回は赤色で試しています。自分の好きな色にしてみてください。
最後の「5」はRayの描写距離を設定しています。大きくすればより遠くまでRayを可視化することができます。

        if (Physics.Raycast(ray, out hit, 10.0f))
        {
            Destroy(hit.collider.gameObject);
        }
    }

そしてこれが、Rayが何かの対象に衝突した時、その対象物を破壊(Destroy)するコードになります。
Destroy()はゲーム開発においてかなり多用すると思うので、マスターしておくといいです。
上記でも記述したように、「hit」には衝突した対象物の情報が入っています。
「hit.collider.gameObject」とすることで対象物のGameObjectを指定することができます。

自由に実装して遊んでみよう!

敵の生成や落下する実装は今回は省きますが、ちょっと凝れば下のような簡単なシューティングゲームを作ることができます。
Rayは光線なので、一瞬で遠くまで届きますし、衝突をトリガーにいろんなことができるので、応用すれば幅広い用途で使用することができます。
(青いCubeが300点加点、赤いCubeが-100点されるシステムです。)
Qiita RayCast.gif

ただし、こんな便利なRayCastも実は重い処理になります。
ので、あまり多用しすぎると少々危険なので程々にしましょう笑

以上で終わります!ここまでお疲れ様でしたー!
またどこかで〜?

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

【Unity】単一シーン/マルチシーンで良い感じに状態管理がしたい

はじめに

Unityを使ってインタラクティブなインスタレーションをよく作っていますが、ゲーム等と同様に「タイトル画面」→「体験画面」→「結果画面」→「タイトル画面」...のような画面遷移・状態制御をしなければならないことが多くあります。

毎回スクラッチで作っていくのもしんどくなってきたので、良い感じにやる方法を調べて、テンプレートにしていきたく、備忘録的にまとめました。

Unityでの状態管理を良い感じにしてくれる君

調べていたら、下記の2つのライブラリを発見したので、それぞれ解説してみたいと思います。

ImtStateMachine

ImtStateMachine は、Sinoa氏が開発している IceMilkTea という、Unityゲームエンジン向け開発サポートライブラリの一部です。
ソースが1ファイルしかないので、導入が簡単でパフォーマンスも高く、ライセンスがゆるい点などが魅力的です。

このライブラリは、1つのシーンにおいて各オブジェクトの状態制御が簡単に行えるので、 1シーンで完結する場合の状態制御をしたいときに有効 だと思います。

下記の記事が詳しく解説してくれているので、非常にわかりやすいです。

Unidux

こちらは Redux という、状態を管理するフレームワークをUnityで扱うためのプラグインです。
もともとReduxは、ReactJSが扱うUIのstate(状態)を管理をするためのフレームワークとしてつくられたため、Webアプリを作る人には馴染みがあるかもしれません。

Reduxの説明に関しては、下記の記事が参考になりました。

Unidux では、PageScene という概念でそれぞれの状態(State)を管理しています。
いわゆる1画面が1Pageに相当し、1つのPageのなかに1~n[個]のSceneがあるようなイメージです。

Uniduxではシーン遷移機能を兼ね備えており、シーン遷移時(Page遷移時)にパラメータを渡すこともできるので、マルチシーンを行き来する場合や、1画面に複数シーンを配置したいときなどの状態管理に対して有効 であると思いました。

Uniduxを用いたマルチシーンでの状態管理のサンプル

今回はUniduxを用いた複数シーンの状態管理を行うサンプルを作ってみました。
本家のSceneTrasitionのサンプルや、下記の記事を大いに参考にして作成しました。

最終サンプル

こちらにアップしました。

開発環境

  • Unity 2019.4.14f1
  • Unidux v0.3.4
  • UniRx v7.1.0

Page, Scene構成

今回は以下のようなPage, Scene構成にしてみたいと思います。
どのPageにも共通して存在する Base というSceneの上で、それぞれのPageに対応しているSceneが切り替わるイメージです。
状態(State)に応じてPageが切り替わり、それに応じて対応しているSceneが切り替わります。
Contents というSceneでは、異なる2つの状態(Type)を持つようにし、自由に行き来できるようにします。

Scene Page Type
Base (Permanent) -
Title Title -
Contents Contents Type1
Contents Contents Type2
Result Result -

State定義

まず、管理したい状態(State)の定義を行います。

1: それぞれのPage, Sceneの状態を PageName.cs , SceneName.cs に記述します。

PageName.cs
public enum PageName
{
    PAGE_TITLE,
    PAGE_CONTENTS,
    PAGE_RESULT,
}

2: 状態管理したいオブジェクトを State クラスに記述します。

State.cs
[Serializable]
public class State : StateBase
{
    public SceneState<SceneName> Scene = new SceneState<SceneName>();
    public PageState<PageName> Page = new PageState<PageName>();
}

3: 各Pageと、そこに追加したいSceneの対応を SceneConfig クラスに記述します。

SceneConfig.cs
public class SceneConfig : ISceneConfig<SceneName, PageName>
{
    private IDictionary<SceneName, int> _categoryMap;
    private IDictionary<PageName, SceneName[]> _pageMap;

    public IDictionary<SceneName, int> CategoryMap
    {
        get
        {
            return this._categoryMap = this._categoryMap ?? new Dictionary<SceneName, int>()
            {
                {SceneName.Base, SceneCategory.Permanent},
                {SceneName.Title, SceneCategory.Page},
                {SceneName.Contents, SceneCategory.Page},
                {SceneName.Result, SceneCategory.Page},
            };
        }
    }

    public IDictionary<PageName, SceneName[]> PageMap
    {
        get
        {
            return this._pageMap = this._pageMap ?? new Dictionary<PageName, SceneName[]>()
            {
                {PageName.PAGE_TITLE, new[] {SceneName.Title}},
                {PageName.PAGE_CONTENTS, new[] {SceneName.Contents}},
                {PageName.PAGE_RESULT, new[] {SceneName.Result}},
            };
        }
    }
}

Uniduxの設定

次に、Uniduxを使うための設定をいくつか行います。

PageData

各Pageにて、保持しておきたいデータを定義します。
その際に IPageData というinterfaceを継承してclassを作成しておくことで、Uniduxのシングルトンからパラメータがアクセスできるようにしておきます。
また、今回はInspectorから ContentsType を指定しておきたかったので、 [Serializable] をつけています。

ContentsPageData.cs
[Serializable]
public class ContentsPageData : IPageData
{
    public ContentsType ContentsType;

    public int MouseClickCount
    {
        get => this.mouseClickCount;
        set => this.mouseClickCount = value;
    }

    private int mouseClickCount;

    public ContentsPageData()
    {
        this.mouseClickCount = 0;
    }

    public ContentsPageData(ContentsType ContentsType, int mouseClickCount)
    {
        this.ContentsType = ContentsType;
        if(mouseClickCount > 0)
        {
            this.mouseClickCount = mouseClickCount;
        }
        else
        {
            this.mouseClickCount = 0;
        }
    }
}

Unidux(Singleton)

どのページにも共通で存在する Base シーンのHierarchyに、Uniduxのシングルトンを作成します。

png

Dispatcher

Dispatcherは、状態(State)を変更するActionを、後述するReducerに伝える役割を持っています。
UniduxではUniRxが使えるので、購読しているイベント(Obeservable)発生時に応じてReducerにActionを渡しています。

Reducer

Reducerは、管理している状態(State)を変更できる存在です。
Dispatcherから送られてきたActionを実行し、状態を変更します。
今回は、この部分はサンプルからほとんど変えていません。

PageReducer.cs
public class PageReducer : PageDuck<PageName, SceneName>.Reducer
{
    public PageReducer() : base(new SceneConfig())
    {
    }

    public override object ReduceAny(object state, object action)
    {
        // It's required for detecting page scene state location
        var _state = (State) state;
        var _action = (PageDuck<PageName, SceneName>.IPageAction) action;
        _state.Page = base.Reduce(_state.Page, _state.Scene, _action);
        return state;
    }
}

Watcher

管理している状態に変更があった際に、それをUnity側で監視するためのWatcherを、共通の Base シーンに追加します。
ここで実際のScene変更 SceneManager.LoadSceneAsync() 等を行っています。

シーン遷移

今回はシーン遷移するためのActionとしてButtonを用います。
Dispatcherに、そのイベントが発生したら(今回はボタンを押したら)PageやSceneが切り替わるように設定をしていきます。

Subscribe()のなかで Unidux.Dispatch(action) することで、Reducerにページ情報 PageDuck<PageName, SceneName> を送信(Push)します。

this.GetComponent<Button>()
    .OnClickAsObservable()
    .Select(_ => PageDuck<PageName, SceneName>.ActionCreator.Push(PageName.PAGE_TITLE))
    .Subscribe(action => Unidux.Dispatch(action))
    .AddTo(this);

また、 Contents シーンでは画面をクリックした回数をカウントして保持しておき、 Result シーンに遷移するときにその数値を Score として渡すようにしてみます。

CountMouseClickDispatcher.cs
public class CountMouseClickDispatcher : MonoBehaviour
{
    private ContentsPageData contentsPageData;

    // Start is called before the first frame update
    void Start()
    {
        this.UpdateAsObservable()
            .Where(_ => Input.GetMouseButtonDown(0))
            .Where(_ => !EventSystem.current.IsPointerOverGameObject())
            .Subscribe(_ => 
            {
                var type = Unidux.State.Page.GetData<ContentsPageData>().ContentsType;
                var count = Unidux.State.Page.GetData<ContentsPageData>().MouseClickCount;
                count += 1;
                this.contentsPageData = new ContentsPageData(type, count);

                this.ChangeSceneData();
            })
            .AddTo(this);
    }

    private void ChangeSceneData()
    {
        var action = PageDuck<PageName, SceneName>.ActionCreator.SetData(this.contentsPageData);
        Unidux.Dispatch(action);
    }
}
GoToResultButtonDispatcher.cs
public class GoToResultButtonDispatcher : MonoBehaviour
{
    private int score;

    void Start()
    {
         this.GetComponent<Button>().OnClickAsObservable()
            .Select(_ => this.score = Unidux.State.Page.GetData<ContentsPageData>().MouseClickCount)
            .Subscribe(_ => this.DispatchPageData())
            .AddTo(this);
    }

    private void DispatchPageData()
    {
        var action = PageDuck<PageName, SceneName>.ActionCreator.Push(PageName.PAGE_RESULT, new ResultPageData(this.score));
        Unidux.Dispatch(action);
    }
}

シーン描画

先述しましたが、管理しているStateの情報を取得することができるので、それをRendererで描画してみたいと思います。

Renderer

Unidux.State.Page.GetData<T> でシングルトン内のパラメータにアクセスすることができます。

private void Render(State state)
{
    ContentsPageData pageData = state.Page.GetData<ContentsPageData>();
    this.ContentsTypeText.text = pageData.ContentsType.ToString();
}

+α: ページ遷移時にフェードイン・アウトを足す

ページを遷移するときにフェード等のトランジションを使うこともよくあるので、
プラスアルファとして今回はシンプルな黒のフェードイン・アウトを追加してみようと思います。

条件として、フェードイン・アウトはPage間の遷移時にのみ実行され、同一Pageでパラメータが変更された場合には実行されないようにします。
(つまり、 Contents シーンでTypeが変更されても、フェードが発動しないようにします)

1: まず、共通で使用する Base シーンにフェード用の Image を追加します。
各Sceneが Base シーンの上に描画されてもフェード用の Image がいちばん手前になるように、 CanvasSort Order1 に変更します。

png

2: フェードアニメーションを行う SceneTransitionFaderRenderer クラスを作成します。
フェード自体は Image の透明度を制御するだけなので、[0,1]でいいかんじにアニメーションするやつが書ければOKです。

3: Page全体を監視している PageWatcher にて、状態変更の命令があったら最初にフェードインが走るように設定をします。

4: フェードインが完了したら、SceneTransitionFaderRenderer 内でフラグ CanSceneTransition を立てるようにします。

5: 先ほどのフラグを PageWatcher で監視しておくことで、フェードインが完了した後に、Page, Sceneの変更処理およびフェードアウトが走るようにします。

Unidux.Subject
      .Where(state => state.Page.IsStateChanged && this.currentPageName != state.Page.Name)
      .Subscribe(_ => this.faderRenderer.FadeIn())
      .AddTo(this);

this.UpdateAsObservable()
    .Where(_ => this.faderRenderer.CanSceneTransition)
    .Subscribe(_ => 
    {
        this.UpdatePage(Unidux.State);
        this.sceneWatcher.UpdateScenes(Unidux.State.Scene);
        this.currentPageName = Unidux.State.Page.Name;
    })
    .AddTo(this);

これで完成です。

gif

おわりに

Unityで良い感じに状態管理をしたく、 ImtStateMachineUnidux について調べました。
ImtStateMachine はひとつのシーン内での状態管理に便利で、 Unidux は複数シーンを行き来するときの状態管理に便利であることがわかりました。

Uniduxに関しては、実際にサンプルを作ってみました。

まだまだ改良の余地はかなりあると思いますが、マルチシーンにて状態管理するサンプルが増えた程度に思っていただければ幸いです。

参考リンクまとめ

各所に記載していた参考記事や、その他参考にした記事をまとめています。

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