- 投稿日:2019-02-11T21:10:13+09:00
オブジェクトをジグザグに動かしてみる
はじめに
スーパーマリオ・ソニック等アクションゲームに代表されるものに、物体・地形が動くものがほぼまちがいなくある。
その中でも基本となる上下に動くものを今回作成すると同時に、2種類の方法を用いて挙動の違いを調べてみる。方法1 Mathf.Sinメソッド
3座標のうち
・2座標を動かす:円運動
・1座標を動かす:ジグザグpublic class CubeLR : MonoBehaviour { float x; //三角関数の数値設定 float speed = 3f; //スピードの数値設 float radius = 0.1f; //半径の設定 // Update is called once per frame void Update () { x = radius *Mathf.Sin(Time.time * speed); //三角関数による動きの設定。 //X座標のみ三角関数による動きの設定を反映 transform.position = new Vector3(x+transform.position.x,transform.position.y,transform.position.z); } }方法2 PingPongメソッド。
PingPong(Time.time(移動スピード),3(移動距離));
public class test : MonoBehaviour { // Start is called before the first frame update void Start() { } // Update is called once per frame void Update() { transform.position = new Vector3(Mathf.PingPong(Time.time,3),transform.position.y,transform.position.z); } }結果
青Cube : PingPong
緑Cube : Mathf.Sin感想
両方ともほぼ同じ動きになるが、PingPongは速度、距離の設定がとても簡単である。
わざわざMathf.Sin関数を使うメリットは今のところ感じられません。
- 投稿日:2019-02-11T19:11:59+09:00
C#による画像処理の速度についての比較検証
1. はじめに
この記事は、C#による画像処理の速度について比較している記事になります。
C#のフレームワークである「.NET Framework」における「GetPixel」「SetPixel」は一般的に速度が遅いと言われているため、実際に、エッジを検出する画像処理のアルゴリズムで、①と②を比較してみました。
① C# unsafeコード使用のアルゴリズム
② .NET FrameworkのGetPixelとSetPixel使用のアルゴリズムC# unsafeとは、ざっくりC言語のポインタを使えるようにすることです。
ポインタを使うことで、速いアプリを開発することが可能になります。
C# unsafeはメモリを破壊する恐れがあり推奨されていません。画像処理などの重い処理で速度がでない箇所で
最終的な手段としての使用がお勧めかと思います。2. 結果
まずは結果からです。unsafeコードの方が早く、約32倍の速度がでました。
測定時間は下記の処理の総時間です。
・画像からPixelデータを取得する
・Pixelデータにエッジ検出用のフィルタをかける
・画像にPixelデータを設定する②を①と全く同じアルゴリズムにしたら、200倍ほどの速度差になってしまったため、
②については、ある程度速度がでるように調整しています。
① unsafeコード ② .NET Framework 170 ms 5517 ms ※検証対象の画像解像度:フルHD(1920×1080)
おまけ結果画像
今回、エッジを検出するアルゴリズムの精度は気にしていなかったが、そこそこエッジを検出できています。タイルの線と、輪郭が検出できています!!!
自販機のKIRINの文字も検出できてます!!!
時計の数字が検出できていて、これもなかなかいい感じです!!!
3. 成果物
成果物のソースコードはGitHubにアップしております。ご自由に使用ください。
GitHub:https://github.com/toshinomi/ImageProcessingSpeedComp
開発環境 言語 フレームワーク アプリ種類 Visual Studio 2019 Preview 2 C# .NET Framework 4.6 Windows From ※サードパーティ製品ライブラリは未使用で、C#だけでアルゴリズムを書いているため
他のバージョンのVisual Studioでも、コンパイルできます。
※今回のアプリは比較的重い処理のため、下記の工夫も入れています。説明は省略させて頂きます。
・画像処理部分はタスクの非同期処理で行い、画面が固まらないようにしています。
・非同期処理から画面の更新をしています。
・非同期処理を画面から停止できるようにしています。「File Select...」ボタン:画像を選択します
「All Clear」ボタン:画面表示全てをクリアします
「Filter Start(Unsafe)」ボタン:①のアルゴリズムを実行します。
「Filter Start(Normal)」ボタン:②のアルゴリズムを実行します。
「Stop」ボタン:①または②実行中の処理を停止させます。
「View」チェックボックス:処理の進捗を表示させます。4. C# unsafeについて
C#やJava(JVM系)のプログラム言語は、コンピュータのメモリ上の任意の場所に自由にアクセスする手段(ポインタ)の利用が禁止または制限されています。
ポインタは非常に便利な機能である反面、システム開発においてバグの原因になりやすく、大きな弊害になっており、昨今のプログラミング言語はポインタがないものが多くなりました。
ただし、C#に関しては、C言語、C++との相互運用性や、プログラムの実行効率向上のために、ポインタが残っています。C#でポインタを使用するには、下記の手順が必要になります。ポインタがあるのは大変ありがたいです!!!
・unsafeキーワードで囲むunsafe { // ポインタなどの処理 }・コンパイル時に、/unsafe オプションを付ける
※Visual Studio 2019 Preview 2 の場合、赤枠で囲っているところにチェックを入れます。
5. アルゴリズム比較
アルゴリズムについては大差ありません。unsafeで囲って、画像のPixelデータのアドレスを取得して
フィルタ処理後に計算値を設定するだけです。これだけで32倍速になるなら試す価値があると思います。① unsafeコード
FormMain.cs// unsafeで囲みます unsafe { for (nIndexHeight = 0; nIndexHeight < nHeightSize; nIndexHeight++) { for (nIndexWidth = 0; nIndexWidth < nWidthSize; nIndexWidth++) { // バイト型のポインタで、画像のPixelデータのアドレスを取得しています。 byte* pPixel = (byte*)bitmapData.Scan0 + nIndexHeight * bitmapData.Stride + nIndexWidth * 4; double dCalB = 0.0; double dCalG = 0.0; double dCalR = 0.0; double dCalA = 0.0; int nIndexWidthMask; int nIndexHightMask; int nFilter = 0; while (nFilter < m_nFilterMax) { for (nIndexHightMask = 0; nIndexHightMask < nMasksize; nIndexHightMask++) { for (nIndexWidthMask = 0; nIndexWidthMask < nMasksize; nIndexWidthMask++) { if (nIndexWidth + nIndexWidthMask > 0 && nIndexWidth + nIndexWidthMask < nWidthSize && nIndexHeight + nIndexHightMask > 0 && nIndexHeight + nIndexHightMask < nHeightSize) { // フィルタ処理のために、バイト型のポインタで、画像のPixelデータのアドレスを取得しています。 byte* pPixel2 = (byte*)bitmapData.Scan0 + (nIndexHeight + nIndexHightMask) * bitmapData.Stride + (nIndexWidth + nIndexWidthMask) * 4; dCalB += pPixel2[0] * m_dMask[nIndexWidthMask, nIndexHightMask]; dCalG += pPixel2[1] * m_dMask[nIndexWidthMask, nIndexHightMask]; dCalR += pPixel2[2] * m_dMask[nIndexWidthMask, nIndexHightMask]; dCalA += pPixel2[3] * m_dMask[nIndexWidthMask, nIndexHightMask]; } } } nFilter++; } // ポインタなので、SetPixelのようなメソッドを呼ばなくて、そのままフィルタ処理後の値を設定できます。 pPixel[0] = DoubleToByte(dCalB); pPixel[1] = DoubleToByte(dCalG); pPixel[2] = DoubleToByte(dCalR); pPixel[3] = DoubleToByte(dCalA); } } m_bitmapImageFilter.UnlockBits(bitmapData); }② .NET Framework
FormMain.csfor (nIndexHeight = 0; nIndexHeight < nHeightSize; nIndexHeight++) { for (nIndexWidth = 0; nIndexWidth < nWidthSize; nIndexWidth++) { byte bytePixelB; byte bytePixelG; byte bytePixelR; byte bytePixelA; double dCalB = 0.0; double dCalG = 0.0; double dCalR = 0.0; double dCalA = 0.0; int nIndexWidthMask; int nIndexHightMask; int nFilter = 0; while (nFilter < m_nFilterMax) { for (nIndexHightMask = 0; nIndexHightMask < nMasksize; nIndexHightMask++) { for (nIndexWidthMask = 0; nIndexWidthMask < nMasksize; nIndexWidthMask++) { if (nIndexWidth + nIndexWidthMask > 0 && nIndexWidth + nIndexWidthMask < nWidthSize && nIndexHeight + nIndexHightMask > 0 && nIndexHeight + nIndexHightMask < nHeightSize) { // Pixelデータの取得はフィルタ処理中でなく、事前に取得しています。 // ここでGetPixelするとオーバーヘッドが凄すぎて断念しました... byte bytePixel2B = m_pixelData[nIndexWidth + nIndexWidthMask, nIndexHeight + nIndexHightMask, (int)Pixel.B]; byte bytePixel2G = m_pixelData[nIndexWidth + nIndexWidthMask, nIndexHeight + nIndexHightMask, (int)Pixel.G]; byte bytePixel2R = m_pixelData[nIndexWidth + nIndexWidthMask, nIndexHeight + nIndexHightMask, (int)Pixel.R]; byte bytePixel2A = m_pixelData[nIndexWidth + nIndexWidthMask, nIndexHeight + nIndexHightMask, (int)Pixel.A]; dCalB += bytePixel2B * m_dMask[nIndexWidthMask, nIndexHightMask]; dCalG += bytePixel2G * m_dMask[nIndexWidthMask, nIndexHightMask]; dCalR += bytePixel2R * m_dMask[nIndexWidthMask, nIndexHightMask]; dCalA += bytePixel2A * m_dMask[nIndexWidthMask, nIndexHightMask]; } } } nFilter++; } bytePixelB = DoubleToByte(dCalB); bytePixelG = DoubleToByte(dCalG); bytePixelR = DoubleToByte(dCalR); bytePixelA = DoubleToByte(dCalA); // フィルタ処理後に、SetPixelで画像のPixelデータを設定しています。 m_bitmapImageFilter.SetPixel(nIndexWidth, nIndexHeight, Color.FromArgb(bytePixelA, bytePixelR, bytePixelG, bytePixelB)); } }6. まとめ
unsafeコードが圧倒的に早く(32倍速)、速度問題に対しての有効な解決手段の一つであることが示せました。
C#はマルチプラットフォームの言語の一つで、Web(ASP.NET)、Android、iOS(Xamarin)、
デスクトップ(.NET Framework)、ゲーム(Unity)などの広範囲に使用されており、
速度問題にあたる可能性が高い言語なので今後も有効な手段を模索していきたいと思います。以上です。
- 投稿日:2019-02-11T16:06:56+09:00
Subscribe内でInputクラスを使ってはいけない
- 投稿日:2019-02-11T16:06:56+09:00
Subscribe内でInputクラスを使ってはいけないケース
//追記(2019/2/11 17時頃)
設定を変更したところ、問題なく動作しました。
そのため改めて書き直しました。内容
Observable.Internal内でScheduler.ThreadPoolと書き、
Subscribe内でInputクラスを用いるとデッドロックを起こします。実際のコード
Test.csObservable.Interval(System.TimeSpan.FromSeconds(1), Scheduler.ThreadPool).Take(10).Subscribe(t => { Debug.Log(t); if(Input.anyKey) { Debug.Log("Push!"); } });出力結果
0 //これ以降何も出力されない補足
Audio系クラスでも同様の事象が起きます。
- 投稿日:2019-02-11T16:02:12+09:00
コンソールアプリケーションにアクティビティインジケータを表示
概要
コンソールアプリケーションにおいて、重たい処理を実行中であることをユーザーに伝えるためのアクティビティインジケータ(ビジーマーク)を表示するプログラムをC#とF#で作成しました。
一定時間毎に
...
を表示するためにRx(Observable.Timer
)を利用しました。
処理中
処理中.
処理中..
処理中...
- 処理が終わるまで上記の表示の繰り返し、処理が完了したら下記の表示で上書き
処理完了
C#版
nugetで
System.Reactive
をインストールしておきます。using System; using System.Reactive.Linq; using System.Threading; using System.Threading.Tasks; class Program { // アクティビティインジケータを出力する関数 static void IndicationPrinter(string msg1, string msg2, int i) { Console.SetCursorPosition(0, Console.CursorTop); if (i < 0) { Console.WriteLine(msg2); } else { var n = i % 4; Console.Write($"{msg1}{new string('.', n)}{new string(' ', 3 - n)}"); } } // インジケータを出力しながら関数funcをする非同期で実行する関数 static Task<T> RunWithActivityIndicator<S, T>(string msg1, string msg2, Func<S, T> func, S arg) { var sTimer = Observable.Timer(TimeSpan.FromSeconds(0), TimeSpan.FromSeconds(0.2)); var timerSubscription = sTimer.Subscribe(i => IndicationPrinter(msg1, msg2, (int)i)); return Task.Run(() => { return func(arg); }).ContinueWith((Task<T> t) => { timerSubscription.Dispose(); IndicationPrinter(msg1, msg2, -1); return t.Result; }); } static void Main(string[] args) { Console.WriteLine("C# Ver."); //Console.CursorVisible = false; //カーソルを消したい場合 Func<string, string> heavyWeightFunc1 = (string arg) => { Thread.Sleep(2500); return $"=={arg}=="; }; // 非常に時間がかかる関数1 (Func<int>) Func<int> heavyWeightFunc2 = () => { Thread.Sleep(1500); return 0; }; var msg1 = "処理中"; var msg2 = "処理完了"; var task1 = RunWithActivityIndicator(msg1, msg2, heavyWeightFunc1, "hoge"); Console.WriteLine($"処理結果:{task1.Result}"); Console.ReadKey(); } }F#版
nugetで
FSharp.Control.Reactive
をインストールしておきます。open System open System.Reactive.Linq open FSharp.Control.Reactive open System.Threading open System.Threading.Tasks // アクティビティインジケータを出力する関数 let indicationPrinter msg1 msg2 i = Console.SetCursorPosition(0, Console.CursorTop) |> ignore if i < 0 then printfn "%s" msg2 else match i%4 with | 0 -> ignore( printf "%s" (msg1+" ")) | 1 -> ignore( printf "%s" (msg1+". ")) | 2 -> ignore( printf "%s" (msg1+".. ")) | _ -> ignore( printf "%s" (msg1+"...")) // インジケータを出力しながら関数funcをする非同期で実行する関数 let runWithActivityIndicator (msg1,msg2) (func:'S->'T) arg = let indicationPrinter = indicationPrinter msg1 msg2 let sTimer = Observable.Timer(TimeSpan.FromSeconds(0.),TimeSpan.FromSeconds(0.2)) let timerSubscription = sTimer.Subscribe( fun i -> int(i) |> indicationPrinter ) let task = async { return func arg } |> Async.StartAsTask task.ContinueWith( fun (t:Task<'T>) -> timerSubscription.Dispose() indicationPrinter -1 t.Result ) [<EntryPoint>] let main argv = printfn "F# Ver." //Console.CursorVisible <- false; // カーソルを消したい場合 // 非常に時間がかかる関数1 (string->string) let heavyWeightFunc1 arg = Thread.Sleep(1500) "==" + arg + "==" // 非常に時間がかかる関数2 (unit->string) let heavyWeightFunc2 () = Thread.Sleep(2500) 0 // 非常に時間がかかる関数3 (unit->unit) let heavyWeightFunc3 () = Thread.Sleep(1000) let msg = ("処理中","処理完了") let task1 = runWithActivityIndicator msg heavyWeightFunc1 "hoge" printfn "%s" ("処理結果 : " + task1.Result ) let task2 = runWithActivityIndicator msg heavyWeightFunc2 () printfn "%s" ("処理結果 : " + (task2.Result.ToString()) ) let task3 = runWithActivityIndicator msg heavyWeightFunc3 () task3.Wait() printfn "." Console.ReadKey() |> ignore 0参考資料
- 投稿日:2019-02-11T15:39:28+09:00
[Resharperチュートリアル]Live Template/Surround Template(コードスニペット)の使い方/作り方
LiveTemplate / SurroundTemplateとは
要するにコードスニペット。標準のVisual Studioでforと打ってTab押したらfor文の雛型が生成されるやつ。
=>公式情報:ライブテンプレートを使用したソースコードの作成
サラウンドテンプレートは選択範囲をtry-catchブロックで囲ったり、lockで囲ったりする場合に使用。
Visual Studioでカスタムのコードスニペットを作るのはxmlファイルいじったりしてめんどくさいけど、Resharperなら簡単に作れる。
使い方
liveTemplateの使用例:自作のテストメソッド作成用テンプレート。"tm"と打ってEnter→テストしたいメソッドを入力。
- このように簡単に雛型を作れることがきちんとテストを書くモチベーションにもなる!
surroundTemplateの例:try-catchの例
- ショートカット無し
- try-catchで囲みたいブロックを選択→ブラシマークをクリック→[Surround With]→tryをクリック
- try-catchはよく書くのでショートカットを覚えると生産的
作り方
Live Templates
自分がよく使うテストメソッド生成テンプレートの作り方
- [Resharperメニュー]→[Tools]→[Templates Explorer...]を選択
3.下図のように編集して[Ctrl + s]で保存
→これで冒頭のGitのように"tm"と打つとコード補完でテストメソッド用のスニペットが選択できるようになる。
なお既存コードからテンプレートの雛型を作成することもできる。
2.[Resharper]メニュー→[Tools]→[Create Live Templates from Selection...]を選択
4.雛型を編集。shortcut, Descriptionを入力し、変数化したい部分をドルマークで囲む。
この例ではクラス名にマクロ[Containing type member name]にして、クラス名が自動で入るようにしている。
Surround Templatesの作り方
ほぼLiveTemplatesと同様。今回は[C#Tips.ログ]Disposable-usingを使った簡単計測ログ出力方法で紹介した計測ログ出力関数用usingのテンプレートを作成してみる
- [Resharperメニュー]→[Tools]→[Templates Explorer...]を選択
3.デフォルトの\$SELECTION$変数が選択範囲になるものとして編集して[Ctrl + s]で保存
↓このように使える。
まとめ
これ知っとくと楽やで^^。ほんの些細な定型句でもチマチマテンプレート化していくと、退屈な単純作業が楽しいテンプレート遊びと化す!
それでは良いプログラミングライフをノシ
- 投稿日:2019-02-11T15:39:28+09:00
[Resharperチュートリアル.テンプレート]Live Template/Surround Template(コードスニペット)の使い方/作り方
LiveTemplate / SurroundTemplateとは
要するにコードスニペット。標準のVisual Studioでforと打ってTab押したらfor文の雛型が生成されるやつ。
=>公式情報:ライブテンプレートを使用したソースコードの作成
サラウンドテンプレートは選択範囲をtry-catchブロックで囲ったり、lockで囲ったりする場合に使用。
Visual Studioでカスタムのコードスニペットを作るのはxmlファイルいじったりしてめんどくさいけど、Resharperなら簡単に作れる。
使い方
liveTemplateの使用例:自作のテストメソッド作成用テンプレート。"tm"と打ってEnter→テストしたいメソッドを入力。
- このように簡単に雛型を作れることがきちんとテストを書くモチベーションにもなる!
surroundTemplateの例:try-catchの例
- ショートカット無し
- try-catchで囲みたいブロックを選択→ブラシマークをクリック→[Surround With]→tryをクリック
- try-catchはよく書くのでショートカットを覚えると生産的
作り方
Live Templates
自分がよく使うテストメソッド生成テンプレートの作り方
- [Resharperメニュー]→[Tools]→[Templates Explorer...]を選択
3.下図のように編集して[Ctrl + s]で保存
→これで冒頭のGifのように"tm"と打つとコード補完でテストメソッド用のスニペットが選択できるようになる。
なお既存コードからテンプレートの雛型を作成することもできる。
2.[Resharper]メニュー→[Tools]→[Create Live Templates from Selection...]を選択
4.雛型を編集。shortcut, Descriptionを入力し、変数化したい部分をドルマークで囲む。
この例ではクラス名にマクロ[Containing type member name]にして、クラス名が自動で入るようにしている。
Surround Templatesの作り方
ほぼLiveTemplatesと同様。今回は[C#Tips.ログ]Disposable-usingを使った簡単計測ログ出力方法で紹介した計測ログ出力関数用usingのテンプレートを作成してみる
- [Resharperメニュー]→[Tools]→[Templates Explorer...]を選択
3.デフォルトの\$SELECTION$変数が選択範囲になるものとして編集して[Ctrl + s]で保存
↓このように使える。
まとめ
これ知っとくと楽やで^^。ほんの些細な定型句でもチマチマテンプレート化していくと、退屈な単純作業が楽しいテンプレート遊びと化す!
それでは良いプログラミングライフをノシ
- 投稿日:2019-02-11T13:25:07+09:00
async/await × メソッドチェーン
概要
async/awaitを使ったメソッドをチェーンにする方法を紹介します。
簡単だった割に記事がなかったので記録として残しておきます。前提条件
以下のクラスを仮定します。
Test.csclass Test { public async Task Operation() { //ここの中にメソッドチェーンを書きます } //適当に用意 public Test Start() { //適当な処理 return this; } //適当に用意 public Test End() { //適当な処理 return this; } //適当な非同期処理を用意 private async Task<Test> HogeAsync() { await Fuga(); //適当な処理 return this; } }書き方
末尾に非同期処理がある場合
以下のように書きます。馴染みのある方が多いかと。
Test.cspublic async Task Operation() { await Start().HogeAsync(); }冒頭に非同期処理がある場合
以下のように書きます。()で一工夫することが必要です。
Test.cspublic async Task Operation() { (await HogeAsync()).End(); }中間に非同期処理がある場合
以下のように書きます。上記の書き方を組み合わせた感じになります。
Test.cspublic async Task Operation() { (await Start().HogeAsync()).End(); }まとめ
async/awaitが用いられた関数をメソッドチェーンの中に組み込むことで、処理の流れを分かりやすく記述することができました。
その一方で、末尾以外に来る場合には()を使わなければいけないため、少し読みづらくなってしまうのが難点です。
- 投稿日:2019-02-11T00:22:29+09:00
VSCodeで c# のバージョンを変える方法
使用エディタ
visual studio code
問題
visual studio code
で C#6 のままで作業している場合、
例えば以下のプロパティの get ステートメントはエラーになる。適当なプロパティ例public int ID { get => this.id; }エラー文Feature 'expression body property accessor' is not available in C# 6. Please use language version 7.0 or greater. [Assembly-CSharp]要は
language version
というのを 7.0以上にしなさいということのようだ。※ ちなみに、この記述のドキュメントはこちら
https://docs.microsoft.com/ja-jp/dotnet/csharp/programming-guide/statements-expressions-operators/expression-bodied-members#property-set-statements対応方法
[プロジェクト名].csproj
の中の<LangVersion>6</LangVersion>
を7
以上にしてあげればOK。<PropertyGroup> <LangVersion>7</LangVersion> </PropertyGroup>これでC#7.0の文を書いてもエラーが出なくなった。
ちなみにUnity
だと、デフォルトでAssembly-CSharp.csproj
というファイル名だと思う。