【ゲーム開発のためのC#入門講座・基礎拡張編】定数を使ってみよう【#9】

2.0_C#基礎拡張編

例えば年齢による割引があったら

例えば店舗経営ゲーで、客の年齢による割引システムを導入できるとします。

仕様

①12歳以下なら半額
②60歳以上なら二割引
③上記以外は通常価格

早速実装してみましょう。

public class Hello{
    public static void Main(){
        float amount = CalcAmount(1000, 20);
        System.Console.WriteLine(amount);
    }
    
    public static float CalcAmount(float amount, int age)
    {
        if (age <= 12)
        {
            return amount * 1.1f * 0.5f;
        }
        else if (age < 60)
        {
            return amount * 1.1f;
        }
        else 
        {
            return amount * 1.1f * 0.8f;
        }
    }
}
出力エリア

1100

小数の計算が必要になるので、floatを使っています。

無事計算結果が表示されましたね。ただ、金額が思っていたのと違うようです。どうも各条件の処理にある「1.1f」のせいのようですが、はて、この数値は何なんでしょう?

え、実装したのは三ヶ月前の自分?

……思い出せない(滝汗

魔法の数字マジックナンバー

このように、その意味や意図が実装した担当者にしかわからない数字のことをマジックナンバーといいます

いや担当者お前だろって? 未来の自分は別人です(いい意味でも悪い意味でも)。

「どういう意味や意図なのかよくわからないけれど、どうやら正しく動いているらしいぞ、不思議だなあ」というちょっとした皮肉が込められた言葉ですね。

マジックナンバーがあると、その数字が何を表しているのかわからないため、実装内容の理解や修正に時間がかかります(場合によっては理解も修正もできないかも)。

え、だったら変数に値を入れてあげて、その変数名で意味を表せばいいじゃんって?

素晴らしい発想です! 早速それで対処してみましょう!

マジックナンバーを変数に置き換えてみよう

過去のメモを漁ったら当時の自分の走り書きが残っていました。

どうやら消費税を表したかったようです。現実的な店舗経営ゲームのようですね。

また未来の自分が混乱しないよう、マジックナンバーを変数に置き換えてみましょう。

public class Hello{
    public static void Main(){
        float amount = CalcAmount(1000, 20);
        System.Console.WriteLine(amount);
    }
    
    public static float CalcAmount(float amount, int age)
    {
        float consumptionTax = 1.1f;
        if (age <= 12)
        {
            return amount * consumptionTax * 0.5f;
        }
        else if (age < 60)
        {
            return amount * consumptionTax;
        }
        else 
        {
            return amount * consumptionTax * 0.8f;
        }
    }
}

これで「1.1f」の正体は消費税込みの値段を算出するための数字である、ということが理解できるようになりましたね。

これでめでたしめでたし……と、その前にひとつだけ気になることがあります。

それは、変数とは文字通り値が変えられるものであるということ。

例えば政治的な要素も絡むもので、消費税がゲーム内で変動するというのであれば変数のままでもよいかと思います。ただ、そういう訳でないのなら、消費税は一律固定で10%な訳です。

にもかかわらず、理論上はプログラムの途中で消費税を書き換えることが可能です。

public class Hello{
    public static void Main(){
        float amount = CalcAmount(1000, 20);
        System.Console.WriteLine(amount);
    }
    
    public static float CalcAmount(float amount, int age)
    {
        float consumptionTax = 1.1f;
        if (age <= 12)
        {
            return amount * consumptionTax * 0.5f;
        }
        else if (age < 60)
        {
            // ↓途中で変更ができてしまう!
            consumptionTax = 1.2f;
            return amount * consumptionTax;
        }
        else 
        {
            return amount * consumptionTax * 0.8f;
        }
    }
}

今回のように変数名から意味が容易に想像できるものであれば上書きしてしまう可能性は低いと思います。が、複雑なシステムになればなるほどリスクは増えそうですよね。

そこで出番となるのが定数です。

定数って?

変数が値を変えられる記憶装置であるのに対して、定数は一度入れたら二度と値を変えることができない記憶装置です。

定数として扱いたい場合は、型の前に「const」をつけるルールになっています。

<構成>
const 型 変数名 = 格納したい値;

定数を使うと「この値は絶対不変。変更することがないものなんだよ」ということを表すことができます。

それでは、先程の実装を定数に置き換えてみましょう。

public class Hello{
    public static void Main(){
        float amount = CalcAmount(1000, 20);
        System.Console.WriteLine(amount);
    }
    
    public static float CalcAmount(float amount, int age)
    {
        // ↓constをつけて定数に変更!
        const float consumptionTax = 1.1f;
        if (age <= 12)
        {
            return amount * consumptionTax * 0.5f;
        }
        else if (age < 60)
        {
            // ↓途中で変更ができてしまう!
            consumptionTax = 1.2f;
            return amount * consumptionTax;
        }
        else 
        {
            return amount * consumptionTax * 0.8f;
        }
    }
}

この状態で実行すると、定数として宣言しているにもかかわらず値を変更しようとしているため、下記のようなエラーが発生するはずです。確認してみましょう。

出力エリア

<出力エリア>
Compilation failed: 1 error(s), 0 warnings
Main.cs(18,13): error CS0131: The left-hand side of an assignment must be a variable, a property or an indexer

これならプレイヤーにゲームを届ける前に問題を検知することができますね。

それでは、定数を書き換えようとしている処理を削除してみて、もう一度動作を確認してみましょう。

public class Hello{
    public static void Main(){
        float amount = CalcAmount(1000, 20);
        System.Console.WriteLine(amount);
    }
    
    public static float CalcAmount(float amount, int age)
    {
        // ↓constをつけて定数に変更!
        const float consumptionTax = 1.1f;
        if (age <= 12)
        {
            return amount * consumptionTax * 0.5f;
        }
        else if (age < 60)
        {
            return amount * consumptionTax;
        }
        else 
        {
            return amount * consumptionTax * 0.8f;
        }
    }
}
出力エリア

1100

無事に処理が成功しました。

これで、消費税を意味している絶対不変の値であることを未来の自分にもわかるようにしてあげることができましたね。

マジックナンバーを誤解しないで

マジックナンバーというものを学習すると、ついついあらゆる数字を変数や定数に置き換えてしまいがちです。

例えば先程のプログラム、年齢の判定部分が数字でしたよね。

あれを試しに定数に置き換えてみましょう。

public static float CalcAmount(float amount, int age)
{
    // ↓年齢判定の数字をconstに置き換え
    const int const12 = const12;
    const int const60 = const60;
    const float consumptionTax = 1.1f;
    if (age <= const12)
    {
        return amount * consumptionTax * 0.5f;
    }
    else if (age < const60)
    {
        return amount * consumptionTax;
    }
    else 
    {
        return amount * consumptionTax * 0.8f;
    }
}

なんかコードが無意味に増えたし、むしろ分かり辛くなってますよね?

あくまで意味や意図が理解できない数字がマジックナンバーです。

コード上にあるベタ打ちの数字すべてがマジックナンバーという訳ではありません。

「age <= 12」とか「age < 60」は、このままでも意味がちゃんと理解できますよね?

何でもかんでも定数や変数にすると、逆にコードが読み辛くなって未来の自分や共同制作者が愚痴を零すことになります。

その数字を見た時、「誰も知らない第三者にいきなりコードを見せても理解できそうかどうか」を判断基準として、マジックナンバーかどうかを見極めるようにするとよいでしょう。

Unityでの活躍ポイント

今回は解説の時点で活躍ポイントが想像できる内容になっているかと思いますので、割愛させていただきます。

この値は変えてほしくない、という場合は積極的に定数を使っていきましょう。

実践演習

それでは、実際に定数を使ってみましょう。

演習①

仕様

下記のコードのうち、少なくとも下記コード内においては変更されることがないものについて、定数に置き換えてください。

public class Hello{
    public static void Main(){
        int playerHP = 100;
        int playerAttack = 10;
        int enemyHP = 200;
        int enemyAttack = 6;
        while (playerHP > 0 && enemyHP > 0)
        {
            enemyHP = enemyHP - playerAttack;
            playerHP = playerHP - enemyAttack;
        }
        if (playerHP <= 0 && enemyHP <= 0) {
            System.Console.WriteLine("引き分け!");
        }
        else if (playerHP > 0) 
        {
            System.Console.WriteLine("勝利!");
        }
        else 
        {
            System.Console.WriteLine("ゲームオーバー");
        }
    }
}

演習②

定数は変数の絶対不変版です。

なので変数と同様、数字以外のものでも格納することができます。

試しに、文字列を定数にしてみましょう。

仕様

下記コードのstring型の変数を定数に変更してください。

public class Hello{
    public static void Main(){
        string text = "Unityでゲーム作りたい!";
        System.Console.WriteLine(text);
    }
}

答え合わせ

演習①の答え

public class Hello{
    public static void Main(){
        int playerHP = 100;
        // ↓処理の途中で値が変わることはないので、定数に変更
        const int playerAttack = 10;
        int enemyHP = 200;
        // ↓処理の途中で値が変わることはないので、定数に変更
        const int enemyAttack = 6;
        while (playerHP > 0 && enemyHP > 0)
        {
            enemyHP = enemyHP - playerAttack;
            playerHP = playerHP - enemyAttack;
        }
        if (playerHP <= 0 && enemyHP <= 0) {
            System.Console.WriteLine("引き分け!");
        }
        else if (playerHP > 0) 
        {
            System.Console.WriteLine("勝利!");
        }
        else 
        {
            System.Console.WriteLine("ゲームオーバー");
        }
    }
}

演習②の答え

public class Hello{
    public static void Main(){
        // ↓文字列を定数に変更
        const string text = "Unityでゲーム作りたい!";
        System.Console.WriteLine(text);
    }
}

まとめ

  • 意味や意図が実装した人にしかわからない数字のことをマジックナンバーという
  • 変数が値を変えられる記憶装置であるのに対して、定数は一度入れたら二度と値を変えることができない読み取り専用記憶装置
  • 定数にしたい場合は型の前に「const」をつける
  • マジックナンバーはあくまで意味や意図が理解できない数字のこと。ベタ打ちの数字すべてがマジックナンバーという訳ではないので注意してね!

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

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

Posted by yuumekou