【ゲーム開発のためのC#入門講座・応用学習編】コンストラクタについて学ぼう【#5】

4.0_C#応用学習編

おさらい

  • クラスに属する変数のことをメンバー変数という
  • メンバー変数は関数の中でだけ利用されるローカル変数と違い、インスタンスが存在する限り共にあり続ける。ズッ友
  • メンバー変数を活用することで、インスタンス毎に共有しておきたい情報を管理することができる
  • ただし安易に使うと調査が大変で地獄を見ることになるので、ご利用は計画的に

前回の記事ではメンバー変数について学習しました。

下記のコードでいうと、クラスに属する「Name」「Level」「HP」「ATK」がメンバー変数に該当します。

public class BattleCharacter
{
    // ↓メンバー変数
    public string Name = "";
    public int Level = 0;
    public int HP = 0;
    public int ATK = 0;
​
    public int Attack()
    {
        return ATK + Level * 2 / 5;
    }
}

そして「リンク」や「ゴブリン」といったインスタンスを作成した際に、そのインスタンス毎に値を設定することができるんでしたね。

BattleCharacter link = new BattleCharacter();
// ↓インスタンス毎に値を設定することができる
link.Name = "リンク";
link.Level = 10;
link.HP = 25;
link.ATK = 10;
​
BattleCharacter goblin = new BattleCharacter();
// ↓インスタンス毎に値を設定することができる
goblin.Name = "ゴブリン";
goblin.Level = 5;
goblin.HP = 10;
goblin.ATK = 7;

名無しの権兵衛でも処理が実行できてしまう

とても便利なメンバー変数なのですが、今のままだと、実は値を設定しなくても実行することができてしまいます。

BattleCharacter link = new BattleCharacter();
BattleCharacter goblin = new BattleCharacter();
​
// ↑こんな風に何も設定しないままでも、
// ↓プログラム自体は実行できてしまいます
​
while (true)
{
    goblin.HP -= link.Attack();
    if (goblin.HP <= 0)
    {
        break;
    }
    link.HP -= goblin.Attack();
    if (link.HP <= 0)
    {
        break;
    }
}
​
if (goblin.HP <= 0)
{
    Console.WriteLine("勝利!");
}
else
{
    Console.WriteLine("ゲームオーバー!");
}
​
public class BattleCharacter
{
    public string Name = "";
    public int Level = 0;
    public int HP = 0;
    public int ATK = 0;
​
    public int Attack()
    {
        return ATK + Level * 2 / 5;
    }
}

この場合、「link」というインスタンスも、「goblin」というインスタンスも、メンバー変数の値は初期値のままです。名前は空っぽですし、ATKもLevelも0なのでダメージは発生しません。幸いHP0以下でループ処理を抜けるようになっているので、無限ループはせずに済むのですが……。

皆さんが今使う分には、特に問題にはならないはずです。クラスの構造や使い方をきちんと把握していますからね。ですが未来の自分、あるいはこのクラスを再利用したい第三者が使うのであれば、必ずしもそうとは言い切れないはずです。

安全に使ってもらうためにも、このクラスを使うのなら、この値は設定必須! ということを強要することはできないものでしょうか?

それを実現してくれるのがコンストラクタです。

コンストラクタで設定必須にしよう

コンストラクタとはインスタンス生成時に必ず実行される特殊なメソッドのことです。

早速具体例を見てみましょう。

BattleCharacter link = new BattleCharacter();
BattleCharacter goblin = new BattleCharacter();
​
while (true)
{
    goblin.HP -= link.Attack();
    if (goblin.HP <= 0)
    {
        break;
    }
    link.HP -= goblin.Attack();
    if (link.HP <= 0)
    {
        break;
    }
}
​
if (goblin.HP <= 0)
{
    Console.WriteLine("勝利!");
}
else
{
    Console.WriteLine("ゲームオーバー!");
}
​
public class BattleCharacter
{
    public string Name = "";
    public int Level = 0;
    public int HP = 0;
    public int ATK = 0;
    
    // ↓これがコンストラクタ!!
    public BattleCharacter(string name)
    {
        Name = name;
    }
​
    public int Attack()
    {
        return ATK + Level * 2 / 5;
    }
}

クラスに「戻り値の定義がない、クラス名と同名のメソッド」が追加されていますね。

これがコンストラクタです。

構文は下記の通りとなります。

public クラス名(引数)
{
    ……インスタンス生成時に実行したい処理……
}

このコンストラクタはインスタンス生成時に必ず実行されます。

そのため、コンストラクタに引数を定義した場合、インスタンス生成時に必ず何らかの値を設定することを強要できます。

