ブルーノのC#プログラミング & unity勉強日記

プログラミング素人、ブルーノの自主勉強ノートです。他のプログラミングを勉強したい方の助けになれば幸いです。その他趣味の雑記もしていきたいです

Unityスクリプト / Mathematics編

f:id:covory10101101276:20180423211739j:plain

山の羊の群

 

みなさんこんにちは🌚

テニスの錦織がまた最近浮上してきてくれて嬉しいブルーノです。ノーシードのタフさを感じたと言っていましたがやはり一度落ちると調子を取り戻すのは大変のようですね。リスクとの兼ね合いも考えながら頑張って欲しいです🤯

 

さて今回は、今までのものとは少し違い、目に見えないけどゲームやアプリを作る上で欠かせなさそうな数学的要素の取り扱いについて学んでいきたいと思います。

 

乱数

乱数という言葉の存在や仕組みはポケモンのおかげで今やだいぶ普通の人にも浸透してきたのではないかと勝手に推測していますが、乱数とは実行するまで結果が確定できないランダムな値のことを指します。乱数を使うにはUnityEnigine.Randomクラスvalueプロパティから取得します。

UnityEnigine.Randomクラス

public sealed class Random

 

valueプロパティ

public static float value { get; }

このプロパティを使用することで、0.0 ~ 1.0までのランダムな値を取得します。乱数は呼び出すたびに新しいものが用意されます。実際に書いてみましょう

f:id:covory10101101276:20180424164429p:plain

Vector3の各座標を0 ~ 5の値で生成してはその座標にcubeを作るというのを100回繰り返すコードです。結果は以下の通りになります。

f:id:covory10101101276:20180424165527p:plain

なんか綺麗ですね。この結果は再生するたびに違うものになります

f:id:covory10101101276:20180424165609p:plain

先ほどはvalueプロパティの値に色々引いたりかけたりして5という範囲までの数字を得ましたが、それをしなくても同じくRandomクラスのRangeプロパティを使用することで最初から指定した範囲内で乱数を得ることもできます。

Rangeプロパティ

public static float Range(float min, float max);

public static int Range(int min, int max);

minとmaxパラメータにそれぞれの型の数字を入力することでその範囲内の乱数を得ることができます。ちなみにmaxの値は含まれません

 先ほど乱数はランダムな数値と言いましたが、実は完全にランダムではありません。シード値と呼ばれるタネをもとに作られます。基本的にシード値には現在時刻を利用しますが、Randomクラスのseedプロパティを利用すると自分でシード値を設定できます。つまり、シード値を保持しておけばそれを基に同じ乱数を生成することができるのです。

seedプロパティ

public static int seed { get; set; }

 

四元数

我々はUnityの中で角度を扱う場合、一般的にオイラー角を使用します。オイラー角は区間上にX, Y, Z軸のそれぞれの角度を持っていて、Vector3変数でそれを扱うことが多いです。しかしオイラー角ではジンバルロックという問題があり、正確に回転を表現できないことがあります。そこで4番目の数字を使うことでこれを回避しようということで、実際のUnity内部では四元数というものを用いています。まあ難しい解説は私にもわからないのでポイントだけ抑えると、「なぜUnityでは四元数で計算しているのに我々がUnityエディタ等で触れるのはオイラー角だけなのか」ということ。つまり四元数を我々に触れさせる前にオイラー角に直してくれているんですね。

まず四元数はQuaternion構造体を用いて表されます。

Quaternion構造体

public Quaternion(float x, float y, float z, float w);

 そして四元数オイラー角に直すには、Euler()メソッドを使います。

public static Quaternion Euler(Vector3 euler);

public static Quaternion Euler(float x, float y, float z);

 引数にVector3型の情報を入れることでQuatrnion構造体に変換してくれます。これで、Unityが扱う他の空間系のメソッドに使うことができます。

では当然逆のもの、つまりQuaternion構造体からオイラー角を出してくれるメソッドもあるはずですね。それがeulerAnglesプロパティになります。

eulerAnglesプロパティ

public Vector3 eulerAngles { get; set; }

四元数オイラー角間の計算にはコストがかかるため、Updateメソッドなどで連続して使用するのは避けたほうがいいらしいです。

他にも掘り出せばキリがないので、ゲームのときに頻出しそうなコンテンツだけを抜粋してみました。今回はこれで終わります。

 

人の気持ちというのはとても単純で複雑ですね。見方を変えれば全く理解できなかったものの気持ちというのも理解できるもんですね😙そしてそのきっかけは結局自分に回帰するのだと思います😎気の持ちようで周りは変わる

あーきのこスープ飲みたい

Unityスクリプト / 入力編(2/2)

f:id:covory10101101276:20180420220448j:plain

オカバンゴ・デルタ - ボツワナ

みなさんこんにちは🌚

 いきなり夏みたいな暑さで溶けそうなブルーノです。少し家を空けたら家の外より中の方が暑くなっていました🤑

さて今回は前回に引き続きUnityの入力について、少し特殊な入力方法について勉強していきます。

 

インプットマネージャー / Input Manager

Unityには、インプットマネージャーと呼ばれる機能があり、これを使うと複数の入力デバイスからの操作を1つのコマンドで設定することができます。簡単に説明すると、例えば「垂直方向」という名前をつけてその操作方法の中身を作っておけば、PSのコントローラーでもGCのコントローラーでも垂直方向キーにこの「垂直方向」インプットマネージャーを適応すれば微妙な調整や検証もいらずに直感的に操作方法を当てることができます。もう一つの特徴としては、単純にON・OFFで操作しないカーブや移動といった動作を滑らかに自然にしてくれる効果があります。百聞は一見に如かず ↓

f:id:covory10101101276:20180422161912p:plain

インプットマネージャーは[Edit]→[Project Setting]→[Input]から設定します

f:id:covory10101101276:20180422162014p:plain

色々メニューがありますが、[Horizontal]を覗いてみましょう。

