20201017のUnityに関する記事は10件です。

ElectronでVulkanを動かす技術は新しい形のゲームエンジンを作り出すか?

この話の続きみたいな記事です。

https://qiita.com/nanikore55554/items/5a620bfe4f7956ae8a85

Electron(より正確に言うとNode.js上)である程度現実的な形でVulkanを動かす事に成功しました。ですが、大半の人から見れば「で、それが何の役に立つの?」と言われると思います。

で、考えました。この技術の使いみちを。

これを使えば個人でもUnityのようなゲームエンジンを開発可能?

個人的に真っ先に思ったのが「これを使えばゲームエンジン(正確には3Dモデリングツールなどといった3D開発ソフト)を個人や小さな会社でも簡単に作れるようになるのではないのか?」ということです。

というのもElectronやNW.jsなどのNode.jsを用いたGUIツールキットはマルチプラットフォームが簡単にできるということの他にHTMLやCSSの技術を転用できるため、非常に作りやすい(≒人員を集めやすく、開発スピードが早い)という長所があります。(この辺は好き嫌いが分かれやすく、node.jsの方が使いづらくて嫌だという人も多いでしょうが)

そして、この技術は同時にある二つの問題も解決します。それはGUIツールキットの先行きがElectronを除きどれもこれも不安定であること、もう一つは3D APIとGUIツールキットの相性が致命的に悪いという事です。

GUIツールキットの問題点の具体例

各種GUIツールキットの問題点を書いてみました。
これは私個人の感想も多分に含まれていますので客観的ではないかもしれませんが・・・。

Qt

  1. C++のC++のクロスプラットフォームのGUI
  2. かなりメジャー
  3. 使いやすい
  4. Vulkanにも対応可能(ただ情報が少ない)
  5. Autodesk社のMayaなど3Dツールでの採用実績あり
  6. 無料だとLGPLかGPLを強制させられる
  7. 有料版の値段が高すぎる(年に50万ぐらいかかる模様)
  8. バグの多さなどプロジェクトの先行きが不安視されている・・のか? (そういう話を聞いた事があるけど実際どうなのかよく分からない。)

C++ Builder

  1. C++のクロスプラットフォームのGUI
  2. C#の感覚で使えて使いやすい(そもそもC#の元祖の一つらしい)
  3. Vulkanが使えない(理論上は可能なようですが、現実的には相当きつい)
  4. マイナー(昔はよく使われていたようですが)

wxWidgets

  1. 昔からあるC++のクロスプラットフォームのGUIツールキット
  2. 専用ライセンスだがソースコードの公開及びリバースエンジニアリングを認める必要性がない
  3. Vulkanが動く(Githubに参考となるソースコード有り)
  4. サンプルになるコードがあまりない
  5. 使いづらい
  6. マイナー(知名度とラッパーの多さの割にあまり使われていない模様)

ImGUI

  1. OpenGL上で動くGUI。VulkanやDirectX上にも対応可能
  2. 使いづらい
  3. 日本語を打つとき問題有り
  4. そもそもデバッグなどの埋め込み用であり本格的なGUIソフトには不向き
  5. ImGUIと似たようなライブラリはいくつかあるが、どれもImGUIと同じ欠点を抱えている。

Xamarin

  1. MSが作ったC#で動くクロスプラットフォームのGUIツールキット
  2. Vulkanを使えない (理論上は不可能じゃないようですが)
  3. オワコン化扱いしているひとが結構いる

React Native

  1. Facebookが作ったC#で動くクロスプラットフォームのGUIツールキット
  2. Googleのサジェストでオワコン化扱いされている

Flutter

  1. 最近流行りのGUIツールキット。基本的にはスマホ用
  2. 使いやすい
  3. 最近はPC用の開発が進んでいる。
  4. 3D APIに対応していない(そのうち対応してもおかしくなさそうですが)
  5. まだ最近できたばかりで突然オワコン化する危険がある。(多分大丈夫でしょうが)

Electron

  1. 「ブラウザを内包して動かす」という力技で動くGUIツールキット
  2. HTMLとCSSで動くので作りやすい。
  3. Visual Studio CodeやSlackなどの採用実績有り
  4. 理由は不明だが、マイナーだったりオワコン扱いされていない。
  5. ブラウザなので重い、メモリをやたら使う
  6. 3D APIがWebGLしか使えない(よってゲームエンジンを作る時に大幅に不利になる)
  7. ただし、最後二つの問題点は・・・

要するに

長々と書いていきましたが、要するにどのGUIツールキットも致命的な問題点を抱えていて、オワコン化したりマイナーになるスピードが非常に早い。おまけに3D APIと致命的に相性が悪い

