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

     

  • 相关阅读:
    LC.225. Implement Stack using Queues(using two queues)
    LC.232. Implement Queue using Stacks(use two stacks)
    sort numbers with two stacks(many duplicates)
    LC.154. Find Minimum in Rotated Sorted Array II
    LC.81. Search in Rotated Sorted Array II
    LC.35.Search Insert Position
    前后端分离:(一)
    Redis基本使用(一)
    GIT篇章(二)
    GIT篇章(一)
  • 原文地址:https://www.cnblogs.com/rufi/p/WPFSuperMarioGame.html
Copyright © 2011-2022 走看看