シンプルなアクションゲームを作ってみよう6-スクリプトの基本-

シンプルなアクションゲームを作ってみようの第6回です。

今回はUnityで作成するスクリプトの基本的な記述の仕方について見ていきます。

前回はキャラクターモデルにRigidbodyとCapsule Colliderを取り付け設定をしました。

シンプルなアクションゲームを作ってみよう5-RigidbodyとCapsuleColliderの取り付け-
シンプルなアクションゲームで使用するキャラクターにRigidbodyとCapsuleColliderコンポーネントを取り付けます。

シンプルなアクションゲームを作ってみようの他の記事は

シンプルなアクションゲームを作ってみよう

シンプルなアクションゲームを作るのを通してUnityの使い方を学ぶカテゴリです。

から参照出来ます。

スポンサーリンク

オブジェクト指向

UnityではC#言語を使い、基本的にはオブジェクト指向型でスクリプトを作成します。

オブジェクト指向はオブジェクト単位でプログラミングをする考え方で、現実の世界をそのままプログラムにするという感じです。

その現実の世界の物の設計図がクラスというもので、そのクラスの中にそのクラスが持つデータと処理を記述し、何らかの処理をする場合はそのクラスに「処理をしてくれ」という命令を送るだけで実際の処理はそのクラスが請け負います。

例えば、人間クラスを作ったとしたら人間の性別や年齢などのデータをフィールドやプロパティとして持ち、喋るや食べるといった処理をメソッドとして持つようにスクリプトを作成します。

クラスは現実の世界の物の設計図なので、その設計図から違うデータを持つインスタンス(固有の物)を生成することが出来ます。

例えば、人間クラスから太郎君や花子ちゃん等の複数の人間を作れます。

詳しい事はC#のプログラミング言語の本を見た方がいいです・・・(^_^;)

Unityの場合はMainメソッド(処理をしてくれ!と命令を送る役割)から処理の流れを作るのではなく、StartやUpdateメソッドがUnityから呼ばれるのでそちらに実行したい処理を記述していきます。

Scriptsフォルダの作成

まずは、スクリプトを入れておく用のフォルダを作成します。

Assetsフォルダ内で右クリックからCreate→Folderを選択し、名前をScriptsとします。

今後、ゲームで使用するスクリプトはAssets/Scriptsフォルダの中に入れていきます。

SimpleActionGameのプロジェクトでScriptsフォルダを作成する

スクリプトの種類によってさらにフォルダを分けていくと、よりわかりやすくなります。

Practiceシーンを開く

今回の記事では、以前作成したPracticeシーンを使用します。

ヒエラルキーには、元からあるMain Camera、Directional Lightの他に、Cubeゲームオブジェクトを作成しておきます。

SimpleActionGameでスクリプトの作成とテストの為にCubeを作成する

新しいスクリプトの作成

次に、新しくスクリプトを作成します。

Assets/Scriptsフォルダ内で、右クリックからCreate→C# Scriptを選択し、名前をCubeScriptとします。

スクリプトの名前は何をするスクリプトなのか? が、わかりやすい名前を付けます。

わたくしのセンスのない名前の付け方はマネしないでください・・・・|ω・)

CubeScriptをゲームオブジェクトに取り付ける

スクリプトファイルが出来たので、次にCubeゲームオブジェクトにCubeScriptスクリプトを取り付けます。

やり方としては、CubeScriptファイルをヒエラルキーのCubeにドラッグ&ドロップするか、CubeゲームオブジェクトのインスペクタにCubeScriptをドラッグ&ドロップするか、CubeゲームオブジェクトのインスペクタのAdd ComponentのScriptsからCubeScriptを選択するか、など、複数のやり方があります。

CubeScriptスクリプトをゲームオブジェクトに取り付ける

CubeScriptファイルをダブルクリックすると、スクリプトの編集が出来るVisual Studioが開きます。

デフォルトのスクリプトの説明

Visual StudioでCubeScriptを開くと、以下のように既にいくつか記述がされています。

これは、テンプレートでこうなっているだけなので、スクリプトの中身はある程度自由に書き換えられます。