AxisのSizeはこの入力の設定の数ですね。デフォルトでは18個入っているようです。足したい場合はこの値を増やしてリストを増やします。ちなみに個々の設定を仮想軸と言い、2方向の入力の仕方を設定します。(水平や垂直など。1方向でも可)

では上から順に各項目を説明します

Name: この仮想軸の名前を決めます。

Descriptive Name:仮想軸の正方向の説明です。ゲームランチャーでボタンを割り当てる際に表示されます。

Descrictive Negative:負方向の説明になります

Negative Button:負方向のボタンを設定できます

Positive Bottun:正方向のボタンを設定できます

Alt Negative Bottun:負方向の予備を設定できます

Alt Positive Bottun:正方向の予備を設定できます

Gravity:ボタンを話したときにどれくらいの速度で0に戻ろうとするかを設定します。大きいほどすぐ0に戻ります

Dead:0の範囲を設定します。つまり絶対値が0以上の値を入力しておけばそれ以下の時は反応しなくなります。

Sensitivity:反応の感度です。高ければすぐに反応し、低ければ鈍くなります。

Snap:反対方向の入力をされた時、値をすぐに0から始めるかを設定できます。

Invert:正負の方向を逆にします。

Type:仮想軸の操作を適応するデバイスの種類を設定します。

Axis:方向を指定します。例えば上のTypeでマウスの仮想軸を[X axis]に設定していた場合、カーソルを水平方向に移動した場合反応します。

Joy Num:コントローラーの番号を指定します。デフォルトでは全てのコントローラーを受け付けます。

そして設定した仮想軸を扱うにはGetAxis()メソッドを使用します 

GetAxis()メソッド

public static float GetAxis(string axisName)

 axisNameパラメータに使用する仮想軸の名前を記述します。例を書いてみます。

f:id:covory10101101276:20180422202359p:plain

とてもわかりやすい。だからGetAxisメソッドの中はstring型で戻り値はfloatなんですね。たまに使うことはありましたが疑問に思っていました。

f:id:covory10101101276:20180422202518p:plain

わかりにくいですが、キーを押した瞬間からは少しだけ加速し、離してから滑らかに停止します。

 

加速度

バイスの動きを感知する加速度センサーは今やどんなあらゆるケータイに搭載されており、ゲーム機にもその機能を取り入れたものはどんどん出ています。いまいちその使い方に成功したように思えるゲームを私はあまり知らないのですが、新しい機能はどんどん開拓してみたいですね。

accelerationプロパティは現在の加速度をVector3型で取得できるプロパティです。

f:id:covory10101101276:20180422210815p:plain

そのままオブジェクトのRotateに適用できるので設定は楽ですね。

f:id:covory10101101276:20180422210826p:plain

IPhoneを振るとその方向によってCubeも異なる方向へ回転し始めます。操気弾を使ってるみたいで気分が良いです。

 

ジャイロスコープ

加速度センサーではデバイスの動きを感知することはできますが、傾きは感知されていません。一般的にスマホなどの傾けて遊ぶゲームは加速度センサーよりもこちらのジャイロスコープの方が使われているのではないでしょうか。

ジャイロスコープから得られる情報はgyroプロパティから取得することができます。

gyroプロパティ

public static Gyroscope gyro { get; }

このプロパティはジャイロスコープからの情報をUnityEngine.Gyroscopeクラスオブジェクトを返します。

なお、デフォルトではジャイロスコープの機能はOFFになっているのでスクリプトから利用する場合はまずenabledプロパティで有効にします。

enabledプロパティ

public bool enabled { get; set; }

 このプロパティにtrueがセットされていればジャイロスコープの情報を得ることができます。そしていよいよ、現在のデバイスの姿勢をattitudeプロパティから取得します。

attitudeプロパティ

public Quaterion attitude { get; }

 実際に作ってみましょう

f:id:covory10101101276:20180423202254p:plain

25行目で一度Quaterion型の変数anglesに角度を格納し、その中の数値をGUIに表示します

f:id:covory10101101276:20180423202451p:plain

バイスを傾けるとそれに応じてCubeも傾きました。意思を持ってるみたいで面白いですね。今回はここまでにしておきます。

 

最近よく自分で料理を作ってみようとチャレンジしているのですが結局煮るか炒めるかになってしまうんですよね。なんかワンランク上のおしゃれな料理入門に最適な料理ってないですかねー🤔

Unityスクリプト / 入力編(1/2)

f:id:covory10101101276:20180420130851j:plain

キューケンホフ公園 - オランダ

みなさんこんにちは🌚

ミッチ・アルバムの作品を勉強中のブルーノです。人間が決められた原子の組成でこの記憶も感情もあるのなら私が今ここに存在している確率でもう一度この私でいつか蘇ることができるのではないかと思うんですがどうでしょう?

 

さて今回は、あらゆる機器からの入力を受け取る方法について書いていきます。

我々が機械に物理的に意思を伝える方法としてはマウスやキーボード、タッチなど様々なものがあり、自分が作るゲームやアプリを使うデバイスが持つ入力装置からの入力を受け取れるようにしなければなりません。Unityでどうやってそれを行うのかを見ていきます。前編はマウス・キーボード・タッチの超基本的な入力についてです。

 

マウス入力

まず、全ての入力はUnityEngine.Inputクラスに内包されています。

UnityEnigine.Inputクラス

public sealed class Input

マウスカーソルの座標はmousePositionプロパティです。

mousePositionプロパティ

public static Vector3 mousePosition { get; }

 ゲーム画面の左下隅を(0, 0)としてマウスカーソルの座標をピクセル単位で返します。Vector3なのでx, y, zは横・高さ・奥行きがありますが奥行きのzの値は常に0です。

例を見てみましょう

f:id:covory10101101276:20180420142431p:plain