よって、個人や小さな会社がUnityのようなゲームエンジンやBlenderのような3Dモデリングツールを作るのは難易度があまりにも高すぎます。
Unity社やUEのEpic Gamesみたいな大企業ならまだしも、小さな会社が3D APIと相性が良いGUIツールキットを作るなんて無理でしょう(そもそもGUIツールキットを作ること自体難易度があまりにも高い)。しかも、GUIツールキットをを自作しても一気にオワコン化するリスクを抱えます。このリスクは大企業でも非常に高いです。

しかし、Electronはこの欠点を抱えていません。

もしElectronでゲームエンジンを作れたら

上で考察したようにGUIツールキットでオワコンに中々ならないと考えられるのはElectronのようなウェブブラウザベースのGUIツールキットぐらいです。それどころかあのMSが非常にメモリを使う、重いという欠点があるにも関わらずわざわざ自社製のGUIツールキットを使わずにElectronでVisual Studio Codeを作っているぐらいです。それだけElectronの技術は有用であり、他のGUIツールキットを使う事がElecronと比べて差がつけられている事が伺いしれます。

つまり、「Electronで3D APIが動けば、ゲームエンジンを作る上でかなり有利になる」というわけです。しかし、今までのElectronはWebGLしか動かず、ゲームエンジンを作る上ではかなり不利でした。ですが、最初に書いたように「ElectronでVulkanを動かすことに成功した」ため、この欠点が消えました。

メモリをやたら使う欠点をどう捉えるか?

しかし、Electronにはもう一つ問題があります。それは「メモリをやたら使う」事です。また、上の「ElectronでVulkanを動かす技術」は割とCPUに負担がかかります。

ですが、この問題はもはや些細な事だと思います。理由はPCの性能が上がっており、Electrnの重さはあまり気にならない状況まで来ているからです。でなきゃ、MSはわざわざElectronでVSCodeを作ったりしないでしょう。

かのRubyの作者であるまつもとゆきひろ氏が「コンピュータ資源を「人間のために使っていい時代」が来た」という趣旨の発言をしましたが、まさにゲームエンジン制作もそういう時代に来ていると思います。

無論、UEのような最新のグラフィック技術をフルに活用したゲームエンジンを作るのなら話は別ですが、それとはまた別の方向、最新の性能とグラフィック技術をフル活用しない代わりに作りやすさや拡張のしやすさを重視したゲームエンジンを作る場合、Electronは非常に有用でしょう。

個人でゲームエンジンを作る際の大きな助けとなるVulkan

もう一つ、ゲームエンジン制作で有利なのはVulkanが使えるということです。

知っている方も多いと思いますが、VulkanはOpenGLに代わるクロスプラットフォームの3D APIです。つまり、これでプログラムを書けば多くのハードで3Dのプログラムがどこでも動く代物です。実際にWindowsとAndroid、更にはあのニンテンドースイッチでも動きます。

しかも本来Vulkanに対応していないはずのMacとiOSでも動きます。というのもMoltenVKと言いまして、metalとVulkanを互換するライブラリがあるからです。
(普通にiOSとmac専用APIであるMetalを使ったほうが動作が早いプログラムを作れるでしょうが。)

更にいえばXbox Series Xでも少なくとも理論上は動きます。MoltenVKと同じでrostkatzeというDirectXとも互換するためのライブラリがあるのです。
(ただ、コンソールゲーム機はオープンソースのソフトウェアの使用制限が大変きついため実際は規約で不可能の可能性も非常に高いですが・・・)。

極端な事を言えばVulkanはOpenGLでは達成できなかった3D APIのデファクトスタンダードになる可能性を秘めており、Vulkanのソースさえ書けば3Dソフトのクロスプラットフォームは簡単に実現可能というわけです。
(最もそのVulkanのソースを書くこと自体が大変難易度が高いわけですが)

最後に感じたこと

  1. C++ Builderを作っているエンバカデロ・テクノロジーズはVulkanを使えるようにするべきでは?
  2. Firefoxを作っているMozilaはFirefox版Electronを作るべきでは?
  3. 結局書いてきたけどエンジンを自作するならUnityとUEを使ったほうが早いと言われても仕方ないと思う
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

UnityのStandardAssetsで、ThirdPersonControllerが機能しなかった原因はBuildSettingsのPlatform選択にあった

起きていたこと

Unityでキャラクターを動かしたくてStandardAssetsをインポートしました。
ThirdPersonControllerをヒエラルキーにドラッグアンドドロップした後、早速キャラクターを動かそうとしてみたのですが、

  • 矢印コントローラーを押下しても移動ができない
  • スペースキーを押下してもジャンプしない
  • しゃがむ(cキー)事だけできる