スクリプトの保存はCtrl+Sキーで出来、Visual Studioで保存したらUnityエディターで直ぐに反映され、Unityを実行して確認が出来ます。

名前空間のusing指定

using なんちゃら;

とあるのは、クラスを名前空間から指定しなくてもいいようにする為の記述です。

例えば

という記述を削除した場合、MonoBehaviourクラスはUnityEngine名前空間に所属しているので

上のようにUnityEngine名前空間から記述をしないといけなくなります。

クラスの定義

上の部分がクラスの定義部分で、C# Scriptを作成した時のファイル名と、public指定したclassの後の名前を一致させる必要があります。

今回の場合は、CubeScriptというファイル名なのでpublic classの後はCubeScriptという名前にする必要があります。

大文字と小文字を区別するので、コンソールにエラーがでたら、ここら辺を確認してください。

継承

継承は、そのクラスが他のクラスやインタフェース(どういう機能を持つかの看板みたいなもの)の機能を継承して、そのクラスで使用出来るようにする為のものです。

上の場合はMonoBehaviourクラスを継承して、CubeScriptクラスを作成するという事になります。

Unityでは、ゲームオブジェクトに取り付けるスクリプトは、ほとんど(必ず?)MonoBehaviourクラスを継承しているクラスになります。

ゲームオブジェクトに取り付ける必要がないスクリプトの場合は、objectクラスやScriptableObjectクラスを継承して作成します。

StartメソッドとUpdateメソッド

CubeScriptクラスにデフォルトで用意されているStartメソッドとUpdateメソッドについて見ていきます。

コメント

StartメソッドとUpdateメソッドの前に記述してある分はコメントです。

//(スラッシュ二つ)の後の行がコメントでスクリプトには影響を及ぼしません。

人間が見てわかりやすい説明などを記述します。

自分しかスクリプトを使わない場合でも、時間が経つと自分でも何をしているのかわからない! ということにもなるので、コメントはちょくちょく残しておくといいです。

/* から */の間もコメントになるので、複数行に渡ってコメントにする場合はこちらも使います。

Startメソッド

Startメソッドは、このスクリプトが取り付けられたゲームオブジェクトがアクティブな場合に、最初に1回だけ実行されるメソッドです。

通常はこのメソッド内でスクリプトの初期化等を行います。

Updateメソッド

Updateメソッドは毎フレーム実行されるメソッドなので、スクリプトで何らかの処理を実行したい場合は、このメソッドの中に記述していくことが多くなります。

その他、MonoBehaviourクラスで定義されているものは、Unityのスクリプトリファレンスで確認出来ます。

UnityEngine.MonoBehaviour - Unity スクリプトリファレンス
MonoBehaviour is the base class from which every Unity script derives.

CubeScriptに記述をしてみよう

スクリプトを作成した時に、デフォルトで記述してある内容については見てきましたが、データであるフィールドやプロパティ、処理をするメソッドを自分で記述して、より理解を深めていきましょう。

CubeScriptに記述する内容を考える

CubeScriptは毎フレーム、ワールドのX軸の正の方向に移動させるスクリプトにします。

CubeScriptにデータとして移動スピード(speed)を用意し、処理をするメソッドとしてMoveメソッドを作ることにします。

フィールドの作成

まずは、移動スピードを表すspeedフィールドをCubeScriptの中に作成します。

[SerializeField]という記述はSerializeFieldアトリビュートをspeedフィールドに取り付けていて、これを取り付けるとこのスクリプトを取り付けたゲームオブジェクトのインスペクタでspeedの値を設定できるようになります。

フィールドは最初の文字を小文字、もしくは_(アンダーバー)を付けて定義するのが慣習になっています。

privateというのはアクセス修飾子で、このspeedがどのクラスからアクセス出来るかを表しています。

privateの場合は、このクラス内からしかアクセス出来ません。

floatというのは小数点を扱える型です。

= 2fでデフォルト値として2のfloat値を設定しています。

インスペクタのCubeScriptの右の3つの丸を押してResetを選択した時に、ここで設定した値にリセットされます。

CubeScriptのspeedをデフォルト値にリセットする

