Silverlightのゲームサイクル

この記事では、Silverlightでのゲームループの使用について説明します。 アニメーションを操作し、アニメーションに影響するユーザーアクションを処理するためのすべてのロジックを含む関数。 機械制御の例。



写真をクリックして例をご覧ください。



Silverlightは優れたアニメーションをサポートしています。 Blendでプロパティを取得し、キーを配置します。 そしてすべてが機能し、コントロールはユーザーのアクションに応答します。 プログラム的に、 ストーリーボードを介して、プロパティを状態Aから状態Bに変更するアニメーションも、途中で問題なく簡単に行えます。 しかし、複雑なアニメーション(物理、衝突計算、アニメーションカーブの動的な変更)に関しては、 ストーリーボードを介したアニメーションの実装はコードを大幅に複雑にするか、まったく不可能です。





この問題における古典的なアプローチは、タイマーを作成し、更新されるたびにアニメーションロジックを再カウントすることです。



Silverlightでは、これらはタイマーになります。

しかし、Silverlightでプログラムアニメーションを実装するのにより適切なのは、グローバルRenderingイベントである静的クラスSystem.Windows.Media.CompositionTargetを使用することです。



サンプルコード、単純なゲームループ:



//   private int _lastTime; private void MainPage_Loaded(object sender, RoutedEventArgs e){ //  ,     CompositionTarget.Rendering += _gameLoopUpdate; _lastTime = Environment.TickCount; } private void _gameLoopUpdate(object sender, EventArgs e){ int currentTime = Environment.TickCount; int frameTime = currentTime - _lastTime; _lastTime = currentTime; //    }
      
      







frameTime変数には、最後の再描画以降に経過した時間が含まれます。 この時間は大きく異なる可能性があることを考慮する価値があります。 フレーム間の急激な動きを避けるには、この時間に依存する可変プロパティを作成する必要があります。 60に等しいfps(1秒あたりのフレームレート)は、16ミリ秒後にすべてのフレームが表示されることを意味しないことを理解することが重要です。

原則として、それだけです。残っているのは、独自のアニメーションロジックを記述することだけです。 なぜなら Silverlight自体がオブジェクトの再描画を担当します。プロパティに値を入力するだけで、残りは心配する必要はありません。



生きている例



たとえば、「ゲーム」というマシンを箱の中に作成します。 キーボードの矢印は車を制御します。 左、右、ガス、リバース/ブレーキ、すべてが他のどこにでも似ています。

新しいSilverlightプロジェクトを作成します。 「MainPage.xaml」を開きます。 「LayoutRoot」という名前のメイングリッドに、次を配置します。



 <Grid Margin="12" Background="#FF084E0F"/> <Viewbox IsHitTestVisible="False" Margin="12"> <TextBlock TextWrapping="Wrap" Text=" " Foreground="#FF00228D" RenderTransformOrigin="0.5,0.5" Margin="10,0" Width="129" Height="16"> <TextBlock.RenderTransform> <CompositeTransform Rotation="-35"/> </TextBlock.RenderTransform> </TextBlock> </Viewbox> <Canvas x:Name="ContentPanel" Margin="12"> <TextBlock x:Name="tbInfo" Text="TextBlock" Foreground="White" FontSize="16" /> <Image x:Name="car" Width="70" Source="car.png" Height="35" /> </Canvas>
      
      







写真「car.png」を自分またはこれで使用できます。

car.png



主なことは、車の鼻が右に見えることです。



アニメーション中のサイズ変更の問題。


アニメーション化されたオブジェクトのサイズが原因で、アニメーションを操作するときに発生する可能性のある問題がいくつかあります。

  • Autoで幅または高さの寸法を設定すると、回転中心の変化はそれぞれ考慮されず、回転のアニメーションに問題がある可能性があります。 絶対単位の次元。
  • 開始時、 ActualWidthActualHeightはゼロです。 複数のオブジェクトがまだ描画されていません。 計算でこれを考慮してください。




ゲームワールドが作成されました。 ロジックを追加します。



キーストローク処理



キーはそれほど単純ではありません。 最初は、Silverlightは同時に押されるキーの定義をサポートせず、修飾キー(Ctrl、Shift、Alt)をカウントしません。 KeyDownイベントは、最後に押されたキーのみを処理します。 つまり 一度に1つのキーを押す必要があります。 例えば、彼らはガスを絞った、そしてガスの間、左に曲がらない。 解決策として、Ctrl / Shiftにガス/ブレーキをかけ、両手で操縦します。 不便です...