// ↓インスタンス生成時に、()の中に引数を何も設定していないのでエラー 
BattleCharacter link = new BattleCharacter();
// ↓インスタンス生成時に、()の中に引数を何も設定していないのでエラー
BattleCharacter goblin = new BattleCharacter();

コンストラクタが実行できない=インスタンスを生成できないんですね。

きちんとコンストラクタを実行できるよう引数を設定してあげれば、このエラーは解消することができます。

// ↓コンストラクタの引数を設定
BattleCharacter link = new BattleCharacter("リンク");
// ↓コンストラクタの引数を設定
BattleCharacter goblin = new BattleCharacter("ゴブリン");

整理すると、

  1. コンストラクタを使って「name」という引数設定を必須としている
  2. コンストラクタ内で、引数「name」をメンバー変数「Name」に設定している
  3. 結果的に必ずメンバー変数「Name」を設定しなければならない作りにしている

ということですね。

引数の数を増やせば、「Level」「HP」「ATK」も必須にすることができます。

public class BattleCharacter
{
    public string Name = "";
    public int Level = 0;
    public int HP = 0;
    public int ATK = 0;
    
    // ↓引数を増やせばすべて設定必須にすることも可能
    public BattleCharacter(string name, int level, int hp, int atk)
    {
        Name = name;
        Level = level;
        HP = hp;
        ATK = atk;
    }
​
    public int Attack()
    {
        return ATK + Level * 2 / 5;
    }
}

これならば未来の自分や他の人に再利用してもらう際にも、必ずメンバー変数を設定した上で使ってもらえますね。

暗黙のコンストラクタ実装

「ふむふむ、コンストラクタはインスタンス生成時に必ず実行される、と……あれ、でも最初のプログラムってコンストラクタ実装してなくない?」

public class BattleCharacter
{
    public string Name = "";
    public int Level = 0;
    public int HP = 0;
    public int ATK = 0;
    
    // コンストラクタは影も形もない!
 
    // どこにある?

    public int Attack()
    {
        return ATK + Level * 2 / 5;
    }
}

このようにひとつもコンストラクタが実装されていない場合はビルドを行う際、C#が自動的にコンストラクタを追加してくれています。

public class BattleCharacter
{
    public string Name = "";
    public int Level = 0;
    public int HP = 0;
    public int ATK = 0;
    
    // コンストラクタがひとつもない場合、
    // 下記のコンストラクタをビルド時に自動実装
    public BattleCharacter()
    {
        // 何も処理はないよ!
    }

    public int Attack()
    {
        return ATK + Level * 2 / 5;
    }
}

引数も処理も何もないコンストラクタが暗黙的に実装されていたんですね。

そのため「コンストラクタはインスタンス生成時に必ず実行される」というルールはきちんと守られている訳です。

それって本当に必要な制約?

コンストラクタは便利な反面、引数ありの場合は必ず何かを設定しなければならないという大きな制約でもあります。

例えば名前だけはユーザが入力して決める場合など、引数に設定できる値がその時点ではないがインスタンスは生成したいなんてことがあると困ってしまうんですね。

// ↓コンストラクタの仕様上、必ず値を設定しなきゃいけないけど、
//  この時点ではまだ名前は決まってないのだ!
BattleCharacter hero = new BattleCharacter("???");

そのため、コンストラクタで設定を強要するのではなく、これまでと同じようにインスタンスを生成してから設定してもらうというのも、決して悪い選択肢ではありません。

BattleCharacter hero = new BattleCharacter();
// ↓利用者が好きなタイミングで設定できるので、これはこれで悪くない
hero.Name = Console.ReadLine();

絶対必要な項目に限定して設定を強要するだとか、割り切ってコンストラクタでの設定は一切行わないとか、運用方法はプログラマーの設計思想に委ねられています。

皆さんがベストだと思う選択を行ってくださいね。

Unityでの活躍ポイント

MicrosoftやUnity Technologiesが作ったクラスを再利用する場合、引数なしのコンストラクタではインスタンス生成ができないものもあります。

// ↓ファイル情報を扱うクラス。
//  コンストラクタはFileInfo(string fileName)であるため、
//  fileNameを設定しないとインスタンス生成ができない
FileInfo file = new FileInfo("hoge.txt");

今回コンストラクタを学んだことで、引数ありのコンストラクタが実装されているんだな、引数の値を設定しないとこのクラスは使っちゃいけないよというクラス設計者からのメッセージなんだな、ということにすぐ気付けると思います。

実践演習

それでは実際にコンストラクタを使ってみましょう。

演習①コンストラクタを実装しよう!

仕様

