- 投稿日:2020-12-16T23:01:11+09:00
今年業務で使ったc#のスキルまとめ
C# Advent Calendar 2020の17日目の記事です。
はじめに
今年は業務でwindowsアプリを開発したので、今回は備忘録的に今年使ったスキルを書きます。
多分基礎的なことばっかりです。
- 開発環境
- VisualStudio 2019
レジストリ操作
インストーラーを使って開発したwindowsアプリをインストールしてもらう想定だったため、インストール時にインストーラーがレジストリ登録したバージョン情報などをアプリで表示する必要などがありました。
windowsでレジストリを開く場合は
win + R キー
を押してregedit
と入力する方法が一般的だと思います。次はc#で
HKEY_USERS
のサブキーの一覧(.DEFAULT, S-1-5-XX)を取得します。
using Microsoft.Win32
を追加することに注意です。// サブキーの一覧を取得 var keys = Registry.Users.GetSubKeyNames(); foreach (var key in keys) { Console.WriteLine($"取得したサブキー : {key}"); }Registry.Users で
HKEY_USERS
を読み込んで、GetSubKeyNames()
でサブキーの一覧(string型の配列)を取得しています。取得したサブキー : .DEFAULT 取得したサブキー : S-1-5-19 取得したサブキー : S-1-5-20 取得したサブキー : S-1-5-21-3102848314-395816088-1389192899-1001 取得したサブキー : S-1-5-21-3102848314-395816088-1389192899-1001_Classes 取得したサブキー : S-1-5-18次はキーに書かれている値を取得してみましょう。
HKEY_CURRENT_USER
のサブキーSoftware\Microsoft\Edge\BLBeacon
のversion
の値をc#で読み込みます。
using (var regkey2 = Registry.CurrentUser.OpenSubKey(@"Software\Microsoft\Edge\BLBeacon", false)) { var ver = (string)regkey2.GetValue("version"); Console.WriteLine($"BLBeacon ver : {ver}"); }先ほどと違うのは
Registry.CurrentUser
でHKEY_CURRENT_USER
を指定して、OpenSubKey()
でSoftware\Microsoft\Edge\BLBeacon のサブキーを開きます。
GetValue()
の引数に取得したい名前を渡します。今回はREG_SZ
の値が欲しいので、string
でcastします。BLBeacon ver : 87.0.664.60最後にサブキーを作る例です。
32bit/64bitアプリでサブキーを作成する場所が変わったりするので注意が必要です。try { using(var prevKey = RegistryKey.OpenBaseKey(RegistryHive.LocalMachine, RegistryView.Registry64)) using(var error = prevKey.OpenSubKey("Software", true)) { var _ = error?.CreateSubKey("test"); Console.WriteLine("レジストリに書き込み出来ました。"); } } catch(Exception e) { Console.WriteLine(e.ToString()); }レジストリへのサブキー・値の作成(Create)には管理者権限が必要なので、上記のコードを管理者権限以外で実行するとCreateSubKeyで例外が発生することになります。
System.Security.SecurityException: 要求されたレジストリ アクセスは許可されていません。 場所 System.ThrowHelper.ThrowSecurityException(ExceptionResource resource) 場所 Microsoft.Win32.RegistryKey.OpenSubKey(String name, Boolean writable)管理者権限で実行した場合
HKEY_LOCAL_MACHINE\SOFTWARE\test
が作成されます。
レジストリへの書き込みを行うアプリを作る場合は、管理者権限が必要になることを注意しましょう。json操作
設定画面でパラメータを反映できるアプリでは、起動するたびに初期値を表示するのではなく、前回アプリ終了時の設定をそのまま表示させたいと思います。
そのような場合に、どのように設定値を保存するかでJSON(JavaScript Object Notation)形式
でデータを保持しておいて、アプリ終了時にファイルへ書き込みし、アプリ起動時にファイルを読み込むという手法がよくとられます。(少なくとも私の会社のアプリはjsonにデータもたせてます。)jsonのライブラリはいくつかありますが、Nugetから簡単に使用できる
Newtonsoft.Json
で例を出します。[JsonObject("target")] public class TargetJson { [JsonProperty("happy")] public bool Boolean { get; set; } [JsonProperty("name")] public string Name { get; set; } [JsonProperty("pi")] public double Number { get; set; } [JsonProperty("list")] public List<int> List { get; set; } [JsonProperty("object")] public ObSample ObS { get; set; } } public class ObSample { [JsonProperty("currency")] public string Name { get; set; } [JsonProperty("value")] public double Value { get; set; } }
TargetJson
クラスはbool、string、double、List<int>、stringとdoubleのプロパティをもつObSampleクラス
の プロパティをもつクラスになります。このようなオブジェクトをjsonに書き込むようにする処理を
シリアライズ
とよぶそうです。var t = new TargetJson() { Boolean = true, Name = "arisugawa", Number = 3.14, List = new List<int> { 3, 3, 4 }, ObS = new ObSample() { Name = "USB", Value = 33.45, }, }; // 第二引数でインデントをつけれる string j = Newtonsoft.Json.JsonConvert.SerializeObject(t, Newtonsoft.Json.Formatting.Indented); // file書き込み File.WriteAllText(@"write.json", j);変数
t
にTargetJsonクラスのインスタンスを代入し、Newtonsoft.Json.JsonConvert.SerializeObject()
で変数tのオブジェクトをjsonに書き込める形式に変換(string型へ)してあげています。
最後に、File.WriteAllText()
でwrite.json という名前のファイルに書き込んでいます。上の画像がwrite.jsonに書き込まれている内容です。
JSONはkey : value
の組み合わせになり、 valueは各プロパティにセットした値ですが、keyの名前(例えばbool型のhappy)はどこで設定しているのでしょうか。[JsonProperty("happy")] public bool Boolean { get; set; }
Boolean
プロパティにJsonProperty
という属性がついていますね。ここで設定した値がjsonのkeyになります。ファイルなどに書かれたjsonオブジェクトを読み込んで活用したい場合は
デシリアライズ
とよばれる動作が必要になります。var sr = new StreamReader("write.json"); // ファイルの内容をすべて読み込みます。 string j = sr.ReadToEnd(); // string型の文字列をもとにTargetJson型のオブジェクトにデシリアライズ TargetJson s = Newtonsoft.Json.JsonConvert.DeserializeObject<TargetJson>(j); // オブジェクトのプロパティにアクセス Console.WriteLine(s.ObS.Name);バージョニング
個人でつくるHelloWorldやサンプルアプリならバージョンについて深く考えることはないかと思いますが、お客様提供するアプリの場合は障害発生したときの解析や問い合わせ対応のためにバージョンを付与するのは必須かと思います。
VisualsStudio2019(devenv.exe)のファイルバージョン
バージョニングのルールやどのタイミングでどの番号をいくつ上げるかなどは所属する組織で違うはずですが、
windowsアプリの場合はMicrosoftのドキュメントに倣っているところが多いのではないでしょうか。// AssemblyInfo.csの内容 // アセンブリのバージョン情報は次の 4 つの値で構成されています: // // メジャー バージョン // マイナー バージョン // ビルド番号 // リビジョン // [assembly: AssemblyVersion("1.0.0.0")] [assembly: AssemblyFileVersion("1.0.0.0")]バージョン情報の更新は、
Properties\AssemblyInfo.cs
の"1.0.0.0"の内容を直接書き換えても反映されます。が、プロジェクト-右クリック、プロパティ-アプリケーション-アセンブリ情報
で表示されるGUI上で変更するほうが楽かと思います。ちなみにこのGUIで変更した内容はAssemblyInfo.csに反映されます。アプリのアイコン設定
visualstudioで作成したアプリはデフォルトのアイコンになります。
アイコンを変更する場合は
プロジェクト-右クリック、プロパティ-アプリケーション-リソース-アイコンとマニフェスト
で icoファイルを指定します。icoファイルはjpgやpngをコンバートして作成しましょう。
アイコンファイル(*.ico)作成icoファイルを設定後は、プロジェクトにicoファイルが追加されます。
ビルドしてexeを生成・実行すると指定したicoがアイコンとして表示されているはずです。
まとめ
コーディング的なスキルというより、リリースまでの設定に近い部分が多くなりました。
windowsアプリだとUWPによるアプリ開発が主流と思いますが、今回はいわゆるレガシーアプリ開発で、開発のルールなどもUWP開発とは少し違うことになりました。(UWPだとマニフェストの設定やstoreへの登録などが必要ですね。)正直、ドキュメントがチームに充実しているかで変わってきますね。
以上になります。備考
インストールシールド12
業務ではIS12を使ってインストーラーを開発してます。
- 投稿日:2020-12-16T22:52:34+09:00
UnityとFirebaseでアプリ開発する際にユーザーデータの取り扱い方まとめ
こちらは Firebase Advent Calendar 2020 の 16日目の記事です。
概要
本記事は、Unity #2 Advent Calendar 2020 の 8日目の記事(前半)Unity 超初心者が Firebase でアプリ開発する際に必要になるスキル の後半内容となります。
そのため、全て内容を引き継いで解説を進めるため、本記事を読む前に前半をご覧くださいmmふりかえり
前半では、iOS/Android アプリを開発する際にユーザーデータを Firebase Realtime Database (Firebase Database) で保存取得しましょうと話し、その際にセキュリティ面を考えて rule でユーザー毎でアクセスできる設定のために Firebase Authentication (Firebase Auth) を使うことを解説しましたが、実装に関する解説をこれから解説していきます。
Firebase Auth
前半で 認証方法をメアド/パスワードで取り扱う 解説をしました。
もう少し詳しく話すと、例えばユーザーデータを取り扱っているようなカジュアルゲームでニックネームなどは設定するもののメアドやパスワードは設定しないと思います。全てのアプリがそうではないですが、あの裏側ではよしなにユーザー情報が作成されており、ユーザー ↔︎ アプリを勝手に紐づけています。この方法でFirebase Authのメアド/パスワードで実装するとしたら、具体的に次の内容を考えてました。
- @前半をUUID/UDIDなどを用いて@後半はFirebaseが勝手に作っているドメインを扱ってメアド生成メソッドを作る
- 英数字記号をランダムで作ってくれるメアド生成メソッドを作る
- 2と3を
CreateUserWithEmailAndPasswordAsync
に渡してアカウント生成FirebaseAuth.DefaultInstance.CurrentUser
でユーザー情報が取得できたらログイン中で取得できない場合は自動ログイン- 自動ログインの際に
SignInWithEmailAndPasswordAsync
のために2と3をPlayerPrefs
で保存取得できるようにしておくですが、匿名認証 を使うことで1~3を飛ばして4と5を実現することができます。
アプリの要件にもよりますが、アプリ初起動時に裏側で勝手に匿名認証のユーザー作成を行い、ユーザー設定のようなメニューを設けて「アプリ引き継ぎ設定」を作ることで上記1~3はやらなくてよくなります。
このように一般的なWeb/Mobileのアプリ開発とは異なり、ゲームやコンテンツのアプリはどれだけユーザーに面白い・楽しいと思ってもらいとにかく面倒な要素を取っ払うか重要になります。FirebaseAuth.SignInAnonymouslyAsync
早速、匿名証明でユーザー作成をやってみましょう。
公式のサンプルコードを参考にし、delegate
を用いてFirebaseAuth
だけを管理するクラスで以下のように実装するとよいでしょう。FirebaseAuthManager.csusing UnityEngine; using Firebase.Auth; public class FirebaseAuthManager : MonoBehaviour { FirebaseAuth _auth; FirebaseUser _user; public FirebaseUser UserData { get { return _user; } } public delegate void CreateUser(bool result); void Awake() { // 初期化 _auth = FirebaseAuth.DefaultInstance; // すでにユーザーが作られているのか確認 if (_auth.CurrentUser.UserId == null) { // まだユーザーができていないためユーザー作成 Create((result) => { if (result) { Debug.Log($"成功: #{_user.UserId}"); } else { Debug.Log("失敗"); } }); } else { _user = _auth.CurrentUser; Debug.Log($"ログイン中: #{_user.UserId}"); } } /// <summary> /// 匿名でユーザー作成 /// </summary> public void Create(CreateUser callback) { _auth.SignInAnonymouslyAsync().ContinueWith(task => { if (task.IsCanceled) { Debug.LogError("SignInAnonymouslyAsync was canceled."); callback(false); return; } if (task.IsFaulted) { Debug.LogError("SignInAnonymouslyAsync encountered an error: " + task.Exception); callback(false); return; } _user = task.Result; Debug.Log($"User signed in successfully: {_user.DisplayName} ({_user.UserId})"); callback(true); }); } }
delegate
は、上記のようにdelegateの型に合うメソッドを用意すれば他クラスにメソッドの引数として渡すことができ、その他クラスのメソッドの処理終わりのタイミングで渡したメソッドが呼び出される仕組みとなります。++C++; // 未確認飛行 C さんが解説されている記事 がとてもわかりやすいので、こちらを参考にされてくださいmm「匿名アカウントを永久アカウントに変換する」
先ほども解説した通り、最初に匿名でユーザーを作り、後からメアド/パスワードもしくはSNS連携に切り替えることができます。
方法としては、サンプルコードで解説されている通りFirebaseAuth.DefaultInstance.CurrentUser.LinkWithCredentialAsync
を扱うようです。FirebaseAuthManager.cs/// <summary> /// メアド/パスワードでユーザー作成 /// </summary> public void Create(string email, string password, CreateUser callback) { // すでにユーザーが作られているのか確認 if (_auth.CurrentUser.UserId == null) { // 新規でユーザー作成 _auth.CreateUserWithEmailAndPasswordAsync(email, password).ContinueWith(task => { if (task.IsCanceled) { Debug.LogError("CreateUserWithEmailAndPasswordAsync was canceled."); callback(false); return; } if (task.IsFaulted) { Debug.LogError("CreateUserWithEmailAndPasswordAsync encountered an error: " + task.Exception); callback(false); return; } _user = task.Result; Debug.Log($"Firebase user created successfully: {_user.DisplayName} ({_user.UserId})"); callback(true); }); } else { // 認証方法追加 Credential credential = EmailAuthProvider.GetCredential(email, password); _auth.CurrentUser.LinkWithCredentialAsync(credential).ContinueWith(task => { if (task.IsCanceled) { Debug.LogError("LinkWithCredentialAsync was canceled."); callback(false); return; } if (task.IsFaulted) { Debug.LogError("LinkWithCredentialAsync encountered an error: " + task.Exception); callback(false); return; } _user = task.Result; Debug.Log($"Credentials successfully linked to Firebase user: {_user.DisplayName} ({_user.UserId})"); callback(true); }); } }
これでAuthの対応は一旦終わりで、目的のDatabaseの実装に進んでいきます。Firebase Database
最後にDatabaseでは、「Authで作ったユーザー情報をもとにruleの設定」「データの保存と取得」の2点を解説して終わりとなります。
Authでユーザー情報を扱えるようになり、ruleでAuthのユーザーだけDatabaseとやりとりできるようになりました。ruleの設定
設定はとても簡単で、公式にユーザー認証した際のルール設定方法の内容をそのまま以下のように設定すれば大丈夫です。
databaseを初めて使うときにテストモードにしていると上記スクショのようにコメントアウトします。
また、今回はサンプルとして
users
の下にデータを保存と取得する内容でまとめていますが、他にもランキング情報やチャット情報を取り扱う場合は、rankings
やchats
などのように別keyになると思います。
ポイントは、$uid
が先ほどのFirebaseAuth.CurrentUser.UserId
と同じものでなければ許可しないように設定することです。ユーザーデータの保存と取得
Start
メソッドに保存と取得の処理を書いてますが、本来は別クラスから処理を呼び出すことになります。例として、ゲームのクリアタイムをニックネームとともに保存するサンプルを用意してみました。FirebaseDatabaseManager.csusing UnityEngine; using Firebase.Database; public class UserPlayData { public string username; public float time; public UserPlayData(string username, float time) { this.username = username; this.time = time; } } public class FirebaseDatabaseManager : MonoBehaviour { readonly string USER_DATA_KEY = "users"; DatabaseReference reference; [SerializeField] FirebaseAuthSample _auth; public delegate void GetUserDataCallback(UserPlayData result); void Start() { reference = FirebaseDatabase.DefaultInstance.RootReference; // サンプル: 保存 var userData = new UserPlayData("gremito", 10.5f); SaveUserData(userData); // サンプル: 取得 GetUserData((result) => { if(result == null) { Debug.LogWarning("失敗"); } else { Debug.Log($"username: {result.username}"); Debug.Log($"time: {result.time}"); } }); } /// <summary> /// ユーザーデータをJson化してdatabaseに保存(SetRawJsonValueAsync) /// </summary> public void SaveUserData(UserPlayData data) { // 公式サンプル方法: https://firebase.google.com/docs/database/unity/save-data?authuser=0#write_update_or_delete_data_at_a_reference var json = JsonUtility.ToJson(data); reference.Child(USER_DATA_KEY).Child(_auth.UserData.UserId).SetRawJsonValueAsync(json); } /// <summary> /// ユーザーデータを取得 /// </summary> public void GetUserData(GetUserDataCallback callback) { FirebaseDatabase.DefaultInstance.GetReference(USER_DATA_KEY) .Child(_auth.UserData.UserId).GetValueAsync().ContinueWith(task => { if (task.IsFaulted) callback(null); else if (task.IsCompleted) callback(new UserPlayData( task.Result.Child("username").Value.ToString(), float.Parse(task.Result.Child("time").Value.ToString()))); }); } }右スクショのFirebaseコンソールのDatabaseで保存したデータが反映されているようにUnityのデータを保存取得することができました。
これは、最低限の実装なのでAppStore/GooglePlayのストアにアプリをリリースする品質を考えるとトランザクションやイベント、並べ替えとフィルタリングなども上手く扱う必要が出てくるかもしれません。オフライン機能
Realtime Databaseにはオフライン機能があり、最後に通信した最新のデータをFriebase SDK側でデータを保持して取り扱うことができます。
アプリの要件に応じて最新情報が必須な場面は、意図的にアプリを使用できなくする機能実装が必要になります。
反対に最新情報が必要でなくてもアプリを操作できる場面は、特に対応しなくていいかもしれません。例えば、カジュアルゲームのアプリでランキング機能の場合、理想は最新情報が欲しいけど別に後から同期させて最新のランキング情報にすればいいから、オフラインでもゲームを楽しめるケースが考えられます。
まとめ
これでFirebaseのAuthとDatabaseを組み合わせて、しっかりセキュリティを担保したユーザーデータの取り扱い方を知れたと思います。
先ほども解説した通り、ここまでの内容は最低限のサンプルでFirebaseには他にも機能が豊富で、アプリの要件に応じてここで解説していない使える機能があると思います。
これをきっかけにさらにFirebaseを知ることでサーバーサイドの知識も得られますし、一石二鳥以上のスキルアップになると思うのでぜひ活用していきましょう!
- 投稿日:2020-12-16T20:38:27+09:00
Xamarin Community Toolkit カタログ(ビヘイビア・コンバーター編)
この記事は、Xamarin Advent Calendar 2020 16日目の記事です。
はじめに
Xamarin Community Toolkit は、Xamarin.Formsで使用できる便利ツール群です。ビヘイビア、コンバーター、エフェクトなどの汎用的な機能が揃っています。
この記事では、Xamarin Community Toolkitの機能を紹介していきます。執筆時点でのバージョンは、1.0.0-pre5 です。記事内のコードは、GitHubにあげてあります。
XamarinCommunityToolkitCatalog使い方
プレビュー版ですが、NuGetでインストールできます。
Xamarin.CommunityToolkitxamlでのネームスペースは、以下のように指定します。
xmlns:xct="http://xamarin.com/schemas/2020/toolkit"ビヘイビア
AnimationBehavior
タップ時にアニメーションが行われるようにするビヘイビアです。設定できるアニメーションの種類は以下の通りです。
- FadeAnimation (フェード)
- FlipHorizontalAnimation (横フリップ)
- FlipVerticalAnimation (縦フリップ)
- RotateAnimation (回転)
- ScaleAnimation (スケール)
- ShakeAnimation (シェイク)
Command
で、アニメーション終了時に実行するコマンドを設定できます。ただし、仕様なのかバグなのか分かりませんが、2回実行されるので注意が必要です。<ContentPage xmlns="http://xamarin.com/schemas/2014/forms" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" xmlns:xct="http://xamarin.com/schemas/2020/toolkit" x:Class="XamarinCommunityToolkitCatalog.Views.AnimationBehaviorPage" Title="AnimationBehavior"> <StackLayout VerticalOptions="Center" Padding="32, 0" Spacing="16"> <Label Text="Fade" HorizontalTextAlignment="Center" > <Label.Behaviors> <xct:AnimationBehavior Command="{Binding FinishedCommand}"> <xct:AnimationBehavior.AnimationType> <xct:FadeAnimation /> </xct:AnimationBehavior.AnimationType> </xct:AnimationBehavior> </Label.Behaviors> </Label> <Label Text="FlipHorizontal" HorizontalTextAlignment="Center" > <Label.Behaviors> <xct:AnimationBehavior Command="{Binding FinishedCommand}"> <xct:AnimationBehavior.AnimationType> <xct:FlipHorizontalAnimation /> </xct:AnimationBehavior.AnimationType> </xct:AnimationBehavior> </Label.Behaviors> </Label> <Label Text="FlipHorizontal" HorizontalTextAlignment="Center" > <Label.Behaviors> <xct:AnimationBehavior Command="{Binding FinishedCommand}"> <xct:AnimationBehavior.AnimationType> <xct:FlipVerticalAnimation /> </xct:AnimationBehavior.AnimationType> </xct:AnimationBehavior> </Label.Behaviors> </Label> <Label Text="Rotate" HorizontalTextAlignment="Center" > <Label.Behaviors> <xct:AnimationBehavior Command="{Binding FinishedCommand}"> <xct:AnimationBehavior.AnimationType> <xct:RotateAnimation Rotation="360" Duration="1000" /> </xct:AnimationBehavior.AnimationType> </xct:AnimationBehavior> </Label.Behaviors> </Label> <Label Text="Scale" HorizontalTextAlignment="Center" > <Label.Behaviors> <xct:AnimationBehavior Command="{Binding FinishedCommand}"> <xct:AnimationBehavior.AnimationType> <xct:ScaleAnimation Scale="2" /> </xct:AnimationBehavior.AnimationType> </xct:AnimationBehavior> </Label.Behaviors> </Label> <Button Text="Shake"> <Button.Behaviors> <xct:AnimationBehavior EventName="Clicked" Command="{Binding FinishedCommand}"> <xct:AnimationBehavior.AnimationType> <xct:ShakeAnimation /> </xct:AnimationBehavior.AnimationType> </xct:AnimationBehavior> </Button.Behaviors> </Button> </StackLayout> </ContentPage>EventToCommandBehavior
指定したイベントの発火時にコマンドを実行するビヘイビアです。
EventName
でイベント名を、Command
でコマンドを設定します。また、CommandParameter
でコマンドのパラメータ、EventArgsConverter
でEventArgs
のコンバーターの設定もできます。<ContentPage xmlns="http://xamarin.com/schemas/2014/forms" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" xmlns:xct="http://xamarin.com/schemas/2020/toolkit" x:Class="XamarinCommunityToolkitCatalog.Views.EventToCommandBehaviorPage" Title="EventToCommandBehavior"> <Button Text="Button" VerticalOptions="Center" Margin="32, 0"> <Button.Behaviors> <xct:EventToCommandBehavior EventName="Clicked" Command="{Binding ClickedCommand}" /> </Button.Behaviors> </Button> </ContentPage>ImpliedOrderGridBehavior
Grid
でGrid.Column
やGrid.Row
を指定しなくても、暗黙的に設定を行うビヘイビアです。<ContentPage xmlns="http://xamarin.com/schemas/2014/forms" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" xmlns:xct="http://xamarin.com/schemas/2020/toolkit" x:Class="XamarinCommunityToolkitCatalog.Views.ImpliedOrderGridBehaviorPage" Title="ImpliedOrderGridBehavior"> <Grid> <Grid.Behaviors> <xct:ImpliedOrderGridBehavior /> </Grid.Behaviors> <Grid.ColumnDefinitions> <ColumnDefinition Width="*" /> <ColumnDefinition Width="*" /> <ColumnDefinition Width="*" /> </Grid.ColumnDefinitions> <Grid.RowDefinitions> <RowDefinition Height="*" /> <RowDefinition Height="*" /> </Grid.RowDefinitions> <Label Text="0,0" /> <Label Text="1,0" /> <Label Text="2,0" /> <Label Text="0,1" /> <Label Text="1,1" /> <Label Text="2,1" /> </Grid> </ContentPage>MaskedBehavior
入力した文字列を指定したパターンに合わせるビヘイビアです。
Mask
でパターンを設定します。パターンでの任意の文字はデフォルトではX
で表します。この文字は、UnMaskedCharacter
で変更することが可能です。<ContentPage xmlns="http://xamarin.com/schemas/2014/forms" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" xmlns:xct="http://xamarin.com/schemas/2020/toolkit" x:Class="XamarinCommunityToolkitCatalog.Views.MaskedBehaviorPage" Title="MaskedBehavior"> <Entry VerticalOptions="Center" Margin="32, 0"> <Entry.Behaviors> <xct:MaskedBehavior Mask="XXXX-XX" /> </Entry.Behaviors> </Entry> </ContentPage>MaxLengthReachedBehavior
Entry
やEditor
の最大入力文字(MaxLength
)に達した時にイベントのやコマンドを実行するビヘイビアです。イベントはMaxLengthReached
、コマンドはCommand
で設定します。また、ShouldDismissKeyboardAutomatically
をtrue
にすることで、最大入力文字に達した時に、キーボードを閉じるようにすることもできます。<ContentPage xmlns="http://xamarin.com/schemas/2014/forms" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" xmlns:xct="http://xamarin.com/schemas/2020/toolkit" x:Class="XamarinCommunityToolkitCatalog.Views.MaxLengthReachedBehaviorPage" Title="MaxLengthReachedBehavior"> <Entry MaxLength="6" VerticalOptions="Center" Margin="32, 0"> <Entry.Behaviors> <xct:MaxLengthReachedBehavior Command="{Binding MaxLengthReachedCommand}" ShouldDismissKeyboardAutomatically="True" /> </Entry.Behaviors> </Entry> </ContentPage>UserStoppedTypingBehavior
Entry
やEditor
で入力を終えてから一定時間後にコマンドを実行するビヘイビアです。StoppedTypingTimeThreshold
でコマンド実行までの時間(ミリ秒)を、Command
で実行するコマンドを設定します。また、MinimumLengthThreshold
でコマンドの実行を行う最小文字数を設定できたり、ShouldDismissKeyboardAutomatically
をtrue
にすることで、コマンド実行時にキーボードを閉じるようにすることもできます。<ContentPage xmlns="http://xamarin.com/schemas/2014/forms" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" xmlns:xct="http://xamarin.com/schemas/2020/toolkit" x:Class="XamarinCommunityToolkitCatalog.Views.UserStoppedTypingBehaviorPage" Title="UserStoppedTypingBehavior"> <Entry VerticalOptions="Center" Margin="32, 0"> <Entry.Behaviors> <xct:UserStoppedTypingBehavior Command="{Binding UserStoppedTypingCommand}" StoppedTypingTimeThreshold="500" MinimumLengthThreshold="3" ShouldDismissKeyboardAutomatically="True" /> </Entry.Behaviors> </Entry> </ContentPage>ValidationBehavior
バリデーションを行うためのビヘイビアです。
ValidStyle
で有効時、InvalidStyle
で無効時のスタイルを設定できます。また、IsValid
で、有効かどうかを取得できます。
ValidationBehavior
は抽象クラスで、以下が実装してあるクラスです。CharactersValidationBehavior
指定した種類の文字が指定数分含まれているかどうかのバリデーションを行うビヘイビアです。
CharacterType
で文字の種類、MinimumCharacterCount
で最小数、MaximumCharacterCount
で最大数を指定します。
指定できる文字の種類は以下の通りです。
- LowercaseLetter (小文字)
- UppercaseLetter (大文字)
- Letter (LowercaseLetter または UppercaseLetter)
- Digit (数字)
- Alphanumeric (Letter または Digit)
- Whitespace (空白文字)
- NonAlphanumericSymbol (文字、数字、空白文字以外)
- LowercaseLatinLetter (a〜z)
- UppercaseLatinLetter (A〜Z)
- LatinLetter (LowercaseLatinLetter または UppercaseLatinLetter)
- Any (Alphanumeric または NonAlphanumericSymbol または Whitespace)
フラグなので組み合わせることもできます。ただし、
LowercaseLetter
、UppercaseLetter
では日本語の文字は含まれないので、必然的にLetter
でも含まれないので注意が必要です。<ContentPage xmlns="http://xamarin.com/schemas/2014/forms" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" xmlns:xct="http://xamarin.com/schemas/2020/toolkit" x:Class="XamarinCommunityToolkitCatalog.Views.CharactersValidationBehaviorPage" Title="CharactersValidationBehavior"> <ContentPage.Resources> <Style x:Key="invalidEntryStyle" TargetType="Entry"> <Setter Property="TextColor" Value="Red" /> </Style> </ContentPage.Resources> <Entry VerticalOptions="Center" Margin="32, 0"> <Entry.Behaviors> <xct:CharactersValidationBehavior InvalidStyle="{StaticResource invalidEntryStyle}" CharacterType="Letter" MinimumCharacterCount="2" MaximumCharacterCount="5" /> </Entry.Behaviors> </Entry> </ContentPage>EmailValidationBehavior
Eメールの書式になっているかどうかのバリデーションを行うビヘイビアです。
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" xmlns:xct="http://xamarin.com/schemas/2020/toolkit" x:Class="XamarinCommunityToolkitCatalog.Views.EmailValidationBehaviorPage" Title="EmailValidationBehavior"> <ContentPage.Resources> <Style x:Key="invalidEntryStyle" TargetType="Entry"> <Setter Property="TextColor" Value="Red" /> </Style> </ContentPage.Resources> <Entry VerticalOptions="Center" Margin="32, 0"> <Entry.Behaviors> <xct:EmailValidationBehavior InvalidStyle="{StaticResource invalidEntryStyle}" /> </Entry.Behaviors> </Entry> </ContentPage>MultiValidationBehavior
複数の
ValidationBehavior
を組み合わせてバリデーションを行うビヘイビアです。バリデーション毎にError
で無効時の値を設定でき、Errors
で無効と判定されているバリデーションのError
の値を取得できます。<ContentPage xmlns="http://xamarin.com/schemas/2014/forms" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" xmlns:xct="http://xamarin.com/schemas/2020/toolkit" x:Class="XamarinCommunityToolkitCatalog.Views.MultiValidationBehaviorPage" Title="MultiValidationBehavior"> <ContentPage.Resources> <Style x:Key="invalidEntryStyle" TargetType="Entry"> <Setter Property="TextColor" Value="Red" /> </Style> </ContentPage.Resources> <StackLayout VerticalOptions="Center" Margin="32, 0"> <Entry> <Entry.Behaviors> <xct:MultiValidationBehavior x:Name="multiValidationBehavior" InvalidStyle="{StaticResource invalidEntryStyle}" > <xct:CharactersValidationBehavior CharacterType="Digit" MinimumCharacterCount="1" xct:MultiValidationBehavior.Error="数字1文字以上" /> <xct:CharactersValidationBehavior CharacterType="UppercaseLetter" MinimumCharacterCount="1" xct:MultiValidationBehavior.Error="大文字1文字以上" /> <xct:CharactersValidationBehavior CharacterType="Any" MinimumCharacterCount="8" xct:MultiValidationBehavior.Error="8文字以上" /> </xct:MultiValidationBehavior> </Entry.Behaviors> </Entry> <Label Text="{Binding Errors[0], Source={x:Reference multiValidationBehavior}}" /> </StackLayout> </ContentPage>NumericValidationBehavior
数値の範囲や小数点以下の桁数のバリデーションを行うビヘイビアです。
MinimumValue
で最小値、MaximumValue
で最大値、MinimumDecimalPlaces
で小数点以下の最小桁数、MaximumDecimalPlaces
で小数点以下の最大桁数を設定します。<ContentPage xmlns="http://xamarin.com/schemas/2014/forms" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" xmlns:xct="http://xamarin.com/schemas/2020/toolkit" x:Class="XamarinCommunityToolkitCatalog.Views.NumericValidationBehaviorPage" Title="NumericValidationBehavior"> <ContentPage.Resources> <Style x:Key="invalidEntryStyle" TargetType="Entry"> <Setter Property="TextColor" Value="Red" /> </Style> </ContentPage.Resources> <Entry VerticalOptions="Center" Margin="32, 0"> <Entry.Behaviors> <xct:NumericValidationBehavior InvalidStyle="{StaticResource invalidEntryStyle}" MinimumValue="1.0" MaximumValue="10.0" MinimumDecimalPlaces="1" MaximumDecimalPlaces="3" /> </Entry.Behaviors> </Entry> </ContentPage>RequiredStringValidationBehavior
指定した文字列が入力されているかどうかのバリデーションを行うビヘイビアです。
RequiredString
で対象の文字列を設定します。<ContentPage xmlns="http://xamarin.com/schemas/2014/forms" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" xmlns:xct="http://xamarin.com/schemas/2020/toolkit" x:Class="XamarinCommunityToolkitCatalog.Views.RequiredStringValidationBehaviorPage" Title="RequiredStringValidationBehavior"> <ContentPage.Resources> <Style x:Key="invalidEntryStyle" TargetType="Entry"> <Setter Property="TextColor" Value="Red" /> </Style> </ContentPage.Resources> <Entry VerticalOptions="Center" Margin="32, 0"> <Entry.Behaviors> <xct:RequiredStringValidationBehavior InvalidStyle="{StaticResource invalidEntryStyle}" RequiredString="hoge" /> </Entry.Behaviors> </Entry> </ContentPage>TextValidationBehavior
文字列の長さや、正規表現によるバリデーションを行うビヘイビアです。
MinimumLength
で最小の長さ、MaximumLength
最大の長さ、RegexPattern
で正規表現のパターンを設定できます。また、DecorationFlags
を設定することで、文字列を加工した後にバリデーションを行うことが可能です。DecorationFlags
は以下のものが用意されています。
- TrimStart (先頭の空白文字を削除)
- TrimEnd (末尾の空白文字を削除)
- Trim (先頭と末尾の空白文字を削除)
- NullToEmpty (nullを空文字に変換)
- ReduceWhiteSpaces (空白文字を削除)
ちなみに、
CharactersValidationBehavior
、EmailValidationBehavior
、UriValidationBehavior
は、TextValidationBehavior
のサブクラスです。<ContentPage xmlns="http://xamarin.com/schemas/2014/forms" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" xmlns:xct="http://xamarin.com/schemas/2020/toolkit" x:Class="XamarinCommunityToolkitCatalog.Views.TextValidationBehaviorPage" Title="TextValidationBehavior"> <ContentPage.Resources> <Style x:Key="invalidEntryStyle" TargetType="Entry"> <Setter Property="TextColor" Value="Red" /> </Style> </ContentPage.Resources> <Entry VerticalOptions="Center" Margin="32, 0"> <Entry.Behaviors> <xct:TextValidationBehavior InvalidStyle="{StaticResource invalidEntryStyle}" MinimumLength="4" MaximumLength="8" DecorationFlags="Trim" RegexPattern="^\w+$" /> </Entry.Behaviors> </Entry> </ContentPage>UriValidationBehavior
URIのフォーマットであるかどうかのバリデーションを行うビヘイビアです。
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" xmlns:xct="http://xamarin.com/schemas/2020/toolkit" x:Class="XamarinCommunityToolkitCatalog.Views.UriValidationBehaviorPage" Title="UriValidationBehavior"> <ContentPage.Resources> <Style x:Key="invalidEntryStyle" TargetType="Entry"> <Setter Property="TextColor" Value="Red" /> </Style> </ContentPage.Resources> <Entry VerticalOptions="Center" Margin="32, 0"> <Entry.Behaviors> <xct:UriValidationBehavior InvalidStyle="{StaticResource invalidEntryStyle}" /> </Entry.Behaviors> </Entry> </ContentPage>コンバーター
BoolToObjectConverter
bool
値を任意の値に変換するコンバーターです。TrueObject
にはtrue
の時の値、FalseObject
にはfalse
の時の値を設定します。<ContentPage xmlns="http://xamarin.com/schemas/2014/forms" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" xmlns:xct="http://xamarin.com/schemas/2020/toolkit" x:Class="XamarinCommunityToolkitCatalog.Views.BoolToObjectConverterPage" Title="BoolToObjectConverter"> <ContentPage.Resources> <xct:BoolToObjectConverter x:Key="boolToObjectConverter" x:TypeArguments="x:String" TrueObject="真" FalseObject="偽"/> <x:Boolean x:Key="value">true</x:Boolean> </ContentPage.Resources> <Label VerticalOptions="Center" HorizontalOptions="Center" Text="{Binding ., Source={StaticResource value}, Converter={StaticResource boolToObjectConverter}}" /> </ContentPage>ByteArrayToImageSourceConverter
byte
配列をImageSource
に変換するコンバーターです。<ContentPage xmlns="http://xamarin.com/schemas/2014/forms" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" xmlns:xct="http://xamarin.com/schemas/2020/toolkit" x:Class="XamarinCommunityToolkitCatalog.Views.ByteArrayToImageSourceConverterPage" Title="ByteArrayToImageSourceConverter"> <ContentPage.Resources> <xct:ByteArrayToImageSourceConverter x:Key="byteArrayToImageSourceConverter" /> </ContentPage.Resources> <Image VerticalOptions="Center" HorizontalOptions="Center" Source="{Binding ImageBytes, Converter={StaticResource byteArrayToImageSourceConverter}}" /> </ContentPage>DateTimeOffsetConverter
DateTimeOffset
をDateTime
に変換するコンバーターです。<ContentPage xmlns="http://xamarin.com/schemas/2014/forms" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" xmlns:xct="http://xamarin.com/schemas/2020/toolkit" xmlns:sys="clr-namespace:System;assembly=netstandard" x:Class="XamarinCommunityToolkitCatalog.Views.DateTimeOffsetConverterPage" Title="DateTimeOffsetConverter"> <ContentPage.Resources> <xct:DateTimeOffsetConverter x:Key="dateTimeOffsetConverter" /> <sys:DateTimeOffset x:Key="value"> <x:Arguments> <x:Int32>2020</x:Int32> <x:Int32>12</x:Int32> <x:Int32>16</x:Int32> <x:Int32>0</x:Int32> <x:Int32>0</x:Int32> <x:Int32>0</x:Int32> <x:TimeSpan /> </x:Arguments> </sys:DateTimeOffset> </ContentPage.Resources> <DatePicker VerticalOptions="Center" HorizontalOptions="Center" Date="{Binding ., Source={StaticResource value}, Converter={StaticResource dateTimeOffsetConverter}}" /> </ContentPage>DoubleToIntConverter
double
をint
に変換するコンバーターです。Math.Round
で丸められます。パラメーターで、乗数を指定することもできます。<ContentPage xmlns="http://xamarin.com/schemas/2014/forms" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" xmlns:xct="http://xamarin.com/schemas/2020/toolkit" x:Class="XamarinCommunityToolkitCatalog.Views.DoubleToIntConverterPage" Title="DoubleToIntConverter"> <ContentPage.Resources> <xct:DoubleToIntConverter x:Key="doubleToIntConverter" /> <x:Double x:Key="value">1.25</x:Double> </ContentPage.Resources> <Label VerticalOptions="Center" HorizontalOptions="Center" Text="{Binding ., Source={StaticResource value}, Converter={StaticResource doubleToIntConverter}, ConverterParameter=10}" /> </ContentPage>EqualConverter
パラメーターの値と等しければ
true
、違っていればfalse
に変換するコンバーターです。<ContentPage xmlns="http://xamarin.com/schemas/2014/forms" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" xmlns:xct="http://xamarin.com/schemas/2020/toolkit" x:Class="XamarinCommunityToolkitCatalog.Views.EqualConverterPage" Title="EqualConverter"> <ContentPage.Resources> <xct:EqualConverter x:Key="equalConverter" /> <x:String x:Key="value">value</x:String> </ContentPage.Resources> <Label VerticalOptions="Center" HorizontalOptions="Center" Text="{Binding ., Source={StaticResource value}, Converter={StaticResource equalConverter}, ConverterParameter=value}" /> </ContentPage>IndexToArrayItemConverter
パラメーターに設定した
Array
のインデックスの値に変換するコンバーターです。<ContentPage xmlns="http://xamarin.com/schemas/2014/forms" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" xmlns:xct="http://xamarin.com/schemas/2020/toolkit" x:Class="XamarinCommunityToolkitCatalog.Views.IndexToArrayItemConverterPage" Title="IndexToArrayItemConverter"> <ContentPage.Resources> <xct:IndexToArrayItemConverter x:Key="indexToArrayItemConverter" /> <x:Int32 x:Key="value">1</x:Int32> <x:Array x:Key="array" Type="{x:Type x:String}"> <x:String>value0</x:String> <x:String>value1</x:String> <x:String>value2</x:String> </x:Array> </ContentPage.Resources> <Label VerticalOptions="Center" HorizontalOptions="Center" Text="{Binding ., Source={StaticResource value}, Converter={StaticResource indexToArrayItemConverter}, ConverterParameter={StaticResource array}}" /> </ContentPage>IntToBoolConverter
0なら
false
、0以外ならtrue
に変換するコンバーターです。<ContentPage xmlns="http://xamarin.com/schemas/2014/forms" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" xmlns:xct="http://xamarin.com/schemas/2020/toolkit" x:Class="XamarinCommunityToolkitCatalog.Views.IntToBoolConverterPage" Title="IntToBoolConverter"> <ContentPage.Resources> <xct:IntToBoolConverter x:Key="intToBoolConverter" /> <x:Int32 x:Key="value">0</x:Int32> </ContentPage.Resources> <Label VerticalOptions="Center" HorizontalOptions="Center" Text="{Binding ., Source={StaticResource value}, Converter={StaticResource intToBoolConverter}}" /> </ContentPage>InvertedBoolConverter
true
ならfalse
、false
ならtrue
に変換するコンバーターです。<ContentPage xmlns="http://xamarin.com/schemas/2014/forms" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" xmlns:xct="http://xamarin.com/schemas/2020/toolkit" x:Class="XamarinCommunityToolkitCatalog.Views.InvertedBoolConverterPage" Title="InvertedBoolConverter"> <ContentPage.Resources> <xct:InvertedBoolConverter x:Key="invertedBoolConverter" /> <x:Boolean x:Key="value">true</x:Boolean> </ContentPage.Resources> <Label VerticalOptions="Center" HorizontalOptions="Center" Text="{Binding ., Source={StaticResource value}, Converter={StaticResource invertedBoolConverter}}" /> </ContentPage>IsNotNullOrEmptyConverter
null
、空文字、空白文字のみではない場合はtrue
、それ以外はfalse
に変換するコンバーターです。<ContentPage xmlns="http://xamarin.com/schemas/2014/forms" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" xmlns:xct="http://xamarin.com/schemas/2020/toolkit" x:Class="XamarinCommunityToolkitCatalog.Views.IsNotNullOrEmptyConverterPage" Title="IsNotNullOrEmptyConverter"> <ContentPage.Resources> <xct:IsNotNullOrEmptyConverter x:Key="isNotNullOrEmptyConverter" /> <x:String x:Key="value"> </x:String> </ContentPage.Resources> <Label VerticalOptions="Center" HorizontalOptions="Center" Text="{Binding ., Source={StaticResource value}, Converter={StaticResource isNotNullOrEmptyConverter}}" /> </ContentPage>IsNullOrEmptyConverter
null
、空文字、空白文字のみである場合はtrue
、それ以外はfalse
に変換するコンバーターです。<ContentPage xmlns="http://xamarin.com/schemas/2014/forms" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" xmlns:xct="http://xamarin.com/schemas/2020/toolkit" x:Class="XamarinCommunityToolkitCatalog.Views.IsNullOrEmptyConverterPage" Title="IsNullOrEmptyConverter"> <ContentPage.Resources> <xct:IsNullOrEmptyConverter x:Key="isNullOrEmptyConverter" /> <x:String x:Key="value"> </x:String> </ContentPage.Resources> <Label VerticalOptions="Center" HorizontalOptions="Center" Text="{Binding ., Source={StaticResource value}, Converter={StaticResource isNullOrEmptyConverter}}" /> </ContentPage>ItemSelectedEventArgsConverter
ListView
のItemSelected
イベントの引数であるSelectedItemChangedEventArgs
を、そのプロパティであるSelectedItem
の値に変換するコンバーターです。<ContentPage xmlns="http://xamarin.com/schemas/2014/forms" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" xmlns:xct="http://xamarin.com/schemas/2020/toolkit" x:Class="XamarinCommunityToolkitCatalog.Views.ItemSelectedEventArgsConverterPage" Title="ItemSelectedEventArgsConverter"> <ContentPage.Resources> <xct:ItemSelectedEventArgsConverter x:Key="itemSelectedEventArgsConverter" /> </ContentPage.Resources> <ListView> <ListView.ItemsSource> <x:Array Type="{x:Type x:String}"> <x:String>value0</x:String> <x:String>value1</x:String> <x:String>value2</x:String> </x:Array> </ListView.ItemsSource> <ListView.Behaviors> <xct:EventToCommandBehavior EventName="ItemSelected" Command="{Binding ItemSelectedCommand}" EventArgsConverter="{StaticResource itemSelectedEventArgsConverter}" /> </ListView.Behaviors> </ListView> </ContentPage>ItemTappedEventArgsConverter
ListView
のItemTapped
イベントの引数であるItemTappedEventArgs
を、そのプロパティであるItem
の値に変換するコンバーターです。<ContentPage xmlns="http://xamarin.com/schemas/2014/forms" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" xmlns:xct="http://xamarin.com/schemas/2020/toolkit" x:Class="XamarinCommunityToolkitCatalog.Views.ItemTappedEventArgsConverterPage" Title="ItemTappedEventArgsConverter"> <ContentPage.Resources> <xct:ItemTappedEventArgsConverter x:Key="itemTappedEventArgsConverter" /> </ContentPage.Resources> <ListView> <ListView.ItemsSource> <x:Array Type="{x:Type x:String}"> <x:String>value0</x:String> <x:String>value1</x:String> <x:String>value2</x:String> </x:Array> </ListView.ItemsSource> <ListView.Behaviors> <xct:EventToCommandBehavior EventName="ItemTapped" Command="{Binding ItemTappedCommand}" EventArgsConverter="{StaticResource itemTappedEventArgsConverter}" /> </ListView.Behaviors> </ListView> </ContentPage>ListIsNotNullOrEmptyConverter
null
ではない、もしくは、IEnumerable
が空ではない場合はtrue
、それ以外はfalse
に変換するコンバーターです。<ContentPage xmlns="http://xamarin.com/schemas/2014/forms" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" xmlns:xct="http://xamarin.com/schemas/2020/toolkit" xmlns:scg="clr-namespace:System.Collections.Generic;assembly=mscorlib" x:Class="XamarinCommunityToolkitCatalog.Views.ListIsNotNullOrEmptyConverterPage" Title="ListIsNotNullOrEmptyConverter"> <ContentPage.Resources> <xct:ListIsNotNullOrEmptyConverter x:Key="listIsNotNullOrEmptyConverter" /> <scg:List x:Key="value" x:TypeArguments="x:Int32"> <x:Int32>0</x:Int32> </scg:List> </ContentPage.Resources> <Label VerticalOptions="Center" HorizontalOptions="Center" Text="{Binding ., Source={StaticResource value}, Converter={StaticResource listIsNotNullOrEmptyConverter}}" /> </ContentPage>ListIsNullOrEmptyConverter
null
、もしくは、IEnumerable
が空である場合はtrue
、それ以外はfalse
に変換するコンバーターです。<ContentPage xmlns="http://xamarin.com/schemas/2014/forms" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" xmlns:xct="http://xamarin.com/schemas/2020/toolkit" xmlns:scg="clr-namespace:System.Collections.Generic;assembly=mscorlib" x:Class="XamarinCommunityToolkitCatalog.Views.ListIsNullOrEmptyConverterPage" Title="ListIsNullOrEmptyConverter"> <ContentPage.Resources> <xct:ListIsNullOrEmptyConverter x:Key="listIsNullOrEmptyConverter" /> <scg:List x:Key="value" x:TypeArguments="x:Int32"> <x:Int32>0</x:Int32> </scg:List> </ContentPage.Resources> <Label VerticalOptions="Center" HorizontalOptions="Center" Text="{Binding ., Source={StaticResource value}, Converter={StaticResource listIsNullOrEmptyConverter}}" /> </ContentPage>ListToStringConverter
IEnumerable
の各値を文字列にして、Separator
もしくはパラメーターで指定した区切り文字を使用して連結した文字列に変換するコンバーターです。区切り文字は、パラメーターで指定した方が優先されます。<ContentPage xmlns="http://xamarin.com/schemas/2014/forms" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" xmlns:xct="http://xamarin.com/schemas/2020/toolkit" xmlns:scg="clr-namespace:System.Collections.Generic;assembly=mscorlib" x:Class="XamarinCommunityToolkitCatalog.Views.ListToStringConverterPage" Title="ListToStringConverter"> <ContentPage.Resources> <xct:ListToStringConverter x:Key="listToStringConverter" Separator=", " /> <scg:List x:Key="value" x:TypeArguments="x:String"> <x:String>value0</x:String> <x:String>value1</x:String> <x:String>value2</x:String> </scg:List> </ContentPage.Resources> <Label VerticalOptions="Center" HorizontalOptions="Center" Text="{Binding ., Source={StaticResource value}, Converter={StaticResource listToStringConverter}}" /> </ContentPage>MultiConverter
複数のコンバーターを順番に適用して変換するコンバーターです。
MultiConverterParameter
で、適用するコンバーター毎にパラメーターを設定することができます。<ContentPage xmlns="http://xamarin.com/schemas/2014/forms" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" xmlns:xct="http://xamarin.com/schemas/2020/toolkit" x:Class="XamarinCommunityToolkitCatalog.Views.MultiConverterPage" Title="MultiConverter"> <ContentPage.Resources> <xct:MultiConverter x:Key="multiConverter" > <xct:EqualConverter /> <xct:BoolToObjectConverter x:TypeArguments="x:String" TrueObject="同じ" FalseObject="違う" /> </xct:MultiConverter> <x:Array x:Key="multiParams" Type="{x:Type xct:MultiConverterParameter}" > <xct:MultiConverterParameter ConverterType="{x:Type xct:EqualConverter}" Value="value" /> </x:Array> <x:String x:Key="value">value</x:String> </ContentPage.Resources> <Label VerticalOptions="Center" HorizontalOptions="Center" Text="{Binding ., Source={StaticResource value}, Converter={StaticResource multiConverter}, ConverterParameter={StaticResource multiParams}}" /> </ContentPage>NotEqualConverter
パラメーターの値と違っていたら
true
、等しければfalse
に変換するコンバーターです。<ContentPage xmlns="http://xamarin.com/schemas/2014/forms" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" xmlns:xct="http://xamarin.com/schemas/2020/toolkit" x:Class="XamarinCommunityToolkitCatalog.Views.NotEqualConverterPage" Title="NotEqualConverter"> <ContentPage.Resources> <xct:NotEqualConverter x:Key="notEqualConverter" /> <x:String x:Key="value">value</x:String> </ContentPage.Resources> <Label VerticalOptions="Center" HorizontalOptions="Center" Text="{Binding ., Source={StaticResource value}, Converter={StaticResource notEqualConverter}, ConverterParameter=value}" /> </ContentPage>StateToBooleanConverter
StateToCompare
や、パラメーターで指定したLayoutState
と等しければtrue
、違っていればfalse
に変換するコンバーターです。StateToCompare
とパラメーター両方指定した場合は、パラメーターが優先されます。LayoutState
は、Xamarin Community Toolkitの機能であるStateLayout
で使用されます。<ContentPage xmlns="http://xamarin.com/schemas/2014/forms" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" xmlns:xct="http://xamarin.com/schemas/2020/toolkit" x:Class="XamarinCommunityToolkitCatalog.Views.StateToBooleanConverterPage" Title="StateToBooleanConverter"> <ContentPage.Resources> <xct:StateToBooleanConverter x:Key="stateToBooleanConverter" StateToCompare="Success" /> <x:Static x:Key="value" Member="xct:LayoutState.Error" /> </ContentPage.Resources> <Label VerticalOptions="Center" HorizontalOptions="Center" Text="{Binding ., Source={StaticResource value}, Converter={StaticResource stateToBooleanConverter}}" /> </ContentPage>TextCaseConverter
文字列を大文字や小文字に変換するコンバーターです。
Type
または、パラメーターで、大文字にする場合はUpper
、小文字にする場合はLower
と指定します。Type
とパラメーター両方指定した場合は、パラメーターが優先されます。<ContentPage xmlns="http://xamarin.com/schemas/2014/forms" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" xmlns:xct="http://xamarin.com/schemas/2020/toolkit" x:Class="XamarinCommunityToolkitCatalog.Views.TextCaseConverterPage" Title="TextCaseConverter"> <ContentPage.Resources> <xct:TextCaseConverter x:Key="textCaseConverter" Type="Upper" /> <x:String x:Key="value">Value</x:String> </ContentPage.Resources> <Label VerticalOptions="Center" HorizontalOptions="Center" Text="{Binding ., Source={StaticResource value}, Converter={StaticResource textCaseConverter}}" /> </ContentPage>TimeSpanToDoubleConverter
TimeSpan
をそれが表す秒数の合計(TotalSeconds
)に変換するコンバーターです。<ContentPage xmlns="http://xamarin.com/schemas/2014/forms" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" xmlns:xct="http://xamarin.com/schemas/2020/toolkit" xmlns:sys="clr-namespace:System;assembly=netstandard" x:Class="XamarinCommunityToolkitCatalog.Views.TimeSpanToDoubleConverterPage" Title="TimeSpanToDoubleConverter"> <ContentPage.Resources> <xct:TimeSpanToDoubleConverter x:Key="timeSpanToDoubleConverter" /> <sys:TimeSpan x:Key="value"> <x:Arguments> <x:Int32>12</x:Int32> <x:Int32>30</x:Int32> <x:Int32>10</x:Int32> </x:Arguments> </sys:TimeSpan> </ContentPage.Resources> <Label VerticalOptions="Center" HorizontalOptions="Center" Text="{Binding ., Source={StaticResource value}, Converter={StaticResource timeSpanToDoubleConverter}}" /> </ContentPage>おわりに
Xamarin Community Toolkitのビヘイビア、コンバーターを紹介しました。
Xamarin Community Toolkitには、エフェクトやコントロールなど紹介していない機能がまだあります。続きは、Xamarin Advent Calendar 2020 20日目で公開予定です。
- 投稿日:2020-12-16T18:44:53+09:00
.NET5.0/C#9.0でオートシェイプ風図形描画ライブラリを作ろう!(Chapter1)
※本記事は下記のエントリから始まる連載記事となります。
.NET5.0/C#9.0でオートシェイプ風図形描画ライブラリを作ろう!(Chapter0)Chapter1 画面上に図形を描画するための仕組みを作ろう
実装イメージ
具体的な実装に入る前に、まずは図形を描画する部分のクラス構成をざっくりと考えてみます。
- 四角形や楕円などの図形は、IShapeインターフェースを実装するものとします。今は描画処理
Draw()
のみが実装されています。- 描画処理の実装部分は
IGraphics
インターフェースをDraw()
メソッドで受け取ることで外部から注入します(Method Injecton)- IGraphicsのSkiaSharp用の実装は
CoreShape.Extensions.SkiaSharp
に用意します。ひとまずはこの構成にしたがって実装を進めてみたいと思います。
Chapter1.1 四角形を描画してみよう
それでは四角形(矩形)を描画する処理を例に具体的な実装について見てみましょう。
RectangleShape
IShapeインターフェースを継承したRectangleShapeクラスを作成します。
Draw()
メソッドの実装はひとまず空のままにしておきます。public class RectangleShape : IShape { public void Draw(IGraphics g) { } }座標や色情報を格納する構造体を用意する
図形描画のためのパラメータとして以下が必要になります。
- 位置(Point)
- サイズ(Size)
- 色(Color)
これらを構造体として定義します。また、位置(Point)とサイズ(Size)を組み合わせて四角形を表す構造体(Rectangle)も定義しておきます。
これらは構造体のデザインのガイドラインにしたがってIEquatableを実装した書き換え不可な構造体として定義します。今回は「C#9.0 SourceGeneratorでReadonly構造体を生成するGeneratorを作ってみました。」で紹介した
ReadonlyStructGenerator
を使って実現します。
下記のように構造体を定義し、必要なプロパティをinitアクセサー付きで追加します。
IEquatable<T>
とその他諸々の実装はGeneratorによって自動生成されます。[ReadonlyStructGenerator.ReadonlyStruct] public partial struct Point { public float X { get; init; } public float Y { get; init; } }[ReadonlyStructGenerator.ReadonlyStruct] public partial struct Size { public float Width { get; init; } public float Height { get; init; } }[ReadonlyStructGenerator.ReadonlyStruct] public partial struct Rectangle { public Point Location { get; init; } public Size Size { get; init; } public float Left => Location.X; public float Top => Location.Y; public float Right { get; } public float Bottom { get; } public Rectangle(Point location,Size size) { Location = location; Size = size; Right = location.X + size.Width; Bottom = location.Y + size.Height; } public Rectangle(float left, float top, float width, float height) : this(new Point(left, top), new Size(width, height)) { } }[ReadonlyStructGenerator.ReadonlyStruct] public partial struct Color { public byte R { get; init; } public byte G { get; init; } public byte B { get; init; } public byte A { get; init; } public Color(byte r, byte g, byte b, byte a = 255) => (R, G, B, A) = (r, g, b, a); }プロパティの追加
- 四角形の位置、サイズの情報はRectangle構造体(Boundsプロパティ)で管理します。
- 輪郭の色や太さはStrokeクラス、塗りつぶしの色はFillクラスで管理します。
public class RectangleShape : IShape { public Rectangle Bounds { get; protected set; } public Stroke? Stroke { get; set; } public Fill? Fill { get; set; } public RectangleShape(Rectangle bounds) { Bounds = bounds; } public void Draw(IGraphics g) { } }public class Stroke { public Color Color { get; set; } = Color.Black; public float Width { get; set; } = 1f; public Stroke(Color color , float width) { Color = color; Width = width; } }public class Fill { public Color Color { get; set; } = Color.White; public Fill(Color color) { Color = color; } }これで四角形の描画に必要なパラメータは揃いました。
IGraphics の実装
次に、実際に輪郭や塗りつぶしを描画する処理を定義するIGraphicsインターフェースに四角形を描画するためのメソッドを追加します。
輪郭はDrawRectangle()
メソッド、塗りつぶしはFillRectangle()
メソッドを使って描画します。
パラメータには位置とサイズを表すRectangle
と、輪郭の色とサイズを指定するStroke
または 塗りつぶしの色を指定するFill
を受け取ります。public interface IGraphics { void DrawRectangle(Rectangle rectangle, Stroke stroke); void FillRectangle(Rectangle rectangle, Fill fill); }
RectangleShape.Draw()
メソッドの実装
IGraphics
にメソッドを追加したので、RectangleShape
のDraw()
メソッドが記述できるようになりました。
輪郭を描かない(線なし)、塗りつぶさない(塗りつぶしなし)の場合に対応するためにStroke
とFill
をNULL許容としておきます。
(参照型も既定でNULL非許容となっているので、クラス名の後に?を付けてNULL許容を明示する必要があります。)public class RectangleShape : IShape { public Rectangle Bounds { get; protected set; } public Stroke? Stroke { get; set; } public Fill? Fill { get; set; } public virtual void Draw(IGraphics g) { if (Fill is not null) { g.FillRectangle(Bounds, Fill); } if (Stroke is not null) { g.DrawRectangle(Bounds, Stroke); } } }ここまでで
CoreShape
側での実装は完了です。
続いてCoreShape
(RectangleShape
)を利用する側の実装に移ります。
IGraphics
のSkiaSharp
実装SkiaGraphics
クラス
CoreShape.Extensions.SkiaSharp
のプロジェクトへ移動し、IGraphics
インターフェースを継承したSkiaGraphics
クラスを作成します。
SkiaSharp
ではSKCanvas
クラスが図形描画を行うメソッドを持っています。
コンストラクタでSKCanvas
のオブジェクトを受け取って利用するようにします。後は
DrawRectangle()
、FillRectangle()
でSkCanvasのDrawRect()
を実行するだけです。public class SkiaGraphics : IGraphics { protected virtual SKCanvas Canvas { get; set; } public SkiaGraphics(SKCanvas canvas) { Canvas = canvas; } public virtual void DrawRectangle(Rectangle rectangle, Stroke stroke) { var rect = new SKRect(rectangle.Left, rectangle.Top, rectangle.Right, rectangle.Bottom); var paint = new SKPaint() { Style = SKPaintStyle.Stroke, Color = new SkColor(stroke.Color.R, stroke.Color. G,stroke. Color.B, stroke.Color.A), StrokeWidth = stroke.Width } Canvas.DrawRect(rect, paint); } public virtual void FillRectangle(Rectangle rectangle, Fill fill) { var rect = new SKRect(rectangle.Left, rectangle.Top, rectangle.Right, rectangle.Bottom); var paint = new SKPaint() { Style = SKPaintStyle.Fill, Color = new SkColor(fill.Color.R, fill.Color.G, fill.Color.B, fill.Color.A) } Canvas.DrawRect(rect,paint); } }型変換用拡張メソッドの定義
SkiaGraphics
の実装はこれで良いのですが、1つだけ(ちょっとした)問題があります。
SkiaSharpの描画メソッドを利用する場合、座標や色などの指定にSkRect
やSkColor
といったSkiaSharp独自のオブジェクトを利用する必要があります。
CoreShape
でも独自にRectangle
やColor
構造体を定義しています。また、描画パラメータStroke
やFill
に相当する値をSkiaSharp
ではSkPaint
で指定する必要があります。したがって、
CoreShape
から渡ってきたこれらの値を一旦SkiaSharp
用の値に変換してあげる処理が必要になります。これらの処理を色々なところで毎回行うのは非常に面倒なので、拡張メソッドとしてまとめて定義してしまいましょう。
CoreShape.Extensions.SkiaSharp
のプロジェクトに以下のクラスを追加します。(型変換用)
public static class TypeConvertExtensions { public static SKPoint ToSk(this Point p) => new SKPoint(p.X, p.Y); public static SKSize ToSk(this Size s) => new SKSize(s.Width, s.Height); public static SKRect ToSk(this Rectangle rect) => new SKRect(rect.Left, rect.Top, rect.Right, rect.Bottom); public static SKColor ToSk(this Color color) => new SKColor(color.R, color.G, color.B, color.A); }(SkPaintにStroke,Fillの値を設定)
public static class SkPaintExtensions { public static SKPaint SetStroke(this SKPaint paint, Stroke stroke) { paint.Style = SKPaintStyle.Stroke; paint.Color = stroke.Color.ToSk(); paint.StrokeWidth = stroke.Width; return paint; } public static SKPaint SetFill(this SKPaint paint, Fill fill) { paint.Style = SKPaintStyle.Fill; paint.Color = fill.Color.ToSk(); return paint; } }これらを使って先ほどの
DrawRectangle()
、FillRectangle()
を書き換えると...public virtual void DrawRectangle(Rectangle rectangle, Stroke stroke) { Canvas.DrawRect(rectangle.ToSk(), new SKPaint().SetStroke(stroke)); } public virtual void FillRectangle(Rectangle rectangle, Fill fill) { Canvas.DrawRect(rectangle.ToSk(), new SKPaint().SetFill(fill)); }なんということでしょう!こんなにすっきりと記述できました!
長い道のりでしたが、画面に四角形を描画するまであと一息です!Windowに四角形を描画する
SampleWPFのプロジェクトへ移動します。
MainWindowのコードビハインド MainWindow.xaml.cs に追加したsKElement_PaintSurface
イベントハンドラに以下のコードを記述します。
RectangleShape
を生成し、Fill
とStroke
を設定します- イベント引数の
SKPaintSurfaceEventArgs
から取得できるSKCanvas
オブジェクトを渡してSkiaGraphics
を生成します。- 生成した
SkiaGraphics
のインスタンスを渡してRectangleShape
のDraw()
メソッドを実行します。public partial class MainWindow : Window { private void sKElement_PaintSurface(object sender, SKPaintSurfaceEventArgs e) { var shape = new RectangleShape(100, 100, 200, 150) { Stroke = new Stroke(CoreShape.Color.Red, 2), Fill = new Fill(CoreShape.Color.LightSkyBlue) }; var g = new SkiaGraphics(e.Surface.Canvas); shape.Draw(g); } }ここまで来れば後は実行するのみです!
はい、ようやくWindowに四角形を描くことに成功しました!
これで図形を描画する仕組みが整いましたので、後はこの仕組みに乗っかって他の図形を描画するクラスを追加していけば良いですね。
例えば楕円を描画するクラスを追加したい場合は下記のようにするだけで完了です。
RectangleShape
を継承したOvalShape
のクラスを用意IGraphics
に楕円描画用のメソッド(DrawOval()
FillOval()
のような)を追加OvalShape
のDraw()
メソッド内にDrawOval()
FillOval()
を実行する処理を記述IGraphics
を継承したクラスでDrawOval()
FillOval()
の実装を記述次回
次回の予定は
- Chapter2 ドラッグ操作で図形を動かしてみよう
です。
- 投稿日:2020-12-16T18:43:30+09:00
.NET5.0/C#9.0でオートシェイプ風図形描画ライブラリを作ろう!(Chapter0)
.NET5.0も正式リリースされ、C#言語バージョンも9.0となりました。
とはいえ、私自身C#の知識が7.0くらいの知識からアップデートされていなかったりします。そこで今回はC#8、9辺りの新しい機能を意識しつつ、.NET5.0のアプリケーションを一から作ってみようと思います。
作るもの
さて、何を作るかですが
「ほとんどの人がどのようなものか知っている(仕様の説明が要らない)」
「実装の結果が視覚的にわかりやすい」
との理由からWord、Excel等についているオートシェイプ機能のような図形描画のライブラリを作ることとしました。
- オートシェイプ機能のような図形描画のライブラリを.NET5.0クラスライブラリとして作ります。
- このライブラリを利用したサンプルアプリケーションをWPFで作成します。
- サンプルでは2DグラフィックスエンジンのSkiaSharpを使います
- (それ以外はノープランです)
※ 行き当たりばったりで設計しながら作る過程を連載記事として公開してゆく予定ですので、「それあまり良くない設計ですよ」とか「もっといい方法ありますよ」などフィードバックをいただけますと大変うれしいです。
ソースコード
本記事のソースコードはGitHub上に置いておきます。
https://github.com/pierre3/CoreShape
(「blog/cahpter0-2」のように章ごとにブランチを切っていく予定です)
記事一覧(予定)
- Chapter0 ソリューションの作成
- Chapter1 画面上に図形を描画するための仕組みを作ろう
- Chapter2 ドラッグ操作で図形を動かしてみよう
- Chapter3 ドラッグ操作で図形のサイズを変更してみよう
- Chapter4 UIの実装も考えて使い勝手をよくしよう
それでは、まずは開発の準備から始めましょう!
Chapter0 ソリューションの作成
Visual Studio2019 の最新バージョン(12/13現在はVersion 16.8.3)を使用して開発します。
プロジェクトの構成
以下のような構成でプロジェクトを作成します。
CoreShape
: 今回作成するメインのプロジェクト (.NET5.0 クラスライブラリ)CoreShape.Extensions.SkiaSharp
: SkiaSharp向けのCoreShape拡張。インターフェースの実装や拡張メソッドなどを定義(.NET5.0クラスライブラリ)SkiaSharp
: 2Dグラフィックスエンジン(NuGetライブラリ参照)SampleWPF
: サンプルアプリ(WPF(.NET5.0)App)CoreShapeライブラリはどのプラットフォーム、ライブラリにも依存しないポータブルな状態を保つようにします。
プロジェクトの作成
.NET5.0プロジェクトテンプレートの表示
.NET5.0 対応のプロジェクトテンプレートを利用するには今のところVisual Studioのオプション設定を変更する必要があります。
- メニューバーで「ツール」-「オプション」を選択
- オプションダイアログの左側で「環境」-「プレビュー機能」を選択
- 「「新しいプロジェクト」ダイアログに全ての.NET Coreテンプレートを表示する」にチェックを付ける
.NET5.0クラスライブラリの作成
下記の手順で「CoreShape」及び「CoreShape.Extensions.SkiaSharp」のプロジェクトを作成します。
- メニューバーで「ファイル」-「新規作成」-「プロジェクト」を選択
プロジェクト名「CoareShape」を入力して「次へ」
メニュー「ファイル」-「追加」-「新しいプロジェクト」から「CoreShape.Extensions.SkiaSharp」プロジェクトも同様に作成します。
WPF(.NET5.0)Appの作成
続けてWPFアプリケーション(SampleWpf)のプロジェクトを作成します。
- メニューバーで「ファイル」-「追加」-「新しいプロジェクト」を選択
プロジェクト名を入力して「次へ」
SkiaSharpのNuGetパッケージを追加
「ツール」-「NuGetパッケージマネージャ」-「ソリューションのNuGetパッケージの管理」を開き、「SkiaSharp」のパッケージを検索し、プロジェクトに追加します。
- 「SampleWPF」プロジェクトに「SkiaSharp.Views.WPF」の最新安定バージョンを追加します
- 「CoreShape.Extensions.SkiaSharp」プロジェクトに「SkisSharp」の最新安定バージョンを追加します。
プロジェクトの参照
下記の通りにプロジェクト間の参照設定を行います。
- SampleWPF
→ CoreShape、CoreShape.Extensions.SkiaSharp を参照- CoreShape.Extensions.SkiaSharp
→ CoreShape を参照NULL許容コンテキストを有効にする
作成されたcsprojファイルに
<Nullable>enable</Nullable>
を追加します。
こうすることで参照型もNULL非許容が前提となり、NULL以外の値に初期化しないと警告が出るようになります。(作成した全てのプロジェクトでこの設定を行います)<Project Sdk="Microsoft.NET.Sdk"> <PropertyGroup> <TargetFramework>net5.0</TargetFramework> <Nullable>enable</Nullable> </PropertyGroup> </Project>描画面の設定
SampleWPFでMainWindow.xamlを開き、SkiaSharpで図形を描画するためのコントロール
SKElement
を追加します。<Window x:Class="SampleWPF.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:skiaSharp="clr-namespace:SkiaSharp.Views.WPF;assembly=SkiaSharp.Views.WPF" xmlns:local="clr-namespace:SampleWPF" mc:Ignorable="d" Title="MainWindow" Height="450" Width="800"> <Grid> <skiaSharp:SKElement x:Name="skElement" PaintSurface="sKElement_PaintSurface" /> </Grid> </Window>そしてコードビハインド(MainWindow.xaml.cs)には描画面を更新する際のイベント
PaintSurface
のイベントハンドラを追加しておきます。private void sKElement_PaintSurface(object sender, SKPaintSurfaceEventArgs e) { //ここに図形描画の処理を記述します。 }これで実装前の準備は完了です。
次回
次回は
です。
- 投稿日:2020-12-16T16:24:17+09:00
NuGet Source がなぜか消えない
今までDisktopを使っていたが、旅先なので、LapTopを持っていくことにした。環境の移行とかはしたのだが、現地で、VSを使うと、何とリストアがVSで、こける。なんでやねん、、、
明らかに、先日削除した ServiceFabric のパッケージソースがあって、それが悪さをしてそう。ちなみに、VS のパッケージソースからは削除済みなのにいまだに出てしまう。気を取り直して、先日学んだコマンドラインの方式でやってみる。
dotnet restore Determining projects to restore... C:\Program Files\dotnet\sdk\5.0.101\NuGet.targets(131,5): error : The local source 'C:\Program Files\Microsoft SDKs\Service Fabric\packages' doesn't exist. [C:\Users\tsushi\source\repos\ScaleControllerSpike\ScaleControllerSpike\ScaleControllerSpike.csproj]同じですな。ではパッケージソースは?
dotnet nuget list source Registered Sources: 1. Local Package Source [Disabled] C:\LocalNuGet 2. nuget.org [Enabled] https://api.nuget.org/v3/index.json 3. azure_app_service [Enabled] https://www.myget.org/F/azure-appservice/api/v2 4. Microsoft Visual Studio Offline Packages [Enabled] C:\Program Files (x86)\Microsoft SDKs\NuGetPackages\まだおるがな。じゃあ、コマンドで削除や。
dotnet nuget remove source "Microsoft Azure Service Fabric SDK" Package source with Name: Microsoft Azure Service Fabric SDK removed successfully.うむ。確認。
dotnet nuget list source Registered Sources: 1. Local Package Source [Disabled] C:\LocalNuGet 2. nuget.org [Enabled] https://api.nuget.org/v3/index.json 3. azure_app_service [Enabled] https://www.myget.org/F/azure-appservice/api/v2 4. Microsoft Visual Studio Offline Packages [Enabled] C:\Program Files (x86)\Microsoft SDKs\NuGetPackages\ 5. Microsoft Azure Service Fabric SDK [Enabled] C:\Program Files\Microsoft SDKs\Service Fabric\packagesまだおるがな、、、
多分これは、
nuget.config
のデフォルトの問題と思い調査すると次のスタックオーバーフローがヒット。
スコープ NuGet.Config の場所 説明 Solution カレントフォルダ VSのソリューションのフォルダ User Windows: %appdata%\NuGet\NuGet.config
, Mac/Linux:~/.config/NuGet/NuGet.Config
もしくは~/.nuget/NuGet/NuGet.Config
Solutionのレベルで上書きされる Computer Windows: %ProgramFiles(x86)%\NuGet\Config
, Mac/Linux:$XDG_DATA_HOME
つまり~/.local/share
もしくは/usr/local/share
User もしくは Solution レベルで上書きされる なるほど。だから、ユーザーレベルのを消しても、それが、Computerレベルのものだったら、消えないわけか。
コンピューターレベルのデフォルトにいたので、
ServiceFabricSDK.config
を消すことで無事解決。Resource
- 投稿日:2020-12-16T12:08:55+09:00
SQLiteUnityKitの読み込み先をResourcesフォルダからにしてみる
この記事は大阪工業大学 Advent Calendar 2020の18日目の記事です。
はじめに
はじめましての方ははじめまして、それ以外の人はこんにちは。
今年度大学に行かなさ過ぎてただのニートと化してるnihuといいます。
軽く自己紹介でもした方がいいかなと思ったんですけど、僕のTwitterのホーム画見たら大体わk
需要がない気がしたので割愛します。
あと今回のはやってること自体は難しくないですがunityちょっと知らないとわからないかもしれない...申し訳ない...1.目的
UnityでSQLを扱うのに便利なSQLiteUnityKitというものがあります。
詳しくは他にわかりやすい記事かいっぱいあると思うのでここでは詳しく書きませんが、
これを導入すると簡単にデータベースを扱えるようになるアプリです。で、このアプリ、データベースファイルを読み込む先がstreamingAssetsからに限定されています。
streamingAssets以外に置くとビルド後に場所が変わってしまったりするから当然と言われれば当然なのですが、
streamingAssetsに置くと作成したアプリがデスクトップ向けのものだとフォルダの中にデータベースファイルがそのまま入っていて
少し知っている人なら簡単に中身を見れるという問題があります。(まだ初心者で知らないだけの場合もあるので隠蔽とかする方法あるなら誰か教えてください泣いて喜びます)
前使ったときは見られても問題ないなーぐらいに思ってたんでスルーしてたんですが、
今後作りたいと考えてるものの中で見られると嫌だなというケースが出てきたので、なんか方法ないかなと思って考えたのがResourcesフォルダから読み込む方法です。
(unity詳しい人ならAssetBundleじゃないのって思うと思うんですけどまだ使ったことないのでResourcesからで大目に見てください...)とりあえず考えたはいいけど実際できるのかと調べた結果できたのでこれからやった方法を纏めていきます。
2.方法の模索
まずSQLiteUnityKitのSqliteDatabaseのコンストラクタを見てデータベースのファイルをどうやって読み込んでいるか見てみました。
SqliteDatabase.cspublic SqliteDatabase (string dbName){ pathDB = System.IO.Path.Combine (Application.persistentDataPath, dbName); //original path string sourcePath = System.IO.Path.Combine (Application.streamingAssetsPath, dbName); //if DB does not exist in persistent data folder (folder "Documents" on iOS) or source DB is newer then copy it if (!System.IO.File.Exists (pathDB) || (System.IO.File.GetLastWriteTimeUtc(sourcePath) > System.IO.File.GetLastWriteTimeUtc(pathDB))) { if (sourcePath.Contains ("://")) { // Android WWW www = new WWW (sourcePath); // Wait for download to complete - not pretty at all but easy hack for now // and it would not take long since the data is on the local device. while (!www.isDone) {;} if (String.IsNullOrEmpty(www.error)) { System.IO.File.WriteAllBytes(pathDB, www.bytes); } else { CanExQuery = false; } } else { // Mac, Windows, Iphone //validate the existens of the DB in the original folder (folder "streamingAssets") if (System.IO.File.Exists (sourcePath)) { //copy file - alle systems except Android System.IO.File.Copy (sourcePath, pathDB, true); } else { CanExQuery = false; Debug.Log ("ERROR: the file DB named " + dbName + " doesn't exist in the StreamingAssets Folder, please copy it there."); } } } }色々書かれてますが要約するとandroid以外はデータベースをPersistentDataPathにコピーしてコピーしたものを参照している仕組みだとわかります。
つまりResourcesフォルダからデータを読み込んで同じところにファイルを生成できればあとは全く同じでも問題ないということになります。
ただResourcesフォルダ内のものはよくわからん形式に圧縮されてるので何かいい方法はないかと調べてると以下のサイトをみつけました。これを見るとTextAssetというクラスを使うとResourcesそのままのデータを読み込めるらしいのでつまり
ファイルを読み込む→パス作ってSystem.IO.File.WriteAllBytesで読み込んだバイナリデータをファイルに書き込む→ファイル作れた^q^
という流れを汲めば恐らく上手く動作するはずだと考え、次にSqliteDatabaseの書き換えに移りました。
3.ソースコードの書き換え
ほんとは上の方法で正しくコピーできるかとかテストしたんですが、問題なく動いたのと書く時間がないので割愛して書き換え後のSqliteDatabaseのコンストラクタのコードを記述します。
SqliteDatabase.cspublic SqliteDatabase (string dbName){ pathDB = System.IO.Path.Combine (Application.persistentDataPath, dbName+".db"); //original path //string sourcePath = System.IO.Path.Combine (Application.streamingAssetsPath, dbName); //if DB does not exist in persistent data folder (folder "Documents" on iOS) or source DB is newer then copy it if (!System.IO.File.Exists (pathDB) /*|| (System.IO.File.GetLastWriteTimeUtc(sourcePath) > System.IO.File.GetLastWriteTimeUtc(pathDB))*/) { TextAsset textAsset=(TextAsset)Resources.Load(dbName); System.IO.File.WriteAllBytes(pathDB, textAsset.bytes); /*if (sourcePath.Contains ("://")) { // Android WWW www = new WWW (sourcePath); // Wait for download to complete - not pretty at all but easy hack for now // and it would not take long since the data is on the local device. while (!www.isDone) {;} if (String.IsNullOrEmpty(www.error)) { System.IO.File.WriteAllBytes(pathDB, www.bytes); } else { CanExQuery = false; } } else { // Mac, Windows, Iphone //validate the existens of the DB in the original folder (folder "streamingAssets") if (System.IO.File.Exists (sourcePath)) { //copy file - alle systems except Android System.IO.File.Copy (sourcePath, pathDB, true); } else { CanExQuery = false; Debug.Log ("ERROR: the file DB named " + dbName + " doesn't exist in the StreamingAssets Folder, please copy it there."); } }*/ } }元々のソースコードでいらない部分はコメントアウトして残しています。色々適当だったり殆ど原形なかったりしますがまあこれで行けると思います(適当)。
そしてテスト用に書いたソースコードはこちらです。
test.csvoid Start() { string test="1"; SqliteDatabase sqliteDatabase=new SqliteDatabase("testdata"); string testQuery=string.Format("select * from test where id = '{0}'",test); DataTable dataTable=sqliteDatabase.ExecuteQuery(testQuery); string name=""; foreach (DataRow dr in dataTable.Rows){ name=(string)dr["name"]; Debug.Log(name); } }これで必要なものはそろったのでtest.csを適当なオブジェクト作ってアタッチしてみてテストプレイを実行してみます。
ヤッターーー!!!
というわけでうまく作動できました。おわりに
絶対他にいい方法ある気しかしない(震え)
初め調べたとき似たようなの出てこなかったので作るかってなったけど後で調べてたらしっかり見てはないけどバインドして隠蔽云々書かれている記事見つけて「これ...いるんか?」ってなったけど見なかったことにしました()
けど今の自分ができるなかでやってみて一応うまくできたので良かったかなと思います(難しいことはしてないけど)
上にも書きましたけどこんなことしなくてもファイル隠せるよなど詳しい人いたら教えてください...(おわり)
- 投稿日:2020-12-16T11:00:38+09:00
C#でフロントエンド開発ができる「Blazor WebAssembly」を試してみた
はじめに
本記事はBlazor WebAssembly使ってみて得た知見をまとめることを目的としています。
Blazor WebAssemblyとは
BlazorとはC#、Razor(後ほど説明します)、およびHTMLをベースにしたWeb UIフレームワークのことを指します。
Blazor WebAssemblyとは今年の5月にマイクロソフトから正式リリースされたC#と.NET Coreを用いてWebアプリケーションの開発を可能にするフレームワークです。
私のようなフロントエンド言語は苦手だけどC#ならわかる人におすすめです。
開発してみる
Visual StudioでBlazorアプリを選択し、新規プロジェクトを立ち上げます。Blazor WebAssembly Appを選択して作成ボタンを押します。
プロジェクトをビルドして実行すると、ブラウザが勝手に立ち上がり以下のような画面が表示されます。今回は画面の赤枠部分について解説します。
Razor構文
上記図の部分はRazorファイルというもので作られています。RazorファイルではHTMLページ内にRazor構文でC#のコードを埋め込んで開発することができます。変数をHTMLコードとして動的に出力したい場合には、変数名の先頭に”@”を付け、@code内でC#でメソッドやプロパティ、フィールド変数を定義することができます。
currentCount
という変数とcurrentCount
をインクリメントするメソッドIncrementCount()
が@Code内で定義されています。HTMLページ内にそれらが埋め込まれており、Click me
ボタンを押すと、メソッドが実行され、currentCount
の値が更新されます。@page "/counter" <h1>Counter</h1> <p>Current count: @currentCount</p> <button class="btn btn-primary" @onclick="IncrementCount">Click me</button> @code { private int currentCount = 0; private void IncrementCount() { currentCount++; } }
さいごに
Javascriptが分からなくてもRazor構文というものを使えばC#でロジックを書くことができるのはとてもお手軽だと思いました。まだ新しいので日本語の情報が少なく開発し辛い場面もありますが、Webでちょっと遊んでみたいという人にはとても良いと思いました。
最後までお読みいただきありがとうございました。
- 投稿日:2020-12-16T10:48:24+09:00
Explicit Interface
自分が見ているレポのコードリーディングで次のような構文が出てきて何だろう?と思ったので調べてみた。同じメソッド名でこんな定義が出来るのが知らなかった。調べてみると、Explicit Interface という構文っぽい。
async Task<ScaleMetrics> IScaleMonitor.GetMetricsAsync() { return await GetMetricsAsync(); } public Task<KafkaTriggerMetrics> GetMetricsAsync() :Explicit Interface の構文を使うと、複数のインターフェイスで同じ名前のメソッドがあっても、インターフェイスを明示した方が使われる。つまりこのケースだと、次のサンプルがわかりやすいかもしれない。同じ
GetSome()
のメソッド。1つは、戻り値がint
もう一つの Explicit Interface がついている方の戻りは、string
インターフェイスを介してアクセスすると、該当の Interface の メソッドが呼ばれる。class Program : IInterface { static void Main(string[] args) { Console.WriteLine("Hello World!"); var p = new Program(); // int GetSome() is for common usage. int a = p.GetSome(); IInterface i = new Program(); // If you use interface, it will explicit interface. string b = i.GetSome(); } public int GetSome() { return 1; } string IInterface.GetSome() { return "hello"; } } interface IInterface { string GetSome(); }一つ実験してみる。インターフェイスに、
Hello
メソッドを足す。interface IInterface { string GetSome(); void Hello(); }
Program
に、Hello
メソッドを別途定義するvoid Hello() { Console.WriteLine("Hello from the class."); } void IInterface.Hello() { Console.WriteLine("Hello from the interface."); }メインメソッドを 変更して、それぞれ
Hello()
が呼ばれるようにする。static void Main(string[] args) { Console.WriteLine("Hello World!"); var p = new Program(); // int GetSome() is for common usage. int a = p.GetSome(); p.Hello(); IInterface i = new Program(); // If you use interface, it will explicit interface. string b = i.GetSome(); i.Hello(); }Result
予想通り!
Hello World! Hello from the class. Hello from the interface.リソース
- 投稿日:2020-12-16T09:25:50+09:00
【2020年度版】Unityで初学者から中級者までを駆け抜けるためのおすすめ書籍【中学生から大人まで】
はじめに
Unityの書籍紹介は色々あるのですが、初学者へのおすすめ書籍まとめという記事が多い印象です。それ自体はいいのですが、
初学者が、一通り読んで学んだ、その先に何ををすればいいのかというところまで幅広くまとめている記事はあまり見かけないなと思ったので作ります。
Life is Tech ! のCampやSchoolで中高生という初学者と日々コミュニケーションをしているので初学者から中級者がやっぱりメインになりますが、頑張って上級者になるための道のりも引いてみます。読む上での注意
基本的には、やっと中級にやってきた筆者がこれまで読んできた書籍が中心です。
上級向けとして、ゲームエンジンのアーキテクチャや、ゲームエンジンに頼らない実力が必要と考えている方もいるかもしれませんが、対象から外れていますのであしからず。また、個人の好みによって同系統の別の方がおすすめ、ということもあるかと思います。
そして著者が読んでいないUnity本も数多あります。
「この場合はこの書籍がおすすめ」ということがあればぜひコメント欄で教えて下さい。
また、書籍画像とリンクはAmazonサイトから主に引用しています。対象者
- 初学者:これからUnityを覚えたい!プログラミングを学んでみたい!という人
- いわゆるプログラミング初心者
- 中級者:Unityの基本的な動作は覚えられた。ゲームを作ってみた人
- ゲームは作ってみてからの伸びしろが大きいですよね!
- 上級者:自分で1つのゲームはすらすら作れるようになって、さらにレベルアップしたい人
- いわゆるデザイン領域もこのあたりから入ってくるのですが、1人で作ることを今回は想定しています。
初学者から中級者
初学者から中級者までは一直線に駆け抜けていきましょう!
初学者向け(Unityとプログラミングの基本)
いわゆる初心者向けまとめ記事では、この入門教科書を色々紹介してくれていることが多いのですが、
初心者にとって大事なことは、様々な参考書使うより1冊を信じてしっかりやり遂げることだと思っています。
その思いを信じて、今回は思い切って1つのみしか紹介しません!Unityの教科書 Unity 2020完全対応版
いわゆる「ねこの本」です。
2Dの内容が多めとなっているが、基本的なUnityの操作からプログラミングまでを覚えることができる。
とくにいいな、この本を紹介したいなと思ったのが、ゲームの作り方を簡潔に説明している点。
わかりやすいゲームでのアクションのギミックだけでなく、「監督役、工場」などのわかりやすい単語で、
ゲームを管理する役割までを説明されている点は、自分が人に教えるときに見習いたいと思っています。中級者向け(1冊を信じてやり遂げた人に贈るもう1冊)
初学者向けよりは詳しく記載されているが、UnityやC#についての基本から一気通貫で網羅している書籍をもう一度やりましょう。
いわゆる「強くてニューゲーム」です。ここでの一冊目は著者がまさにUnityを勉強し直すときに信じてやり遂げた書籍から紹介します。Unity5 3D/2Dゲーム開発実践入門 Unity2019対応版
去年辺りまで、Unity5の本だったので紹介しづらかったのですが、2019対応版が発売されたのでこれからは堂々と紹介できます。
ゲームロジックの考え方がわかりやすくて有用だったり、GI(グローバルイルミネーション)など演出として有用な部分も
丁寧に説明されているので、もう一冊基本から初めて、だけど基本だけじゃなく更にゲームを作るための方法を学ぶことができる
素晴らしい書籍だと思っています。中級者から上級者になるための三種の神器!
さてさて、「それでは上級編!」といきたいところですが、、
Unityは、そしてゲームづくりというものは、とても奥が深いのです。
そのため、もう少しだけ、ゆっくりしっかりとレベルアップをしていきましょう!
それぞれ、下記で紹介する上級向けのジャンルを代表する書籍です。
まずは、この3種類の方向性に進むとといいのかなと思います。
- Unityの幅広さを知る:「Unityデザイナーズバイブル」
- まずは、Unityでできる表現の幅広さを体験してみましょう!
- UIの作り方の記事もあるのでおすすめです。
- プログラミング寄りが好みなら、プログラミングバイブルもOKです
- プログラミング理解のためにC#を学ぶ:「なるほどなっとくC#入門」
- 特にプログラミングも一緒に始めたという人に向けておすすめです。
- UnityとC#を分けて考えられるようになりましょう!
- ゲームを面白くするためにゲームデザインを学ぶ:「ゲームプランとデザインの教科書」
- 理論の体型より具体的な例を重視してこちらを紹介します。
- まずは、自分の作ったゲームを面白くする手法を学んでみましょう。
上級者向け
上級編からは、様々なジャンルに分かれて様々な書籍があります。
この記事では、筆者の考えから学びやすいだろうという順番に並べていますが、本質的に順番はないと考えています。
順番を来にせず、興味があるジャンルから学んでいってください。そして…ここからは遠慮なく物量作戦です。
後半の分量の多さにびっくりさせてしまったかもしれません。
それだけゲームづくりとは複合的で総合的なモノづくりだと思っています。
だからこそ創ることが面白いのです。
それでは上級者への道のりを覗いてみましょう!ゲームメカニクス
ゲームメカニクスというのは幅広く様々なな意味を持つ単語なのですが、ここでは主にプログラミングやUnityでの操作を多く含む
ゲームの仕組みづくりというものをゲームメカニクスと表しています。Unityゲーム プログラミング・バイブル
Unity2018までで、できることを利用した、まさに機能のバイブル。
章ごとに完結しているので、気になった技術から試すことができます。使ったことがない機能を使ってみるという点でもおすすめ。
ただ、Unityは最近の進化が早いので、Unity標準でできることがAssetで紹介されていたりするものもあります(Anima2Dなど)。Unity デザイナーズバイブル
プログラミングバイブルが出版されたときの感想は上記の説明に加えて実は、「ProBuilderがあればベストだった。。」と思っていました。
が、まさにそのような声に応えるようなカタチで出版されたプログラミング・バイブルの姉妹編が出版されました!
デザイナーと名前にあるように、プログラマーだけじゃなく幅広い人にUnityの使い方を紹介してくれています。
こちらも素晴らしい書籍なのですが、一点だけ注意点があります。
それは、Amazonnoレビューでもある通り、誤植が多い点です。
ここまでいろんな書籍を通して作品を作っていれば自己解決できるレベルなのですが、再販が行われる際にはブラッシュアップされているとうれしいですね!ゲームの作り方 改訂版 Unityで覚える遊びのアルゴリズム
そろそろ、Unityの基本だけではなく、ゲームメカニクスの作り方も気になってきます。
実際ありそうなゲームサンプルにて以下のようなの実現方法を学ぶことができます。
- タッチ操作
- 迷路のCSVファイル管理
- 複数カメラの利用
- ミスタードリラー的ブロックゲーム
- シューティングのロックオン管理
サンプル的な意味合いが強いので、考え方を理解して自分で作ってみることをおすすめします!
ゲームデザイン
こちらでは、ゲームメカニクスに比べ、ゲームという仕組みそのものを面白くするための書籍を紹介します。
ゲームデザインという言葉はいろいろな使われ方をしますが、ここではまずはゲームをより面白くするためのゲーム全体の仕組みと考えてみてください。ゲームデザインバイブル
これまで、ゲームデザインを学びたいというときに紹介する本は迷っていることが多かったです。
ゼビウスの遠藤さんによるゲームデザイン講義実況中継や、オライリーによるレベルアップのゲームデザインなどを伝えてきましたが、2019年についにこの書籍が紹介されました。
バイブルという名前ゲームプランとデザインの教科書 ぼくらのゲームの作り方
この後出てくる、リーダブルコードの部分でも触れているのですが、日本の書籍と海外の書籍は、本というものの作り方が少し違うなぁと感じる時があります。
これは個人的な意見なのですが、日本の書籍の方がよりストーリー的でリスト的で、より具体的な印象を受けます。
ゲームデザインバイブルはシステマチックすぎるなぁと感じた人には、こちらの書籍を読んでみることをおすすめします。
後半にある実際のゲームクリエイターの方々によるインタビューとノウハウ紹介には、刺激を受けると思います。3Dゲームをおもしろくする技術 実例から解き明かすゲームメカニクス・レベルデザイン・カメラのノウハウ
こちらの本には、プログラミングは一切出てきません。
ただひたすらにデジタルゲームをゲーム設計としてのメカニクス(格闘ゲームのタイミング設計など)、レベルデザイン、カメラテクニックなどから
分析しています。このような書籍が他にないからこそ、そして実際のゲームが題材だからこそ自分のゲーム体験を省みることにも繋がります。ゲームメカニクス大全
ゲームという長い歴史の中で、デジタルゲームが生まれたのはほんの最近に過ぎません。
ゲームというものは、テーブルゲーム、ボードゲームと一緒に発展してきました。
この本では、そのようないわゆるアナログゲームに現れるメカニクスを網羅し分類に挑戦しているという意欲的な一冊です。おもしろいゲームシナリオの作り方 ―41の人気ゲームに学ぶ企画構成テクニック
ゲームデザインの中には、もちろんゲームのフィクションというものも含まれます。
この本は、その中でもゲームシナリオだけに注目している本です。
しかし、この中で出てくるヒーローズジャーニーという考え方は確実に学びにつながるのでそこだけでも読む価値があります。
そして、本当に様々な面白いゲームの面白いシナリオを紹介してくれるのであなたのインプットも広げてくれることでしょう。ゲームデザインについてその他の書籍
まだまだ紹介したい本はたくさんあるぐらい、ゲームデザインとは奥深いのですが一旦ここまで。
- HALF-REAL
- ビデオゲームとは何かを考える
- ゲームの企画書
- ゲームを作ったクリエイター達は何を考えていたのか
- 中ヒットに導くゲームデザイン
- この本の目的は原題にあらわれています。
- Game Design Workshop: A Playcentric Approach to Creating Innovative Games:革新的ゲームを創るためのプレイ中心アプローチ
- つまり、ゲーム作りのプロトタイピングについて考えられているものです。
プログラミングとして、C#を学ぶ
プログラミングは、深く知れば知るほど面白い世界です。
一緒にC#という言語と考え方を知っていきましょう。新・標準プログラマーズライブラリ なるほどなっとく C#入門
スラスラわかるC#
前述のなるほどなっとくなどで、クラスやポリモーフィズムなどを理解したら、もう少し深くC#について学んでみましょう。
匿名関数やデリゲートなど、プロパティなど、もう少し深ぼってC#を改めて知ること画できます。実戦で役立つ C#プログラミングのイディオム/定石&パターン
上記2冊でC#という言語を学んだら、一歩進んだC#の書き方を身に着けましょう。
ゲーム制作のための書籍ではないのですが、C#らしいコードを学ぶことができます。UniRx/UniTask完全理解 より高度なUnity C#プログラミング
上級以上のレベルになってしまうと思われますが、ゲーム制作で非常に便利な考え方はリアクティブプログラミングです。
Unityでは、実質的にUniRXが当てはまります。さらには、C#のTaskをUnity向けに拡張されたUniTaskについても合わせられた、とりすーぷ先生の書籍も紹介されてください。
(筆者もこれから読んで、まずは完全理解したと言えるようにがんばります。)コーディング一般
ゲームプログラマのためのコーディング技術
そろそろ、コードが煩雑になってきて管理が難しくなってくる頃。
リーダブルコードももちろんおすすめだけど、こちらのほうが実践的内容から始めるので初学者にはわかりやすいと考えています。
(また、リーダブルコードはTHEアメリカン書籍という体裁なため、普段本を読まない日本系の方だと読みづらいのではないかとも感じています。)この本では、命名の原則からオブジェクト指向のクラス設計などまで、実践的にプログラミングの設計を学ぶことができます。
特にゲーム制作に役立つ形でのSOLID原則のわかりやすい説明は多くの方に紹介したいです。プリンシプル オブ プログラミング3年目までに身につけたい一生役立つ101の原理原則
KISS?DRY?SOLID?YAGNI?疎結合??
などなどプログラミングを調べていると出てくる様々な先人たちの研鑽の結果を手早く知ることができます。
この本にはコードは出てきません。コードを書くプログラマーがどのように考えているのかを学びましょう。リーダブルコード
もちろん、リーダブルコードをおすすめさせてください。この本に関しては、様々な方が紹介をしているので、詳しくは調べてみてください。
命名についての項目などとても有益なことばかりが記載されています。アルゴリズムとデータ構造
ゲームを作っていると、Webアプリケーションやアプリ開発に比べて早い段階で、数多くのデータやオブジェクトを管理する、アルゴリズムを構築する
必要に迫られると感じています。
(もちろんWebアプリケーションなどでもありますが、ライブラリがとても有用なため中級者あたりまでは意識することが少ないと感じます)
そのため、どこかでしっかりアルゴリズムやデータ構造を学びたいとなった場合はこちらです。
そして、穴掘り法による迷路やA*によるその解法などを実装してみましょう!デザインパターン
さて、デザインパターンです。ここまで学習を進めていると、デザインパターンという言葉は様々なところで聞いたことがあるはずです。
デザインパターン自体は、建築家アレグザンダーのパターンランゲージという考え方をモノづくりに応用しようとする試みがあり、
そしてGoFによるオブジェクト指向における再利用のためのデザインパターンに繋がります。
(参考:Wikipedia)
デザインパターン自体の歴史が長くなってきていて今のゲーム開発に即時に活用できるかというと難しいのですが、生き残る古典というものにはやはり価値があると思います。増補改訂版 Java言語で学ぶデザインパターン入門
日本におけるデザインパターンの入門書としてはもはや右に出るものはないかと思います。
非常にわかりやすくデザインパターンを知ることができます。Javaで説明されていますが、基本の部分はC#とはほぼ一緒なので違和感なく読めます。Game Programming Patterns ソフトウェア開発の問題解決メニュー
デザインパターンを実際にゲームに応用するためにはという視点ではこちらの書籍を紹介させてください。
この本の前半では、書籍内でよく参照するデザインパターンのおさらいから始まるので、この本から読んでも頑張れば読み進めることができます。ゲーム数学、物理
ゲームを創る中では数学と物理が非常に多く出てきます。さらには、より高度なことをしたいと考えたらシェーダーというものを実装することもあるでしょう。
そのようなときのために、ゲームで使われる数学、物理、そしてゲームの動かされ方を知っておくことも有用です。ゲームアプリの数学 Unityで学ぶ基礎からシェーダーまで
数学や物理の説明を行っている書籍は様々なので、自分が読みやすいと思ったものを読み進めることが重要です。
この書籍では、実際にUnityを使って試すことができるので、手を動かしたいという方におすすめです。ゲームを動かす技術と発想R
ゲーム内のための数学だけではなく、Unityを使っているだけならなかなか触れることがない、
ゲームがそもそもどう動いているのかというより低レイヤー名部分までわかりやすく説明してくれています。Unityシェーダープログラミングの教科書 ShaderLab言語解説編
※こちらのみ、Boothです。
ゲームづくりをしている中で、演出を深めようとしたら現れる、シェーダーというもの。
シェーダーとはなにか、そしてどの様に創ることができるのかを知ることができます。
ゲーム演出
Unity ゲームエフェクト マスターガイド

いわゆる初心者向けまとめ記事では、この入門教科書を色々紹介してくれていることが多いのですが、
初心者にとって大事なことは、様々な参考書使うより1冊を信じてしっかりやり遂げることだと思っています。
その思いを信じて、今回は思い切って1つのみしか紹介しません!Unityの教科書 Unity 2020完全対応版
いわゆる「ねこの本」です。
2Dの内容が多めとなっているが、基本的なUnityの操作からプログラミングまでを覚えることができる。
とくにいいな、この本を紹介したいなと思ったのが、ゲームの作り方を簡潔に説明している点。
わかりやすいゲームでのアクションのギミックだけでなく、「監督役、工場」などのわかりやすい単語で、
ゲームを管理する役割までを説明されている点は、自分が人に教えるときに見習いたいと思っています。中級者向け(1冊を信じてやり遂げた人に贈るもう1冊)
初学者向けよりは詳しく記載されているが、UnityやC#についての基本から一気通貫で網羅している書籍をもう一度やりましょう。
いわゆる「強くてニューゲーム」です。ここでの一冊目は著者がまさにUnityを勉強し直すときに信じてやり遂げた書籍から紹介します。Unity5 3D/2Dゲーム開発実践入門 Unity2019対応版
去年辺りまで、Unity5の本だったので紹介しづらかったのですが、2019対応版が発売されたのでこれからは堂々と紹介できます。
ゲームロジックの考え方がわかりやすくて有用だったり、GI(グローバルイルミネーション)など演出として有用な部分も
丁寧に説明されているので、もう一冊基本から初めて、だけど基本だけじゃなく更にゲームを作るための方法を学ぶことができる
素晴らしい書籍だと思っています。中級者から上級者になるための三種の神器!
さてさて、「それでは上級編!」といきたいところですが、、
Unityは、そしてゲームづくりというものは、とても奥が深いのです。
そのため、もう少しだけ、ゆっくりしっかりとレベルアップをしていきましょう!
それぞれ、下記で紹介する上級向けのジャンルを代表する書籍です。
まずは、この3種類の方向性に進むとといいのかなと思います。
- Unityの幅広さを知る:「Unityデザイナーズバイブル」
- まずは、Unityでできる表現の幅広さを体験してみましょう!
- UIの作り方の記事もあるのでおすすめです。
- プログラミング寄りが好みなら、プログラミングバイブルもOKです
- プログラミング理解のためにC#を学ぶ:「なるほどなっとくC#入門」
- 特にプログラミングも一緒に始めたという人に向けておすすめです。
- UnityとC#を分けて考えられるようになりましょう!
- ゲームを面白くするためにゲームデザインを学ぶ:「ゲームプランとデザインの教科書」
- 理論の体型より具体的な例を重視してこちらを紹介します。
- まずは、自分の作ったゲームを面白くする手法を学んでみましょう。
上級者向け
上級編からは、様々なジャンルに分かれて様々な書籍があります。
この記事では、筆者の考えから学びやすいだろうという順番に並べていますが、本質的に順番はないと考えています。
順番を来にせず、興味があるジャンルから学んでいってください。そして…ここからは遠慮なく物量作戦です。
後半の分量の多さにびっくりさせてしまったかもしれません。
それだけゲームづくりとは複合的で総合的なモノづくりだと思っています。
だからこそ創ることが面白いのです。
それでは上級者への道のりを覗いてみましょう!ゲームメカニクス
ゲームメカニクスというのは幅広く様々なな意味を持つ単語なのですが、ここでは主にプログラミングやUnityでの操作を多く含む
ゲームの仕組みづくりというものをゲームメカニクスと表しています。Unityゲーム プログラミング・バイブル
Unity2018までで、できることを利用した、まさに機能のバイブル。
章ごとに完結しているので、気になった技術から試すことができます。使ったことがない機能を使ってみるという点でもおすすめ。
ただ、Unityは最近の進化が早いので、Unity標準でできることがAssetで紹介されていたりするものもあります(Anima2Dなど)。Unity デザイナーズバイブル
プログラミングバイブルが出版されたときの感想は上記の説明に加えて実は、「ProBuilderがあればベストだった。。」と思っていました。
が、まさにそのような声に応えるようなカタチで出版されたプログラミング・バイブルの姉妹編が出版されました!
デザイナーと名前にあるように、プログラマーだけじゃなく幅広い人にUnityの使い方を紹介してくれています。
こちらも素晴らしい書籍なのですが、一点だけ注意点があります。
それは、Amazonnoレビューでもある通り、誤植が多い点です。
ここまでいろんな書籍を通して作品を作っていれば自己解決できるレベルなのですが、再販が行われる際にはブラッシュアップされているとうれしいですね!ゲームの作り方 改訂版 Unityで覚える遊びのアルゴリズム
そろそろ、Unityの基本だけではなく、ゲームメカニクスの作り方も気になってきます。
実際ありそうなゲームサンプルにて以下のようなの実現方法を学ぶことができます。
- タッチ操作
- 迷路のCSVファイル管理
- 複数カメラの利用
- ミスタードリラー的ブロックゲーム
- シューティングのロックオン管理
サンプル的な意味合いが強いので、考え方を理解して自分で作ってみることをおすすめします!
ゲームデザイン
こちらでは、ゲームメカニクスに比べ、ゲームという仕組みそのものを面白くするための書籍を紹介します。
ゲームデザインという言葉はいろいろな使われ方をしますが、ここではまずはゲームをより面白くするためのゲーム全体の仕組みと考えてみてください。ゲームデザインバイブル
これまで、ゲームデザインを学びたいというときに紹介する本は迷っていることが多かったです。
ゼビウスの遠藤さんによるゲームデザイン講義実況中継や、オライリーによるレベルアップのゲームデザインなどを伝えてきましたが、2019年についにこの書籍が紹介されました。
バイブルという名前ゲームプランとデザインの教科書 ぼくらのゲームの作り方
この後出てくる、リーダブルコードの部分でも触れているのですが、日本の書籍と海外の書籍は、本というものの作り方が少し違うなぁと感じる時があります。
これは個人的な意見なのですが、日本の書籍の方がよりストーリー的でリスト的で、より具体的な印象を受けます。
ゲームデザインバイブルはシステマチックすぎるなぁと感じた人には、こちらの書籍を読んでみることをおすすめします。
後半にある実際のゲームクリエイターの方々によるインタビューとノウハウ紹介には、刺激を受けると思います。3Dゲームをおもしろくする技術 実例から解き明かすゲームメカニクス・レベルデザイン・カメラのノウハウ
こちらの本には、プログラミングは一切出てきません。
ただひたすらにデジタルゲームをゲーム設計としてのメカニクス(格闘ゲームのタイミング設計など)、レベルデザイン、カメラテクニックなどから
分析しています。このような書籍が他にないからこそ、そして実際のゲームが題材だからこそ自分のゲーム体験を省みることにも繋がります。ゲームメカニクス大全
ゲームという長い歴史の中で、デジタルゲームが生まれたのはほんの最近に過ぎません。
ゲームというものは、テーブルゲーム、ボードゲームと一緒に発展してきました。
この本では、そのようないわゆるアナログゲームに現れるメカニクスを網羅し分類に挑戦しているという意欲的な一冊です。おもしろいゲームシナリオの作り方 ―41の人気ゲームに学ぶ企画構成テクニック
ゲームデザインの中には、もちろんゲームのフィクションというものも含まれます。
この本は、その中でもゲームシナリオだけに注目している本です。
しかし、この中で出てくるヒーローズジャーニーという考え方は確実に学びにつながるのでそこだけでも読む価値があります。
そして、本当に様々な面白いゲームの面白いシナリオを紹介してくれるのであなたのインプットも広げてくれることでしょう。ゲームデザインについてその他の書籍
まだまだ紹介したい本はたくさんあるぐらい、ゲームデザインとは奥深いのですが一旦ここまで。
- HALF-REAL
- ビデオゲームとは何かを考える
- ゲームの企画書
- ゲームを作ったクリエイター達は何を考えていたのか
- 中ヒットに導くゲームデザイン
- この本の目的は原題にあらわれています。
- Game Design Workshop: A Playcentric Approach to Creating Innovative Games:革新的ゲームを創るためのプレイ中心アプローチ
- つまり、ゲーム作りのプロトタイピングについて考えられているものです。
プログラミングとして、C#を学ぶ
プログラミングは、深く知れば知るほど面白い世界です。
一緒にC#という言語と考え方を知っていきましょう。新・標準プログラマーズライブラリ なるほどなっとく C#入門
スラスラわかるC#
前述のなるほどなっとくなどで、クラスやポリモーフィズムなどを理解したら、もう少し深くC#について学んでみましょう。
匿名関数やデリゲートなど、プロパティなど、もう少し深ぼってC#を改めて知ること画できます。実戦で役立つ C#プログラミングのイディオム/定石&パターン
上記2冊でC#という言語を学んだら、一歩進んだC#の書き方を身に着けましょう。
ゲーム制作のための書籍ではないのですが、C#らしいコードを学ぶことができます。UniRx/UniTask完全理解 より高度なUnity C#プログラミング
上級以上のレベルになってしまうと思われますが、ゲーム制作で非常に便利な考え方はリアクティブプログラミングです。
Unityでは、実質的にUniRXが当てはまります。さらには、C#のTaskをUnity向けに拡張されたUniTaskについても合わせられた、とりすーぷ先生の書籍も紹介されてください。
(筆者もこれから読んで、まずは完全理解したと言えるようにがんばります。)コーディング一般
ゲームプログラマのためのコーディング技術
そろそろ、コードが煩雑になってきて管理が難しくなってくる頃。
リーダブルコードももちろんおすすめだけど、こちらのほうが実践的内容から始めるので初学者にはわかりやすいと考えています。
(また、リーダブルコードはTHEアメリカン書籍という体裁なため、普段本を読まない日本系の方だと読みづらいのではないかとも感じています。)この本では、命名の原則からオブジェクト指向のクラス設計などまで、実践的にプログラミングの設計を学ぶことができます。
特にゲーム制作に役立つ形でのSOLID原則のわかりやすい説明は多くの方に紹介したいです。プリンシプル オブ プログラミング3年目までに身につけたい一生役立つ101の原理原則
KISS?DRY?SOLID?YAGNI?疎結合??
などなどプログラミングを調べていると出てくる様々な先人たちの研鑽の結果を手早く知ることができます。
この本にはコードは出てきません。コードを書くプログラマーがどのように考えているのかを学びましょう。リーダブルコード
もちろん、リーダブルコードをおすすめさせてください。この本に関しては、様々な方が紹介をしているので、詳しくは調べてみてください。
命名についての項目などとても有益なことばかりが記載されています。アルゴリズムとデータ構造
ゲームを作っていると、Webアプリケーションやアプリ開発に比べて早い段階で、数多くのデータやオブジェクトを管理する、アルゴリズムを構築する
必要に迫られると感じています。
(もちろんWebアプリケーションなどでもありますが、ライブラリがとても有用なため中級者あたりまでは意識することが少ないと感じます)
そのため、どこかでしっかりアルゴリズムやデータ構造を学びたいとなった場合はこちらです。
そして、穴掘り法による迷路やA*によるその解法などを実装してみましょう!デザインパターン
さて、デザインパターンです。ここまで学習を進めていると、デザインパターンという言葉は様々なところで聞いたことがあるはずです。
デザインパターン自体は、建築家アレグザンダーのパターンランゲージという考え方をモノづくりに応用しようとする試みがあり、
そしてGoFによるオブジェクト指向における再利用のためのデザインパターンに繋がります。
(参考:Wikipedia)
デザインパターン自体の歴史が長くなってきていて今のゲーム開発に即時に活用できるかというと難しいのですが、生き残る古典というものにはやはり価値があると思います。増補改訂版 Java言語で学ぶデザインパターン入門
日本におけるデザインパターンの入門書としてはもはや右に出るものはないかと思います。
非常にわかりやすくデザインパターンを知ることができます。Javaで説明されていますが、基本の部分はC#とはほぼ一緒なので違和感なく読めます。Game Programming Patterns ソフトウェア開発の問題解決メニュー
デザインパターンを実際にゲームに応用するためにはという視点ではこちらの書籍を紹介させてください。
この本の前半では、書籍内でよく参照するデザインパターンのおさらいから始まるので、この本から読んでも頑張れば読み進めることができます。ゲーム数学、物理
ゲームを創る中では数学と物理が非常に多く出てきます。さらには、より高度なことをしたいと考えたらシェーダーというものを実装することもあるでしょう。
そのようなときのために、ゲームで使われる数学、物理、そしてゲームの動かされ方を知っておくことも有用です。ゲームアプリの数学 Unityで学ぶ基礎からシェーダーまで
数学や物理の説明を行っている書籍は様々なので、自分が読みやすいと思ったものを読み進めることが重要です。
この書籍では、実際にUnityを使って試すことができるので、手を動かしたいという方におすすめです。ゲームを動かす技術と発想R
ゲーム内のための数学だけではなく、Unityを使っているだけならなかなか触れることがない、
ゲームがそもそもどう動いているのかというより低レイヤー名部分までわかりやすく説明してくれています。Unityシェーダープログラミングの教科書 ShaderLab言語解説編
※こちらのみ、Boothです。
ゲームづくりをしている中で、演出を深めようとしたら現れる、シェーダーというもの。
シェーダーとはなにか、そしてどの様に創ることができるのかを知ることができます。
ゲーム演出
Unity ゲームエフェクト マスターガイド
GraphViewの基本的な使い方
1. EditorWindowを作成する
GraphViewを使うためにはまずEditorWindowを用意します
ScriptGraphView.cspublic class ScriptGraphWindow : EditorWindow { [MenuItem("Tool/ScriptGraph")] public static void Open() { ScriptGraphWindow window = GetWindow<ScriptGraphWindow>(); window.Show(); } }2. GraphViewを作る
GraphViewを作成します
※UnityEngine.UIElementsに依存した機能が各所に使われているのでusingミスに注意(1敗)ScriptGraphView.csusing UnityEngine.UIElements; using UnityEditor.Experimental.GraphView; public class ScriptGraphView : GraphView { public ScriptGraphView() : base() { // 親のサイズに合わせてサイズを設定 this.StretchToParentSize(); // ズームインアウト SetupZoom(ContentZoomer.DefaultMinScale, ContentZoomer.DefaultMaxScale); // ドラッグで描画範囲を移動 this.AddManipulator(new ContentDragger()); // ドラッグで選択した要素を移動 this.AddManipulator(new SelectionDragger()); // ドラッグで範囲選択 this.AddManipulator(new RectangleSelector()); } }ScriptGraphWindow.csprivate void OnEnable() { var scriptGraph = new ScriptGraphView(); this.rootVisualElement.Add(scriptGraph); }これでエディタのメニューから
Tools/ScriptGraph
を選んで開いてみます
なんもでません そりゃそうだ
このまま次のノード作成に行く前にちょっとおしゃれにしたいので寄り道します
エディタの背景が殺風景だと殺風景なシナリオしか出てこないんです
無駄じゃないんです2_a. 背景を付ける
Resourcesに以下のファイルを追加します
GraphViewBackGround.ussGridBackground { --grid-background-color: #282828; --line-color: rgba(193,196,192,0.1); --tick-line-color: rgba(193,196,192,0.1); --spacing: 20 }![Something went wrong]()ファイルを読み込んで背景を追加します
ScriptGraphView.cspublic class ScriptGraphView : GraphView { public ScriptGraphView() : base() { // 省略 // ussファイルを読み込んでスタイルに追加 this.styleSheets.Add(Resources.Load<StyleSheet>("GraphViewBackGround")); // 背景を一番後ろに追加 this.Insert(0, new GridBackground()); } }作成したussファイルをいじれば即反映されるのでカスタマイズも簡単です
3. Nodeを作る
次は作成したGraphViewに載せるノードを作ります
MessageNode.csusing UnityEngine; using UnityEngine.UIElements; using UnityEditor.Experimental.GraphView; public class MessageNode : Node { private TextField textField; public MessageNode() { // ノードのタイトル設定 this.title = "Message"; // ポート(後述)を作成 var inputPort = Port.Create<Edge>(Orientation.Horizontal, Direction.Input, Port.Capacity.Multi, typeof(Port)); inputPort.portName = "In"; inputContainer.Add(inputPort); var outputOort = Port.Create<Edge>(Orientation.Horizontal, Direction.Output, Port.Capacity.Single, typeof(Port)); outputOort.portName = "Out"; outputContainer.Add(outputOort); // メイン部分に入力欄追加 textField = new TextField(); // 複数行対応 textField.multiline = true; // 日本語入力対応 textField.RegisterCallback<FocusInEvent>(evt => { Input.imeCompositionMode = IMECompositionMode.On; }); textField.RegisterCallback<FocusOutEvent>(evt => { Input.imeCompositionMode = IMECompositionMode.Auto; }); this.mainContainer.Add(textField); } }これをGraphViewに追加してみます
ScriptGraphView.cspublic ScriptGraphView() : base() { // 省略 this.Add(new MessageNode()); }これで全てのノードをプログラムで追加しまくりのハードコーディングしまくりで
色々な賞も受賞しまくりです4. Nodeをエディタから作れるようにする
もちろん嘘なのでShaderGraphの右クリックで出てくるアレを作ります
ScriptGraphSearchWindowProvider.csusing System; using System.Collections.Generic; using UnityEditor.Experimental.GraphView; using UnityEngine; public class ScriptGraphSearchWindowProvider : ScriptableObject, ISearchWindowProvider { private SctiptGraphWindow _window; private ScriptGraphView _graphView; public void Init(ScriptGraphView graphView,ScriptGraphWindow window) { _window = window; _graphView = graphView; } public List<SearchTreeEntry> CreateSearchTree(SearchWindowContext context) { var entries = new List<SearchTreeEntry>(); entries.Add(new SearchTreeGroupEntry(new GUIContent("Create Node"))); entries.Add(new SearchTreeEntry(new GUIContent(nameof(MessageNode))) { level = 1, userData = typeof(MessageNode)}); return entries; } public bool OnSelectEntry(SearchTreeEntry SearchTreeEntry, SearchWindowContext context) { var type = SearchTreeEntry.userData as Type; var node = Activator.CreateInstance(type) as Node; // ノードの生成位置をマウスの座標にする var worldMousePosition = _window.rootVisualElement.ChangeCoordinatesTo(_window.rootVisualElement.parent, context.screenMousePosition - _window.position.position); var localMousePosition = _graphView.contentViewContainer.WorldToLocal(worldMousePosition); node.SetPosition(new Rect(localMousePosition, new Vector2(100, 100))); _scriptGraphView.AddElement(node); return true; } }これをScriptGraphView側で生成して設定します
ScriptGraphView.cspublic ScriptGraphView(ScriptGraphWindow window) : base() { // 省略 // 右クリックでノード作成するウィンドウ追加 var searchWindowProvider = ScriptableObject.CreateInstance<ScriptGraphSearchWindowProvider>(); searchWindowProvider.Init(this, window); this.nodeCreationRequest += context => { SearchWindow.Open(new SearchWindowContext(context.screenMousePosition), searchWindowProvider); }; }これで右クリックでノード生成できるようになりました
ここまでやって受賞しまくりです
4_a. Nodeを作るたびにメニューに追加するのは辛い
entries.Add(new SearchTreeEntry(new GUIContent(nameof(MessageNode))) { level = 1, userData = typeof(MessageNode)});
何度もこんなコードを書くのは辛いので少し楽にしますScriptGraphNode.cspublic class ScriptGraphNode : Node { }MessageNode.cspublic class MessageNode : ScriptGraphNode { // 省略 }ScriptGraphSearchWindowProvider.cspublic List<SearchTreeEntry> CreateSearchTree(SearchWindowContext context) { var entries = new List<SearchTreeEntry>(); entries.Add(new SearchTreeGroupEntry(new GUIContent("Create Node"))); foreach (var assembly in AppDomain.CurrentDomain.GetAssemblies()) { foreach (var type in assembly.GetTypes()) { if (type.IsClass == false) continue; if (type.IsAbstract) continue; if (type.IsSubclassOf(typeof(ScriptGraphNode)) == false) continue; entries.Add(new SearchTreeEntry(new GUIContent(type.Name)) { level = 1, userData = type }); } } return entries; }これでScriptGraphNodeを継承したNodeが自動でノード作成メニューに表示されるようになります
5. Nodeを繋ぐ
ノードのついているIn Out のポートを接続します
ポートの接続に関する条件付けができる関数がGraphViewに用意されているので
オーバーライドして条件を記載しますScriptGraphView.cspublic override List<Port> GetCompatiblePorts(Port startPort, NodeAdapter nodeAdapter) { var compatiblePorts = new List<Port>(); foreach (var port in ports.ToList()) { // 同じノードは繋げない if (startPort.node == port.node) continue; // Input - Input , Output - Outputは繋げない if (startPort.direction == port.direction) continue; // ポートタイプが違うものは繋げない if (startPort.portType != port.portType) continue; compatiblePorts.Add(port); } return compatiblePorts; }ノードが増えてきた頃に重要になりそうです
GraphViewの基本的な機能はここまでになります
なまじノードの拡張性が高いせいで保存、読み込みなどは各自で用意する必要がありますGraphViewで作ったデータの保存、読込
私のつまづきポイントのみの解説となります
冒頭に記載したgithubにコードがあるので詳細はそちらで確認お願いします手順としては以下になります
1.保存するデータ(ScriptableObject)を用意
2.編集がScriptableObjectに反映されるように
3.ノードのシリアライザ、デシリアライザを用意
4.ProjectウィンドウのScriptableObject選択でEditorWindowが開くように
5.開いた際にノードが生成されるように
6.ノードをつなぐエッジが保存されるように
7.ノードをつなぐエッジが生成されるように2.編集がScriptableObjectに反映されるように
保存するデータはこんな感じです
ScriptGraphAsset.cs[CreateAssetMenu(fileName = "scriptgraph.asset", menuName ="ScriptGraph Asset")] public class ScriptGraphAsset : ScriptableObject { public List<ScriptNodeData> list = new List<ScriptNodeData>(); }ScriptNodeData.cs[Serializable] public class ScriptNodeData { public int id; public NodeType type; public Rect rect; public int[] outIds; public byte[] serialData; }ノード作成時にスクリプタブルオブジェクトにデータを追加すればとりあえずの保存はできます
罠ですScriptGraphSearchWindowProvider.cspublic bool OnSelectEntry(SearchTreeEntry SearchTreeEntry, SearchWindowContext context) { // 省略 node.SetPosition(new Rect(localMousePosition, new Vector2(100, 100))); _scriptGraphView.AddElement(node); ScriptGraphData data = Serialize(node); // ここで追加しよう! _scriptGraphAsset.list.Add(data); return true; }なぜか、これでは正しいpositionが保存されません
_scriptGtaphView.AddElement()
と同じフレームでnodeのpositionを取得しようとすると
Rect(0,0,float.Nan,float.Nan)
が帰ってきます対策としてパッケージマネージャの
EditorCoroutine
などでノード作成の次のフレームで保存するように調整しましょう終わりに
かなりざっくりとですが今graphViewを扱う上で最低限の情報を書きました
実際にこのシステムでノベルゲームを作るとなると拡張が必要になりますが
拡張のためのサンプルもあるので(BranchNode)プログラマの人なら割と誰でも拡張ができると思いますストーリー、グラフィック、サウンド、さまざまな拡張に対応し、いずれノベルゲームが完成する
そんな未来を信じています
- 投稿日:2020-12-16T00:12:31+09:00
Maya の MeshSync を少し便利にする
はじめに
これは Maya Advent Calendar 2020 16日目の記事です。
小ネタかつ既知かもしれませんが、MeshSyncの導入や紹介の手引きをする上で気になっていた箇所がありましたのでまとめてみました。文中はかなりUnity色が強いです。ご了承ください。
MeshSync って何?
雑にまとめるとアーティスト作業にフォーカスしたプロセス間通信のUnity版です。アセットのクオリティはイテレーション回数に比例しますが、最近のスマホアプリ製作はUnity側で調整しながらアセット編集を行わずしてクオリティを上げることが出来ない要素が多分に増えたため、MeshSyncのようなリアルタイムプレビュー機能は必須です。
MeshSyncを用いた作業の導線を考えた時、
1. PlayMode中に修正したいオブジェクトを見つける
2. PrefabにあるFBXのパスを調べる
3. Mayaで上記のFBX(ma)を開いて、MeshSync関連コマンドの実行
の3ステップを踏む必要がアセット毎に毎回発生するので、これを1つにまとめたいと考えました。エディタ拡張スクリプト
Mayaの
userSetup.py
のような下準備を不要にしつつ、C#スクリプト1ファイルに機能をまとめてみます。一部ハードコードしている部分がありますので、ご自身の環境に読み替えてください。ポイントとして以下の条件で処理を分岐させています。
- Mayaが起動していない
- コマンドライン引数にMELを渡して起動+更新
- Mayaが起動している
commandport
に直接MELを渡して更新余談として、MeshSyncコンポーネントのプロパティをMayaに送りたいのですが、それらメンバーは
internal
です。アクセスする方法がなくもないのですが、それらを含めるとアドカレ内容の範囲を逸脱してUnityの方に書かないといけなくなりそうなので、ここでは省略します。
using System.Text; using System.Diagnostics; using System.Net.Sockets; using UnityEditor; using UnityEngine; public class RemoteControll { public static void Send(dynamic request) { using (var client = new TcpClient("127.0.0.1", 7001)) { var data = Encoding.UTF8.GetBytes(request); using (var stream = client.GetStream()) { stream.Write(data, 0, data.Length); } client.Dispose(); } } } [InitializeOnLoad] public static class SceneViewCameraTool { static SceneViewCameraTool() { SceneView.onSceneGUIDelegate += (sceneView) => { Handles.BeginGUI(); using (new GUILayout.VerticalScope(GUI.skin.box, GUILayout.Width(100))) { GUILayout.Label("Tool"); if (GUILayout.Button("MeshSync", GUILayout.Width(100))) { var app = "/Applications/Autodesk/maya2017/Maya.app/Contents/bin/maya"; var port = "if(`commandPort -q \\\":7001\\\"` == false) commandPort -name \\\":7001\\\";"; var cmd = "UnityMeshSync_Export;"; if (Process.GetProcessesByName("maya").Length > 0) { RemoteControll.Send( cmd + $"viewPlace -p -an true -eye {sceneView.camera.transform.position.x} {sceneView.camera.transform.position.y} {sceneView.camera.transform.position.z} -fov {sceneView.camera.fieldOfView} `lookThru -q`;"); return; } var selection = Selection.activeGameObject; if (selection != null) { var meshsync = GameObject.Find("MeshSyncServer"); if (meshsync == null) { EditorApplication.ExecuteMenuItem("GameObject/MeshSync/Create Server"); Selection.activeGameObject = selection; } var fbx = AssetDatabase.GetAssetPath( PrefabUtility.GetCorrespondingObjectFromOriginalSource(selection)); fbx = Application.dataPath.Replace("Assets", "") + fbx; var setup = $"evalDeferred \\\"UnityMeshSync_Settings -p 8080; {cmd}\\\" -lp;"; var args = $"-file \"{fbx}\" -hideConsole -nosplash -command \"{port} {setup}\""; var process = new Process { StartInfo = { FileName = app, Arguments = args, WindowStyle = ProcessWindowStyle.Hidden | ProcessWindowStyle.Minimized, CreateNoWindow = true } }; process.Start(); } } } Handles.EndGUI(); }; } }普段はデュアルディスプレイで作業されている方も多いと思いますが、キャプチャの都合上1画面で収録しました。SceneView上での拡張に関しては完全に個人的な好みです。
補足事項
UnityMeshSync_
~ コマンドはpluginがロードされた後になるので、evalDeferred
で遅延実行します。
viewPlace
コマンドはUnity側の画角を再現してみるために、おまけ程度に使ってみました。Prefabから直接FBXのパスを参照したりしていますが、実際のところ各社各プロジェクトによって対象を書き換える必要があるかと思います。
おわりに
内製エンジン環境だと(気の利く社内の開発者が作り込んでくれているので)あまり気にしていなかったことも、商用エンジンはそういう痒いところを自分で作って便利にするか、札束で叩いたアセットで解決するしかないです。
細かい処理でもめんどくさがらずにやっておくと、時間短縮になって後が楽になりますね。
TODO
- Cameraの同期
- Material参照
- Component付替
- 投稿日:2020-12-16T00:00:40+09:00
UnityでプラットフォームごとにUIの判定エリアを変える
この記事はクラスター Advent Calendar 2020の16日目です。前回はulaphさんによる「SwiftでViewの状態をenumで管理する」でした。C#からSwiftへ軽やかに転身していてカッコいい。
はじめに
こんにちは。クラスター株式会社でデザイナーとして主にUIデザインをやっています。
Unityでマルチプラットフォームに対応したUIを作る場合、同じにできる機構はなるべく共通になるよう努めます。とは言え、無闇矢鱈にすべてを共通化するわけではありませんし、プラットフォームごとの対応が簡単に出来るものは対処しておきたいです。マウスイベントやタップ判定などを行う判定のエリアもその1つです。
プラットフォームごとの判定エリアのサイズ
一般的にマウスカーソルデバイス……ここではPCですが、これのホバーやクリックを判定するエリアのサイズは、UIの見た目と一致していることがほとんどです。
一方スマートフォンでは操作する指よりもUIが小さいため、判定エリアのサイズは見た目以上のサイズになっているのが一般的です。例えばiOSのソフトウェアキーボードは入力する内容を予測して動的に判定エリアのサイズが変化しています。すごい。
参考: ソシオメディア | iPhone の当たり判定を検証した
入力を予測して……はさすがに困難を極めるので、プラットフォームごとでオン/オフする程度の簡単な対処を考えてみました。
Unity UIでボタンを組む
自分がUnityでよく組むタイプのボタンです。Unityのプリセットのようにコンポーネントを1つのオブジェクトにまとめ過ぎず、ビジュアルの責任も適度に分けたほうがデザインの柔軟性があり、かつメンテナンス性も高いです。
ボタンの判定エリアを広げる
操作時にフィードバックを返す
TargetGraphic
以下に、Imageコンポーネントをアタッチして、アルファ値を0にしたTargetArea
というゲームオブジェクトを配置しています。このオブジェクトのサイズをボタンよりも広げ、Raycast Targetをオンにすることで判定エリアを広げています。PCプラットフォームで
TargetArea
を削除するプラットフォームを判別する方法を調べると2つ出てきました。
2のRuntimePlatformで以下のよう書かれています。
プラットフォーム別コンパイルを使用するほうが、実行時にチェックする必要がないため軽くて高速なコードを生成できます。
使用しているデバイスが途中で変わる……などということは起きないので、スマートフォン向けに追加した
TargetArea
をPCの場合はプラットフォーム依存コンパイルを使って削除するのが簡単そうです。Unityが用意してくれている
UNITY_STANDALONE
を使います。囲んだコードが、すべてのPCプラットフォームでコンパイルに含まれます。DestroysSelfWhenStandalone.csusing UnityEngine; public class DestroysSelfWhenStandalone: MonoBehaviour { void Start() { #if UNITY_STANDALONE Destroy(this.gameObject); #endif } }プラットフォームを切り替えて実行してみる
PCプラットフォームに切り替えて実行した時のみ
TargetArea
が削除されました。まとめ
共通化はUnityの大きな利点の1つではありますが、プラットフォームに向けたこういうちょっとした最適化は今後も積み重ねていきたいものです。
心残りが1点。そもそも対象のゲームオブジェクトを環境ごとでビルドに含めない方法を取れれば良かったなと思います(スマートフォン側には虚無のスクリプトが残ることに)。自分で調べた範囲ではその方法は見つけられませんでした。残念。
次回は2tatuki4さんによる「ワールド制作でメモリが大変なことになった時に見るやつを書く」です。お楽しみに!