今、寄り道になってしまっているのは、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 で書いたらどうなるのか?ってのが次。