OnGUIメソッドから現在の座標を入力してみました。GUI.Label関数の引数の文章は""を入れてstring型にしないと直接Input.mousepositionプロパティの値(float型)を入れることはできないんですね。いやstring型にしないといけないのはわかってるけどこんな簡単でいいのか😃

f:id:covory10101101276:20180420142435p:plain

このスクショをとるときの最後の地点が限りなく左下でした笑

 

マウスボタン

マウスのボタンが押されているかはGetMouseBottun()メソッドから得ます。

GetMouseBottun()メソッド

public static bool GetMouseBottun(int button);

buttonパラメータにはそれぞれ左ボタン0, 右ボタン1,真ん中のボタン2のいずれかが入ります。指定したボタンが押されていればtrue,そうでなければfalseを返します。

例を書いてみます。

f:id:covory10101101276:20180420180227p:plain

16行目でvar rに、マウスが押されていれば10を、そうでなければ1を代入し、次の17行目でY軸の回転量にそれを代入します。

f:id:covory10101101276:20180420180359p:plain

マウスを押した時と通常時で回るスピードが変わる!

ところでこれだと、Update()メソッドに記述すると押された時のフレーム全てにtrueを返してしまうので決定ボタンとかの時は変な反応になってしまいますね。そこで「押してない→押してる」や「押してる→押してない」の時だけ反応するメソッドもあります。それが以下の二つです

GetMouseBottunDown()メソッド

public static bool GetMouseBottunDown(int button);

GetMouseBottunUp()メソッド

public static bool GetMouseBottunUp(int button);

Downの方は押された瞬間のみtrueを返し、Upは話された瞬間のみtrueを返します。どちらも戻り値はtrueです。

 

キーボード入力

キーボードのキーが押されているかはGetKey()メソッドを使用します。 

GetKey()メソッド

public static bool GetKey(KeyCode Key)

public static bool GetKey(String name)

KeyCodeとは列挙型の変数で、中には全てのキーに対応した変数があります。そしてそれは文字通り標準的なキーボードのキー名を入れると出てきます。例えばスペースキーならSpace, AならAといった感じです。string型のnameパラメータはその文字を小文字で表します。今の例ならスペースは"space"、Aなら"a"といった感じです。

例を見てみましょう。

f:id:covory10101101276:20180420182043p:plain

さっきとほぼ同じですが、17行目がスラッシュ(Slash, / )キーを押された時、に変わっています。

 

f:id:covory10101101276:20180420182140p:plain

結果も同じですね。

当然マウスの時と同じように「押してない→押してる」や「押してる→押してない」の時だけ反応するメソッドも存在します。

GetKeyDown()メソッド

public static bool GetKeyDown(KeyCode Key);

public static bool GetKeyDown(string name);

GetMouseBottunUp()メソッド

public static bool GetKeyUp(KeyCode Key);

public static bool GetKeyDown(string name);

 

マルチタッチ

スマホのタッチはマウス同様座標を取得するポインティングデバイスという種類のうちの一つに入りますが、マウスとは違いスマホでは複数の入力を得ることができ、それをマルチタッチと呼びます。現在画面をタッチしているポイントの数を取得するにはtouchCountプロパティを使います。

touchCountプロパティ

Public static int touchCount { get; }

それぞれのタッチされているポイントの情報を扱いたい場合はGetTouch()メソッドから使います。

GetTouch()メソッド

public static Touch GetTouch(int index);

indexパラメータにはtouchCountプロパティの返した値、つまりタッチされているポイントの数だけ範囲を持つインデックスになります。3本指でタッチしている間はインデックスは最大3になり、最初のポインタのインデックスは0にあたります。

全てのタッチ情報を配列で得たい場合はtouchesプロパティで得られます。

touchesプロパティ

public static Touch[] touches { get; }

 得る情報はUnityEngine.Touch構造体で表されています。

UnityEngine.Touch構造体

public struct Touch

タッチ情報の座標はpositionプロパティから得ることができます。

public Vector2 position { get; }

これもまたマウスの時と同じように左下隅を原点として(横, 縦)の座標を返します。

では実際に書いてみます。

f:id:covory10101101276:20180420195050p:plain

for文のiをインデックスにもつtouch.positionの座標を全て表示します。

GUI.Label()メソッドだけだとインデックスの数を一つ送りにできなかったので別のint型のmで計算しました。

結果は以下のようになります

f:id:covory10101101276:20180420195322p:plain

5本指でスマホに触れながらスクショをとりました。タッチ入力はUnityRemoteというアプリからできるのでそれを使いました。

得られたタッチの開始された瞬間や離された瞬間、現在移動しているかどうかの情報もphaseプロパティを使うことで得ることができます。

phaseプロパティ

public Touchphase phase { get; }

TouchPhaseは列挙型の変数で、中にはそれぞれ

Beganメンバ → タッチされた瞬間

Endedメンバ → 離された瞬間

Movedメンバ → 動いている間

Statinaryメンバ → 停止している間

Canceldメンバ → 何らかの原因でタッチの状態を追跡できなかった場合

が入っています。実際に確かめてみます。

f:id:covory10101101276:20180420202210p:plain

f:id:covory10101101276:20180420202221p:plain

一方の指は動かしながら、もう一方の指は止まった状態でスクショしました。ちゃんと「Moved」と「Stationary」と表示されていますね。

 

タップ

タップ?タッチとどう違う?と一瞬思いますが

タッチ → タッチパネルに触れている

タップ → タッチパネルに短い間隔で押して離す

という違いがあるんですね。このタップの回数はtapCountプロパティで取得できます。

tapCountプロパティ

public int tapCount { get; }

個々のタッチにはそれぞれIDが割り振られ、連続して押されたものなのかどうかを判断できます。そしてその連続でタップされた回数分だけこのプロパティに蓄積されるというわけです。実際に書いてみます

f:id:covory10101101276:20180420201045p:plain

f:id:covory10101101276:20180420201122p:plain

短い間隔でタップするとその指の回数はカウントされていきます。

