程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> .NET網頁編程 >> 關於.NET >> 用WPF做2D游戲

用WPF做2D游戲

編輯:關於.NET

WPF也提供3D圖形的功能,不過3D的建模和動畫比較復雜,這裡先做一個2D的 游戲引擎練練手。

實例

一直想做一個超級馬裡奧的游戲,就從這個游戲做起,畫了一部分圖,已經完 成的有走動、跳躍、發射子彈、邊界檢查和場景滾動,沒有關卡,沒有敵人。

下面是游戲截圖:

實現

行走中邁腿擺臂的動畫是通過切換圖片幀來實現的,切換幀有兩種方法,一種 是放一個Image,然後用ObjectAnimationUsingKeyFrames來改變Image的Source屬 性:

<Storyboard x:Key="walkLeftStoryboard">
             <ObjectAnimationUsingKeyFrames Duration="00:00:00.4"  RepeatBehavior="Forever"
                                             Storyboard.TargetName="marioImage"  Storyboard.TargetProperty="Source" />
         </Storyboard>
         <Image Name="marioImage">
             <Image.RenderTransform>
                 <TranslateTransform x:Name="marioTranslate"  X="0"  Y="0"/>
             </Image.RenderTransform>
         </Image>

然後用C#代碼添加幀: 

public static System.Drawing.Bitmap LoadBitmap(Uri uri)
         {
             StreamResourceInfo info =  Application.GetResourceStream(uri);
             return new System.Drawing.Bitmap (info.Stream);
         }
         public static BitmapSource CreateBitmapSource (System.Drawing.Bitmap frame)
         {
             BitmapSource bs =  System.Windows.Interop.Imaging.CreateBitmapSourceFromHBitmap(
               frame.GetHbitmap(), IntPtr.Zero,  Int32Rect.Empty, BitmapSizeOptions.FromEmptyOptions());
             return bs;
         }
         internal static void AddKeyFrames (ObjectAnimationUsingKeyFrames animation, params  System.Drawing.Bitmap[] frames)
         {
             double percent = 0;
             double pace = 1.0 / frames.Length;
             foreach (var frame in frames)
             {
                 BitmapSource bs =  CreateBitmapSource(frame);
                 animation.KeyFrames.Add(new  DiscreteObjectKeyFrame(bs, KeyTime.FromPercent(percent)));
                 percent += pace;
             }
         }

使用這種方法需要先把大圖在代碼裡切割成四個小圖,由於要用到 System.Drawing所以代碼不能遷移到Silverlight。

於是我又想了第二種方法,用ImageBrush做控件背景,然後用 ObjectAnimationUsingKeyFrames來切換它的ViewBox。  

<Storyboard x:Key="walkLeftStoryboard">
             <ObjectAnimationUsingKeyFrames Duration="00:00:00.4"  RepeatBehavior="Forever"
                                             Storyboard.TargetName="marioImage"  Storyboard.TargetProperty="CurrentFrame" />
         </Storyboard>
         <game:AnimatedImage x:Name="marioImage"  Image="/SuperMario;component/Images/mario.png" CurrentFrame="0, 0,  0.5, 0.5" Width="134" Height="131">
             <game:AnimatedImage.RenderTransform>
                 <TranslateTransform x:Name="marioTranslate"  X="0"  Y="0"/>
             </game:AnimatedImage.RenderTransform>
         </game:AnimatedImage>

用C#代碼添加幀:

internal static void AddKeyFrames (ObjectAnimationUsingKeyFrames animation, Rect[] frames)
         {
             double percent = 0;
             double pace = 1.0 / frames.Length;
             foreach (var frame in frames)
             {
                 animation.KeyFrames.Add(new  DiscreteObjectKeyFrame(frame, KeyTime.FromPercent(percent)));
                 percent += pace;
             }
         }

AnimatedImage是一個自定義的控件,控件模板如下: 

<Style TargetType="local:AnimatedImage">
         <Setter Property="Template">
             <Setter.Value>
                 <ControlTemplate TargetType="local:AnimatedImage">
                     <Border BorderThickness="0">
                         <Border.Background>
                             <ImageBrush ImageSource="{Binding Path=Image,RelativeSource= {RelativeSource TemplatedParent}}"
                                          Stretch="UniformToFill" AlignmentX="Left"  AlignmentY="Top" Viewbox="{Binding Path=CurrentFrame,RelativeSource= {RelativeSource TemplatedParent}}"/>
                         </Border.Background>
                     </Border>
                 </ControlTemplate>
             </Setter.Value>
         </Setter>
     </Style>

後來發現SliverLight裡的TileBrush沒有ViewBox屬性,所以還是無法遷移

接下來就是在GameLoop中根據鍵盤按鍵控制動畫的開始和停止,並用 marioTranslate來改變人物的位置。

GameLoop可由CompositionTarget.Rendering事件指定:

GameLoop gameLoop;
         private void Window_Loaded(object sender,  RoutedEventArgs e)
         {       
             ......
             gameLoop = new GameLoop(player,  Scenes.Level1);
             CompositionTarget.Rendering += new  EventHandler(CompositionTarget_Rendering);
         }
         void CompositionTarget_Rendering(object sender,  EventArgs e)
         {
             gameLoop.ProcessChanges();
         }

在GameLoop中還需要注意的就是跳躍過程中重力效果的模擬和對物體、台階、 邊界的碰撞檢查,這個就不多說了,看代碼:

public void ProcessChanges()
         {
             TimeSpan timeSpan = DateTime.Now -  lastTime;
             double step = timeSpan.TotalSeconds;
             lastTime = DateTime.Now;
             double x = Sprite.X;
             double y = Sprite.Y;
             double dx = step * Sprite.Speed;
             if (Sprite.IsWalkingLeft)
             {
                 x -= dx;
                 Scene.ScrollRightt(x, dx);
             }
             else if (Sprite.IsWalkingRight)
             {
                 x += dx;
                 Scene.ScrollLeft(x, dx);
             }
             if (Map.CanMoveTo(x, Sprite.Y,  Sprite.Width, Sprite.Height))
             {
                 Sprite.X = x;
             }
             if (Sprite.IsJumping)
             {
                 y -= (1 - Sprite.JumpTime) *  step * 400;
                 if (Sprite.JumpTime < 1  && Map.CanMoveTo(Sprite.X, y, Sprite.Width,  Sprite.Height))
                 {
                     Sprite.Y = y;
                     Sprite.JumpTime +=  step;
                 }
                 else
                 {
                     Sprite.IsJumping =  false;
                     Sprite.IsFalling =  true;
                     Sprite.JumpTime = 0;
                 }
             }
             else if (Sprite.IsFalling)
             {
                 y += 800 * Sprite.FallTime *  step;
                 if (Map.CanMoveTo(Sprite.X, y,  Sprite.Width, Sprite.Height))
                 {
                     Sprite.Y = y;
                     Sprite.FallTime +=  step;
                 }
                 else
                 {
                     Sprite.IsFalling =  false;
                     Sprite.FallTime = 0;
                 }
             }
             else
             {
                 y += 1;
                 if (Map.CanMoveTo(Sprite.X, y,  Sprite.Width, Sprite.Height))
                 {
                     Sprite.Y = y;
                     Sprite.IsFalling =  true;
                     Sprite.FallTime =  step;
                 }
             }
         }

下一步

下一步我打算用XAML矢量圖來做動畫,場景物體等也全都用矢量圖,這樣的好 處一是可以任意放大縮小,二是動畫效果會更加流暢一些。

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved