そらい研究室

関心のある物事について、少し深堀りして解説しています。

MENU

UnityC#ゲームプログラミング入門【イラスト付き/全10章】

コンポーネント

コンポーネントとは、ゲームオブジェクトにつける「機能」のこと。

例えばキャラクターというゲームオブジェクトがあったとして、その位置を右にずらそうと思ったら、キャラクターの位置情報を管理しているコンポーネントである「Transformコンポーネント」の値をインスペクターウィンドウで直接変えるか、プログラムを書いて変更する。

ゲームオブジェクトが持つコンポーネントの種類は、「Transform」以外にも様々なものがある。

  • Transform
    位置、角度、大きさ
  • Sprite Renderer
    2D画像をゲームオブジェクトとして表示
  • Rigidbody 2D
    物理挙動を実装する機能
  • Box Collider2D
    四角い衝突判定
  • Circle Collider2D
    丸い衝突判定

プレハブとインスタンス

「キャラ」というプレハブから4つのキャラ(ゲームオブジェクト)を生成したとき、生成された4つのキャラ(ゲームオブジェクト)はインスタンスという。

プレハブに対する変更は、そのプレハブをもとに生成したすべてのインスタンスに反映される。

そのため、まとめて一つのものとしてスクリプトや設定などを扱いたい時は、同じプレハブをもとにインスタンスとしてオブジェクトを生成するとよい。

変数について

ゲームを作るときには、キャラクターのパラメーターや位置情報、文字情報、フラグなどを保存するのに変数を用いる。変数はデータを入れるための箱のようなもので、

  • 整数値を入れるときは「int型」

  • 小数値を入れるときは「float型」

  • 真と偽を入れるときは「bool型」

  • 文字列を入れるときは「string型」

という種類の変数を用いる。

型を指定して変数を宣言する場合

//int(整数)型変数
int hp = 20;   //fがいらない

//float(浮動小数点)型変数
float friendShip = 0.7f; //fをつける必要があるので注意。

//bool型変数
bool evoflag = false; 

//string型変数
string name= "ねこぼっくす"; 

先頭に「public」をつけて宣言するとpublic変数となり、インスペクタ上で値を変更することができる。

ゲームの試作段階ではパラメータにいろんな数値を入れて、挙動を見ながらつくっていくことになるが、そういうときにパラメータをpublic変数にしておけば、テストが簡単になる。

//public変数
public int hp = 10;  //インスペクタから変更可能。

また、数値を増やしたいときは、

int counter = 0;

void FixedUpdate()
{
    counter++;
    Debug.Log("今のcounter変数内の値は"+counter);
}

というようなかたちで、「++(インクリメント)」を使う。

Unityでの座標について

シーンビューやゲームビューにおける軸については、(0,0)が中央。
そして、座標は(x,y)と示す。なので例えば(1,0)では、右に1マス。(0,1)で上に1マスとなる。

座標を意識しなくても、Unityではキャラクターやアイテム、ステージを配置することができるが、スクリプトから座標を指定して配置したりすることがあるので、その時のために覚えておくと良い。

ちなみに3Dゲームの場合はz方向に奥行きが足されるので、座標は(x,y,z)と表す。

3次元の情報を扱うときに使うVector型変数

Vector3型変数は、XYZの3次元の値をひとつにまとめて3次元の情報を記録するときに用いる変数で、ゲームオブジェクトの位置情報を保存したり、位置情報を書き直すときなどに使用する。

ゲームオブジェクトの位置情報をもとにVector3型変数を作成する場合

ゲームオブジェクトの位置をプログラムで扱えるposという名前の変数に変換しているイメージ。

下の例のように変数posにゲームオブジェクトの位置が入力できていれば、変数の値を書き換えることで位置を変更したり、移動しているように見せることができる。

//Vector3型に格納する
Vector3 pos = transform.position; //transformコンポーネントからpositionプロパティを取り出して、Vector3型変数に入力

新しい座標を入力してVector3型変数を作成する場合

上の例はゲームオブジェクトから位置情報を取得して変数posに書き込んだ。一方、こちらはVector3型の値(X,Y,Z軸の値)をnew演算子というものを使って新しく作成してメモリ上に確保している。

ようするに、新しく座標を作り出して変数posに代入している。

Vector3 pos = new Vector3(0f, 0f, 0f); //第1引数はx座標、第2引数はy座標、第3引数はz座標

変数の型を変換するとき

変数の型を変換することをキャストという。
例えば、float型の変数「pi」からint型の変数「seisuPi」に変換するときには、下のように書く。

やっていることは、小数値で表した円周率3.141592を3という整数値に書き換えているだけ。

float pi = 3.141592f; //キャスト前の変数
int seisuPi; //キャスト後の値を格納する用の変数

seisuPi = (int)pi; //キャスト

列挙型変数