speedはこのクラスからしかアクセス出来ないので、値をセットしたり、値を取得したい場合はセッターやゲッターと呼ばれるメソッドを用意しそこからアクセスします。

今回は、speedのアクセス修飾子をprivateにし[SerializeField]アトリビュートを取り付ける事で、インスペクタで値を設定出来るようにしていますが、アクセス修飾子をpublicにすると[SerializeField]アトリビュートを取り付けなくてもインスペクタで値を設定出来ます。

フィールドの値をインスペクタで設定出来るようにする

ただ、フィールドは基本privateにすると思っているので、アクセス修飾子はprivateにして[SerializeField]を取り付けています。

データとしてフィールドではなくプロパティにしてゲッターやセッターを記述せずアクセスすることも出来ます。

必要に応じて説明します。

今回はspeedの型をfloatにしていますが、他にも型はあります。

boolは真か偽かの型
intは整数型
charは文字型
stringは文字列型
floatは浮動小数点型の単精度
doubleは浮動小数点型の倍精度

等があります。

これらのような基本型に加えて、自分で作成したクラスの型も宣言出来ます。

例えば

上のようにCubeScript型のcubeScriptフィールドを宣言することも出来ます。

メソッドの作成

CubeScriptでは、毎フレームCubeゲームオブジェクトを移動させるので、Updateメソッドに移動処理を記述します。

全ての処理をUpdateメソッド内にダラダラと書いてもいいんですが、ある一定の処理は、まとめてメソッドとして作成した方が不具合が発生した時に、どこで不具合が発生しているかがわかりやすくなります。

そこで、Moveメソッドを作成し、Updateメソッド内から呼び出すことにします。

メソッド名は最初に動詞を付けます。

移動をする処理をするメソッドならばMove等を最初に付けます。

UpdateメソッドではMoveメソッドを呼び出しているだけです。

メソッドの呼び出しには

のようにメソッド名の後に小括弧を付けます。

小括弧の中にはメソッドに渡す引数を指定する事も出来ますが、今回は使わないので引数(渡す値)は渡しません。

Moveメソッドはprivateアクセス修飾子を付け、このクラスからしか呼べないようにしています。

他のクラスからもメソッドを呼べるようにする場合は、public等のアクセス修飾子を指定します。

privateの後のvoidは、このメソッドを処理して得られた何らかの値を呼び出し元に返したい場合に、返す値の型を指定します。

ですが、今回作成したMoveメソッドは移動処理だけして、特に値を返さないのでvoidを指定して値を返さないということを表しています。

transformはMonoBehaviourクラスで定義されていてTransformコンポーネントを表しています。

transform.positionでこのスクリプトを取り付けたゲームオブジェクト(Cubeゲームオブジェクト)の位置を取得出来ます。

の部分は、X軸にspeed、Y軸は0、Z軸も0の値を設定してVector3型(3つの軸を表すベクトルの型)のインスタンスを作成しています。

Time.deltaTime

Vector3の値にTime.deltaTimeを掛けています(スクリプトでは*の記号で掛けるを表す)。

Time.deltaTimeは、前回のUpdateメソッドの実行終了から現在のUpdateメソッドを実行するまでの時間を取得出来ます。

つまりUpdateメソッドが1秒間に30回実行される(30fps)の場合は

Time.deltaTime × 30フレーム = 1秒

となります。

1秒間にUpdateメソッドが呼び出される時間は、パソコンの処理速度等によって変わってきてしまうので、Time.deltaTimeを掛ける事で移動距離が変わるのを避けるようにすることが出来ます。

Time.deltaTimeをかけないとUpdateメソッドが呼ばれる度にX軸に2m移動しますが、Time.deltaTimeをかけることで1秒間に2m移動するというように調整出来ます。

この計算した値を、最後にtransform.positionに足して、再度transform.positionに入れています。

その記号が

+=

です。

+=は、左辺の値に右辺の値を足して再度左辺に代入するということです。

以下の記述は

下のように記述したのと同じです。

上のスクリプトでは、左辺と右辺がイコールなのではなく、右辺の計算をした後に左辺に代入するという意味です。

実行してみよう