しかし、すべてがそれほど悪いわけではありませんが、良いことさえあります。 インターネット( Silverlightで複数のキー入力を検出 )で、この問題を解決し、ゲームループ内でキーストロークを処理できるクラスが見つかりました。これは、KeyDownハンドラー内よりもはるかに便利です。



プロジェクトにクラスを追加します。



  //  .    , //   ,  . //         public sealed class KeyHandler { private bool[] isPressed = new bool[256]; private UserControl targetCanvas = null; public void ClearKeyPresses(){ for (int i = 0; i < 256; i++){ isPressed[i] = false; } } public void ClearKey(Key k){ isPressed[(int) k] = false; } public void Attach(UserControl target){ ClearKeyPresses(); targetCanvas = target; target.KeyDown += new KeyEventHandler(target_KeyDown); target.KeyUp += new KeyEventHandler(target_KeyUp); target.LostFocus += new RoutedEventHandler(target_LostFocus); } public void Detach(UserControl target){ target.KeyDown -= new KeyEventHandler(target_KeyDown); target.KeyUp -= new KeyEventHandler(target_KeyUp); target.LostFocus -= new RoutedEventHandler(target_LostFocus); ClearKeyPresses(); } private void target_KeyDown(object sender, KeyEventArgs e){ isPressed[(int) e.Key] = true; } private void target_KeyUp(object sender, KeyEventArgs e){ isPressed[(int) e.Key] = false; } private void target_LostFocus(object sender, EventArgs e){ ClearKeyPresses(); } public bool IsKeyPressed(Key k){ int v = (int) k; if (v < 0 || v > 82) return false; return isPressed[v]; } public bool IsKeyPressed(Key[] keys){ foreach (Key k in keys){ if (this.IsKeyPressed(k)) return true; } return false; } }
      
      







アニメーション



次のグローバル変数をMainPage.xaml.csに追加します。

 //   private KeyHandler _keyHandler = new KeyHandler(); //       private RotateTransform _rotateHelper = new RotateTransform(); //       private TranslateTransform _translateTransform = new TranslateTransform(); private RotateTransform _rotateTransform = new RotateTransform();
      
      







RenderTransformプロパティを介して車のアニメーションを作成します。 移動、回転などを担当します。



なぜなら 変換は、開始位置を基準にしてオブジェクトをアニメーション化します。 Canvas.TopCanvas.Leftが オブジェクト(私たちの車)に設定されていないことを確認してください。



