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

2.0_C#基礎拡張編

おさらい

少しおさらいをしましょう。

  • 関数とは別の場所に新しく処理を定義する機能のこと
  • 定義した一連の処理に名前をつけることができる
  • 任意の場所から何度でも呼び出すことができる

これが前編で学習した内容です。

これだけでも非常に便利ではあるのですが、実はこれだけだとうまくいかないケースがあります。

うまくいかないケース

前回使ったRPG戦闘システムのダメージ計算式を今回も使います。

ダメージ計算式

<計算式>
攻撃力 – 相手の防御力) * スキルの攻撃倍率(100%) / 100

前回はプレイヤー側の攻撃ダメージを計算しただけでした。今回は計算されたダメージをエネミーの体力から引く処理を追加したいと思います。

public class Hello{
    public static void Main(){
        int enemyHP = 25;
        CalcDamage();
        enemyHP -= damage;
        System.Console.Write("エネミーの残りHP:");
        System.Console.WriteLine(enemyHP);
    }
    
    public static void CalcDamage()
    {
        int playerATK = 10;
        int enemyDEF = 5;
        int skillRate = 100;
        int damage = (playerATK - enemyDEF) * skillRate / 100;
    }
}
出力エリア

Compilation failed: 1 error(s), 0 warnings
Main.cs(5,20): error CS0103: The name `damage’ does not exist in the current context

おや、エラーになってしまいました。どうしてでしょう?

理由は変数のスコープ

なぜエラーになってしまったのか。

それには変数のスコープというものが関わってきます。

スコープというのは、変数を参照することができる範囲のことです。

変数というのは基本的に、その関数の中で作られたものしか参照できないんです。

public class Hello{
    public static void Main(){
        // ↓この中で作った変数はこの関数の中でしか参照できない
        int enemyHP = 25;
        CalcDamage();
        enemyHP -= damage;
        System.Console.Write("エネミーの残りHP:");
        System.Console.WriteLine(enemyHP);
    }
    
    public static void CalcDamage()
    {
        // ↓この中で作った変数はこの関数の中でしか参照できない
        int playerATK = 10;
        int enemyDEF = 5;
        int skillRate = 100;
        int damage = (playerATK - enemyDEF) * skillRate / 100;
    }
}

計算したダメージは関数「CalcDamage」の中の変数「damage」に格納されています。そのため、この変数に格納された値を関数「Main」から参照することはできません。

だから「そんな変数、この関数内では見当たらないんですけど!?」とコンピュータにキレられてしまった訳ですね。

でも、今回のように処理結果を呼び出し元で参照したいことってありますよね。

そこで、関数の出力定義機能の出番です。

関数の出力を定義して処理結果を受け取ろう

関数の基本構文をもう一度見直してみましょう。

public static void 関数名()
{
    ……前提条件の変数を使った処理……
}

この基本構文にある「void」という表記、これは実は「何も処理結果を返さないよ」という意味です。この部分を任意のデータ型に変えることで、関数の中で処理した結果をひとつだけ返すことができます

実際に試してみましょう。

public class Hello{
    public static void Main(){
        int enemyHP = 25;
        // ↓返された処理結果を変数に格納
        int damage = CalcDamage();
        enemyHP -= damage;
        System.Console.Write("エネミーの残りHP:");
        System.Console.WriteLine(enemyHP);
    }
    
    public static int CalcDamage()
    {
        int playerATK = 10;
        int enemyDEF = 5;
        int skillRate = 100;
        int damage = (playerATK - enemyDEF) * skillRate / 100;
        // ↓処理結果を返す命令
        return damage;
    }
}
出力エリア

エネミーの残りHP:20

今度はきちんと想定通りに動いてくれましたね。

public static 返したい処理結果のデータ型 関数名()
{
    ……前提条件の変数を使った処理……
    return 返したい処理結果;
}

新しいキーワード「return」が出てきましたが、これは「指定の処理結果を返しなさい」という命令です。これも英文として考えれば直感的でわかりやすいですね。

呼び出した側は結果を受け取って処理に使うことができます。

これが関数の構成要素「3.処理結果を返すことができる」です。

エネミーの攻撃も実装してみよう

無事、プレイヤー(勇者)の攻撃処理を実装することができたので、今度はエネミーの攻撃処理も同様に実装してみましょう。

public class Hello{
    public static void Main(){
        int playerHP = 50;
        int enemyHP = 25;
        enemyHP -= CalcDamage();
        System.Console.Write("エネミーの残りHP:");
        System.Console.WriteLine(enemyHP);
        
        playerHP -= CalcPlayerDamage();
        System.Console.Write("勇者の残りHP:");
        System.Console.WriteLine(playerHP);
    }
    
    public static int CalcDamage()
    {
        int playerATK = 10;
        int enemyDEF = 5;
        int skillRate = 100;
        int damage = (playerATK - enemyDEF) * skillRate / 100;
        return damage;
    }
    
    public static int CalcPlayerDamage()
    {
        int enemyATK = 20;
        int playerDEF = 10;
        int skillRate = 100;
        int damage = (enemyATK - playerDEF) * skillRate / 100;
        return damage;
    }
}
出力エリア

エネミーの残りHP:20
勇者の残りHP:40

きちんと実装できましたね。

ただ、よくよく実装内容を見てみると、新しく作った関数「CalcDamage」と「CalcPlayerDamage」ってそっくりの処理ですよね。計算式が共通なので当然といえば当然ですが、誰が攻撃するかで計算の前提となる攻撃力と防御力が変わるだけで、やっていることは全く同じなのです。何だか非効率的ですね。

それでは、今度はそんな時のための関数の入力定義機能を活用してみましょう。

関数の入力を定義して処理をまとめよう

再度関数の構文を確認してみましょう。

public static 返したい処理結果のデータ型 関数名()
{
    ……前提条件の変数を使った処理……
    return 返したい処理結果;
}

実はこの「()」の中に、その処理を実行するために必要な前提条件となる変数を定義することができます。

public static 返したい処理結果のデータ型 関数名(前提条件となる変数)
{
    ……前提条件の変数を使った処理……
    return 返したい処理結果;
}

実際に試してみましょう。

処理をシンプルにしたいので、一旦敵の攻撃処理は除外します。

public class Hello{
    public static void Main(){
        int playerATK = 10;
        int skillRate = 100;
        int enemyHP = 25;
        int enemyDEF = 5;
        enemyHP -= CalcDamage(playerATK, enemyDEF);
        System.Console.Write("エネミーの残りHP:");
        System.Console.WriteLine(enemyHP);
    }
    
    public static int CalcDamage(int atk, int def)
    {
        int skillRate = 100;
        int damage = (atk - def) * skillRate / 100;
        return damage;
    }
}
出力エリア

エネミーの残りHP:20

今までと同じようにプレイヤー(勇者)の攻撃ダメージ処理が行われましたね。

関数定義のところを見ると、確かに前提条件となる変数が定義されています。

// ↓処理を行うための前提条件となる変数「atk」と「def」が定義されている
public static int CalcDamage(int atk, int def)
{
    int skillRate = 100;
    int damage = (atk - def) * skillRate / 100;
    return damage;
}

そして呼び出し側を見ると、今までは「()」とだけ記述し中に何も入れていなかった部分に、処理を実行するにあたって使って欲しい値を設定していますね。

// ↓「()」の中に処理で使って欲しい値を設定
enemyHP -= CalcDamage(playerATK, enemyDEF);

このように、関数はその処理を実行するにあたって前提となる変数を定義することができます。そしてその関数が呼び出された時は、「()」の中で指定された変数や値を代入してから処理を実行します。

つまり処理結果だけでなく、処理の前提条件となるデータの受け渡しが可能なんですね。また、処理結果はひとつしか返せませんが、前提条件となる変数は「,(カンマ)」区切りで複数設定することが可能です。

この機能を活用すれば、前提となるデータは異なるものの実行したい処理は同じというものもひとまとめにすることができそうです。まさに今回の計算式がそうですね。

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

public class Hello{
    public static void Main(){
        int playerHP = 50;
        int playerATK = 10;
        int playerDEF = 10;
        
        int enemyHP = 25;
        int enemyATK = 20;
        int enemyDEF = 5;
        
        enemyHP -= CalcDamage(playerATK, enemyDEF);
        System.Console.Write("エネミーの残りHP:");
        System.Console.WriteLine(enemyHP);
        
        playerHP -= CalcDamage(enemyATK, playerDEF);
        System.Console.Write("勇者の残りHP:");
        System.Console.WriteLine(playerHP);
    }
    
    public static int CalcDamage(int atk, int def)
    {
        int skillRate = 100;
        int damage = (atk - def) * skillRate / 100;
        return damage;
    }
}
出力エリア

エネミーの残りHP:20
勇者の残りHP:40

きちんと戦闘の計算処理をひとつの関数にまとめることができました!

これが関数の構成要素「1.何らかの入力を受け取ることができる」です。

改めて、関数って?

関数を構成するすべての要素が揃いました。

  1. 何らかのデータを受け取ることができる(入力)
  2. 定義した処理を実行することができる(処理)
  3. 処理結果を返すことができる(出力)

これをまとめて一言で言うと、関数とは何らかの入力を受けて実行した処理の結果を出力する機能のこととなるわけですね。

この入力で使っている変数のことをプログラミングでは引数(ひきすう)またはパラメータと呼びます。

そして出力のことを戻り値(もどりち)と呼びます。

public static 戻り値のデータ型 関数名(引数)
{
    ……引数を使った処理……
    return 戻り値;
}

これが関数の構文です。

前編で紹介した通り、引数はなくてもOKですし、何も処理結果を返したくない場合は戻り値のデータ型に「void」を指定すればOKです。

Unityでの活躍ポイント

ここまでプログラミングを勉強してきて「色々わかってはきたけど、これ全部自力で実装していくのって気が遠くなりそう」と思った方もいるかもしれません。ですが、大丈夫です。この世界の誰一人として、何もかも自力で実装している人はいません

どういうこと? と思われるかと思いますが、例えば今までずっと使ってきた「System.Console.WriteLine」という命令、これも実は関数なのです。

// 引数のデータをコンソール(出力エリア)に表示する関数
// いわれてみれば関数名(引数)という記述ですよね
System.Console.WriteLine("C#");

C#の開発元であるMicrosoftの誰かさんがこの「WriteLine」という名前の関数を作ってくれたんですね。きっとそこでは色んな処理が実装されているんだと思います。もしかしたら1,000行、10,000行のコードが書かれているかもしれません。

が、自分達はそんなこと気にせずとも、「どんなことをしてくれる関数なのか」さえ知っておけばたった1行のコードでそれを利用することができるのです。

これが関数の最大の恩恵であり、プログラミングという魔法の正体です。

一見すると「謎の英文と数字だけで何でこんな複雑な動きが実現できるんだろう」と思ってしまいますが、これまでたくさんの人が作ってきた関数のおかげで、あたかも「1行書くだけでもすごいことをしてくれる魔法みたいな技術」になっていたんですね。

Unityも同じです。Unityを開発したUnity Technologiesが、ゲーム開発に使えそう・便利そうと思える関数をいっぱい作ってくれています。ゲーム開発者はそれを組み合わせながら足りない部分だけを自分達で開発することで、個人でも短時間で複雑かつクオリティの高いゲームを作ることができるのです。

実践演習

それでは入力・出力も含めた関数を使ってみましょう。

演習①ダメージ計算

仕様

ダメージ計算をする、下記構成の関数「CalcDamage」を作ってください。
計算式:(攻撃力 – 相手の防御力 / 2) * スキルの攻撃倍率(100%) / 100
引数①:atk(int型)
引数②:def(int型)
引数③:skillRate(int型)
戻り値:int型

テンプレート
public class Hello{
    public static void Main(){
        int playerHP = 50;
        int playerATK = 10;
        int playerDEF = 10;
        
        int enemyHP = 25;
        int enemyATK = 20;
        int enemyDEF = 5;
    
        int skillRate = 100;
        
        // ↓ここで関数呼んでます
        enemyHP -= CalcDamage(playerATK, enemyDEF, skillRate);
        System.Console.Write("エネミーの残りHP:");
        System.Console.WriteLine(enemyHP);
        
        // ↓ここで関数呼んでます
        playerHP -= CalcDamage(enemyATK, playerDEF, skillRate);
        System.Console.Write("勇者の残りHP:");
        System.Console.WriteLine(playerHP);
    }
}

演習②残りHPを表示する関数を作ろう

仕様

エネミーと勇者の残りHPを表示する、下記構成の関数「DisplayHP」を作ってください。
引数①:name(string型)
引数②:hp(int型)
戻り値:なし

public class Hello{
    public static void Main(){
        int playerHP = 50;
        int playerATK = 10;
        int playerDEF = 10;
        
        int enemyHP = 25;
        int enemyATK = 20;
        int enemyDEF = 5;
        
        int skillRate = 100;
        
        enemyHP -= CalcDamage(playerATK, enemyDEF);
        // ↓ここで関数呼んでます
        DisplayHP("エネミー", enemyHP);
        
        playerHP -= CalcDamage(enemyATK, playerDEF);
        // ↓ここで関数呼んでます
        DisplayHP("勇者", playerHP);
    }
    
    public static int CalcDamage(int atk, int def)
    {
        int skillRate = 100;
        int damage = (atk - def) * skillRate / 100;
        return damage;
    }
}

答え合わせ

演習①の答え

public class Hello{
    public static void Main(){
        int playerHP = 50;
        int playerATK = 10;
        int playerDEF = 10;
        
        int enemyHP = 25;
        int enemyATK = 20;
        int enemyDEF = 5;

        int skillRate = 100;
        
        // ↓ここで関数呼んでます
        enemyHP -= CalcDamage(playerATK, enemyDEF, skillRate);
        System.Console.Write("エネミーの残りHP:");
        System.Console.WriteLine(enemyHP);
        
        // ↓ここで関数呼んでます
        playerHP -= CalcDamage(enemyATK, playerDEF, skillRate);
        System.Console.Write("勇者の残りHP:");
        System.Console.WriteLine(playerHP);
    }
    
    public static int CalcDamage(int atk, int def, int skillRate)
    {
        int damage = (atk - def / 2) * skillRate / 100;
        return damage;
    }
}

演習②の答え

public class Hello{
    public static void Main(){
        int playerHP = 50;
        int playerATK = 10;
        int playerDEF = 10;
        
        int enemyHP = 25;
        int enemyATK = 20;
        int enemyDEF = 5;
        
        int skillRate = 100;
        
        enemyHP -= CalcDamage(playerATK, enemyDEF);
        // ↓ここで関数呼んでます
        DisplayHP("エネミー", enemyHP);
        
        playerHP -= CalcDamage(enemyATK, playerDEF);
        // ↓ここで関数呼んでます
        DisplayHP("勇者", playerHP);
    }
    
    public static int CalcDamage(int atk, int def)
    {
        int skillRate = 100;
        int damage = (atk - def) * skillRate / 100;
        return damage;
    }
    
    public static void DisplayHP(string name, int hp)
    {
        System.Console.Write(name + "の残りHP:");
        System.Console.WriteLine(hp);
    }
}

まとめ

  • 関数とは何らかの入力を受けて実行した処理の結果を出力する機能のこと
  • 入力のことを引数またはパラメータ、出力のことを戻り値という
  • 入力と出力を上手に活用することで様々な処理をひとまとめにすることができる
  • 誰かが作ってくれた様々な関数のおかげでプログラミングは魔法のような動きをする

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

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

Posted by 夕目紅