Open18

C# 、Unity やる

nukopynukopy

C# やる。とりあえず全体像掴んでもの作るところまで。以下が分かれば OK。

  • 処理系、実行形態
  • 文法
    • 基礎的な文法
    • 非同期処理の扱い
    • interface 周り
    • パッケージの作り方、使い方
    • 設計(これは C# に限らないけど)
    • コーディング規約、ベストプラクティス的なやつ
  • ユニットテスト
  • デプロイ(主にコマンドラインからの実行方法。Unity はまた別。)

最短経路はこっち。ただ体系的な勉強とのバランスを上手く取りたい。

https://qiita.com/growsic/items/74e9318883762ae10a89

nukopynukopy

処理系、実行形態

「C# でソースコードを書いてからどういう形でコンピュータに実行させるか」を理解する。.NET Framework、.NET Core、.NET、Mono とかの用語もよく分からないからそこらの全体像も把握する。

以下 2 つ読めばだいたい分かるはず。

https://ufcpp.net/study/csharp/ab_dotnet.html

https://learn.microsoft.com/ja-jp/dotnet/csharp/tour-of-csharp/#net-architecture

ざっくり時系列

.NET Framework:Windows 用開発プラットフォーム
Mono:主にモバイル(iOS、Android)、他 Linux、MacOS 用開発プラットフォーム
↓
.NET Core:Linux、MacOS 用開発プラットフォーム
↓
(.NET Framework は保守モード。.NET Core と Mono が統合され「.NET」と呼ぶようになった。)
.NET
nukopynukopy
nukopynukopy

C# のエントリーポイントについて

今まで C# を書いたことはないけれど、自分の中では C# はクラス定義してそこに静的な Main メソッドを生やしてそこがエントリーポイントになるイメージだった。

.NET で新規プロジェクト作成時にこの Main メソッドがなくてもコンパイルエラーにならずにビルドが成功していたので疑問だったが、下記の記事で疑問が解決した。

https://zenn.dev/redpeaks/articles/96fe78c7690eeb

以下、記事から抜粋。.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

その他

https://ufcpp.net/study/csharp/structured/miscentrypoint/

nukopynukopy

デリゲート

主な使い所はイベントハンドラ。

https://ufcpp.net/study/csharp/sp_delegate.html

デリゲート(delegate: 代表、委譲、委託)とは、メソッドを参照するための型です。 C言語やC++言語の勉強をしたことがある人には、 「デリゲートとは関数ポインターや関数オブジェクトをオブジェクト指向に適するように拡張したもの」 と言った方が分かりやすいかもしれません。

デリゲートは用途も関数ポインターとほとんど同じで、 述語やイベントハンドラ(「イベント」で説明)等に利用します。 ただし、C言語の関数ポインターと違い、 インスタンスメソッドを参照したり、 複数のメソッドを同時に参照する事が出来ます。

delegate(委譲)という言葉のニュアンスとしては、 「他のメソッドに処理を丸投げするためのオブジェクト」というような意味です。 イベントが起きたときのイベントハンドリングをどのメソッドに丸投げ(委託)するかを指示するためなどに使われます。

ポイント
C# では、メソッドも他の型と同じように扱えます(変数に代入して使ったり、他のメソッドの引数や戻り値にしたりできる)。
デリゲート: メソッドを代入するための変数の型。
例: delegate int DelegateName(int x, int y);

nukopynukopy

.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 
nukopynukopy

勉強ログ

12/3

"A tour of C#" の「はじめに」を読んで実装。はじめて C# 書いた!ファイルを分割して実装したスタックを Program.cs で読み込めてるのがなんでかよく分かってない。デフォルトの名前空間?みたいなのに勝手に紐付けされるのかな。Swift みたいに、スコープがファイルスコープじゃなくてプロジェクトスコープ?

https://learn.microsoft.com/ja-jp/dotnet/csharp/tour-of-csharp/

https://github.com/nukopy/csharp-introduction/releases/tag/v0.1.0

nukopynukopy

12/8

Unity 猫本 2021 の 4 章まで終わった。

Unity エディタの基本操作(「Asset の追加→シーンにオブジェクトを配置→スクリプトでそのオブジェクトの挙動を実装→スクリプトや各種コンポーネントをアタッチしてゲームを再生する」の一連の流れ)に慣れてきた。

UI にボタン追加できるようになった。今回はクリックイベントでシーンをリセットする処理を発火させるようにしてリセットボタンを実装してみた。

https://github.com/nukopy/UnityNekoBook2021/releases/tag/chap04_SwipeCar

nukopynukopy

12/9

Unity 猫本 2021 の 5 章まで終わった。

Prefab の使い方や Physics なしでの愚直な当たり判定の実装方法を学んだ。少しゲームっぽくなってきた感がある。ユーザのステータスに応じて UI の表示を変えたり、自分で SE を追加したりしてゲームの仕上がりを高める練習ができた。

ただし、当たり判定の管理の実装がよろしくなかった。見通し悪いし循環参照を生んでしまいかねない実装の仕方をしてしまった。動くは正義としてきれいな設計はまた進んでから学ぶ。コードはかなりひどい...

https://github.com/nukopy/UnityNekoBook2021/releases/tag/chap05_CatEscape

nukopynukopy

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>();
nukopynukopy

プロジェクト初期化時にやること

  • Build Settings
    • Platform の選択
    • Scenes in Build にてゲームシーンを追加
  • Project Settings (Build Settings > Player Settings...)
    • Orientation の設定
    • etc...
  • Game タブ
    • 実行したときのフレームの描画速度をモニターの更新速度に合わせるため、Aspect > VSync (Game view only) に設定
    • 画面サイズの設定:指定のプラットフォームに設定
  • Assets(画像、音声など)の追加
  • Scene の作成・保存(SampleScene は削除)
nukopynukopy

Prefabs(プレファブ)

  • オブジェクトの設計図

Prefabs の作り方

  1. 既存のゲームオブジェクトを用いて Prefab を作成
  2. ジェネレータスクリプトを作成
  3. 空のオブジェクトにジェネレータをアタッチ(オブジェクトとジェネレータスクリプトの紐付け)
  4. 3 のオブジェクトにアタッチしたのジェネレータスクリプトのコンポーネントに Prefab を渡す(ジェネレータスクリプトと Prefab の紐付け)
nukopynukopy

ゲームの制作

  • ゲームの企画
  • ゲームの設計
    • 画面上のオブジェクトをすべて書き出す
    • オブジェクトを動かすためのコントローラスクリプトを決める
    • オブジェクトを自動生成するためのジェネレータスクリプトを決める
    • UI を更新するための監督スクリプトを用意する
    • スクリプトを作る流れを考える
nukopynukopy

Q&A

あとでまとめて調べるやつ

  • Unity エディタの Game タブの Ascpect > VSync って何?「実行したときのフレームの描画速度をモニターの更新頻度に合わせるため」とあるが意味分かってない。
  • UI の設計画面(Canvas)ってシーンビューでなんでこんなクソデカなの?
  • シーンビューに直接アセットの画像を配置してできたゲームオブジェクトと、+ ボタンから UI > Image を作成した Canvas 配下の Image コンポーネントを持ったゲームオブジェクトは何が違う。Canvas の概念分かってないね。
  • Build Settings で Scenes in Build に GameScene を追加する必要性は何?オフにした状態でビルドしても動いてた。
nukopynukopy

ビルドエラー

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++ → 各プラットフォームのネイティブコードといった流れでクロスプラットフォーム用のアプリケーションをビルドすることができる。

https://docs.unity3d.com/ja/2022.1/Manual/IL2CPP.html

以下、上記リンク先の引用。

IL2CPP を使用してビルドを開始すると、Unity は自動的に以下の手順を実行します。

  1. Roslyn C# コンパイラーは、アプリケーションの C# コードと必要なすべてのパッケージコードを .NET DLL (マネージアセンブリ) にコンパイルします。
  2. Unity はマネージ バイトコードストリッピング を適用します。この手順により、ビルドされたアプリケーションのサイズを大幅に削減することができます。
  3. IL2CPP バックエンドは、すべてのマネージアセンブリを標準の C++ コードに変換します。
  4. C++ コンパイラーは、生成された C++ コードと IL2CPP のランタイム部分をネイティブのプラットフォームのコンパイラーでコンパイルします。
  5. そのコードをターゲットとするプラットフォームによって、実行ファイルか DLL のいずれかにリンクします。

IL2CPP と Mono 両方とも、スクリプトの属性で制御できる便利なオプションをいくつか提供します。詳細は、プラットフォーム依存コンパイル を参照してください。

IL2CPP は、Unity が特定のプラットフォーム用にコードを事前にコンパイルすることを可能にします。このプロセスの最後に Unity が生成するバイナリファイルには、ターゲットプラットフォームに必要なマシンコードがすでに含まれていますが、Mono はこのマシンコードをランタイムにコンパイルしなければなりません。AOT コンパイルは、ビルド時間を増加させますが、ターゲットプラットフォームとの互換性を高め、パフォーマンスを向上させることができます。

どちらのスクリプティングバックエンドも、ターゲットとするプラットフォームごとに新規にビルドする必要があります。例えば、Android と iOS の両方のプラットフォームをサポートするためには、アプリケーションを 2 回ビルドし、2 つのバイナリファイルを作成する必要があります。

アセンブリストリッピングステージでは、最終的なバイナリサイズを小さくすることができます。Unity は、最終的にビルドされるアプリケーションが使用しないバイトコードを削除します。

解決方法

Finder から [Project Root]/Library/Il2cppBuildCache ディレクトリを削除し、再度エディタから File > Build Settings > Build を実行。これでビルドが成功するようになった。