Visual Studio 2017 + ReSharper で Unity (C#) のスクリプトを書こうとして引っかかったこと ( namespace 編)
はじめに
普段 Unity のスクリプトを書くときは JetBrains Rider を使っているのですが、
Visual Studio + ReSharper だと IL Viewer があるし便利やな~、と思ったので、そちらも試してみることにしました。
ら、いくつか不便なところがあったのでメモっておくことにします。
課題
今回引っかかって直したかったのは以下です。
- VisualStudio からクラスを追加すると namespace が Assets.Scripts のように設定されてしまう
( Assets/Scripts より下の階層だけを namespace として扱いたい) - namespace から Assets.Scripts を削除すると、 ReSharper が Warning を出す
- ソリューションエクスプローラーからプロジェクトのプロパティが開かない
特定の namespace を無視する
実は解決したとは言い難いのですが。。。
VisualStudio の 追加 > クラス からクラスを追加すると、下記のようなファイルが追加されます。
using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace Assets.Scripts { class Class1 { } }
問題は「namespace Assets.Scripts」の部分で、 Assets/Scripts より下にある階層だけを namespace として扱いたい訳です。
が、その設定がわからずorz
後述しますが、プロジェクトのプロパティを開いて、「既定の名前空間」を確認しても空になっていました。
対処法
で、どうしたかというと、 追加 > クラス からクラスを追加するときに、
クラス ではなく CSharp MonoBehaviour を追加するようにしました。
そうすると、UnityEditor からファイルを追加したときと同じように namespace 無しの状態でクラスが作成されます。
その状態から namespace を必要に応じて追加するか、
もしくはあきらめて Assets.Scripts を削除する(白目)
ま、まぁそんなにクラスを追加しまくらないですよね?ね?
Rider では普通の C# クラスを追加しても Assets.Scripts を含まない namespace が挿入されるので、
もう少しちゃんとした解決方法が見つかったら追記します。
warning を止める
ReSharper をインストールしている場合、手動で namespace を変更したとしても「namespace Assets.Scripts になってませんよ?」と警告が出されます。
これを防ぐためには、ソリューションエクスプローラー上で Assets 、 Scripts のフォルダを選択し、 プロパティの ReSharper > Namespace Provider を False に変更します。
これで例えば Assets/Scripts/Sample というフォルダ構成だった場合、
namespace Sample としてくれます。
プロジェクトのプロパティを開く
UnityEditor から生成した .sln ファイルを Visual Studio 2017 で開くと、
なぜかソリューションエクスプローラー上のプロジェクト名で右クリック > プロパティを選択してもプロパティ画面が開きません。
なんぞこれ?としばらくググっていたのですが、どうもプラグインとして入れている Tools for Unity が原因のようです。
プロパティ画面を開くことができるようにするには、 ツール > オプション > Tools for Unity > 全般 > その他 で、
プロジェクト プロパティへのアクセス という項目があり、これを True に変更して Visual Studio を再起動すると無事開くことができるようになりました。
おそらく下手にいじると Unity 側で問題が起きる、ということなのかもしれませんが、
せめてメッセージぐらい出してくれても。。。(見落としてるだけかもですが)
おわりに
Unity のスクリプトを書くときは、 やはり Rider が使いやすいなぁとは思うのですが、
C# を書くという点では、 Visual Studio や ReSharper の方が歴史も長く、使いやすい場合もあります。
両方をうまく使いこなしてバリバリコードを書いていきたいところですね( *´艸`)
参照
UnityのAnimatorを今さらながら触ってみた
はじめに
まず懺悔からですが、これまでUnityでアニメーションを扱うとき、レガシーシステムのAnimationを使用していました(..)_。
とはいえそろそろこのままでもマズいってんで、Animatorを使ってみることにしました。
やりたいこと
- 3Dモデル(fbx形式でインポート)のアニメーションを再生・逆再生する
- アニメーションは3Dモデルに付与した状態でUnityにインポート
- uGUI のボタンを押したときにアニメーションを再生する
準備
まず3Dモデルを用意します。
今回はBlenderを使って、このあたりの情報を参考に簡単なアニメーションを付与しました。
インポートした3Dモデルは、 Animations タブで下記のようにフレームとアニメーション名を設定しておきます。
今回はデフォルトの状態(0 ~ 1フレーム)と、デフォルト位置から移動先への移動アニメーション(1 ~ 20フレーム)を設定しました。
Animator Controller
各アニメーションの設定は、 Animator Controller を使用することにします。
まずメニューの Create > Animator Controller で Animator Controller を作成します。
Animator ウインドウを開き、 Create State で State を3つ追加します。
その内の一つはデフォルトの状態を保持する State とします。
State を選択した状態で Inspector を開き、名前と Motion に先ほど3Dモデルに設定した StartPosition を指定します。
また、起動時に自動で実行されてほしいので、 Entry State の上で右クリック > Make Transition で、
この State に紐づけます。
オブジェクトを移動させるアニメーションは、それぞれ Motion に Moved を指定し、
逆再生(移動先から元の位置に戻す)は、 Speed を -1 に設定します。
今回は State の状態に合わせてアニメーションを再生するのではなく、
ボタン押下時に再生するので他の State には紐づけません。
アニメーションを再生する
アニメーションを再生するのは単純に Play("Animator Controller で設定した State 名") で実行可能です。
public Animator CubeAnimator; public void PlayMoveAnime() { CubeAnimator.Play("Move"); } public void PlayReturnAnime() { CubeAnimator.Play("ReturnToStart"); } public void ResetAnime() { // 再生中のアニメーションを一旦止める. CubeAnimator.enabled = false; // デフォルトのアニメーションを再生. CubeAnimator.Play("DefaultPosition"); CubeAnimator.enabled = true; }
- 一応デフォルトのアニメーションに戻す前にアニメーションを止めていますが、
今回の内容であれば無くても問題なく動作します。
アニメーションが終わったか確認する
レガシーシステムの Animation と違い、アニメーションが再生されているかを確認する IsPlaying などのメソッドは無いようです。
これを実現する方法はいくつかあるようですが、
今回は normalizedTime を見ることにしました。
CubeAnimator.GetCurrentAnimatorStateInfo(0).normalizedTime で、
アニメーションが再生中なら 0 ~ 1 未満、再生が完了していたら1以上の値が取得できます。
なお GetCurrentAnimatorStateInfo(0) の 0 は Animator Controller のレイヤー番号です。
今回はデフォルトのまま Base Layer を使用しているので、 0 を指定しています。
using System.Collections; using UnityEngine; public class AnimeController : MonoBehaviour { public Animator CubeAnimator; public void PlayMoveAnime() { CubeAnimator.Play("Move"); StartCoroutine(CheckIsAnimeFinished()); } public void PlayReturnAnime() { CubeAnimator.Play("ReturnToStart"); StartCoroutine(CheckIsAnimeFinished()); } public void ResetAnime() { // 実行中の Coroutine を止めているが効いてない? StopCoroutine(CheckIsAnimeFinished()); CubeAnimator.enabled = false; CubeAnimator.Play("DefaultPosition"); CubeAnimator.enabled = true; } private IEnumerator CheckIsAnimeFinished() { // Play 実行直後はアニメーションが再生されていないため、 0.3 秒待ってからチェック開始. yield return new WaitForSeconds(0.3f); while (true) { // normalizedTime が 1 以上ならチェック完了. if (CubeAnimator.GetCurrentAnimatorStateInfo(0).normalizedTime > 1f) { Debug.Log("Finished"); break; } // 0.1 秒ごとにチェックする. yield return new WaitForSeconds(0.1f); } } }
- ResetAnime() にて、実行中の Coroutine を止めていますが、
処理が止まるより先にアニメーション終了の処理が実行されてしまうため、もう一つフラグを追加するなどの対策が必要そうです。
終わりに
今回のようにシンプルな再生・逆再生を行うだけであれば、Animation より断然シンプルに描くことができて良いですね。
ただ、アニメーションの完了をチェックするなど直接チェックできない部分もあり、
もう少し慣れないとな~、という気持ちもあります。
Animator では uGUI なども触れるようなので、 Tween 系や UniRx などを使って実装していた GUI アニメーションでも使ってみようかと思います。
参照
Blender
Unity
C# 6の String interpolation で遊ぶ
はじめに
最近ついうっかり Effective C# Edition 3 を読み始めました。
その第4項目で、 string.Format() ではなく String interpolation を使いましょう、というものがありました。
C# 6.0 から、以下のように書けるようになったとのこと(なお Unity でも C# 6.0 を使用可能にすれば問題なく動作します)。
// 今まで. string outputTextLegacy = string.Format("Hello {0}", DateTime.Now.ToString("yyyy-M-d dddd")); Console.WriteLine(outputTextLegacy); // 6.0以降. string outputTextNew = $"Hello {DateTime.Now:yyyy-M-d dddd}"; Console.WriteLine(outputTextNew);
...うむ、後者を初見だと何やってるかわからないだろうな\(^o^)/
初見だとわからないなら何度も見れば良いじゃない
という声が聞こえましたので、ちょっと触ってみることにしました。
書き方
$"Hello {DateTime.Now:yyyy-M-d dddd}"
を紐解くと、頭に $ をつけて、文字列として置き換えたい部分を {} でくくり、
フォーマットの形式は : 以降に書きます。
なおフォーマット形式がない場合はデフォルトの形式で出力されます。
また : 以降はすべてフォーマットの形式を指定する文字列として扱われるため、 } との間にスペースなどを入れるとコンパイルエラーとなります。
試す
String interpolation 扱えるものは、先程の DataTime.Now のように値をそのまま書くことも可能ですし、
変数やメソッドも使用できます。
またIDEであれば入力補完もバッチリです( Visual Studio2017 + ReSharper 、 Rider で確認)。
変数を使う
double pi = Math.PI; // 出力結果: 3.14 string outputTextNew = $"{pi:F2}"; Console.WriteLine(outputTextNew);
メソッドを使う
static void Main(string[] args) { // Mainメソッドから呼び出しているためstaticにしているが、普通のメソッドでも使用可能. string outputTextNew = $"{GetMessage()}"; Console.WriteLine(outputTextNew); } private static string GetMessage() { return "Hello"; }
- ただし当然とも言えますが、戻り値が void のメソッドはコンパイルエラーとなり使用できません。
null 合体演算子を使う
Kotlinなどでいうエルビス演算子(Nullのときだけ ?? の右側の値が使用される)も使えちゃいます。
string test = null; var outputTextNew = $"{test ?? "text is null"}";
三項演算子を使う
三項演算子を使う場合は少しだけ注意が必要です。
string test = null; var outputTextNew = $"{(test == null ? "text is null" : DateTime.Now.ToString("yyyy-M-d dddd"))}";
- 上記のように、式を () でくくる必要があります。
Linqを使う
普通に使えるようです。
ただ、あんまりやりすぎると可読性、という面から厳しそうですが。
ListtextSample = new List { "text1", "text2" }; var outputTextNew = $"{textSample.Select(t => t).Count().ToString()}"; Console.WriteLine(outputTextNew);
async / await
当然ながら使えません。
{} のエスケープ
$ をつけている場合、 {} が全て String interpolation として扱われるため、
{} をそのまま表示するにはエスケープが必要になります。
$"Hello World! {{\"text is null\"}}";
その他注意点
これも Effective C# で取り上げられている内容ですが、これを使ってSQL文を組み立ててはいけません。
SQLインジェクションを避けるため、これまで通りプレースホルダーを使用する必要があります。
おわりに
今までの方法と比較して、 String interpolation を使うことでセットする文字列の、
順番や数の間違いなどを防げる、という利点に加えて処理がグッとシンプルに書けそうなので良いと思いました。
ただ途中でも書きましたが、あまりごちゃごちゃやり過ぎると見づらくなったりバグの原因になりそうなので、
ほどほどにしておくのが良さそうですね。
参照
dotnetConf 関西2017に参加してきました
- はじめに
- .NETの今と未来~デバイス&クラウドネイティブを目指して
- Visual Studio拡張とRoslynとDotNet.exeなどいろいろ
- .NET Standard入門
- Introducing Fluent Design System あるいは Metro の先を紡ぐ手がかり
- LT
- その他
- おわりに
はじめに
10/14に日本マイクロソフト関西支店で行われた、dotnetConf関西 2017に参加してきました。
4セッション + LT大会で、バラエティ豊かな内容を聴くことができました。
聴き逃しや勘違いなどもあるかとは思いますが、聴きながら書き留めたメモを中心に書き残しておきます。
(修正などはご指摘いただければ対応します)
またスライドは後から公開されたものがあればリンクを追加する予定です。
会場到着🤓 #dotnetConf pic.twitter.com/BUWSBoil16
— Optional<🍅> 増井将則 (@masanori_msl) 2017年10月14日
.NETの今と未来~デバイス&クラウドネイティブを目指して
dotnetConfについてや、セッションのタイトル通り15年前!に登場した.NETが今どう変わっているか、今後どのように進んでいくか、
ということが紹介されました。
2017/10/22更新
dotnetConf関西2017のものに差し替えました
www.slideshare.net
ここからメモ
dotnetConfについて
dotnetConfはオンラインが中心。ただし今回のようにローカルイベントも世界各地で行われている。
Twitterでの盛り上がりはマイクロソフト本社にも伝わる。そのためどんどんツイートしてほしいとのこと。
マイクロソフト社の活動としてはAzureがメインになってきている。
ただし.NETは.NETで変わらず活動していく。
.NET Framework、.NET Core
Windows上で動作する .NET Framework (現行ver.4.7)と、
マルチプラットフォームで動作する .NET Core (現行ver.2.0。ベースの一部はMono)。
これらは今後も統合されるようなものではなく、それぞれ進化していく。
ただし.NET Frameworkの資産を .NET Core、 Xamarin でも活用できるように、
共通化( .NET Standard ?)も進めていく。
実装は C# がメインであり、最初に実装されはするが、 VB.net 、 F# もしっかりサポートしていく( .NET Framework、 .NET Core 共に)
Xamarin
Xamarinには2種類の作り方がある。
- Xamarin Native -> UI部分はそれぞれのプラットフォームに合わせて作り、共通処理をひとまとめ
- Xamarin Forms -> UI部分から共通。ただしプラットフォームによって制限もあり?
Xamarin Live Player を使うと、 Visual Studio と iOS デバイスをペアリング -> Visual Studio で Xamarin.iOS のデバッグが可能。
ただし XCode でビルドした状態と全く同じ、というわけではないので最終出力する部分は Mac 、 XCode が必要。
.NET Standard
.NET Standard は、 .NET Framework 、 .NET Core 、 Xamarin で共通で利用できるコード。
ver.2.0で2万以上のコードが共通化された。
利用方法としては、共通化させたいコードを .NET Standard ライブラリとして切り出す -> ライブラリとして使用可能
( Nuget パッケージも同時作成)
Container + Docker
現在はCloud上にあらかじめ用意されている機能・サービスを活用していく Cloud Native が中心になってきた。
App service on Linux & Web App for Containers
今までベース部分のOSはWindowsだったが、Linux を使うものが登場した(App service on Linux)。
コンテナでランタイムを実装する -> コンテナさえ動くようにすれば、どこでも動かすことができるように
Write-once, Run-anywhere
Containerを使う上で必要なファイルも Visual Studio で生成・編集できるように。
Visual Studio 2017 では Docker を利用する機能が内包されている。
通常のビルドと同じような操作で Docker のビルドをすることができる。
Serverless
Azure Functions -> AWS の Lambda のような、Serverless を実現する機能。
Functions は Visual Studio で、 .NET 、 C# を使って開発可能。
動作するのはユーザーからトリガーを引くための操作が行われた場合。
ローカルで動かしている.NETコードを基本そのままクラウドに上げることができる
↓
よりモダナイズされた環境での開発・サービス提供を
ここまで
感想
.NET って本当に幅広いんだなぁ(こなみかん)
お話の中で特に気になったのは .NET Standard と Azure でした。
.Net Standard は、例えば自分の普段の作業では Unity と UWP で処理を共通化させる、といったことができそうです。
また、処理を共通化させることができる、という意味ではネイティブだけでなく、 Xamarin にも取り組んでみようかなぁ。
あと Azure はそれ自体も気になるところですし、 Functions も C# で書くのであれば、
AWS Lambda よりも情報を集めやすそうな気がします。
( AWS Lambda では C# がメインの言語ではないため、
他の言語の情報を変換する必要があると思うので)
お試し枠もあるようなので、こちらも近いうちに触ってみたいと思います。
Visual Studio拡張とRoslynとDotNet.exeなどいろいろ
Visual Studio の拡張機能を作成した時にハマったあれこれが紹介されました。
2017/10/22更新
dotnetConf関西2017のものに差し替えました
www.slideshare.net
ここからメモ
拡張:Extensibility -> VSIXプロジェクト
下位互換性をもたせたい場合も、最新(Max ver)のVSで作成する
右クリックでメニューを追加したい場合、Custom Commandを追加し、実装する
ファイル名がすべてCommand名と共通のものになるため、全体に関連するファイルなどはリネームしたほうが良い
例)
- RClickCommandPackage.vsct -> ExTest.vsct
- RClickCommandPackage.cs -> ExTestPackage.cs
ドッキングウィンドウの追加は Custom Command Window?を使う
VS2017でしか使えないパッケージが含まれていてVS2015で使った瞬間クラッシュした
↑
↓
VS2015で作るとVS2017にはインストールできない
関数の型を調べるためにリフレクションを使おうとすると、VSがdllを開きっぱなしにしてしまい、
ビルド時?にエラー(Warning?)が出るので拡張を作る上では問題がある
ここまで
感想
Visual Studio を使って Visual Studio の拡張機能を作成する、というのはまだツラいところがあるのだなぁ
(最新版の Visual Studio が出るたびにビルドし直す必要があったり、プロジェクトのテンプレートにゴミが入っていてエラーになったり)
というのが正直な感想ではあります(´・ω・`)
VS2017でしか使えないライブラリが含まれていてVS2015で使った瞬間クラッシュした(Nugetを使って該当ライブラリを削除)
— Optional<🍅> 増井将則 (@masanori_msl) 2017年10月14日
↑
↓
VS2015で作るとVS2017にはインストールできない
(´・ω・`)#dotnetConf #dotnetkansai
ただ、ここも今後改善されてはくるのだろうと思いますし、
やっぱり自分の使い方に合わせて機能を追加していけるのは良いと思うので、自分としても挑戦はしてみたいなぁ、とも思いました。
また、紹介されていた拡張機能も試してみたいところ。
.NET Standard入門
最初のセッションでも紹介された、 .NET Standard についてガッツリ紹介されました。
ここからメモ
.NET Standard は、 .NET Framework 、 .NET Core 、 Xamarin のどれかだけにべったり依存したものではない。
一般的には可能な限り最小バージョンの .NET Standardを使うのが推奨される。
最適なバージョンは Visual Studio 側で静的に見てくれるわけではないため、
バージョン指定 -> コンパイル -> エラーが出たらバージョンを上げる
という作業を繰り返す必要がある。
実際にはライブラリを作るときに.NET standardが利用される。
- Shared library - Assemblyを共有しない。共有プロジェクト(ソースコードレベルの共有)
- PCL - 今はあまり使われていない
- .NET Standard - Assemblyを共有
今後は .NET Standard を使うことが推奨される?
PCL は .NET Standardの各バージョンと互換性がある。
足りないものはNugetでとってきて解決する。
Visual Studio 2017 Update4 からは .NET Standard2.0 のインストールをしなくても使える、らしい。
dnSpyでデコンパイル
(dnSpyについてはこの辺が参考になりそう?)
https://qiita.com/NetSeed/items/54fbf30cb21c77c05c41
dllでは型が定義されているのではなく、 TypeForwardedTo でアトリビュートで指定されているだけ
同じString型でも、 Windows 、 Android などターゲットに応じて全く別のクラスを参照している
(違いは.NET Coreが吸収する)
最終的には .NET Core の Library を参照するわけだが、そこまでの過程はプラットフォームごとに異なっている。
Nuget Package を参照したクラス?ライブラリを参照した Console App を実行するとエラーでクラッシュする ↓ 実行時に必要なライブラリもファイルを移動しないため
ValueTuple は .NET Framework4.7 から使用できる。 ただし、4.6の場合も .NET Standard で定義してそれを読み込むと.NET Framework からでも使用できる。
ただし、dllファイルを大量に引き連れてきてしまうので注意。
ここまで
感想
自分の実力的に理解しきれなかったところがたくさんあった、
というのが正直なところですが、公開された資料やお話の中にあったキーワードを元に、
中身を追いかけていこうと思います。
また、ググった感じでは Unity で .NET Standard のライブラリを作る、利用する、といった情報は少ないようですが、
こちらも試したいところ。
Introducing Fluent Design System あるいは Metro の先を紡ぐ手がかり
スキューモーフィズムから Metro (Modern UI)を経て、
Hololens などの Mixed Reality などにも対応する、
Fluent Design について紹介されました。
2017/10/22更新
スライドのリンクを追加しました
www.slideshare.net
ここからメモ
スキューモーフィズム -> Metro -> UWP(PCやスマホだけでなく、ラズパイやHololens)にも対応
Hololensの登場により、UIが画面の外に出てくるようになった
Fluent Design * Project Neon Fall Creates Updateで利用可能 * Light、Depth、Motion、Material、Scaleの5つの要素 * コンテンツが繋がっていくデザイン * 3D時代のWindowsのためのデザインシステム * フラットでもなく、スキューモーフィズムでもない、物質の本質を表現する
XamlでAcrylicBrush要素を使うことでアクリルブラシが使用可能に (アクリル板のように半透明になるイメージ)
Lightはマウスカーソルを光源?としてボタンなどにライトを当てることができる
MR Design Lab: UIサンプル
色遣いも決まりがある。
半透明とはいえ、Vista時代のものとは意味が異なっている
ここまで
感想
Fluent Design については、一番普段の仕事に近いのに全く知らなかったため、
早くキャッチアップせねば、という気持ちです(;´Д`)
最近(というほどでもないけど)登場したデザインとしては他に Material Design がありますが、
テイストなどは異なるものの、紙や光など自然界にあるものを表現する、
という部分に共通点があるのかな、などとぼんやり思いました。
LT
本編も濃ければLTも濃い。
あまり書き留めることはできませんでしたが、どれも聴きどころ満載な感じでしたw
ここからメモ
XAML条件分岐
今までは XAML の中で条件分岐を表現するのは難しかった
現在は Conditional XAML を使うことで、条件分岐が使えるようになった。
が、制限はあり、C#でifを書くようには使えないっぽい
Xamarin
Xamarin NativeでもMVVMCrossなどを使うことでDataBindingが使える
Azure Machine LearningでCNNを使って二次元の顔判定
Azure Machine LearningではGUIで機械学習ができる
学習に24時間以上かけられない、という制限がある
その他
なお懇親会にも参加させてもらいましたが、
持ち前のコミュ障を発揮してあまりお話することができずorz
ま、まぁこの辺は次回リベンジということで。
あとお話を聴いているだけでも楽しかった、ということは付け加えておきます。
おわりに
実は .NET 関連の勉強会は初めての参加でしたが、
参加されている方や雰囲気もどことなく違っていて新鮮でした。
.NET Standard や Azure など、気になる情報もたくさん得られたので、
時間はかかるかもですが一つずつ試してみようと思います。
スタッフの皆様、参加された皆様、会場やWifiなどを提供いただいたマイクロソフト様、ありがとうございました!
最後に...
Write once run anywhereは滅びんよ、何度でも甦るさ。それが人々の夢だからだ!
— Optional<🍅> 増井将則 (@masanori_msl) 2017年10月14日
【C#】Taskをキャンセルする
はじめに
前回に引き続き、async / await / Task ネタです。
非同期で処理している内容をストップするにはどうするの?というお話。
Task.Runの中で処理を止める
Task.Run の中で、条件に応じて処理を途中で止める、というのは比較的簡単です。
private async void CallAsyncMethod() { var myTask = await GenerateTextAsync(); // GenerateTextAsync() の処理が完了したら実行される. Debug.Log(myTask); } private async Task< string> GenerateTextAsync() { return await Task.Run< string>( () => { if(何かの条件) { return "Canceled"; } Thread.Sleep( 3000 ); const string sampleText = "Hello\nworld\nおはようございます!\n"; return sampleText; } ); }
普通の処理と同じく、早期リターンするか後の処理を if でくくって Task を完了させてしまえば良いですね。
Task.Runの外から処理を止める
では Task.Run の外側から処理を止めるにはどうするか。
感覚的には Task.Cancel とかすれば良い気がしたのですが、そのようなメソッドはありません。
方法としては、
1. Task.Run の第二引数として、 CancellationToken という Token を渡します。
2. Task.Run の外側(処理を止めたいタイミングで) CancellationToken.Cancel() を実行します。
3. Task.Run のなかで CancellationToken.IsCancellationRequested が True かどうかを確認し、 True なら処理を止めます。
using UnityEngine; using UnityEngine.UI; using System.Threading; using System.Threading.Tasks; public class MainController : MonoBehaviour { public Text ResultText; private int flag; private void Start () { // 非同期処理をCancelするためのTokenを取得. var tokenSource = new CancellationTokenSource(); var cancelToken = tokenSource.Token; CallAsyncMethod(cancelToken); Debug.Log("Start()"); // 処理をキャンセル. tokenSource.Cancel(); } private async void CallAsyncMethod(CancellationToken cancelToken) { var myTask = await GenerateTextAsync(cancelToken); Debug.Log(myTask); } private async TaskGenerateTextAsync(CancellationToken cancelToken) { var context = SynchronizationContext.Current; return await Task.Run ( () => { Thread.Sleep( 1000 ); if (cancelToken.IsCancellationRequested) { Debug.Log("Canceled"); // キャンセルされたらTaskを終了する. return "Canceled"; } const string sampleText = "Hello\nworld\nおはようございます!\n"; Thread.Sleep( 2000 ); context.Post((state) => { ResultText.text = sampleText; }, cancelToken); return sampleText; }, cancelToken); } }
回りくどい気もしますが、 どのような処理を行っているかを確認せず、いきなり強制終了するな、ということなのではないでしょうか。
Token を、 GenerateTextAsync を呼んでいる CallAsyncMethod() ではなく、さらに上流となる Start() で生成・渡しているのは、
前回書いた通り await を使って非同期処理を呼ぶと、それ以後の処理は非同期処理が完了するまで実行されないためです。
なお、処理がキャンセルされたときに例外を投げたい場合は、 cancelToken.ThrowIfCancellationRequested() を使ってシンプルに書くことができます。
if (cancelToken.IsCancellationRequested) { Debug.Log("Canceled"); // キャンセルされたら OperationCanceledException を投げる. cancelToken.ThrowIfCancellationRequested(); }
注意すべき点としては、キャンセルされたときに処理を止める、という処理は自分で書かないといけない、という点です。
あくまでもキャンセルされたことが Task.Run の中で判別できるようにする、という仕組みのようなので。
おわりに
Task がキャンセルされうる、というのは引数に Token を渡すことで判断はできるのですが、
本当にキャンセル処理が含まれているか、というのは静的に確認できると良さそうな気はしました
(とはいえキャンセルフラグが立ったときに、必ずしもその場で処理をストップするとは限らないので難しいかもですが)。
まだちゃんと調べられてはいませんが、 Task は別の Task とまとめたりもできるようなので、
見通しが悪くならない程度に細分化して、必要な処理が揃っているかを確認する、というのが良いのかもしれませんね。
参照
【C#】Unityでasync / await
はじめに
Unity2017以降、C#6の機能を使うことができるようになりました。
今回のテーマである async / await はモバイル環境だとまだ課題があるようですが、
とにかく触ってみることにしました。というお話。
準備
Unity2017.1.1f1 時点では、 C#6 (というか .net 4.6)はデフォルトでは有効になっておらず、
Experimental という扱いになっています。
有効にするには、 Player Settings > Other Settings > Configuration > Scripting Runtime Version を、
Experimental(.Net 4.6 Equivalent) に切り替えて、 Unity Editor を再起動します。
非対応
…とここまでワクテカしながら読み進めてきた方がいましたら残念なお知らせなのですが、
Unity のクラス(というか MonoBehaviour を継承しているクラス)のメソッドは、
今まで通り MainThread 以外からの実行はできません。
後述しますが、 SendMessage や ExecuteEvents.Execute も、定義しているクラス自体は MonoBehaviour を継承していないものの、
別 Thread から実行するとエラーになります。
(おそらく最後、指定したメソッドを実行するところでエラーになっているのではないかと思います)
ということで、ファイルの読み込みや HttpClient など、時間のかかる、かつ C# の機能を使って行われる処理に対して使うことができる状況です。
なお UniRx と組み合わせるとより便利になりそうではあるのですが、
今回はそのまま使ってみることにします。
とにかくやってみる
ということでまずは使ってみましょう。
なお async / await / Task の使い方は参照のリンク先におまかせします(丸投げ)。
using UnityEngine; using UnityEngine.UI; using System.Threading; using System.Threading.Tasks; public class MainController : MonoBehaviour { public Text ResultText; private void Start () { CallAsyncMethod(); // CallAsyncMethod() を呼び終わったら(処理の完了を待たずに)実行される. Debug.Log("Start()"); } private async void CallAsyncMethod() { var myTask = await GenerateTextAsync(); // GenerateTextAsync() の処理が完了したら実行される. Debug.Log(myTask); } private async Task< string> GenerateTextAsync() { // 非同期処理を定義して返す. return await Task.Run< string>( () => { // 三秒間だけ待ってやる. Thread.Sleep( 3000 ); const string sampleText = "Hello\nworld\nおはようございます!\n"; Debug.Log("GenerateTextAsync()"); return sampleText; } ); } }
- Task
を返す GenerateTextAsync() を呼び出すとき、 await をつけ忘れて myTask.Result とかしてしまうと、
Unity Editor がフリーズするので気をつけましょう。 - Debug.Log が実行される順番は 1. Start() 2. GenerateTextAsync() 3. myTask ( GenerateTextAsync() の戻り値)です。
- Debug.Log は Unity の処理ですが、別 Thread から呼び出しても特にエラーにはなりません。
async の Unity 定義メソッドへの適用
await をつけて GenerateTextAsync() を呼ぶためには、そのメソッドに async をつける必要があります。
これは Start() など Unity のメソッドにも適用できます。
ただし、非同期処理を呼んだ後の処理は(例: CallAsyncMethod() の Debug.Log() )、それが完了するまで呼ばれないため、
上記のように別のメソッドに切り分けたほうが良いと思います。
TaskからMainThreadを呼ぶ
さて、前述の通り、 GenerateTextAsync() の Task.Run< string> の中で ResultText (UnityEngine.UI.Text) のテキストを変更することはできません。
Task からこのような処理を行うにはどうすれば良いでしょうか。
Androidには別 Thread から UIThread を呼び出す方法があります。
mainActivity.runOnUiThread(new Runnable() { @Override public void run() { // MainThread での処理. } });
これと同様の仕組みが、 C# にも存在します。
using UnityEngine; using UnityEngine.UI; using System.Threading; using System.Threading.Tasks; public class MainController : MonoBehaviour { public Text ResultText; private void Start () { CallAsyncMethod(); } private async void CallAsyncMethod() { var myTask = await GenerateTextAsync(); } private async Task< string> GenerateTextAsync() { // MainThreadのコンテキストを取得. var context = SynchronizationContext.Current; return await Task.Run< string>( () => { Thread.Sleep( 3000 ); const string sampleText = "Hello\nworld\nおはようございます!\n"; // MainThreadのコンテキストを通して、Textを変更する. context.Post((state) => { ResultText.text = sampleText; }, null); return sampleText; } ); } }
SynchronizationContext を使って MainThread のコンテキストを取得することで、
別 Thread から Unity の( MainThread からの実行が必要な)処理を実行することができます。
おわりに
C#6 の機能が使えるようになったことで、細かい部分でも色々便利になって良いですね☺
async / await は、将来的に Unity の処理にも使うことができるようになるのかはわかりませんが、
時間のかかる処理を行う場合には是非とも活用していきたいところです。
とりあえず非同期処理のかんたんなところに触れてみましたが、
もう少し突っ込んで触ってみたくもあります。
ということで、次回に続く。。。かもしれない。
参照
【C#】横書き -> 縦書き変換
はじめに
某Twitterにてちょっと話題になった、テキストを縦書きで投稿するためのメモです。
今回はテキストを固定にしていますが、Unity、WPFなどで入力元、出力先を指定してやれば動的に生成したり、
Twitterなどにそのまま投稿することもできます。
縦に書く
TextViewなどの機能を使う場合は別ですが、
文字列を縦に並べるには単純に文字列を一文字ずつに区切って、それを縦に並ぶよう置き換えるのが良さそうです。
なお列の区切りは改行文字とします。
まずは一文字ずつに区切り、ついでに全角・半角文字が混ざると位置がずれてしまうので、半角文字の場合はスペースを追加することにします。
// 元のテキスト. const string sampleText = "Hello\nworld\nおはようございます!\n"; // 改行文字で区切る. var splittedSamples = sampleText.Split('\n'); // ToCharArray()で一文字ずつ切り出し -> charからstringに戻す -> 半角文字にスペース追加. var results = splittedSamples.ToList() .Select(samples => samples.ToCharArray() .Select(sample => sample.ToString()) .Select(sample => Regex.Replace(sample, @"[a-zA-Z0-9|\.\-\\\,]", " " + sample)) .ToList()).ToList();
これで一文字ずつ切り出して、一行ずつにまとめた List< List< string>> の値となります。
なお、長音の縦横置き換えなどは行っていないため、必要に応じて追加してください。
あとはこれを頭から並べていきます。
const string sampleText = "Hello\nworld\naaa\nbbb\n"; var splittedSamples = sampleText.Split('\n'); var results = splittedSamples.ToList() .Select(samples => samples.ToCharArray() .Select(sample => sample.ToString()) .Select(sample => Regex.Replace(sample, @"[a-zA-Z0-9|\.\-\\\,]", " " + sample)) .ToList()).ToList(); var resultMaxCount = results.Select(samples => samples.Count).Max(); var resultText = "\n"; for (var i = 0; i < resultMaxCount; i++) { for (var j = results.Count - 1; j >= 0; j--) { // 列の間に全角スペースをはさむ. resultText += " "; // その列に文字がなかった場合は全角スペースに置き換え. resultText += (results[j].Count - 1 >= i) ? results[j][i] : " "; } resultText += "\n"; }
せっかくなのでこちらも Linq で解決したいところでしたが、
良い方法が思いつかなかったのでfor文を使うことにしました。
また、Twitterに投稿する場合に、一行目の最初の文字より前に入っているスペースが取り除かれてしまうようです。
それを防ぐため、頭に改行を挟んでいます。