ゲームの進行ステータスやパラメーター、種類などのように複数の要素を持つ変数を作成するときに「列挙型変数」を使う。

「列挙型変数」を使えるようになると、プログラムが読みやすくなり、ゲームも作りやすくなるが、少し難しい概念なのでここではいったん飛ばして後から学習すると良い。

sorai-lab.hateblo.jp

リンク先では、 ①状態異常のステータスをつくる②キャラクターのパラメータ設定を行う

の二つのサンプルプログラムを紹介している。

配列について

配列は複数の値(要素)を持つ。複数の箱が並んでいる様子をイメージするといい。

配列のなかにある特定の要素を指定して取り出すときには、要素番号を指定する。C#の配列の要素番号は0スタートなので、1番目の要素を取り出すときには「0」を指定する。

//配列① 初期値のある配列を定義する場合
int[] hairetsu = { 1, 2, 3 }; //配列の宣言と値を設定

void Start()
{
    Debug.Log(hairetsu[0]); //1番目(要素番号0番)の要素を表示する
}
//配列② 空の配列を作り、あとから要素を代入する場合
int[] hairetsu = new int[3]; // 3つの要素を持つ配列を生成

void Start()
{
    hairetsu[0] = 1; // 1つ目の要素に代入
    hairetsu[1] = 2; // 2つ目の要素に代入
    hairetsu[2] = 3; // 3つ目の要素に代入
    
    Debug.Log(hairetsu[0]); //1番目(要素番号0番)の要素を表示する
}

クラスとメソッドについて

クラスとは

クラスとは、ゲームオブジェクトの挙動についてまとめて書いたファイルのこと。似たような「メソッド」や「変数」を集めて一つのクラスとして扱う。Unityではクラス名はC#ファイル名と同じ名前で設定される。単語度の先頭の文字を大文字で記述するルールがある。

下の例は「Charactor」という名前のC#ファイルを作成したときに自動で作成されるクラス。

public class Charactor:MonoBehaviour{
    //このクラス内のメソッドはここに書く
}

作成されたCharactorクラスは、MonoBehaviourクラスから「継承」するかたちで作られている。(なお、継承とはあるクラスが別のクラスの性質を引き継ぐことをいう。)

MonoBehaviourクラスを継承しているので、CharactorクラスではMonoBehaviourクラスの持つ性質(Unity特有のコンポネントのプロパティやメソッド)を作成したスクリプトで利用することができる。

ちなみに、これは覚えなくてもいいけれど「{}」のことをブレスといい、ブレス内をブロックという。

メソッドとは

メソッドとは関数のようなもので、いくつかの処理を一つのグループとしてまとめたもの。

例:メソッド
    public int hp = 100;
    int c;

    int Damage(int a, int b) //第一引数にa、第二引数にbを設定して
    {
        c = a - b; // 計算処理(引数bだけ減算)
        return c; //メソッド内での計算結果を変数cとして返す
    }

    //メソッドの呼び出し
    void Start()
    {
        Damage(hp, 20); // メソッドDamageを呼び出し(第一引数に変数hp、第二引数に20を入れた状態)
        hp = c; //変数hpをcの値で更新する

        Debug.Log(hp); // 変数hpの値を表示   
    }

メソッドは入れた値に対して結果を返す。入れる値のことを「引数」、出力する値を「返り値」という。

この例の場合、Damageメソッドに(hp,20)を引数として入れて、Damageメソッドによって返された返り値である「変数c」の値でhpの値を更新し、Debug.Logを使って出力している。(最終的な出力結果は、Damageメソッド呼出し後の変数hpの値)

引数を変えるだけで結果が変わるため引数と返り値のあるメソッドを活用すれば、同じような処理を何度か行うときに再利用しやすくなる。

ちなみに変数をメソッド内で宣言すると「メソッド内変数」として扱われてメソッド内でしか使えなくなるので、複数のメソッドで同じ変数を扱うときには、メソッド外で宣言する必要がある。(メソッド外で宣言する変数のことを「メンバー変数」という)

例:引数と戻り値のないメソッドの場合

「引数と返り値のないメソッド」は、値を入力せずに実行するメソッド。 同じ処理が何度も登場する場合は、何度も同じコードを書いていると読みにくくなってくるので、 メソッドに切り分けておくのがよい。

//メソッドの作成
void Test()
{
    //このメソッドに実装したい処理はここに書く
}

//メソッドの呼び出し
void Start(){
    Test(); //メソッドTestを実行。メソッドは作っただけでは呼び出されないため、呼び出して使う必要がある。
}

UpdateメソッドとFixedUpdateメソッド

UpdateメソッドとFixedUpdateメソッドは「ブロック内の処理を繰り返し呼び出すメソッド」であり、イベント関数といわれることもある。

Updateメソッドは毎フレーム実行されるが、FixedUpdateメソッドは1秒間に50回固定で実行される。

