用WPF做2D游戏

引言

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

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

实例

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

下面是游戏截图:

实现

mario.png

行走中迈腿摆臂的动画是通过切换图片帧来实现的,切换帧有两种方法,一种是放一个Image,然后用ObjectAnimationUsingKeyFrames来改变Image的Source属性:

ExpandedBlockStart.gif XAML代码
         < 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#代码添加帧: 

ExpandedBlockStart.gif 代码
         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。  

ExpandedBlockStart.gif 代码
         < 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#代码添加帧:

ExpandedBlockStart.gif 代码
         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中还需要注意的就是跳跃过程中重力效果的模拟和对物体、台阶、边界的碰撞检查,这个就不多说了,看代码:

ExpandedBlockStart.gif 代码
         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矢量图来做动画,场景物体等也全都用矢量图,这样的好处一是可以任意放大缩小,二是动画效果会更加流畅一些。

 

 

转载于:https://www.cnblogs.com/rufi/archive/2010/03/04/WPFSuperMarioGame.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值