今、寄り道になってしまっているのは、WinRT でモーダルダイアログを出す場合には async/await が必須っぽいのと、アニメーション自体は非同期で動くのだから、それを連続させる場合には async/await を使ったほうが便利ではないか?という思惑と、ならば、Awaitable パターンを実装してみるのが良いのでは?というのと、MessageDialog は IAsyncOperation を実装していて…ってところで、非同期のパターンを同期っぽく書くために await を連続させれば、非同期処理を手続の連続で「わかりやすく」書けるのでは?っての確認のためです。ええ、何を言っているかややこしいのですが、これから一手ずつ試していきます。
■参考資料はここから
自分の資料がてら、だらだらと。
非同期メソッドの内部実装 (C# によるプログラミング入門)
http://ufcpp.net/study/csharp/sp5_awaitable.html
非同期メソッド入門 (8) ? コンパイラ要件 : xin9le note
http://xin9le.net/archives/147
WinRT と await を掘り下げる – Windows 8 アプリ開発者ブログ – Site Home – MSDN Blogs
http://blogs.msdn.com/b/windowsappdev_ja/archive/2012/04/30/winrt-await.aspx
.NET タスクを WinRT 非同期処理として公開する – Windows 8 アプリ開発者ブログ – Site Home – MSDN Blogs
http://blogs.msdn.com/b/windowsappdev_ja/archive/2012/06/25/net-winrt.aspx
WinRT と await を掘り下げる – Windows 8 アプリ開発者ブログ – Site Home – MSDN Blogs
http://blogs.msdn.com/b/windowsappdev_ja/archive/2012/04/30/winrt-await.aspx
■アニメーションを連続させる
まずは、アニメーションの連続技をどう書けばよいのか?ってのがスタートです。
[WinRT] 指定位置からコントロールをアニメーションさせる | Moonmile Solutions Blog
http://www.moonmile.net/blog/archives/4066
なところをやっているのは、花札ゲームのアニメーションが、
- 手札から1枚、場に出すアニメーション
- 役ができれば、それを知らせるアニメーション
- マッチした札を取り札に加えるアニメーション
- 山から1枚、場に出すアニメーション
- 役ができれば、それを知らせるアニメーション
- マッチした札を取り札に加えるアニメーション
なんてのを一連の流れで動かすわけです。途中に2枚選択のモーダルダイアログも表示するので、このあたりは「状態遷移」の問題もあります。で、yeild return を使ってコード的に連続させる(イテレーションを使う)っていう技を知った
C# の yield return の使い道 – カタチづくり
http://d.hatena.ne.jp/u_1roh/20080302/1204471238
Hisuiチュートリアル / 直線作図機能を作ってみよう (1)
http://www.quatouch.com/products/hisui/hisui-1_5_0_0-20080204/doc/tutorial/putline.html
ので、これでも良いかも、と思ったものの、花札ゲームの場合はモーダルダイアログが間に入ってくるしどうしたものか?と悩むわけです。
で、モーダルダイアログの悩みはさておき、アニメーションを連続させる場合にはどう実装するのかを確かめてみます。
アニメーション自体は、storyboard を使って、あらかじめ blend で作っておくわけですが、1 から 6 までのアニメをあらかじめ作っておくことはできません。それぞれの部分に分けて storyboard を作って置くし、開始と終了の座標がことなるので、実行時に位置を変えないといけません。
本格的なゲームであれば、アニメーションの座標を動的に計算するのがいいんでしょうが、花札ゲームの場合は「XAML を活用すること」を(私が)主旨にしているので、できるだけ storyboard を活用したい…と言いますか、活用できるかどうかの実験っていう意味もあります。
■Storyboard の Completed イベントで連続させるアイデア
storyboard にはアニメーションが完了した時に Completed イベントが発生します。なので、それぞれの Completed イベントで次の storyboard を Begin させれば、それぞれの storyboard を連続で動作できるはずです。
1 2 3 4 | sb1.Completed += (s,e) => { sb2.Begin(); }; sb2.Completed += (s,e) => { sb3.Begin(); }; sb3.Completed += (s,e) => { sb4.Begin(); }; sb4.Completed += (s,e) => { ; }; |
ラムダ式で書けばこんな風に、Completed イベントで連続させるわけです。なかなか壮観な感じがしますが、単純に連続させるだけならば、これでもいいかもしれません。
■実際に作ってみる
あらかじめ、こんな風に4つの Image を置いておきます。移動用の松の札が、ぐるぐる連続して回るようにするわけです。
テンプレート用の storyboard はこんな感じ。開始位置と終了位置を変更するために、sbStartX などの名前を付けておきます。
1 2 3 4 5 6 7 8 9 10 | <Storyboard x:Name= "sbMove" > <DoubleAnimationUsingKeyFrames Storyboard.TargetProperty= "(UIElement.RenderTransform).(CompositeTransform.TranslateX)" Storyboard.TargetName= "pictAni" > <EasingDoubleKeyFrame x:Name= "sbStartX" KeyTime= "0" Value= "180.597" /> <EasingDoubleKeyFrame x:Name= "sbEndX" KeyTime= "0:0:1" Value= "182.09" /> </DoubleAnimationUsingKeyFrames> <DoubleAnimationUsingKeyFrames Storyboard.TargetProperty= "(UIElement.RenderTransform).(CompositeTransform.TranslateY)" Storyboard.TargetName= "pictAni" > <EasingDoubleKeyFrame x:Name= "sbStartY" KeyTime= "0" Value= "-152.239" /> <EasingDoubleKeyFrame x:Name= "sbEndY" KeyTime= "0:0:1" Value= "171.642" /> </DoubleAnimationUsingKeyFrames> </Storyboard> |
アニメーションの位置計算を簡単にするために、移動用の Image は (0,0) に移動させてしまいます。
札自体をクリックしたらアニメーションを開始。もう一度クリックすると停止します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 | public MainPage() { this .InitializeComponent(); // 最初の位置を(0,0) にしてしまう this .pictAni.Margin = new Thickness(0); } /// <summary> /// 札をクリックする /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void pictAniClick( object sender, TappedRoutedEventArgs e) { if (_moving == false ) { // アニメーションを開始する SetMovePos(pict1, pict2); this .sbMove.Completed += sbMove_Completed; this .sbMove.Begin(); _moving = true ; _count = 0; } else { // アニメーションを停止する this .sbMove.Stop(); this .sbMove.Completed -= sbMove_Completed; _moving = false ; } } bool _moving = false ; int _count = 0; /// <summary> /// アニメーション完了時のイベント /// </summary> /// <param name="sender"></param> /// <param name="e"></param> void sbMove_Completed( object sender, object e) { _count = (_count + 1) % 4; switch (_count) { case 0: SetMovePos(pict1, pict2); break ; case 1: SetMovePos(pict2, pict3); break ; case 2: SetMovePos(pict3, pict4); break ; case 3: SetMovePos(pict4, pict1); break ; } sbMove.Begin(); } /// <summary> /// 開始座標と終了座標の設定 /// </summary> /// <param name="pictStart"></param> /// <param name="pictEnd"></param> void SetMovePos(Image pictStart, Image pictEnd) { Point pt1 = pictStart.TransformToVisual( null ).TransformPoint( new Point()); Point pt2 = pictEnd.TransformToVisual( null ).TransformPoint( new Point()); this .sbStartX.Value = pt1.X; this .sbStartY.Value = pt1.Y; this .sbEndX.Value = pt2.X ; this .sbEndY.Value = pt2.Y ; Debug.WriteLine( "{0},{1} -> {2},{3}" , this .sbStartX.Value, this .sbStartY.Value, this .sbEndX.Value, this .sbEndY.Value); } |
ひとつの storyboard しか利用していないので、Completed イベントでは座標の切り替えしかしてません。この部分は、Completed が別々になるとなかなかややこしくなるかと。逆にいえば、複数の Completed イベントを同じイベントメソッドにしてしまって、そのイベントメソッドに状態遷移を書けばコードがややこしくならないかも。
で、このアニメーション部分は非同期なんだから、async/await で書いたらどうなるのか?ってのが次。