という状態でした。
isan.gif
しゃがむことしかできないイーサン

原因

Build SettingsのPlatformがAndroidになっていた。
build-settings.png

PCに変更したところ、矢印キー押下でキャラクターが動くようになりました。

StandardAssetsを、スマホ時のキャラクター操作に使う場合はジョイステックを表示することになる

元々は、スマホ時にフリック,スワイプでキャラクターを操作したくてStandardAssetsを利用しました。
ですが調べて見たところ、StandardAssetsでスマホの操作を行う場合はジョイスティックによる操作になるみたいでした。
なのでそもそもStandardAssetsを利用しない方向となりました。
参考記事: Unity5版Standard Assetsを使って仮想ジョイスティックを実装

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

【Unity】Tilemapに当たり判定をつける

Rigidbody2Dをつける

Body TypeをStaticにする。
image.png

Tilemap Collider 2DとComposite Cllider 2Dをつける

Used By Compositeにチェックをつける。
こうすることでパネル一つ一つにcolliderが付いていたが、パネルの塊で一つのcolliderがつくようになる。
image.png

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

【Unity】ステージ(Tilemap)に当たり判定をつける

Rigidbody2Dをつける

Body TypeをStaticにする。
image.png

Tilemap Collider 2DとComposite Cllider 2Dをつける

Used By Compositeにチェックをつける。
こうすることでパネル一つ一つにcolliderが付いていたが、パネルの塊で一つのcolliderがつくようになる。
image.png

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

【Unity】ステージの作り方

①Tilesフォルダーを作成

こちらはステージ関係のものを保存するために使用します。
image.png

②Tile Paletteを選択

左上のメニューからWindow > 2D > Tile Palette
image.png

③paletteを作成

Create New Paletteをクリックし、nameを「StagePalette」にする。
Createをクリック
image.png

④Paletteに画像をドラッグアンドドロップ

image.png

あとはメニューの筆や消しゴムを使ってステージを書きましょう!

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

Unity講座 #5コンパスを作ってみる

はじめに

某インターンで制作物のプレゼンをする必要があったため、一年生の頃に作ったゲームをつい最近アップデートしました。
そこで新たに実装した機能の実装方法を解説しようと思い、この記事を書いています。

せっかくなのでUnity講座の一環としてしまおうということで書いていますが、前回までの内容に比べ、内積・外積といった数学的な内容のものも含んでいるので、前提知識がないとちょっと厳しいかもしれません。

数学のお話はなるべく補足説明を入れて解説しようと思いますが「分からない」とか「間違いがあるよ」などなどあればコメントください。
それでは解説に入っていきましょう。

機能概要

Videotogif.gif

自分のゲームは簡単に言うと敵が追ってきて、捕まるとゲームオーバーというゲームです。
したがって、どのくらい近いかを確認したりしたいですが、ミニマップのようなものを作ってしまうと簡単になりすぎてしまうかなと考えました。
これまでの機能として、後方確認はいつでもできましたが、壁の向こう側にいたりすると全く気づけませんでした。
そこで、方向だけは常に分かるようにしようと考えました。
方角だけでもわかれば逃げやすくなるだろうということですね。

一応距離をついでに表示させたりも考えましたが、そこまで来るとミニマップと変わらないかなぁと思って、今回は実装を見送りました。
自分のゲームでは追ってくる敵に対してコンパスを適用していますが、ゴール地点だったり、アイテムを目標にすることも可能ですので、使えるゲームジャンルは割と広いかなと思っています。

それでは早速実装に入っていきましょう。

前準備(Canvas&デバッグ用)

まず最初に、コンパスのリングとデバッグ用のテキストを準備しました。
console.log()はたしかに便利なのですが、プレイ中に確認しにくいのと、Update内で実行している処理で呼び出すと、ログが埋まってしまうので、実装中は画面に数値を表示することをおすすめします。
準備.png

空オブジェクトのEnemyCompassの直下にUI/Textを作ります。
今回は名前を変えていませんが、プロジェクト内に既に複数のTextが存在しているならば、EnemyCompassDebugTextとかに変更しておいたほうがいいかもしれません。

円形フレーム素材(今回お借りしたもの)

あとはコンパスのフレームに使えそうな素材を見繕ってきました。
「円形フレーム」などで検索すれば、他にもいろいろな素材サイトが見つかると思うので、自分好みのフレームを探してきてください。
コンパスリング.png
コンパスリング2.png