では最後に、先ほど少し触れたタッチ情報のIDの識別の仕方を学びます。

タッチ情報を格納するTouch構造体はクラス(参照型)ではなく構造体(値型)なのでインスタンス化して識別はできません。そこで、Touch構造体にはfingerIDプロパティというIDが割り振られています。では確かめてみます

f:id:covory10101101276:20180420203049p:plain

f:id:covory10101101276:20180420203100p:plain

一度4本指で画面をタッチし、最初の2本を離したのち、また一本の指で画面に触れました。最初の2本を離してIDナンバー0と1は消えましたが、2と3は残ったままでした。そこで新たに触れるとまたナンバー0が生成されました。確かにそれぞれのタッチに識別番号が割り振られているようですね。

我々が普段何気なく行なっている操作も細分化するとこんなにいっぱい種類があってそれら全てに対応する物が用意されているんですねー🙂

今回はここまでにしておきます。

 

仕事の定義って何でしょう。私は社会的に需要があることと責任が伴うことが条件だと思っています。いや、責任が伴うから社会的に需要が生まれるのか?いずれにせよ、その条件を満たせば趣味も仕事にできるのではないのでしょうか。どうでもいいか笑

Unityスクリプト / ゲーム内の時間編

f:id:covory10101101276:20180418193133j:plain

インドネシアのどこか

 

みなさんこんにちは🌚

最近日本酒が臭く感じなくなってぐびぐび飲めるようになってきてしまったブルーノです。別に一人で飲む機会が多いわけではないので友達と飲むときしか影響はないのですが自分の体が恐ろしくなってきました笑

 

さて今回は、ゲーム内の時間の概念とその扱い方についてやっていきたいと思います。

 

ゲームの時間管理

ゲームの中の時間は我々の世界とは違い、フレーム単位で進んでいきます。じゃあそれは何秒なの?と言われるとこれまた設定次第なので一概に何秒とは言えませんが、とにかく我々の1秒と同じではありません。そのフレームの設定から逆算すれば1秒をゲーム内で測れますが、毎回時間を扱う際にそのゲームと我々の時間の比率を計算するのはめんどくさいですよね。そこでUnityEngine.Timeクラスtimeプロパティを使うことで、起動後から呼び出されるまでの時間を我々の時間で測ってくれます!

UnityEngine.Timeクラス

public sealed class Time

timeプロパティ

public static float time { get; }

逆にフレームを測りたい場合は同じくTimeクラスのframeCountプロパティを使用します

frameCountプロパティ

public static int frameCount { get; }

 ちょっとみてみましょう

f:id:covory10101101276:20180418195925p:plain

7行目でまず座標を作り、22行目でTimeクラスからそれぞれframeCountとtimeプロパティを使っています。結果は以下の通りです

f:id:covory10101101276:20180418200106p:plain

フレームと我々の時間の進みはやはり全然違うようですね。

ゲーム内の時間はフレーム単位で進むことはわかりましたが、では我々の時間基準でオブジェクトを動かしたい場合や進行させたいときは時間をただ刻み続けているtimeプロパティじゃあまり意味がありませんね。そこで使われるのがdeltatimeプロパティです。このdeltatimeプロパティは呼び出される直前のフレームから現在のフレームまでにかかった時間を我々の時間単位に直してfloat型で返してくれます。つまりめっちゃ早いです。なんでそれで時間が測れるんだよって思いますがつまり、1秒が60フレームのゲームなら1/60の値を返すので、1秒に動いてほしい値にかけることでその1/60の値になります。それを1秒間に60回かけるのでうまく1秒にその値が収まるという理屈だそうです。厳密には違うそうです笑

では例を見ていきましょう

f:id:covory10101101276:20180418201821p:plain

これをCubeにつけます

f:id:covory10101101276:20180418201842p:plain

およそ1秒で一周していますね。室伏みたいですね。このdeltatimeプロパティは今後ゲームにしろアプリにしろ必須になってきそうですね。

 

フレームレートの設定

UnityはUpdateメソッドが毎回呼び出され更新することでゲームが動いてるように見えます。このUpdateメソッドが1秒間に呼び出される回数のことをフレームレートと言います。この回数はモニタのスペックに影響され、その画面が1秒に描画できる回数分がデフォルトでは設定されます。この画面の1秒に描画できる回数を垂直同期周波数(カッコイイ)というのですが、この設定を変更することもできます。エディタ画面上部の[Edit]から[Project Setting]の[Quality]を開きます

f:id:covory10101101276:20180419181801p:plain

インスペクターにQuality Settingが表示されます。

f:id:covory10101101276:20180419181840p:plain

このメニューの下側「Other」の欄のVSync Countから変更できます。

f:id:covory10101101276:20180419181947p:plain

デフォルトの「Every V Blank」は垂直同期周波数のことを表します。「Don't Sync」は垂直同期しないことを表し、「Every Second Sync」はEvery V Blankの半分の回数になります。

基本的に垂直同期しないという設定は使いませんがEvery Second Syncのように更新回数を減らせばモバイル向けアプリの動作を軽くしたりできます。

 

処理の分割 - コルーチン

RPGゲーなどの場合、次のマップの読み込みやエフェクトの描画、入力の受付など、様々な処理が同時に行われてますが、もし1フレームに次のマップの読み込み全てを行わせていたらとてもじゃないですがPS4オープンワールドなんかは時間がかかってしまいます。マップの読み込みだけでなく、派手なエフェクトの描画なども処理が遅れれば処理落ちし、それがひどいとストレスがすごいですよね。そこでUnityでは、C#がもともと持っているコルーチンという機能を使ってその作業を何フレームにも分割させられるそうです。コルーチンは関数の途中で処理を中断し、その関数から抜け、再度呼び出された時に中断した部分から処理を行える機能です。

コルーチンはC#の「IEnumeratorインターフェイス」で表されます。インターフェイス懐かしいけど難しかった😓これとyield return文を使うことで書くことができます。

