• 用WPF做2D游戏


    引言

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

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

    实例

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

    下面是游戏截图:

    实现

    mario.png

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

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

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

     

  • 相关阅读:
    性能问题分析-OOM内存溢出
    JVM介绍及参数配置
    性能问题分析-CPU偏高
    性能测试常见术语浅析
    性能测试中TPS上不去的几种原因浅析
    MyBatis拦截器:给参数对象属性赋值
    springboot读取配置文件的顺序
    ElasticSearch中文分词
    springboot和ELK搭建配置详情
    java命令行介绍
  • 原文地址:https://www.cnblogs.com/rufi/p/WPFSuperMarioGame.html
Copyright © 2020-2023  润新知