したがって毎フレームで受け取るようにしたい「キー入力」や「ボタンの入力」の受け取りなどの処理はUpdateメソッド内で行うべきである。

一方で、ゲームオブジェクトの移動や回転などの「物理演算処理」や、「カウンターを使った処理」は一定時間内に決まった回数処理が実行されるようにFixedUpdateメソッド内で行う必要がある。

失敗例

  • Updateメソッドでオブジェクトを移動してしまう→マシンスペックの高いパソコンで実行すると、移動速度が異常に早くなってしまう。また、画面上での挙動が安定せずカクカクしてしまう

  • FixedUpdateメソッドでキー入力を受け付けてしまう→FixedUpdateは1秒間に50回、すなわち0.02秒に1回しか実行しない(1秒÷50回=1回あたり0.02秒の間隔)ので、キー入力を受け取れないタイミングが生じてしまう

実行タイミングを操る「コルーチン」

コルーチンとは、途中で処理をストップして次フレーム後や数秒後に処理を再開する機能で、メソッドの仲間。ゲームではタイミングの調整を行う際によく使用する。

コルーチンを使う場面

  • 必殺技を撃つ敵の動きを一瞬止める

  • 敵が攻撃してきた弾の発生タイミングを調整する

  • 敵を倒した後に少し遅れて爆発させる

  • 一文字ずつセリフを表示する

コルーチンの使い方

コルーチンを使うには、下のようにyeild文を含むメソッドを宣言し、

IEnumerator メソッド名() 
{
        //ここに処理を書く
        yield return new WaitForSeconds(1f); //この場合は1秒停止
        //再開後の処理を書く
} 

StartCoroutineメソッドを使って呼び出す

StartCoroutine("コルーチンのメソッド名");

「数秒停止する場合」のサンプル

IEnumerator メソッド名() 
{
        //ここに処理を書く
        yield return new WaitForSeconds(1f); //この場合は1秒停止
        //再開後の処理を書く
} 

void Start()
 {
  StartCoroutine("コルーチンのメソッド名");
 }

処理を停止しているときには、メンバ変数やグローバル変数の値を保持するので、停止した後でも変数の値を使うことができる。

条件分岐と繰り返し

if文

if文は条件に合わせて分岐処理を行う。例えば、下のプログラムではランダムに生成した数値によって条件判定を行い、「通常攻撃」「会心の一撃」「ミス」の三パターンの処理に分岐させている。(サンプルなのでわかりやすくするために、Startのタイミングで実行している)

if文を使った「会心の一撃 判定」のサンプル

int successRateGen;

void Start()
{
    successRateGen = Random.Range(0, 100); //ランダムに0から99までの値を生成

    if (10 <= successRateGen && successRateGen < 90)
    {
        Debug.Log("通常の攻撃!!"); // 変数の値が11~89のとき
    }
    else if (90 <= successRateGen)
    {
        Debug.Log("会心の一撃!!"); //変数の値が90以上のとき
    }
    else
    {
        Debug.Log("ミス!");  //変数の値がそれ以外(10以下)のとき
    }
}

ほかにも、

  • 歩き進めるたびに敵とのエンカウントを設定する

  • 入力されたキーに合わせてキャラクターを移動させる

  • 自分や敵の残HPによって、ゲームクリアorゲームオーバーの判定を行う

など状況やパラメータに合わせて様々な処理を行う上でもよく使うので、if文は使えるようになっておくとよい。

for文

同じ処理を繰り返し行ったり、値を増やしながら繰り返したりするのに使う。 ゲームにて使う場面は正直少ないが何度も連続で攻撃したり、ステージマップを作るときに使用する。

for文を使った「ばくれつけん」のサンプル

int atack;
Debug.Log("ばくれつけん!"); //「ばくれつけん」での攻撃開始のメッセージ

//for文の「i」は初期値0のカウンター。i<3になるまで、1を足していく。
for (int i  = 0 ; i< 3 ; i++ ){ 
atack = Random.Range(10, 16); //10から15までの数を生成
Debug.Log("敵に" + atack + "のダメージ");
}

条件判定で使う演算子

大小を調べる演算子のことを「関係演算子」といい、等しいかどうかを調べる演算子を「等値演算子」という。 また、条件を付けくわえる演算子のことを「論理演算子」という。if文での条件分岐でよく使うので覚えておくとよい。

  • 関係演算子 「>」や「>=」など。A>B、A>=Bというように数値の大小関係を表すのに使う。

  • 等値演算子 「==」を使う。イコールが一個だと値の代入の意味になってしまうので、条件判定に使うときには必ず二個使うようにする。

  • 論理演算子 「AND(&&)、OR(||)」など。ANDは両方が成立する場合、ORはどちらか片方が成立する場合、を意味する。 その他にも論理演算子はあるので必要に応じて調べてみるといいが、この二つで間に合うことが多い。