そしてIEnumeratorインターフェイスを呼び出すにはStartCoroutine()メソッドを使います。

StartCoroutine()メソッド

public Coroutine StartCoroutine(IEnumerator routine);

 routineの場所にIEnumeratorインターフェイスを記述します。

手順としては

IEnumeratorインターフェイスを記述

その中の中断するポイントでyield return文を書く

StartCoroutine()メソッドで呼び出す

となります。実際にちょっと書いてみます

f:id:covory10101101276:20180419200013p:plain

13行目〜30行目にIEnumeratorインターフェイスのRotateRoutineを記述し、その中にyield return null文を入れています。nullは「空っぽ」のことで、特に何もアクションをせずにここで処理を中断するのですが他にも色々なアクションをここで起こすことができます。例えばここに

StartCoroutine() →別のコルーチンに入る

WaitForSeconds() →一定時間待機する。

といったメソッドを入れることができます。とても便利!

さてこのスクリプトではfor文の中で「Vector3変数の r 文だけ回転するという処理を90回繰り返し」for文を抜け、向きを90度変えてcount変数に+1するというのを繰り返すものになっており、count変数が4になった場合初めて文が終わる仕組みになっています。コルーチンは関数の状態を保持してくれるのでこの中でcount変数を宣言しても再び呼び出したときにリセットされないし、rの中身もしっかり保持されていますね。

結果は以下のようになります

f:id:covory10101101276:20180419200603p:plain

4回向きが変わったところで止まりました。Startメソッドの中で書いたものがずっと動き続けたり中身がずっと保持されてるなんて現実世界の考え方にすごく近くなっていますね。これだけわかればますます直感的にゲームをみんなが作れるようになるんじゃないか....?

ほんとはコルーチンはそれ単体で記事を書く必要がありそうなくらい便利で奥深そうですが今回はここまでにしておきます。

 

今年2018年のUnityカンファレンスイベントの早割チケットを実は購入しました!ちっともスキルも伴ってない実戦経験皆無の素人ですがたくさん刺激をもらっておこうと思います😄それまでは勉強だ😤

 

 

Unityスクリプト / コンポーネント編

f:id:covory10101101276:20180410215406j:plain

ケルン大聖堂

みなさんこんにちは🌚

ゴッドイーターが面白くてアニメゲームアプリと全部やってしまっているブルーノです。ディストピア世界って好きなんですよねー。3が楽しみです!

 

さて今回はスクリプトからのコンポーネントの操作についての基礎を学んでいきたいと思います。コンポーネントといえば、シーン上から追加するには画面上部の[Component]もしくはゲームオブジェクトのインスペクター上の[Add Component]ボタンから追加することができ、目に見えるものからその性質まで色々な機能を持たせることができましたね。

f:id:covory10101101276:20180411073906p:plain

f:id:covory10101101276:20180411073914p:plain

 

コンポーネントの追加

全てのコンポーネントUnityEngine.Componentクラスから派生しています。スクリプトを書く際に派生させているMonobehaviourクラスもまたこのComponentクラスから派生しており、スクリプトコンポーネント(部品)として扱われることを表しています。さて、そのコンポーネントの追加ですが、スクリプトでこれを行うにはAddComponent()メソッドを使用します。

AddComponent()メソッド

public Component AddComponent(Type componentType)

public T AddComponent<T>() where T : Component

一つめのcomponentTypeにはコンポーネントの型を表すTypeオブジェクトを指定します。

二つめは前にやったジェネリックを使って型を指定しています。

dreameaters5239.hatenablog.com

dreameaters5239.hatenablog.com

 ジェネリックメソッドは中身の型をメソッド名の<>で表すことで中の同じ名前の部分が全て型パラメータの型になる便利なメソッドでしたね。言葉で今回の例を表すならば「<T>のコンポーネントを追加する」のTの部分に<スクリプトの名前>だったり<摩擦係数の名前>だったりを入れるといったとこでしょうか。 

例の上の文は、斜線の部分にコンポーネント名を入力します。また、componentTypeを入力する際は「typeof」キーワードをつけて戻り値はComponent型となります。例をちょっとみてみましょう

f:id:covory10101101276:20180418102916p:plain

f:id:covory10101101276:20180418102921p:plain

今回はスクリプトを二つ用意し、2つめのスクリプトには常にy軸を回転させることしか書いていません。一つめのスクリプトの9行目でまずCubeを生成し、次の10行目でBruno_Script02を追加しています。

f:id:covory10101101276:20180418104419p:plain

くるくる回っていますね。

さて、先ほども言いましたが、AddComponent()メソッドで返される値はComponent型になって返ってきます。もちろんコンポーネントは皆Componet型を基として派生しているので動作上何も問題はありませんが、それを取得したり編集したりする場合その派生先、今回だったらBruno_Script02型として運用できた方が安全性も高まります。型キャストやas演算子を用いて2行で書くこともできますが、ジェネリックを使用すれば一行で収められます。例をみてみましょう。

f:id:covory10101101276:20180418110149p:plain

f:id:covory10101101276:20180418110200p:plain

Bruno_Script02にAnglesプロパティを追加し、回転量をBruno_Script01から設定しています。これができるのもBruno_Script02型として扱っているためその中のAnglesにアクセスできています。ちなみにさっきのメソッドで扱おうとしたら

f:id:covory10101101276:20180418110445p:plain

「Component型にはAnglesなんてものはないよ!」と怒られました。やはり基底クラスの中で探すようですね。。。

 

コンポーネントの取得

では次にオブジェクトに付いているコンポーネントの取得をしてみます。

コンポーネントの取得はGetComponent()メソッドで行います。

GetComponent()メソッド

public Component GetComponent(Type componentType)

public T GetComponent<T>() where T : Component

AddComponent()メソッドの時の「Add」部分が「Get」に置き換わっただけですね。使い方も同じです。例を書いてみます。