今回のコンパスでは下手に色がついているよりは黒単色のほうがいいかな、と思いImageのColorを黒にしました。
コンパスの後ろの画面を隠さないように、アルファ値をを100くらいにして半透明にしておきましたが、別に気にならないという方は変更しなくても大丈夫です。

人によって違うので割愛しますが、ここで目標物のアイコンなどの素材を準備して、置いておくといいと思います。
ここまでできればコンパス機能の見た目は大丈夫かなと思います。

目標物との角度計算

内積・外積

さて、ここからは数学のお時間です。
流石にベクトルの話からするのは大変なので、高校数学の美しい物語などを確認してもらうとします。
今回は実装方法の話をしたいので、なぜこうなるかについては解説しません。
気になる方はベクトルの勉強をしてみてください。

コンパス機能を実装するためには、自分の正面方向のベクトルと目標物への方向ベクトルのなす角の大きさを計算する必要があります。
正面方向のベクトルは
Player.transform.forward
で、目標物への方向ベクトルは
Target.transform.position-Player.transform.position
で求めることができますね(ベクトルABの求め方は原点をOとしてベクトルOB - ベクトルOAで求めるという公式)

この2つのベクトルのなす角の大きさを調べるのに、内積や外積が役に立つのです。

※一応図を自作しましたが正直出来はいまいちなので、参考リンクの画像などを参照してください

内積は2つのベクトルの各成分の掛け算を取ります。
↑a・↑b=a.x*b.x+a.y*b.y
dot.png

上の式と、この図で示した式を使うことで、2つのベクトルのなす角の大きさをθとしたとき、cosθを求めることができるのです。
今回は3Dゲームなのでy方向の情報もありますが、今回はy方向の情報を切り捨てて二次元の内積を求めてくれる関数、dot_2dを作成しました。
公式ではaベクトルの大きさとbベクトルの大きさを掛けた上で内積の大きさを割っていますが、どちらのベクトルも正規化を行い、大きさを1にしてから計算することで手間を省けます。

float dot_2d(Vector3 aVec, Vector3 bVec)
{
   return (aVec.x * bVec.x + aVec.z * bVec.z);
}

さて、これだけでコンパスができると思いきや、cos関数はcosθ=cos(-θ)という厄介な(ときには便利な)性質があります。
つまり、なす角の大きさはわかっても、正面から見て右に向かう角の大きさなのか、左に向かう角の大きさなのか区別がつかないのです。

そこで使うのが外積です。
こっちは大学数学の内容なので知らない人のほうが多いと思いますが、これを知らずにゲームを作るのはすごく難しいと言うくらい、使えるものです。
こっちは外部サイトの図を見てきてくださいと言う形になりますが、求めることで2つのベクトルのなす角の大きさをθとしたときに、sinθを求めることが出来ます。

内積と違い、2次元と3次元で意味が違います。
2次元のときにはaベクトルとbベクトルで作られる平行四辺形の面積を、3次元ではaベクトルとbベクトルのなす平面の法線ベクトルを求めます。
面積(数値)と法線ベクトル(ベクトル)というように、結果の単位が変わるのは面白いですね。

外積は、内積と違ってマイナスの値も取ります。
また、aベクトルとbベクトルの外積と、bベクトルとaベクトルの外積の値は違うというように、演算の順序が関係してきます。
aベクトルとbベクトルの外積は、aベクトルをx軸正の方向と重なるように置いたとき、
偏角が0からπの間なら正の値に、
偏角がπから2πの間なら負の値になります。
偏角が0やπのときは、2つのベクトルが平行になるので、0になります。
こちらも、y方向の情報を切り捨てて外積を計算してくれる関数、cross_2dを作成しました。

float cross_2d(Vector3 aVec,Vector3 bVec)
{
    return (aVec.x * bVec.z) - (aVec.z * bVec.x);
}

実装に使ってみる

内積と外積を使うことで、2つのベクトルのなす角の大きさθについて、cosθとsinθの値を求めることが出来ました。
この2つの情報を使うことで、偏角が第何象限にあるかを調べることが出来ます。
そうすればあとはAsinやAcosを用いて、偏角θを求めることが出来ます。

var PlayerForward = Player.transform.forward;
var ToEnemyVec = Enemy.transform.position - Player.transform.position;
ToEnemyVec = ToEnemyVec.normalized;

float SinPlayerToEnemy = cross_2d(PlayerForward, ToEnemyVec);
float CosPlayerToEnemy = dot_2d(PlayerForward, ToEnemyVec);