CubeScriptが出来たので、Unityエディターのプレイボタンを押して実行してみましょう。

Cubeゲームオブジェクトが1秒間に2mの速さでワールドのX軸の正の向きに移動しているのを確認出来ます。

CubeゲームオブジェクトをCubeScriptスクリプトで動かす

別のスクリプトを作ってスクリプトの補足

CubeScriptを作り、ゲームで使うスクリプトのイメージが何となくわかったところで、別のスクリプトを作って、スクリプトについてさらに理解を深めてみます。

少し難しくなります(完全に理解しなくてもたぶん大丈夫です)。

Assets/Scriptsフォルダ内で右クリックからCreate→C# Scriptを選択し、名前をTestScriptとします。

ヒエラルキーウインドウ内で右クリックからCreate Emptyを選択し、空のゲームオブジェクトを作成したらTestScriptをドラッグ&ドロップして取り付けます。

空のゲームオブジェクトにTestScriptを取り付ける

TestScriptには以下のように記述します。

TestScriptは、特に意味があるスクリプトではありませんが説明の為に作成しました。

TestScriptではHumanクラスのtaroとhanakoという二つのフィールドを持たせました。

この、taroとhanakoはTestScriptクラス内で使用出来ます。

次にprivateなHumanクラスを見てみます。

HumanクラスはTestScriptクラスに所属するプライベートなクラスとして作成しました。

ageは年齢を表すフィールドです。

フィールドのアクセサー

ageはprivateなのでセッターとゲッターを用意してアクセスします。

SetAgeメソッドでは、int型の数値を受け取ってthis.age(フィールド)に受け取った値を入れています。

SetAgeの括弧の中のageは引数名でこのメソッド内だけで使用出来ます。

ただ、ageフィールドと同じ名前を付けたので

として、this.ageでこのクラスのage(フィールド)にageを入れるというようにしています。

とした場合は、引数として受け取った値を再度引数のageに入れているだけなので意味がありません。

GetAgeメソッドは呼び出されたらageフィールドの値を呼び出し元に返したいので、返す値の型(ここではint型)をpublicの後に書いています。

値を返す場合はreturnを記述して、その後に返す値を指定します。

静的なフィールドとメソッド

testはテスト用に作った静的な(別名クラス)フィールドです。

静的フィールドはクラスに所属しているので、Humanクラスから太郎や花子と別の人間を作ったとしても、静的なtestフィールドは共有しています。

静的なフィールドはクラスに所属しているフィールドなので、セッターやゲッターを用意する場合にもstaticを付けて静的なメソッドとする必要があります。

静的なメソッド内で通常のフィールドは呼び出せません。

これは、太郎や花子などのインスタンスは個別の値(フィールド)を持っていますが、これはインスタンスが生成されて初めて決定するものなので、インスタンスを作らずに呼び出せる静的なメソッドではこれらの値は使えないということです。

プロパティ

次はプロパティについて見ていきます。

上の部分がプロパティで、プロパティ名は慣習的には大文字から始めますが、Unity内のスクリプトでも小文字で始まっているので特別気にしなくてもいいかもしれません。

プロパティの場合は、ゲッターやセッターを作らず直接アクセスします。

getやsetの前にアクセス修飾子を付けてアクセス制限をすることも出来ます。

フィールドとプロパティの違いは、フィールドの場合は基本的にprivateにするのでセッターやゲッターを用意して使いますが、プロパティの場合はそれを使わずアクセスするということ。

プログラミング言語のJavaを勉強した時に書いてあったのは、プロパティしか使えない機能もあったりするというのを見ました(Web系の操作?)。

C#ではどうなんだろうなぁと思いますが、フィールドとプロパティの違いについては調べてみてください・・・・(^_^;)

コンストラクタ

コンストラクタは、クラスからインスタンスを生成した時に呼ばれるメソッドのようなものです。

アクセス修飾子の後にクラス名と同じ名前を書きます。

インスタンス生成時に年齢と名前を受け取り、それをフィールドとプロパティに設定しています。

インスタンスの生成

これでHumanクラスが出来たので、次はHumanクラスのインスタンス(設計図から作った実体)を作っているStartメソッドを見ていきます。