f:id:covory10101101276:20180418113958p:plain

今度はcubeにあるBruno_Script02の情報を取得し、その情報がなければAddComponentで追加するという流れです。

f:id:covory10101101276:20180418114256p:plain

結果は同じですね。

 

コンポーネントの削除

スクリプトからオブジェクトを削除するにはDestroy()メソッドを使用します。

使い方はゲームオブジェクト編の時と同じです。

 

dreameaters5239.hatenablog.com

 例を作成してみます。

f:id:covory10101101276:20180418153052p:plain

15行目にDestroy()メソッドを作り、5秒後に回転量が格納されているrot変数を削除します。

f:id:covory10101101276:20180418153203p:plain

5秒後に止まりました。

 

プロパティをエディタから編集

コンポーネントの状態はインスペクターから覗くことができ、変数をpublicにしておけば変数の値をインスペクター上に表示することもできます。しかし参照型のフィールドをpublicにしておくのはどこからでも容易にアクセスできる状態にしてしまうことに他なりません。そこで、privateな変数をインスペクター上に表示して編集できるようにすることができます。そこで使われるのがUnityEngine.SerializeField属性になります。

UnityEngine.SerializeField属性

public sealed class SerializeField : Attribute

また長い名前ですね。使い方の例を載せます。

f:id:covory10101101276:20180418183137p:plain

private変数の手前に[SerializeField]とつけています。

f:id:covory10101101276:20180418183553p:plain

インスペクターから値を入力することで回ります。

f:id:covory10101101276:20180418184256p:plain

これにより、privateな変数である_x, _y, _zがインスペクターに表示されました。紛らわしいのですが、このインスペクターの名前は_(アンダースコア)の後のものを大文字にしているのでこのインスペクターには9行目は表示されていません。アクセッサの仕様ということでしょうか。これで安全性を強化できますね。

 

必須コンポーネントの指定

では最後に、先ほどやった属性の別のものも重要そうだったのでまとめておきます。

今まで作ったスクリプトの中にも、別のスクリプトが必要なものがいくつもありました。そのスクリプトだけをつけても意味がない場合、もしくは他のコンポーネントが必要な場合、普通であればコンパイルエラーを起こしますよね。その時に何が足りないかがすぐわかると良いですよね。そこで今度はUnityEngine.RequireComponent属性を使うことでそのスクリプトが依存しているコンポーネントを明示することができます。

UnityEngine.RequireComponent属性

public sealed class RequireComponent : Attribute

実際に見てみましょう

f:id:covory10101101276:20180418190702p:plain

f:id:covory10101101276:20180418190712p:plain

Bruno_Script01のクラスよりも頭に[RequiredComponent(typeof(Bruno_Script02))]とありますね。このスクリプトがBruno_Script02に依存していることを表します。そしてBruno_Script02はDebug.Logで"I'm awakend"と言うだけです。

f:id:covory10101101276:20180418191030p:plain

実行するとメッセージが表示されました。さあ本番はここからです。エディタからBruno_Script02を除こうとすると...

f:id:covory10101101276:20180418191043p:plain

Bruno_Script01は02に依存しているため削除できません。と表示されました。こういったゲーム内だけでなく開発側からのサポートをスクリプトから行うこともできるんですね。今回はここまでにしておきます。

 

いやーどうしても毎回この勉強は記事が長くなりがち&ハテナの山です。最近は講義の方も忙しくなってきて、英語の勉強もしなければならなくなってきました。別に英語は嫌いじゃないんですがどうしても単調になりがちですね。根気のない私にはプログラミングの勉強の方が山あり谷ありで楽しいです😊

今日講義で聞いたのですが、英語の勉強は「将来役立つから」とか「学校で覚えなきゃいけないから」といった直接的な理由だと苦手な人は続かないそうです。そういったときは自分の好きなことに結びつけて考え、実際に活用してみると良いそうです。私だったら洋ゲーに多く触れる、とかスクリプトの理解を容易にする、とかですかね。実際英語が少しできるだけでだいぶ助かってます😀

まーでも勉強をやる理由は自分の世界を広げるために他ならないですよね。自分のしたいことができたとき、自分の中にどれだけあらゆることに知識があるか、見聞があるかでその中身の充実度が変わってきますから。それに大学入るまで気づかないなんて勿体無かった....°(ಗдಗ。)°.

Unityスクリプト / ゲームオブジェクトの相関性編

f:id:covory10101101276:20180408223218j:plain

万里の長城

みなさんこんにちは🌚

家事に勉強に大忙しのブルーノです。大量の卵をうまく消費する方法はないものか...

 

さて今回は、またこれも基礎的な内容ですがゲームオブジェクトの親子関係、その設定の仕方をスクリプトで学んでいきたいと思います。

 

ゲームオブジェクトの親子関係について

あるゲームオブジェクトを別のオブジェクトにドラッグ&ドロップするとそのオブジェクトの子になり、傘下に入るというのはこれまでなんども経験してきました。

f:id:covory10101101276:20180409150444p:plain

こんな風になりましたね。エディタ上では設定することが簡単なこの操作ですが、スクリプト上で制御するにはTransformクラスのparentプロパティを使い、以下のように行います。

parentプロパティ

public Transform parent{get; set;}

あるゲームオブジェクトAを別のゲームオブジェクトBの子に設定したい場合、親となるAのtransformプロパティから取得したTransformオブジェクトを、ゲームオブジェクトBのtransformプロパティから取得したTransformオブジェクトparentプロパティに設定します。何いってるかわからないと思うので実際に書いてみます。

f:id:covory10101101276:20180409153146p:plain

子のtransform.parentプロパティに親のtransform情報を入れていますね。結果は以下のようになります。

f:id:covory10101101276:20180409153157p:plain

一瞬でこれらのオブジェクトとその親子関係が築かれました。個人的にはとてもわかりやすい例なのですが、ラディッツは兄貴だから悟空より上ではとかいうのはこの際気にしないでください。

 

