3rd term 6th week - dsuz/csharp GitHub Wiki

今回のテーマ

  • タプル
  • プリプロセッサ ディレクティブ
    • プラットフォーム依存コンパイル

準備

CSharp3-6.unitypackage をダウンロードして Unity のプロジェクトにインポートする。Assets/3-6 Misc/ 以下に今回の内容がある。

タプル

何か処理をして、メソッドから複数の(型の異なる)戻り値を受け取りたいことがある。これまで知っている方法では、以下の方法がある。

  1. out (ref) パラメーター修飾子を使う。Unity では RayCast の処理で使われている。
  2. クラスまたは構造体として値を返す。もしくは新しいクラスまたは構造体を定義し、それを使って値を返す。

これ以外に「タプル」を利用して複数の値を受け渡すことができる。タプルとは匿名の「public のメンバ変数のみを持ち、メソッドを持たない」クラスまたは構造体のようなもの、つまり C 言語(C++ ではない)の構造体のような仕組みを提供する。

タプルの使用例

using System;
using System.Collections.Generic;
using System.Linq;

class Program
{
    static void Main()
    {
        // タプルの使用例
        List<(string name, int age)> members = new List<(string, int)>();   // 文字列 name と整数 age からなる匿名の一時的なクラス・構造体を作るイメージ
        members.Add(("Adam", 53));
        members.Add(("Billy", 40));
        members.Add(("Carol", 18));
        members.Add(("Dean", 25));
        members.Add(("Ethan", 10));

        foreach (var m in members.OrderBy(member => member.age))
        {
            Console.WriteLine($"{m.name} {m.age}");
        }

        // タプルを構成する変数に名前を付けない場合は、Item1, Item2, Item3... という名前になる。
        (string, int, float, float, bool) a;
        a.Item1 = "abc";
        a.Item2 = 1;
        a.Item3 = 2.4f;
        a.Item4 = 0f;
        a.Item5 = false;
    }
}

ゲームを作る時は必要な型が用意されていることが多いのであまり使わないかもしれません。また、分業する時は設計するので、一時的なタプルを使うのではなくきちんと型を定義することが多いかもしれません。従って使うのは限定的ではあるでしょう。

一時的な型を使いたい事は paiza など CBT の問題を解答する時にはよくありますが、これも注意が必要です。現時点では paiza の C# でタプルを使うことはできません。タプルが C# に導入されたのは最近(バージョン 7)であり、環境によっては動作しないことがあります。

参照

  • 独習 C# 7.6.7 - タプル

プリプロセッサ ディレクティブ

プリプロセッサ ディレクティブを使うと、コンパイラーに対して命令することができる。Unity では「プラットフォームごとに異なるコードを実行させる」目的でよく使われる。

プラットフォーム依存コンパイル のページに、各プラットフォームごとにどのようなシンボルを使うのか一覧されている。または #if UNITY などと入力すれば、Visual Studio によって候補が一覧される。

その他の Unity 上での例

エディタースクリプト(つまり UnityEditor 名前空間を使ったスクリプト)を通常のフォルダに置いた状態でビルドすると、以下のようなコンパイル エラーが出ることがある。

Assets\_4-3\Preprocessor Directives\MyEditorScript.cs(10,31): error CS0246: The type or namespace name 'EditorWindow' could not be found (are you missing a using directive or an assembly reference?)

これは、Unity Editor 上では UnityEditor 名前空間のクラスを使えるが、ビルドした環境にはそのクラスが存在しないためである。このような場合は以下の対処ができる。

  1. そのスクリプトを削除してしまう
  2. そのスクリプト全体を #if UNITY_EDITOR ~ #endif で囲む
  3. そのスクリプトを Editor という名前のフォルダに入れる

理屈としては 1. 2. の対処方法も可能ではあるが、通常はそうはしない。3. の方法で解決する。たまに十分にテストされていないアセットで、通常のフォルダにエディタースクリプトを置いているものがあるので、問題が起きた時には適切に対処しましょう。

参照

  1. 独習 C# 4.4.2 - プリプロセッサディレクティブ
  2. Editor フォルダについて

閑話休題: C# において配列は値型か、参照型か

以下のプログラムとその実行結果から、C# において配列は値型か参照型か議論し結論を導け。

using System;

class Program
{
    static void Main()
    {
        int[] arrayA = { 1, 2, 3, 4, 5 };
        int[] arrayB = arrayA;
        arrayB[3] = 100;
        Console.WriteLine(string.Join(", ", arrayA));
        Console.WriteLine(string.Join(", ", arrayB));
    }
}

演習問題A

Physics.Raycast() メソッドの使い方を学びます。

シーン Assets/3-6 Misc/1 Raycast/Raycast を開き、挙動を以下の動画のように修正せよ。

つまり、以下の条件を満たすように修正せよ。

  • 砲台から出ている赤いレーザーが障害物に当たったら遮断され、突き抜けないようにする
  • 左クリックした時の爆発が「レーザーと障害物が当たった座標」で起こるようにする
  • 赤いレーザーが何にも当たっていない場合は、以下のどちらかの挙動をとる
    • 赤いレーザーの終端で爆発する
    • 爆発しない

なお、以下の条件を満たすこと。

  • Tank オブジェクトにアタッチされている TankController スクリプトコンポーネントのみを修正すること

ヒントは TankController のコメントを参照せよ。

演習問題B

シーン Assets/3-6 Misc/2 Exercise/Exercise を開いて実行し、キャラクターを動かせたら、以下の問題に解答せよ。

問題1

シーン内の "EnemyGenerator" オブジェクトを Active にすると、敵が出現する。手裏剣を当てると敵は消滅するが、手裏剣はプレイヤーの前方に発射されるため、狙いをつけるのが難しくプレイが快適ではない。

この問題を解決するために、以下の動画のように敵をロックオンして、ロックオンした敵に向かって手裏剣を投げるようにプログラムを修正せよ。

  • 修正が必要なスクリプトは PlayerController.cs のみである
  • EnemyDetector コンポーネントによりロックオンは行われているので、そちらに手裏剣を投げるように修正すればよい

問題2

以下の動画のように、キャラクターが「2段ジャンプ」できるようにプログラムを修正せよ。

  • 修正が必要なスクリプトは PlayerController.cs のみである
  • PlayerController のメンバ変数 _maxJumpCount, _jumpCount を使うこと
  • _jumpCount は着地するとリセットされ、足場から落ちた時やジャンプする時に 1 が加算されるように使う