C# 、Unity やる
C# やる。とりあえず全体像掴んでもの作るところまで。以下が分かれば OK。
- 処理系、実行形態
- 文法
- 基礎的な文法
- 非同期処理の扱い
- interface 周り
- パッケージの作り方、使い方
- 設計(これは C# に限らないけど)
- コーディング規約、ベストプラクティス的なやつ
- ユニットテスト
- デプロイ(主にコマンドラインからの実行方法。Unity はまた別。)
最短経路はこっち。ただ体系的な勉強とのバランスを上手く取りたい。
処理系、実行形態
「C# でソースコードを書いてからどういう形でコンピュータに実行させるか」を理解する。.NET Framework、.NET Core、.NET、Mono とかの用語もよく分からないからそこらの全体像も把握する。
以下 2 つ読めばだいたい分かるはず。
ざっくり時系列
.NET Framework:Windows 用開発プラットフォーム
Mono:主にモバイル(iOS、Android)、他 Linux、MacOS 用開発プラットフォーム
↓
.NET Core:Linux、MacOS 用開発プラットフォーム
↓
(.NET Framework は保守モード。.NET Core と Mono が統合され「.NET」と呼ぶようになった。)
.NET
文法
↓でやる。とりあえず MS 公式の "A tour of C#"?
その他の基礎教材
C# のエントリーポイントについて
今まで C# を書いたことはないけれど、自分の中では C# はクラス定義してそこに静的な Main メソッドを生やしてそこがエントリーポイントになるイメージだった。
.NET で新規プロジェクト作成時にこの Main メソッドがなくてもコンパイルエラーにならずにビルドが成功していたので疑問だったが、下記の記事で疑問が解決した。
以下、記事から抜粋。.NET 5 からエントリーポイントを省略できるようになったらしい。自分が使っているのは .NET 6 なのでこれが適用される。
.NET 5からはエントリポイント(プログラムの開始場所)を、クラス宣言せずにグローバル部分(namespace宣言の外)に記述できるようになり、簡略化することができるようになった。この記述部分をTop-level statements(最上位レベルのステートメント)と言う。これはつまり、Script言語(javaScript,Python)のようにクラスを定義しなくても使用することができるようになったということである。この機能はシンプルな(クラス構造が不必要な)プログラムを書く時に便利である。この機能は、.NET 6 からは新規作成時のProjectに実装されるようになった。
.NET 5 以降 -> 以下の記述だけで実行可能となった。class宣言もnamespace宣言も無くてよい。namespaceの外に記述すればそこがエントリポイントとして解釈される。
ちなみに、グローバル部分に直接コードを書き、さらにクラスを定義して静的メソッド Main を生やすと、Main メソッドはエントリポイントとして認識されず、グローバル部分がエントリポイントととして扱われる。なお、ビルド時には下記のような warning が出力される。
/Users/pyteyon/RiderProjects/csharp-introduction/IntroductionConsoleApp/IntroductionConsoleApp/Program.cs(57,17): warning CS7022: The entry point of the program is global code; ignoring 'Hello.Main()' entry point. [/Users/pyteyon/RiderProjects/csharp-introduction/IntroductionConsoleApp/IntroductionConsoleApp/IntroductionConsoleApp.csproj]
IntroductionConsoleApp -> /Users/pyteyon/RiderProjects/csharp-introduction/IntroductionConsoleApp/IntroductionConsoleApp/bin/Debug/net6.0/IntroductionConsoleApp.dll
その他
デリゲート
主な使い所はイベントハンドラ。
デリゲート(delegate: 代表、委譲、委託)とは、メソッドを参照するための型です。 C言語やC++言語の勉強をしたことがある人には、 「デリゲートとは関数ポインターや関数オブジェクトをオブジェクト指向に適するように拡張したもの」 と言った方が分かりやすいかもしれません。
デリゲートは用途も関数ポインターとほとんど同じで、 述語やイベントハンドラ(「イベント」で説明)等に利用します。 ただし、C言語の関数ポインターと違い、 インスタンスメソッドを参照したり、 複数のメソッドを同時に参照する事が出来ます。
delegate(委譲)という言葉のニュアンスとしては、 「他のメソッドに処理を丸投げするためのオブジェクト」というような意味です。 イベントが起きたときのイベントハンドリングをどのメソッドに丸投げ(委託)するかを指示するためなどに使われます。
ポイント
C# では、メソッドも他の型と同じように扱えます(変数に代入して使ったり、他のメソッドの引数や戻り値にしたりできる)。
デリゲート: メソッドを代入するための変数の型。
例: delegate int DelegateName(int x, int y);
.NET CLI
.NET CLI のプロジェクトテンプレート一覧。一番基礎的なやつが "Console App"。
➜ dotnet new --list
These templates matched your input:
Template Name Short Name Language Tags
-------------------------------------------- -------------- ---------- --------------------------
ASP.NET Core Empty web [C#],F# Web/Empty
ASP.NET Core gRPC Service grpc [C#] Web/gRPC
ASP.NET Core Web API webapi [C#],F# Web/WebAPI
ASP.NET Core Web App webapp,razor [C#] Web/MVC/Razor Pages
ASP.NET Core Web App (Model-View-Controller) mvc [C#],F# Web/MVC
ASP.NET Core with Angular angular [C#] Web/MVC/SPA
ASP.NET Core with React.js react [C#] Web/MVC/SPA
Blazor Server App blazorserver [C#] Web/Blazor
Blazor WebAssembly App blazorwasm [C#] Web/Blazor/WebAssembly/PWA
Class Library classlib [C#],F#,VB Common/Library
Console App console [C#],F#,VB Common/Console
dotnet gitignore file gitignore Config
Dotnet local tool manifest file tool-manifest Config
EditorConfig file editorconfig Config
global.json file globaljson Config
MSTest Test Project mstest [C#],F#,VB Test/MSTest
MVC ViewImports viewimports [C#] Web/ASP.NET
MVC ViewStart viewstart [C#] Web/ASP.NET
NuGet Config nugetconfig Config
NUnit 3 Test Item nunit-test [C#],F#,VB Test/NUnit
NUnit 3 Test Project nunit [C#],F#,VB Test/NUnit
Protocol Buffer File proto Web/gRPC
Razor Class Library razorclasslib [C#] Web/Razor/Library
Razor Component razorcomponent [C#] Web/ASP.NET
Razor Page page [C#] Web/ASP.NET
Solution File sln,solution Solution
Web Config webconfig Config
Worker Service worker [C#],F# Common/Worker/Web
xUnit Test Project xunit [C#],F#,VB Test/xUnit
勉強ログ
12/3
"A tour of C#" の「はじめに」を読んで実装。はじめて C# 書いた!ファイルを分割して実装したスタックを Program.cs で読み込めてるのがなんでかよく分かってない。デフォルトの名前空間?みたいなのに勝手に紐付けされるのかな。Swift みたいに、スコープがファイルスコープじゃなくてプロジェクトスコープ?
12/4
Unity 猫本 2021 の 2 章まで終わった
12/7
Unity 猫本 2021 の 3 章まで終わった。猫本の内容 +α で、ルーレットの状態管理や、他の GameObject の状態を取得してそれをもとに表示する内容を変えるみたいなよくありそうな実装を無理やりやってみた。
iOS の証明書周りはまじわからん。
12/8
Unity 猫本 2021 の 4 章まで終わった。
Unity エディタの基本操作(「Asset の追加→シーンにオブジェクトを配置→スクリプトでそのオブジェクトの挙動を実装→スクリプトや各種コンポーネントをアタッチしてゲームを再生する」の一連の流れ)に慣れてきた。
UI にボタン追加できるようになった。今回はクリックイベントでシーンをリセットする処理を発火させるようにしてリセットボタンを実装してみた。
12/9
Unity 猫本 2021 の 5 章まで終わった。
Prefab の使い方や Physics なしでの愚直な当たり判定の実装方法を学んだ。少しゲームっぽくなってきた感がある。ユーザのステータスに応じて UI の表示を変えたり、自分で SE を追加したりしてゲームの仕上がりを高める練習ができた。
ただし、当たり判定の管理の実装がよろしくなかった。見通し悪いし循環参照を生んでしまいかねない実装の仕方をしてしまった。動くは正義としてきれいな設計はまた進んでから学ぶ。コードはかなりひどい...
Unity の基礎知識
ゲームオブジェクト、コンポーネント
コンポーネントはゲームオブジェクトに対して機能を提供する(振る舞いを追加する)もの。
1 つのゲームオブジェクトに対して 1 つ以上のコンポーネントを紐付ける(これを「アタッチ」という)ことで、ゲームオブジェクトは様々な振る舞いをできるようになる。
- ゲームオブジェクト
- Transform コンポーネント(座標や回転、移動といったオブジェクトの動きに関する機能を提供)
- Rigidbody コンポーネント(オブジェクトが物理的な挙動を行うことができる機能を提供)
- AudioSource コンポーネント(オブジェクトの挙動を起点として音声を発する機能を提供)
- Script コンポーネント(ユーザ独自のゲームオブジェクトの機能を実装するためのコンポーネント。C# で記述する。)
- etc...
コンポーネントへのアクセスは、ゲームオブジェクト.GetComponent<コンポーネント名>()。例えば以下のように書く。
private GameObject car = GameObject.Find("Car");
private Transform t = car.GetComponent<Transform>();
private CarController cc = car.GetComponent<CarController>();
パッケージ管理
- Unity Registry?
- UPM?
プロジェクト初期化時にやること
- Build Settings
- Platform の選択
- Scenes in Build にてゲームシーンを追加
- Project Settings (Build Settings > Player Settings...)
- Orientation の設定
- etc...
- Game タブ
- 実行したときのフレームの描画速度をモニターの更新速度に合わせるため、Aspect > VSync (Game view only) に設定
- 画面サイズの設定:指定のプラットフォームに設定
- Assets(画像、音声など)の追加
- Scene の作成・保存(SampleScene は削除)
Prefabs(プレファブ)
- オブジェクトの設計図
Prefabs の作り方
- 既存のゲームオブジェクトを用いて Prefab を作成
- ジェネレータスクリプトを作成
- 空のオブジェクトにジェネレータをアタッチ(オブジェクトとジェネレータスクリプトの紐付け)
- 3 のオブジェクトにアタッチしたのジェネレータスクリプトのコンポーネントに Prefab を渡す(ジェネレータスクリプトと Prefab の紐付け)
ゲームの制作
- ゲームの企画
- ゲームの設計
- 画面上のオブジェクトをすべて書き出す
- オブジェクトを動かすためのコントローラスクリプトを決める
- オブジェクトを自動生成するためのジェネレータスクリプトを決める
- UI を更新するための監督スクリプトを用意する
- スクリプトを作る流れを考える
Q&A
あとでまとめて調べるやつ
- Unity エディタの Game タブの Ascpect > VSync って何?「実行したときのフレームの描画速度をモニターの更新頻度に合わせるため」とあるが意味分かってない。
- UI の設計画面(Canvas)ってシーンビューでなんでこんなクソデカなの?
- シーンビューに直接アセットの画像を配置してできたゲームオブジェクトと、+ ボタンから UI > Image を作成した Canvas 配下の Image コンポーネントを持ったゲームオブジェクトは何が違う。Canvas の概念分かってないね。
- Build Settings で Scenes in Build に GameScene を追加する必要性は何?オフにした状態でビルドしても動いてた。
ビルドエラー
tundra: error: Failed to open file "/Users/nukopy/RiderProjects/UnityNekoBook2021/Roulette/Roulette/Library/Il2cppBuildCache/iOS/buildstate/tundra.log.json" for structured logging
何をしたらエラーが起きた?
- プロジェクトルートのディレクトリを Unity エディタ外(Finder 上)で移動した
エラー概要
下記エラーの 3 行目を見ると、Failed to open file "...Library/Il2cppBuildCache/iOS/buildstate/tundra.log.json" for structured logging とあって、iOS ビルドのキャッシュに関するファイルが開けてない。
パスをよく見てみると UnityNekoBook2021/Roulette/Library のはずが UnityNekoBook2021/Roulette/Roulette/Library となっている。おそらく、一度後者のディレクトリ構造のときに一度 Unity プロジェクトの iOS ビルドを行ってビルドに成功しキャッシュが生成され、その後そのプロジェクトのディレクトリ構造を Finder で前者のものにしたら、Unity プロジェクト内でそれが検知されずキャッシュ内のファイルの参照がそのまま残って壊れたのだと思われる。
- エラー全文
Exception: Unity.IL2CPP.Building.BuilderFailedException: Build failed with 0 successful nodes and 0 failed ones
Error: Internal build system error. Backend exited with code 2.
tundra: error: Failed to open file "/Users/nukopy/RiderProjects/UnityNekoBook2021/Roulette/Roulette/Library/Il2cppBuildCache/iOS/buildstate/tundra.log.json" for structured logging
at il2cpp.Program.DoRun(String[] args, RuntimePlatform platform, Il2CppCommandLineArguments il2CppCommandLineArguments, BuildingOptions buildingOptions, Boolean throwExceptions) in /Users/bokken/build/output/unity/il2cpp/il2cpp/Program.cs:line 339
UnityEditorInternal.Runner.RunProgram (UnityEditor.Utils.Program p, System.String exe, System.String args, System.String workingDirectory, UnityEditor.Scripting.Compilers.CompilerOutputParserBase parser) (at /Users/bokken/build/output/unity/unity/Editor/Mono/BuildPipeline/BuildUtils.cs:129)

ちなみに IL2CPP とは、名前の通り C# コンパイラから吐かれた IL(Intermediate Language、中間言語)を C++ コードへ変換するソフトウェアである。これにより、Unity は C# → IL → C++ → 各プラットフォームのネイティブコードといった流れでクロスプラットフォーム用のアプリケーションをビルドすることができる。
以下、上記リンク先の引用。
IL2CPP を使用してビルドを開始すると、Unity は自動的に以下の手順を実行します。
- Roslyn C# コンパイラーは、アプリケーションの C# コードと必要なすべてのパッケージコードを .NET DLL (マネージアセンブリ) にコンパイルします。
- Unity はマネージ バイトコードストリッピング を適用します。この手順により、ビルドされたアプリケーションのサイズを大幅に削減することができます。
- IL2CPP バックエンドは、すべてのマネージアセンブリを標準の C++ コードに変換します。
- C++ コンパイラーは、生成された C++ コードと IL2CPP のランタイム部分をネイティブのプラットフォームのコンパイラーでコンパイルします。
- そのコードをターゲットとするプラットフォームによって、実行ファイルか DLL のいずれかにリンクします。
IL2CPP と Mono 両方とも、スクリプトの属性で制御できる便利なオプションをいくつか提供します。詳細は、プラットフォーム依存コンパイル を参照してください。
IL2CPP は、Unity が特定のプラットフォーム用にコードを事前にコンパイルすることを可能にします。このプロセスの最後に Unity が生成するバイナリファイルには、ターゲットプラットフォームに必要なマシンコードがすでに含まれていますが、Mono はこのマシンコードをランタイムにコンパイルしなければなりません。AOT コンパイルは、ビルド時間を増加させますが、ターゲットプラットフォームとの互換性を高め、パフォーマンスを向上させることができます。
どちらのスクリプティングバックエンドも、ターゲットとするプラットフォームごとに新規にビルドする必要があります。例えば、Android と iOS の両方のプラットフォームをサポートするためには、アプリケーションを 2 回ビルドし、2 つのバイナリファイルを作成する必要があります。
アセンブリストリッピングステージでは、最終的なバイナリサイズを小さくすることができます。Unity は、最終的にビルドされるアプリケーションが使用しないバイトコードを削除します。
解決方法
Finder から [Project Root]/Library/Il2cppBuildCache ディレクトリを削除し、再度エディタから File > Build Settings > Build を実行。これでビルドが成功するようになった。