//第何象限に敵が位置しているか
int State = 0;
//第一象限のとき
if (SinPlayerToEnemy > 0 && CosPlayerToEnemy > 0)
{
    State = 0;
}
//第二象限のとき
else if (SinPlayerToEnemy > 0 && CosPlayerToEnemy < 0)
{
    State = 1;
}
//第三象限のとき
else if (SinPlayerToEnemy < 0 && CosPlayerToEnemy < 0)
{
    State = 2;
}
//第四象限のとき
else if (SinPlayerToEnemy < 0 && CosPlayerToEnemy > 0)
{
    State = 3;
}

float Rad = Mathf.Asin(SinPlayerToEnemy);

//Asinでは(-π/2,π/2)の範囲しか表せないので、象限によって範囲を(-π,π)に拡張する
switch (State)
{
    case 0:
        break;
    case 1:
        Rad = Mathf.PI - Rad;
        break;
    case 2:
        Rad = -Mathf.PI - Rad;
        break;
    case 3:
        break;
}

RadText.text = "Rad:" + Rad;

RadTextが宣言なしに出てきていますが、ここは最初で設定したデバッグ用テキストのTextを変更するように自分で変えてみてください(一応最後にプログラム全体を載せるので参考にしてください)。

別の使いみちをしている方はEnemyを適当な変数名に変更して使ってください。

UI更新

さて、これで目標物への角度を求めることが可能になりました。
しかし、この情報を使ってUIを更新してあげないと使えませんよね。
ここまでついてこれた人なら簡単だと思いますが、UIの更新には求めた偏角を使って、
目標物の画像の位置を(x,y)=(cosθ,sinθ)にしてあげれば大丈夫です。
ただ、このままだと半径が1の単位円上を動くので、UIとしては動きが小さいです。
適当な長さを掛けてあげて、コンパスのリング部分の画像とうまく合うように調整してください。

Vector3 ImageTransform = new Vector3(uiImageRadius * Mathf.Cos(Rad), uiImageRadius * Mathf.Sin(Rad), 0);
EnemyImage.rectTransform.localPosition = ImageTransform;

さて、これで実行してみるとうまくいかないはずです。
90度分右にずれていませんか?

ピンときた方もいるかもしれませんが、二次元平面を半径と偏角を用いて表すときを思い出してみましょう。
偏角は、x軸正方向から反時計回りに動いていきましたね。
つまり、UIではプレイヤーの正面が始まりとして考えているのに、偏角はプレイヤーの右方向を始まりとして計算されてしまっているのです。

対策方法は簡単で、最終的に計算された偏角にπ/2を足してあげればいいだけですね。
つまり正しい形でのUIの更新処理はこの様になります。

//プレイヤーの正面始まりなので、表示のための計算にはラジアンをπ/2だけ移動させる
float RadOffset = Mathf.PI / 2;
Rad += RadOffset;

Vector3 ImageTransform = new Vector3(uiImageRadius * Mathf.Cos(Rad), uiImageRadius * Mathf.Sin(Rad), 0);
EnemyImage.rectTransform.localPosition = ImageTransform;

完成形

自分のゲームでは難易度によって敵の数が変わり、複数体の敵に対応する必要があったため、UIの画像の位置更新処理を関数に分割しました。
そのため、参考にならない部分もあるとは思いますが、なるべくわかりやすくコメントをしたので、取捨選択をして、自分のゲームに組み込んでみてください。

EnemyCompassControl.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;

public class EnemyCompassControl : MonoBehaviour
{
    private GameObject Player;
    private GameObject Burger;
    private GameObject Fries;
    private GameObject Drink;
    //private Text RadText;
    [SerializeField]
    private Image BurgerImage;
    [SerializeField]
    private Image FriesImage;
    [SerializeField]
    private Image DrinkImage;
    [SerializeField]
    [Tooltip("敵のイメージを表示させる円の半径")]
    private float uiImageRadius=180;
    //private Text RadText;

    // Start is called before the first frame update
    void Start()
    {
        Player = GameObject.FindWithTag("Player");
        Burger = GameObject.FindGameObjectWithTag("Burger");
        Fries = GameObject.FindGameObjectWithTag("Fries");
        Drink = GameObject.FindGameObjectWithTag("Drink");
        //RadText = GetComponentInChildren<Text>();

    }

    // Update is called once per frame
    void Update()
    {
        UpdateEnemyImage(Burger, BurgerImage);
        if (Fries != null && Fries.activeInHierarchy)
        {
            UpdateEnemyImage(Fries, FriesImage);
        }
        else
        { 
            FriesImage.enabled = false; 
        }

        if (Drink != null && Drink.activeInHierarchy)
        {
            UpdateEnemyImage(Drink, DrinkImage);
        }
        else
        {
            DrinkImage.enabled = false;
        }
    }