ゲームオブジェクトの移動について

シーン内のゲームオブジェクトの移動には、変形ツールの以下の上下左右に伸びた矢印から行いますよね。

f:id:covory10101101276:20180409153650p:plain

これも親子関係を引き継いでおり、親を動かすと全く同じように子もついてきます。これは子の座標が親の座標を参照して得られているからです。このような座標をローカル座標といい、親のような何者にも属さない世界から絶対的な座標を与えられているものワールド座標といいます。

オブジェクトの空間内におけるワールド座標は、positionプロパティで表されています。

positionプロパティ

public Vector3 position{get; set;}

以下のように書きます。

f:id:covory10101101276:20180409155645p:plain

結果は以下の通りになります。

f:id:covory10101101276:20180409155716p:plain

インスペクターを見てわかる通り座標がpositionプロパティで設定されたものになっていますね。

さて、今やったpositionプロパティはワールド座標を表すプロパティですが、子の親との距離、つまりローカル座標を変更したい場合、また別のプロパティを使います。それがlocalPositionプロパティです。そのままですね。

f:id:covory10101101276:20180409160805p:plain

先ほどのを少しだけ変え、CubeをSphereの親にしました。そしてSphereをCubeの少し隣をキープするよう指定しました。

f:id:covory10101101276:20180409160818p:plain

子であるSphereは自由に動かせますが親であるCubeを動かすとSphereも一緒についてきますね。ゲームなどでカメラを追従させるの等に使えそうです。

では移動について最後に、先ほどやった二つのプロパティはワールドにしろローカルにしろいきなり決められた座標に移動するので前者は常に座標を考えなくてはならず、後者は親依存の距離を常に保つだけなので単純な移動としては使いにくいです。指定した向きに指定した量だけ移動させることができればより移動の考え方が楽になりますよね。そこでTranslate()メソッドが役に立ちます。

Translateメソッド

public void Translate(Vector3 translation);

public void Translate(Vector3 translation, Space relativeTo);

public void Translate(Vector3 translation, Transform relativeTo);

 translationパラメータに移動量のVector3型のベクトルもしくはfloat型の(float x, float y, float z)を指定します。

SpaceというのはTranslateで移動させる向きと量をオブジェクトを軸とするか世界を軸とするかを設定できるSpace列挙型というものです

Space列挙型

public enum Space

この列挙型には自身を軸とするSelfメンバと世界を軸にするWorldメンバが宣言されています。いずれかをrelativeToパラメータに入力して使います。

Transformを入れた場合はそのtransformパラメータを軸とします。つまり他のゲームオブジェクト等を軸として移動させられるわけですね。ここで自身のtransformを入れればSpace.Selfと同じになります。

実際に見てみましょう

f:id:covory10101101276:20180409164341p:plain