メインウィンドウのコンストラクターに入力します。

 public MainPage(){ InitializeComponent(); this.Loaded += MainPage_Loaded; //     var tg = new TransformGroup(); tg.Children.Add(_rotateTransform); tg.Children.Add(_translateTransform); car.RenderTransform = tg; //    _translateTransform.X = 50; _translateTransform.Y = 50; car.MouseLeftButtonDown += car_MouseLeftButtonDown; //    _keyHandler.Attach(this); }
      
      





マウスクリック処理機能は、ほとんどの場合、マシンがマウスからのイベントをキャッチすることを示す必要があります。

  //       void car_MouseLeftButtonDown(object sender, MouseButtonEventArgs e){ _speedPerSecond += 300; }
      
      





ウィンドウが表示されたら、Silverlightプラグインにフォーカスを設定します。そうしないと、キーストロークをキャッチしません。 一部のブラウザではこれは役に立たないため、フォーカスを手動に設定する必要があります。 おもちゃをクリックしてください。 また、ゲームサイクルを準備します。

 private void MainPage_Loaded(object sender, RoutedEventArgs e){ //     ,        System.Windows.Browser.HtmlPage.Plugin.Focus(); //  ,     CompositionTarget.Rendering += _gameLoopUpdate; _lastTime = Environment.TickCount; }
      
      





アニメーションで使用されるグローバル変数を追加します。

  //    private Point _direction = new Point(1, 0); //  private double _speedPerSecond ; // /   , >1 ,<1  private double _accelerationRatioPerSec = 0.60; //   private int _lastTime;
      
      





車を動かすアニメーションによって。 方向ベクトル(単位ベクトル)を原点の周りで回転させ、ベクトルのコンポーネントに速度を掛けて、車が存在する場所を取得します。 同時に、結果の角度は機械の回転角度になります。





マシンとコンテナの境界線の交差点を計算するには、車両の方向転換の角度を考慮して、マシンを囲む長方形を計算します。





ゲームの中心であるゲームサイクルの機能に移りましょう。

  private void _gameLoopUpdate(object sender, EventArgs e){ int currentTime = Environment.TickCount; int frameTime = currentTime - _lastTime; _lastTime = currentTime; //       //          double delta = (double)frameTime / 1000; //       double xCurrentPos = _translateTransform.X; double yCurrentPos = _translateTransform.Y; double currentAngle = _rotateTransform.Angle; //   double addToSpeedPerSec = 300; if (_keyHandler.IsKeyPressed(Key.Up)){ _speedPerSecond += addToSpeedPerSec*delta; } if (_keyHandler.IsKeyPressed(Key.Down)){ _speedPerSecond -= addToSpeedPerSec * delta; } //       double targetAngle = currentAngle; double coefAngle = .5; if (_keyHandler.IsKeyPressed(Key.Right)){ targetAngle += _speedPerSecond * coefAngle*delta; } if (_keyHandler.IsKeyPressed(Key.Left)){ targetAngle -= _speedPerSecond * coefAngle * delta; } //    _rotateHelper.CenterX = car.ActualWidth / 2; _rotateHelper.CenterY = car.ActualHeight / 2; _rotateHelper.Angle = targetAngle; //    _rotateHelper.CenterX = 0; _rotateHelper.CenterY = 0; _direction = _rotateHelper.Transform(new Point(1, 0)); //   _speedPerSecond += (_speedPerSecond*_accelerationRatioPerSec - _speedPerSecond)*delta; // ,       double stepPerFrame = _speedPerSecond * delta; //   double xTargetPos = xCurrentPos + _direction.X * stepPerFrame; double yTargetPos = yCurrentPos + _direction.Y * stepPerFrame; //           Rect borderRect = _rotateHelper.TransformBounds(new Rect(0, 0, car.ActualWidth, car.ActualHeight)); borderRect.X = borderRect.X + xTargetPos; borderRect.Y = borderRect.Y + yTargetPos; //     if (isInContentPanel(borderRect)){ ////     _translateTransform.X = xTargetPos; _translateTransform.Y = yTargetPos; _rotateTransform.Angle = targetAngle; } else{ // ,     _speedPerSecond = 0; } //  tbInfo.Text = string.Format("Speed:{0:F1}", _speedPerSecond); } //    ,    private bool isInContentPanel(Rect shapeBorder){ if (0 <= shapeBorder.Left && shapeBorder.Right <= ContentPanel.ActualWidth) if (0 <= shapeBorder.Top && shapeBorder.Bottom <= ContentPanel.ActualHeight) return true; return false; }
      
      





ループ機能は、次の手順で構成されます。



すべての変更について、前のフレームからの経過時間( delta )を考慮します。 認識しやすくするため、速度は1秒あたりの単位で測定されます。 回転角は車の速度とともに増加します。

キーストロークの処理に注目する価値があります。 なぜなら ゲームサイクルで処理されるため、変更を個別に同期する必要はありません。 また、速度と回転角度を変更するコードがKeyDownハンドラーから呼び出された場合、このような問題が発生していました。



まとめ



車は、多かれ少なかれ複雑なアルゴリズムに従って運転、加速します。 もちろん、加速と回転のより自然なアニメーション、横滑りの効果、道路を横断する老婦人との衝突の誤算...を作成できますが、見るのに十分実装されています:複雑なアニメーション、ゲームループで行うことをお勧めします。



ゲームサイクルにはアーキテクチャ上の利点があり、現時点で発生しているすべてが1か所に集められ、それに影響する要因もあります。 Storyboardを介したアニメーションと比較すると、デバッグが容易であり、非同期性はありません( Storyboardでは、アニメーションの次の段階を開始するために、アニメーションの終わりをキャッチするのに手間がかかります)。



さらに、額にすべてを実装する必要はありません。 上記の例はBehaviorで書き直すのは簡単です。 このビヘイビアをBlendのボタンにアタッチすると、画面上を移動します(必要な理由は明確ではありませんが、可能です)。 以下のリソースでは、物理ライブラリのラッパーへのリンクがあります。これは基本的に同じです。Blendでは、オブジェクトにマークを付け、物理法則に敏感になります。



「車の中の車」のソースコードは、 ここまたはサンプルページからダウンロードできます



リソース:

  1. Blend、Silverlight、WP7、およびFarseerの物理ヘルパー-Farseer物理エンジン物理ライブラリ(http://farseerphysics.codeplex.com/)のSilverlightラッパー。 彼女のコードで、Silverlightで複雑なアニメーションがどのように処理されるかを見つけました。
  2. Silverlightのカスタムアニメーション -振り子の例を使用したゲームループの使用。 記事は英語です。



All Articles