    float cross_2d(Vector3 aVec,Vector3 bVec)
    {
        return (aVec.x * bVec.z) - (aVec.z * bVec.x);
    }

    float dot_2d(Vector3 aVec, Vector3 bVec)
    {
        return (aVec.x * bVec.x + aVec.z * bVec.z);
    }

    void UpdateEnemyImage(GameObject Enemy,Image EnemyImage)
    {
        var PlayerForward = Player.transform.forward;
        var ToEnemyVec = Enemy.transform.position - Player.transform.position;
        //正規化することで角度を求めるときに大きさで割るのを省略する
        ToEnemyVec = ToEnemyVec.normalized;

        float SinPlayerToEnemy = cross_2d(PlayerForward, ToEnemyVec);
        float CosPlayerToEnemy = dot_2d(PlayerForward, ToEnemyVec);

        //第何象限に敵が位置しているか
        int State = 0;
        //第一象限のとき
        if (SinPlayerToEnemy > 0 && CosPlayerToEnemy > 0)
        {
            State = 0;
        }
        //第二象限のとき
        else if (SinPlayerToEnemy > 0 && CosPlayerToEnemy < 0)
        {
            State = 1;
        }
        //第三象限のとき
        else if (SinPlayerToEnemy < 0 && CosPlayerToEnemy < 0)
        {
            State = 2;
        }
        //第四象限のとき
        else if (SinPlayerToEnemy < 0 && CosPlayerToEnemy > 0)
        {
            State = 3;
        }

        float Rad = Mathf.Asin(SinPlayerToEnemy);
        //RadText.text = "Rad:" + Rad;

        //Asinでは(-π/2,π/2)の範囲しか表せないので、象限によって範囲を(-π,π)に拡張する
        switch (State)
        {
            case 0:
                break;
            case 1:
                Rad = Mathf.PI - Rad;
                break;
            case 2:
                Rad = -Mathf.PI - Rad;
                break;
            case 3:
                break;
        }

        //プレイヤーの正面始まりなので、表示のための計算にはラジアンをπ/2だけ移動させる
        float RadOffset = Mathf.PI / 2;
        Rad += RadOffset;

        Vector3 ImageTransform = new Vector3(uiImageRadius * Mathf.Cos(Rad), uiImageRadius * Mathf.Sin(Rad), 0);
        EnemyImage.rectTransform.localPosition = ImageTransform;
    }
}

さいごに

実装してる自分ではわかることでも、人に伝えるために説明し直すのはやはり大変ですね。
正直、同じ内積・外積を使うやり方でも、より簡単な処理方法があるのではないかとも考えています。
もしこういう処理はどうかな?みたいに思ったことがありましたら、コメントなど頂けるとありがたいです。
ここが分からなかった、という意見でもいいので、どうぞ気軽にコメントを下さい。

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

Unity上の3Dオブジェクトをfbxフォーマットでエクスポートする

ウインドウ -> Package managerFBX Exporterをエクスポート。
インストール後は、オブジェクトを選択して、右クリックメニューに追加されたExport to FBXでエクスポートできます。

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

【Unity】UPMのVSCodePackageを使っていると、CallOnGeneratedCSProjectが呼ばれない

TL;DR;

UPMでVisualStudioCodeEditorの代わりにこちらを使ってください。

git@github.com:wallstudio/com.unity.ide.vscode.git

原因っぽいもの

CsProjファイルのReferenceOutputAssemblyの項目がfalseになっているからの様で、これを手動でtrueにすると治る。
ただ、これは自動生成ファイルなので、手直しでOKとはいきません。
もう治ってるみたいなことがかいてあったのですが…

https://issuetracker.unity3d.com/issues/referenceoutputassembly-key-is-set-to-false-in-project-references

とりあえず、当時の対処療法を試みるも、UPMのCode拡張を入れていると、AssetPostprocessor.OnGeneratedCSProjectが呼ばれなくなってしまうらしいです。

public class FIX : AssetPostprocessor
{
  private static string OnGeneratedCSProject(string path, string content)
    => content.Replace(
      "<ReferenceOutputAssembly>false</ReferenceOutputAssembly>",
      "<ReferenceOutputAssembly>true</ReferenceOutputAssembly>");
  }
}

対処

com.unity.ide.vscodeを直接書き換えちゃうのが手っ取り早いということで、こちらです

https://github.com/needle-mirror/com.unity.ide.vscode/compare/master...wallstudio:master

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

