今、寄り道になってしまっているのは、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 を連続で動作できるはずです。
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 などの名前を付けておきます。
<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) に移動させてしまいます。
札自体をクリックしたらアニメーションを開始。もう一度クリックすると停止します。
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 で書いたらどうなるのか?ってのが次。