連続したアニメーションをつなぐために、Completed イベントでつなげる

今、寄り道になってしまっているのは、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. 役ができれば、それを知らせるアニメーション
  3. マッチした札を取り札に加えるアニメーション
  4. 山から1枚、場に出すアニメーション
  5. 役ができれば、それを知らせるアニメーション
  6. マッチした札を取り札に加えるアニメーション

なんてのを一連の流れで動かすわけです。途中に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 で書いたらどうなるのか?ってのが次。

カテゴリー: C#, WinRT パーマリンク