拼吾爱程序人生

首页»  WPF»  用WPF做2D游戏
cobra - 2010-3-4 20:17:00
引言

WPF使用了DirectX作为图形渲染引擎,因此游戏性能的表现要强于GDI+,而且WPF内建的对动画的支持使得游戏的编写更加简化。

WPF也提供3D图形的功能,不过3D的建模和动画比较复杂,这里先做一个2D的游戏引擎练练手。

实例

一直想做一个超级马里奥的游戏,就从这个游戏做起,画了一部分图,已经完成的有走动、跳跃、发射子弹、边界检查和场景滚动,没有关卡,没有敌人。

下面是游戏截图:

附件: SuperMario.png

实现

附件: mario.png

行走中迈腿摆臂的动画是通过切换图片帧来实现的,切换帧有两种方法,一种是放一个Image,然后用 ObjectAnimationUsingKeyFrames来改变Image的Source属性:
XAML代码
  1.         <Storyboard x:Key="walkLeftStoryboard">
  2.             <ObjectAnimationUsingKeyFrames Duration="00:00:00.4" RepeatBehavior="Forever"
  3.                                           Storyboard.TargetName="marioImage" Storyboard.TargetProperty="Source" />
  4.         </Storyboard>

  5.         <Image Name="marioImage">
  6.             <Image.RenderTransform>
  7.                 <TranslateTransform x:Name="marioTranslate"  X="0" Y="0"/>
  8.             </Image.RenderTransform>
  9.         </Image>
复制代码
然后用C#代码添加帧:
  1.         public static System.Drawing.Bitmap LoadBitmap(Uri uri)
  2.         {
  3.             StreamResourceInfo info = Application.GetResourceStream(uri);
  4.             return new System.Drawing.Bitmap(info.Stream);
  5.         }

  6.         public static BitmapSource CreateBitmapSource(System.Drawing.Bitmap frame)
  7.         {
  8.             BitmapSource bs = System.Windows.Interop.Imaging.CreateBitmapSourceFromHBitmap(
  9.               frame.GetHbitmap(), IntPtr.Zero, Int32Rect.Empty, BitmapSizeOptions.FromEmptyOptions());
  10.             return bs;
  11.         }

  12.         internal static void AddKeyFrames(ObjectAnimationUsingKeyFrames animation, params System.Drawing.Bitmap[] frames)
  13.         {
  14.             double percent = 0;
  15.             double pace = 1.0 / frames.Length;
  16.             foreach (var frame in frames)
  17.             {
  18.                 BitmapSource bs = CreateBitmapSource(frame);
  19.                 animation.KeyFrames.Add(new DiscreteObjectKeyFrame(bs, KeyTime.FromPercent(percent)));
  20.                 percent += pace;
  21.             }
  22.         }
复制代码
使用这种方法需要先把大图在代码里切割成四个小图,由于要用到System.Drawing所以代码不能迁移到Silverlight。

于是我又想了第二种方法,用ImageBrush做控件背景,然后用ObjectAnimationUsingKeyFrames来切换它的 ViewBox。
WPF栏目的最新浏览文章:
• 用WPF实现的折线图
• WPF实现超酷样式按钮
• WPF动态加载obj格式的3D模型
• WPF弹性模拟动画
• 用游戏杆控制 WPF 中三维模型
• WPF学习之数据绑定
• WPF 4 动态覆盖图标(Dynamic Overlay Icon)
• [WPF系列教程] 手把手教你做“咖啡”按钮
• WPF动态改变主题颜色
• Windows Presentation Foundation Unleashed(WPF揭秘)
• Wpf Docking Library--类似于VisualStudio界面风格的类库
• 制作WPF的屏幕保护程序
• WPF 4 DataGrid 控件(进阶篇二)
• Programming WPF , 2nd Edition
• 用WPF制作iPhone模拟器
• WPF学习之使用DataGrid
cobra - 2010-3-4 20:18:00
  1. <Storyboard x:Key="walkLeftStoryboard">
  2.             <ObjectAnimationUsingKeyFrames Duration="00:00:00.4" RepeatBehavior="Forever"
  3.                                           Storyboard.TargetName="marioImage" Storyboard.TargetProperty="CurrentFrame" />
  4.         </Storyboard>

  5.         <game:AnimatedImage x:Name="marioImage" Image="/SuperMario;component/Images/mario.png" CurrentFrame="0, 0, 0.5, 0.5" Width="134" Height="131">
  6.             <game:AnimatedImage.RenderTransform>
  7.                 <TranslateTransform x:Name="marioTranslate"  X="0" Y="0"/>
  8.             </game:AnimatedImage.RenderTransform>
  9.         </game:AnimatedImage>