Startメソッドは、アクティブなゲームオブジェクトが登場した時に1回呼ばれるので、ここでtaroとhanakoのインスタンスを生成します。

クラス(設計図)からインスタンス(実体)を作りだすにはnewというキーワードを書き、その後にクラス名()という記述をします。

Humanクラスのコンストラクタでは、年齢と名前を受け取るように作っていたので、インスタンスを生成する時にそれらの値も一緒に渡します。

引数の順番は、コンストラクタ(メソッドも同様)に書いた引数の型の順番と同じにする必要があります。

今回の場合は第1引数がint型、第2引数がstring型なのでその順番で書いています。

作成したインスタンスはTestScriptクラスのフィールドに入れて後で使います。

コンソールに情報を出力する

Debug.Logを使用すると、Unityエディターでプレイボタンを押した時に引数に指定した文字列をConsoleウインドウに表示することが出来ます。

今回の場合は

上のようにtaro.GetAge()とhanako.GetAge()メソッドでint型の値を得て、それをDebug.Logの引数に渡すことで、太郎と花子の年齢をコンソールに出力しています。

アクセス制限されているか確認

HumanクラスのNameプロパティは、privateアクセス修飾子を付けたので、クラス内でしか値を設定出来ません。

上のようにageフィールドは設定出来ますが、Nameプロパティに直接名前を設定する事は出来ません。

今回のスクリプトではNameプロパティのセッターがないので、インスタンス生成時にしか名前を設定出来ません。

静的なフィールドの取得

testフィールドは静的なフィールドなのでインスタンス個々ではなく、クラス自身に所属しているフィールドになります。

上のようにインスタンス名の後にメソッド名を指定してメソッド呼び出しをするのではなく、クラス名の後にメソッド名を書いて呼び出します。

実行してみよう

スクリプトが出来たので、Unityエディターのプレイボタンを押して実行してみましょう。

Consoleウインドウに以下のように表示されました。

TestScriptの実行結果がコンソールに表示された

変数、条件分岐や繰り返し処理について

変数はメソッド内だけで使用するフィールドのようなもので、それが宣言されたブロック({})内でのみ使えます。

ここまでのスクリプトでは、StartメソッドやUpdateメソッドが呼ばれる度に何らかの処理を必ず実行していました。

しかし、何らかの条件が成立したら実行したり、同じ処理を指定した回数実行したいこともあります。

そんな条件が成立しているかを確認するのがif文で、一定回数繰り返す処理はwhileやforやforeach、do-while等いくつかの処理があります。

これらを確認する為に新しくTestScript2スクリプトを作成し、PracticeシーンのGameObjectゲームオブジェクトに取り付けます。

GameObjectのインスペクタのTestScriptの横のチェックを外し、実行しないようにします。

スクリプトのテストの為にTestScript2を取り付ける

まず、最初にStartメソッドで新しくaという名前の変数を宣言し-6を入れています。

その後、Addメソッドに4と変数aを引数として渡して呼び出しています。

Addの戻り値を変数resultに入れています。

メソッドAddは以下のようになっており、

引数aとbに、先ほどAddメソッド呼び出しの引数で渡した4と変数aの値の-6が入ります。

この引数のaと、先ほどAddメソッド呼び出し時に渡した変数aは、まったくの別ものなので気を付けてください。

変数や引数はメソッド等のブロック内で宣言されたものはその中でしか使用出来ないので、例え同じ名前でも別ものになります。

ただし、これは値型(新たに引数の領域を作成する型)の場合であり、参照型(保存先の参照を渡す)の場合は中身が書き換えられてしまうので注意です。

intやfloat等は値型。
stringは参照型ですが、こちらは特別な型になるので呼び出し元の文字列は書き換えられません。
自前のクラス等は参照型になります。

変数aとbを足した結果を変数cに入れ、その値をreturnで呼び出し元に返します。

if文で条件分岐

先ほどAddメソッドを呼び出し、その結果をresult変数に入れました。

resultの値が0以上の時は0以上とコンソールに表示し、
resultの値が0未満の時は0未満とコンソールに表示するようにします。

それを行っているのが以下の処理です。