【Unity】Asmdefに参照を追加してもVSCode側で反映されない

TL;DR;

UPMでVisualStudioCodeEditorの代わりにこちらを使ってください。

git@github.com:wallstudio/com.unity.ide.vscode.git

原因っぽいもの

CsProjファイルのReferenceOutputAssemblyの項目がfalseになっているからの様で、これを手動でtrueにすると治る。
ただ、これは自動生成ファイルなので、手直しでOKとはいきません。
もう治ってるみたいなことがかいてあったのですが…

https://issuetracker.unity3d.com/issues/referenceoutputassembly-key-is-set-to-false-in-project-references

とりあえず、当時の対処療法を試みるも、UPMのCode拡張を入れていると、AssetPostprocessor.OnGeneratedCSProjectが呼ばれなくなってしまうらしいです。

public class FIX : AssetPostprocessor
{
  private static string OnGeneratedCSProject(string path, string content)
    => content.Replace(
      "<ReferenceOutputAssembly>false</ReferenceOutputAssembly>",
      "<ReferenceOutputAssembly>true</ReferenceOutputAssembly>");
  }
}

対処

com.unity.ide.vscodeを直接書き換えちゃうのが手っ取り早いということで、こちらです

https://github.com/needle-mirror/com.unity.ide.vscode/compare/master...wallstudio:master

追記

OmniSharpの方にIssueが上がっていました

https://github.com/OmniSharp/omnisharp-vscode/issues/4113

ReferenceOutputAssemblyという名前的に本来trueであるべきのような気もするのですが(com.unity.ide.vscide側が悪い)どうなのでしょうか。
今まではfalseで動いていましたが。

Unityがプロジェクトメタファイルの出力クラスを切り替えている部分が分かりました。
AssetPostprocessor.cs#L159
どうやら、これからはOnGeneratedCSProjectではなく、IExternalCodeEditorを使って出力機構丸ごと差し替える形でフックするのがよさそうです。
また、力業ではありますが、ReadDirectoryChangesW(Win32API)などで、ファイルの書き換えをフックするという手段もあるにはあります。

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

【Unity】プログレスバーを"いい感じ"に実装する

今回はUnityのプログレスバーをいい感じに実装していこうと思います。

プログレスバーって何?

wikipediaより一部引用

プログレスバー(英: Progress Bar)とは、長時間かかるタスクの進捗状況がどの程度完了したのかを視覚的・直感的に表示するもので、グラフィカルユーザインタフェースの要素(ウィジェット)の一つである。しばしば、ダウンロードやファイル転送のようにパーセント形式で表示される際に使われる。プログレスメーター (英: Progress Meters)とも呼ばれる。
progres_bar.gif

よくゲームとかで見かけるやつです!

いい感じとは?

普段、ネットとかで【プログレスバー 実装】について調べてみると、様々な言語で0% ~ 100%までを等間隔で表示させたりするものが多く紹介されています。
↓↓こんな感じで↓↓

float progress = 0f;
Text progress_text = GetComponent<Text>();
for( int i = 0; i < hoge.length; i++ ) {
  progress = (float)( i / hoge.length ) * 100f;
  progress_text.text = progress.ToString("f2") + "%";
}

私も普段はこのように実装していたのですが、ふとゲームで遊んでいると等間隔じゃなくて、何か序盤は早くて徐々にゆっくりな動きをしているように見えました。

そこで、色々なサイトで同じように考えている人がいないか探してみたところ、TEDで紹介で同じようなことが紹介されていました。

Transcript of "私たちを安心させてくれるプログレスバー"

つまり、最初を早くすることで心理学的に、ユーザーからは実際よりも早く進んでいるという感じるそうです。

なので、今回は $y=x と y=\sqrt{x}$ と $y=\sqrt[3]{x}$の3つを実装していこうと思います。
image.png

長いので先に実行結果だけを見たい人は一番下に動画を載せてます!!

実装

画像を準備する

まず、Unityでプログレスバーを実装するには円の画像を用意します。
今回は適当にPowerPointで適当に円を作りました(白いので見えにくいですがあります)


image.png

↑右クリックしてDLしてください。

Unityで2Dのプロジェクトを新規作成する

私は今回、「ProgressBarText」という名前のプロジェクトを作成しました。(TextとTestのスペル間違えました…)

image.png

背景色を変える

そのままでもいいのですが、文字をみやすくするために「Main Camera」→「Camera」→「Background」を押してカラーパレットから「Silver」を選択しました。
image.png

DLした画像をImagesをフォルダに入れる