クラス「Enemy」に下記のコンストラクタを実装し、
インスタンス生成が行えるようプログラムを修正してください。
<コンストラクタ>
引数:string型「name」
処理:メンバー変数「Name」に引数「name」を設定する

テンプレート
// ↓コンストラクタが引数ありに変更されるので、
//  インスタンス生成が行えるよう、コードを修正しよう!
Enemy enemy = new Enemy();
enemy.Name = "ゴブリンA";
// ここまで

Console.WriteLine(enemy.Name);

public class Enemy
{
    public string Name;
    public int HP = 10;
    public int ATK = 5;
    public int EXP = 1;
    // ↓ここにコンストラクタを実装しよう!
    
    // ここまで
}

演習②コンストラクタの使い方を学ぼう!

仕様

クラス「BattleCharacter」のコンストラクタに下記の処理を追加してください。
①メンバー変数「Level」に下記の戻り値 + 1を設定する。
変数「random」のメソッド「Next」(引数は10)
②メンバー変数「HP」に下記の戻り値 + 1を設定する。
変数「random」のメソッド「Next」(引数は25)
②メンバー変数「ATK」に下記の戻り値 + 1を設定する。
変数「random」のメソッド「Next」(引数は10)

テンプレート
BattleCharacter link = new BattleCharacter();
link.Name = "リンク";
BattleCharacter goblin = new BattleCharacter();
goblin.Name = "ゴブリン";

while (true)
{
    goblin.HP -= link.Attack();
    if (goblin.HP <= 0)
    {
        break;
    }
    link.HP -= goblin.Attack();
    if (link.HP <= 0)
    {
        break;
    }
}

if (goblin.HP <= 0)
{
    Console.WriteLine("勝利!");
}
else
{
    Console.WriteLine("ゲームオーバー!");
}

public class BattleCharacter
{
    public string Name = "";
    public int Level = 0;
    public int HP = 0;
    public int ATK = 0;
    
    public BattleCharacter()
    {
        Random random = new Random();
        // ↓ここに処理を追加しよう!
        
        // ここまで
    }

    public int Attack()
    {
        return ATK + Level * 2 / 5;
    }
}

答え合わせ

演習①の答え

// ↓コンストラクタが引数ありに変更されるので、
//  インスタンス生成が行えるよう、コードを修正しよう!
Enemy enemy = new Enemy("ゴブリンA");
// ここまで

Console.WriteLine(enemy.Name);

public class Enemy
{
    public string Name;
    public int HP = 10;
    public int ATK = 5;
    public int EXP = 1;
    // ↓ここにコンストラクタを実装しよう!
    public Enemy(string name)
    {
        Name = name;
    }
    // ここまで
}

演習②の答え

BattleCharacter link = new BattleCharacter();
link.Name = "リンク";
BattleCharacter goblin = new BattleCharacter();
goblin.Name = "ゴブリン";

while (true)
{
    goblin.HP -= link.Attack();
    if (goblin.HP <= 0)
    {
        break;
    }
    link.HP -= goblin.Attack();
    if (link.HP <= 0)
    {
        break;
    }
}

if (goblin.HP <= 0)
{
    Console.WriteLine("勝利!");
}
else
{
    Console.WriteLine("ゲームオーバー!");
}

public class BattleCharacter
{
    public string Name = "";
    public int Level = 0;
    public int HP = 0;
    public int ATK = 0;
    
    public BattleCharacter()
    {
        Random random = new Random();
        // ↓ここに処理を追加しよう!
        Level = random.Next(10) + 1;
        HP = random.Next(25) + 1;
        ATK = random.Next(10) + 1;
        // ここまで
    }

    public int Attack()
    {
        return ATK + Level * 2 / 5;
    }
}

インスタンス生成時にランダムでパラメータを設定することができましたね。

こんな風に、実装内容によっては引数なしであっても意味のあるコンストラクタを作ることができます。引数を定義して初期値設定を強要することだけがコンストラクタの役割ではないんですね。

引数を設定できること、そしてインスタンス生成時に必ず実行されるということ。

この2点を活かし、皆さんの自由な発想で色んなことを試してみてください。

まとめ

  • コンストラクタはインスタンス生成時に必ず実行される特殊なメソッドのこと
  • コンストラクタを活用することで初期値の設定を強要したり、インスタンス生成時に必ず行っておきたい処理などを実装することができる
  • ただし大きな枷にもなりうるので、活用するもしないも皆さんの自由

次回は複数のスクリプトファイルによるプログラム構築に挑戦します。お楽しみに!

それでは、今回もお疲れ様でした!

また次の記事でお会いしましょう!

Posted by 夕目紅