ifの後の小括弧の中がtrueの時はその後のブロックを実行し、それ以外の時はelse以下のブロックを実行します。

以上や以下を表す場合は、比較演算子の後に=を付けます。

条件分岐を細かく指定したい場合は、以下のようにelse ifを使う事で条件をどんどん足すことも出来ます。

if文の条件について

if文の条件に関しては、最終的にtrueかfalseかで条件が成立したかを判断します。

なのでbool型をそのまま条件文に書く場合はtrueとfalseとの比較をする必要はありません。

例えば

上のようにflagはbool型なので、trueかfalseが入っています。

if文の条件はtrueかfalseかで判断するので、flag == trueと判断する必要がなく、そのままflagを条件式に書くだけです。

==とイコールを二つ並べると左右の値が同じかどうか判断できます。

ある値が0より大きいかを判断し、処理を実行したい場合は以下のようになりますが、先ほどif文の条件式は最終的にtrueかfalseで判断すると言いましたが、trueでもfalseでもありません。

上の場合は

a > 0

という部分が比較されaが0より大きい時にtrueが得られ、そうでない時はfalseが得られます。

なので、条件式には最終的にtrueかfalseが得られる変数や式を書きます。

繰り返し処理

繰り返し処理はいくつかやり方があります。

繰り返し処理はループを抜ける条件に気を付けないと、無限ループになってUnityが落ちてしまうので気を付けてください。

while

whileは無限ループを作りたい場合によく利用されます。

上の場合は繰り返す回数をnumberOfTimes変数に入れ、その後whileとその条件を設定しています。

ここでは条件にtrueを設定し、無限に繰り返すようにしています。

whileテストという文字列に、+記号でnumberOfTimesを連結してコンソールに表示します。

Debug.Logの引数に文字列と+記号で変数を連結させたり、そのまま変数を指定すると数値に置き換わり、さらに文字列に変換されコンソールに表示されます。

その後、numberOfTimesを–記号で1減らします。

if文でnumberOfTimesが0になったら(int型の場合に比較をする場合は==で出来る)breakでwhileループを抜けます。

for

for文は繰り返す回数が決まっている場合によく使われます。

上の場合は変数iを0で初期化し、その後i < numberOfTimesの条件が成立したらブロック内の処理を実行します。今回はforテストにiの値を足したものを出力します。その処理が終わったらi++の処理をしてiに1を足し、その後またi < numberOfTimesの条件を比較しということを繰り返します。

foreach

foreachは、IEnumerableというインタフェースを継承したクラスが持つ全ての要素を繰り返し処理する時に使います。

例えば、文字列、配列等です。

上の場合は 文字列 という文字列を入れたoutputStringに含まれるすべての要素、この場合は全ての文字をコンソールに表示します。

outputStringの要素を取り出し、それを宣言したcharacterに入れ使用しています。

varは暗黙の型指定をする為の表記で、コンパイラが勝手に型を予測してくれます。

var characterの部分をchar characterとしても出来ます。

do-while

do-whileは繰り返しを必ず1回行う処理で使用する事が多いです。

上の場合は、numberOfTimesを1引いた後にnumberOfTimes % 2 == 0でnumberOfTimesを2で割った余りが0の時、つまり偶数の時だけcontinueでそれ以降の処理をせずに次のループを実行します。

continueは繰り返しの処理中で条件に満たない時に、次のループ処理にすぐさま移したい時に使います。

numberOfTimesが-1になったらbreakでdo-whileの処理を抜けます。

今回の場合は、繰り返し条件にtrueを使用しているので、break等を使ってループを抜ける必要があります。

終わりに

今回はスクリプトの作り方とスクリプトの基本的な記述の仕方を見てきました。

ちょっとスクリプトの解説の順序を間違えましたね・・・・、まず最初にTestScript2、次にTestScript、最後に実践的なCubeScriptにすればよかったなぁと思うんですが、難易度的にはこの順番でいいのかも?

Time.deltaTimeはちょっと分かり辛いかもしれませんが、使っているうちに慣れてくると思います。

次回はキャラクターを動かすスクリプトを作成していきます。

タイトルとURLをコピーしました