そして、Assetsのなかに「Images」フォルダを作成して、その中に先ほどDLした円の画像をドラッグ&ドロップします。
image.png

プログレスバーの作成

まずは、Hierarchyの中に「Canvas」を作成して「Render Mode」を「Screen Space – Camera」に変更。
image.png

そして、「Render Camera」を「Main Camera」に設定する。
image.png

こちらのサイトをみながら、実際に作ってみる。
【Unity】uGUIで円形のプログレスバーを簡単に作る

そして、サイズを「ProgressBar」は300×300に、「Center」は250×250に変更。
image.png

進捗率用のTextとラベル用のTextを作成して1つのGameObjectにまとめる

進捗率を表示するため「ProgressText」とラベルとして表示するための「Label」というTextオブジェクトを作成。
その後、Canvasの中に「X_default」という名前で空のGameObjectを作成し、「ProgressBar」「ProgressText」「Label」を「X_default」の下に移動させる。
image.png

作成したX_defaultを複製して名前を変える

作成したX_defaultをコピーして、「X_pow_2」「X_pow_3」という名前にリネームする。
そして、均等になるように調節してそれぞれラベルとプログレスバーの色を変える。
image.png

スクリプトを作成する

Assetsのなかに「Scripts」フォルダを作成して、その中に「ProgressController」と「ButtonController」という名前で2つC#スクリプトを作成する。
image.png

作成したスクリプトにコピペする

ProgressController.cs
using System.Collections;
using UnityEngine;
using UnityEngine.UI;

/// <summary>
/// プログレスバーのコントロールを行うクラス
/// </summary>
public class ProgressController : MonoBehaviour {

    [SerializeField] private Text progress_text;    // 進捗率
    [SerializeField] private Image progress_bar;    // 進捗バー
    [SerializeField] private int pow_value;     // 進捗率を何乗するか


    /// <summary>
    /// 1F目に呼ばれる関数
    /// </summary>
    void Start() {
        // UIを初期化する
        progress_text.text = "0.00%";
        progress_bar.fillAmount = 0f;
    }

    /// <summary>
    /// プログレスバーをぐるぐるさせる関数
    /// </summary>
    /// <returns>イテレータを返す</returns>
    public IEnumerator StartProgress() {

        // 重い処理のつもり
        for( int i = 0; i < 2000; i++ ) {

            // 進捗率
            float progress = i / 2000f;
            // 進捗率をpow_value乗する
            float pow_progerss = Mathf.Pow(progress, 1/(float)pow_value);

            // それぞれ代入する
            progress_bar.fillAmount = pow_progerss;
            progress_text.text = (pow_progerss*100f).ToString("f2") + "%";

            yield return null;
        }

        progress_text.text = "100%";
        progress_bar.fillAmount = 1f;
    }
}
ButtonController
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class ButtonController : MonoBehaviour {

    [SerializeField] ProgressController x_default;
    [SerializeField] ProgressController x_pow_2;
    [SerializeField] ProgressController x_pow_3;


    /// <summary>
    /// ボタンが押されたらプロパティのプログレスバーを動かす
    /// </summary>
    public void OnClickStart() {

        // それぞれのプログレスバーを動かす
        StartCoroutine( x_default.StartProgress() );
        StartCoroutine( x_pow_2.StartProgress() );
        StartCoroutine( x_pow_3.StartProgress() );
    }
}

平方根をとるには、Mathf.Pow関数の第二引数を小数にすることで取れる。

同時に実行するためスタートボタンを作成する

image.png

スクリプトをアタッチする

スクリプトの作成が終わったので、作成した「Button」に「ButtonController」、「X_default」「X_pow_2」「X_pow_3」にそれぞれ「ProgressController」をアタッチする。
image.png

それぞれプロパティをセットする

それぞれのGameObjectに対して、プロパティをセット。
【Button】
image.png

【X_default】【X_pow_2】【X_pow_3】
この3つはほとんどやり方が同じなので、代表として「X_default」のセット方法だけを紹介します。
それぞれの子のProgressBarとProgressTextをセットするのは同じですが、Pow_valueのところだけX_pow2では「2」、X_pow_3では「3」をセットする。
image.png

スタートボタンにクリックされた時の関数を実装する

まず、赤の四角で囲われている「+」ボタンを押すことで関数をセットできる状態になるので、押したら「Button」を画像のようにドラッグ&ドロップする。そして、すぐ右上のボタンを押して画像のように「OnClickStart()」を押すことでセットが完了する。
image.png

実行!!

IMAGE ALT TEXT HERE

このように、プログラムの実行時間自体はどれも同じですが、体感速度が明らかに違うことが分かります!!

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