复制代码
用C#代码添加帧:
  1.         internal static void AddKeyFrames(ObjectAnimationUsingKeyFrames animation, Rect[] frames)
  2.         {
  3.             double percent = 0;
  4.             double pace = 1.0 / frames.Length;
  5.             foreach (var frame in frames)
  6.             {
  7.                 animation.KeyFrames.Add(new DiscreteObjectKeyFrame(frame, KeyTime.FromPercent(percent)));
  8.                 percent += pace;
  9.             }
  10.         }
复制代码
AnimatedImage是一个自定义的控件,控件模板如下:
  1.     <Style TargetType="local:AnimatedImage">
  2.         <Setter Property="Template">
  3.             <Setter.Value>
  4.                 <ControlTemplate TargetType="local:AnimatedImage">
  5.                     <Border BorderThickness="0">
  6.                         <Border.Background>
  7.                             <ImageBrush ImageSource="{Binding Path=Image,RelativeSource={RelativeSource TemplatedParent}}"
  8.                                         Stretch="UniformToFill" AlignmentX="Left" AlignmentY="Top" Viewbox="{Binding Path=CurrentFrame,RelativeSource={RelativeSource TemplatedParent}}"/>
  9.                         </Border.Background>
  10.                     </Border>
  11.                 </ControlTemplate>
  12.             </Setter.Value>
  13.         </Setter>
  14.     </Style>
复制代码
后来发现SliverLight里的TileBrush没有ViewBox属性,所以还是无法迁移。

接下来就是在GameLoop中根据键盘按键控制动画的开始和停止,并用marioTranslate来改变人物的位置。

GameLoop可由CompositionTarget.Rendering事件指定:
  1.         GameLoop gameLoop;
  2.         private void Window_Loaded(object sender, RoutedEventArgs e)
  3.         {     
  4.             ......
  5.             gameLoop = new GameLoop(player, Scenes.Level1);
  6.             CompositionTarget.Rendering += new EventHandler(CompositionTarget_Rendering);
  7.         }

  8.         void CompositionTarget_Rendering(object sender, EventArgs e)
  9.         {
  10.             gameLoop.ProcessChanges();
  11.         }
复制代码
在GameLoop中还需要注意的就是跳跃过程中重力效果的模拟和对物体、台阶、边界的碰撞检查,这个就不多说了,看代码:
  1.         public void ProcessChanges()
  2.         {
  3.             TimeSpan timeSpan = DateTime.Now - lastTime;
  4.             double step = timeSpan.TotalSeconds;
  5.             lastTime = DateTime.Now;

  6.             double x = Sprite.X;
  7.             double y = Sprite.Y;
  8.             double dx = step * Sprite.Speed;
  9.             if (Sprite.IsWalkingLeft)
  10.             {
  11.                 x -= dx;
  12.                 Scene.ScrollRightt(x, dx);
  13.             }
  14.             else if (Sprite.IsWalkingRight)
  15.             {
  16.                 x += dx;
  17.                 Scene.ScrollLeft(x, dx);
  18.             }
  19.             if (Map.CanMoveTo(x, Sprite.Y, Sprite.Width, Sprite.Height))
  20.             {
  21.                 Sprite.X = x;
  22.             }

  23.             if (Sprite.IsJumping)
  24.             {
  25.                 y -= (1 - Sprite.JumpTime) * step * 400;
  26.                 if (Sprite.JumpTime < 1 && Map.CanMoveTo(Sprite.X, y, Sprite.Width, Sprite.Height))
  27.                 {
  28.                     Sprite.Y = y;
  29.                     Sprite.JumpTime += step;
  30.                 }
  31.                 else
  32.                 {
  33.                     Sprite.IsJumping = false;
  34.                     Sprite.IsFalling = true;
  35.                     Sprite.JumpTime = 0;
  36.                 }
  37.             }
  38.             else if (Sprite.IsFalling)
  39.             {
  40.                 y += 800 * Sprite.FallTime * step;
  41.                 if (Map.CanMoveTo(Sprite.X, y, Sprite.Width, Sprite.Height))
  42.                 {
  43.                     Sprite.Y = y;
  44.                     Sprite.FallTime += step;
  45.                 }
  46.                 else
  47.                 {
  48.                     Sprite.IsFalling = false;
  49.                     Sprite.FallTime = 0;
  50.                 }
  51.             }
  52.             else
  53.             {
  54.                 y += 1;
  55.                 if (Map.CanMoveTo(Sprite.X, y, Sprite.Width, Sprite.Height))
  56.                 {
  57.                     Sprite.Y = y;
  58.                     Sprite.IsFalling = true;
  59.                     Sprite.FallTime = step;
  60.                 }
  61.             }
  62.         }
复制代码
下一步

下一步我打算用XAML矢量图来做动画,场景物体等也全都用矢量图,这样的好处一是可以任意放大缩小,二是动画效果会更加流畅一些。(文/Junfeng Liu
查看完整版本: 用WPF做2D游戏