x座標が3より大きくなれば左に1ずつ、つまりxが-1、逆にxが3より小さくなれば右に1ずつ、つまりxが+1されるようになっています。21行目の(_direction * 0.1f ~ の後に座標軸の情報を入力できます。

f:id:covory10101101276:20180409164756p:plain

ひたすら左右に移動しますね。

 

ゲームオブジェクトの回転

ゲームオブジェクトの回転は変形ツールのリサイクルのようなマークで行いますよね。

f:id:covory10101101276:20180409165224p:plain

これにも軸があり、シーン上ではX, Y, Z を自由に回転させられます。

f:id:covory10101101276:20180409165429p:plain

オブジェクトの向きをスクリプトから変更するにはTransformクラスのrotationプロパティで変更します。

rotationプロパティ

public Quaternion rotation {get; set;} 

rotationプロパティはQuaternion構造体という四元数を格納する構造体を計算し簡単なX, Y, Zで表してくれるのですが、私のような数学が苦手な人を含む一般の人には四元数を入力したQuaternion構造体は手動ではとても用意できないのでQuaternion.Eulerメソッドというものを併用します。これはX, Y, Zをパラメータとして送るとそれを四元数に計算しQuaternion構造体として使用してくれるすごいメソッドなのです。なんだかコンパイラみたいですね。例を載せます

f:id:covory10101101276:20180409172014p:plain

Quaternion.Eulerメソッドのおかげで我々でもわかりやすい形式で角度を指定できます。

f:id:covory10101101276:20180409172026p:plain

カクンってなってます。カクンって。

そしてこれもワールド座標での向きなので、ローカル座標の向きも指定できます。それがlocalRotationプロパティです。

localRotationプロパティ

publicQuaternion localRotation{get; set;}

使い方は先ほどのlocalPositionプロパティ等と同じです。例を載せておきます。

f:id:covory10101101276:20180409173717p:plain

f:id:covory10101101276:20180409173729p:plain

左から、手前に45度、左の角度を軸としてYに45度、真ん中を軸としてZ軸に45となっています。

ローカルときたら次は単純な回転ですね。これにはRotate()メソッドを使います。

Rotate()メソッド

public void Rotate(Vector3 axis, float angle);

public void Rotate(Vector3 eulerAngles, Space relativeTo);

axisに方向、angleにその量を入力するか、eulerAnglesに回転量、その後に軸を指定することで使うことができます。簡単そうなのは下の方ですね。

f:id:covory10101101276:20180409175609p:plain

f:id:covory10101101276:20180409175619p:plain

ゆっくり回転してます。。。

 

ゲームオブジェクトの拡縮

さて最後に大きさの変更の仕方を学んでおきます。シーン上のゲームオブジェクトの拡縮にはでっかくなってる感じのボタンから行えますね

f:id:covory10101101276:20180409180106p:plain

スクリプトでこれを行う場合はTransformクラスのlocalScaleプロパティを用います。

localScaleプロパティ

public Vector3 localScale{get; set}

実はこの大きさ(Scale)だけは常に親を継承するのでローカルしかなく、逆にワールド座標系のサイズ指定を行うことはできません。一応ワールド座標系の実際のサイズを取得したい場合はlossyScaleプロパティから取得できるのですが、読み取り専用プロパティなのでこれに値を入れてサイズを変更といったことはできません。

lossyScaleプロパティ

public Vector3 lossyScale{get;}

では実際に書いていきます。

f:id:covory10101101276:20180409181234p:plain

まずは親のみサイズ変更してみます。すると...

f:id:covory10101101276:20180409181247p:plain

子供もおっきくなりました。

f:id:covory10101101276:20180409181649p:plain

では次に子に同じようにスケールを与えると...

f:id:covory10101101276:20180409181718p:plain

めっちゃでかくなりました。今回はここまでにしておきます。

 

いやー久しぶりの投稿ときたらこれですよ。普段から使えるツールバーのメニューもスクリプトで制御しようとしたらこんなに色々あるんですね。でもゲーム中は自由にツールバーで動かしながらゲームを進行できるわけではないのでこれらの知識は非常に活きてくるはずです!!

 

最近はソシャゲにも色々手を出していて、ゴッドイーターのオンラインとレゾナントオプスを両方ともやっています。ソシャゲって据え置きや製品ゲームとは全くシステムものめり方も違いますよね。これも勉強しなければ...

 

Unityスクリプト / ゲームオブジェクト編

f:id:covory10101101276:20180403220944j:plain

コモ湖 モルトラージオ イタリア

 

みなさんこんにちは🌚

新年度が開始して色々やることや把握することが増えたブルーノです。リアルが忙しくなってもこっちをまったり進めていきます🙄

今回は、スクリプトから直接行えるゲームオブジェクトに関する操作について勉強していきます。ゲームオブジェクトの中身の操作というよりは、生成や管理などを今回はまとめます。

 

UnityEngine.GameObjectクラス

今更ですがスクリプトからゲームオブジェクトを生成したりアクセスしたりするにはこのGameObjectクラスを使います。newキーワードでインスタンス化すればスクリプトからオブジェクトの生成ができます。その際のコンストラクタは以下のように指定することができます。

GameObjectクラスのコンストラクタ

public GameObject()

public GameObject(string name)

public GameObject(string name, System.Type, components)

string nameにゲームオブジェクトの名前、System.Typeにはその型、そしてそのゲームオブジェクトに付属させるコンポーネント名を指定することができます。

f:id:covory10101101276:20180403232139p:plain

何もパラメータを渡さずに実行してみました。

f:id:covory10101101276:20180403232241p:plain

[New Game Object]が追加されました。

 

CreatePrimitiveメソッド

今新しくゲームオブジェクトを生成しましたが、シーン上に目視できるようになるためには物体の形を示すメッシュやそれを描画するレンダラーなどのコンポーネントを必要とします。先ほどのコンストラクタからコンポーネントを指定する他に、CreatePrimitiveメソッドを利用することでゲームオブジェクトに基本モデル(プリミティブ)を追加することができます。

CreatePrimitiveメソッド

public static GameObject.CreatePrimitive(PrimitiveType type); 

typeの欄には基本モデルの図形の種類を指定します。

f:id:covory10101101276:20180403234325p:plain

f:id:covory10101101276:20180403234331p:plain

CreatePrimitiveメソッドで生成できる全ての基本モデルを適当に生成しました。PlaneとQuadの違いはポリゴンの数で、Planeは多くの頂点とポリゴンを持ち、Quadは4頂点2ポリゴンで構成されています。

 

Instantiateメソッド

エディタ上でゲームオブジェクトをコピーアンドペーストするだけで簡単にゲームオブジェクトを複製することができますが、これをスクリプトから行うこともできます。

Instantiateメソッド

public static Object Instantiate(Object original);

public static Object Instantiate(Object original, Vector3 position, Quaterion rotation); 

originalパラメータに複製するゲームオブジェクトを指定し、positionに複製する場所、rotationにその向きを指定します。positionとrotationを指定しないと複製元のオブジェクトと重なったり同じ向きになったりします。

f:id:covory10101101276:20180404000137p:plain

先ほどのスクリプトのキューブを複製しました。

f:id:covory10101101276:20180404000150p:plain

 

Destroyメソッド

ゲーム中にシーン中に作り出した弾やアイテム、エフェクトなどは消さなければいつまでも残り続けてしまいます。そこでこのDestroyメソッドを使ってオブジェクトを削除することができます。

Destroyメソッド

public static void Destroy(Object obj, float t);

objに削除するオブジェクトを、tには削除するまでの時間を指定できます。tを指定しなかった場合は0と同じですぐに削除されます。

f:id:covory10101101276:20180404001018p:plain

複製元のCubeを5秒後に削除します。

f:id:covory10101101276:20180404001054p:plain

なお、ゲームオブジェクトを削除する目的でこのメソッドを削除するオブジェクトのスクリプトに組み込み、パラメータに「this」を入れると、削除されるのはオブジェクトじゃなくてそのスクリプトになります。

 

Findメソッド

シーン上のゲームオブジェクトをスクリプトで名前から検索することができます。

Findメソッド

public static GameObject Find(string name);

nameパラメータにシーン上の名前を指定します。この名前による検索は負荷の高い処理であるため、Update()メソッドなどのフレーム単位で乱用するのは望ましくないそうです。

f:id:covory10101101276:20180404002703p:plain

「Cube」という名前のオブジェクトを検索し、cubeという変数にその情報を格納します。そして、Updateメソッドでその名前のオブジェクトを回転させます。もしCubeという名前のゲームオブジェクトがそのシーン上に見つからない場合は例外が投げられます。

f:id:covory10101101276:20180404002939p:plain

今回はここまでにしておきます。

 

色々便利なメソッドがあるようですね!!私は体で覚える派なのでこういったものを取り入れたゲームをどんどん作って実践していきたいと思います。頭の中のアイデアを早く形にできるようになりたいです。