Silverlight MMORPG网页游戏开发课程[一期] 第三课:封装游戏控件

引言

实际游戏开发中我们肯定不会将所有逻辑代码都方在一个文件中,不仅不利于阅读最重要的是非常不利于拓展与重用。面向对象的游戏开发思想告诉我们,是时候对游戏中的对象进行封装了。

3.1通过用户控件(UserControl)封装游戏对象(交叉参考:精灵控件横空出世!①  精灵控件横空出世!②  )

2.2节的基础上首先我们为解决方案添加一个新的项目:Controls(解决方案上右键->添加->新建项目->Silverlight类库)。默认该项目中会包含一个Class1.cs文件,我们删除掉它。(注:解决方案中项目与项目之间交互必须添加引用,例如在MainPage中要使用Controls项目中的控件,那么我们需要在MainPage所在的项目上点右键->添加引用->项目->Controls->确定;后续课程还有创建新的项目,使用方法同样。)

关键时刻到了,在刚新建好的Controls项目上点击右键->添加->新建项,此时我们选择Silverlight用户控件并取名为SpriteSprite(精灵) - 游戏中第一个伟大的对象控件诞生了。

当然首先要做的还是把Sprite.xaml中的Grid换成Canvas,然后将2.2中关于精灵的所有逻辑代码均放进Sprite控件中以实现独立封装,转移及修改内容如下:

1)同创建Controls项目一样的方式创建一个游戏逻辑类库Logic,接下来在其中再新建一个名Enum的文件夹以保存枚举类型,创建两个枚举类分别为:SpriteDirection.cs(精灵朝向)SpriteState.cs(精灵状态)

     ///   <summary>
    
///  精灵朝向
    
///   </summary>
     public   enum  SpriteDirection {
        North 
=   ,
        NorthEast 
=   1 ,
        East 
=   2 ,
        SouthEast 
=   3 ,
        South 
=   4 ,
        SouthWest 
=   5 ,
        West 
=   6 ,
        NorthWest 
=   7
    }
     ///   <summary>
    
///  精灵状态
    
///   </summary>
     public   enum  SpriteState {
        
///   <summary>
        
///  站立(停止)
        
///   </summary>
        Stand  =   ,
        
///   <summary>
        
///  跑动(移动)
        
///   </summary>
        Run  =   1 ,
        
///   <summary>
        
///  攻击(物理)
        
///   </summary>
        Attack  =   2 ,
        
///   <summary>
        
///  施法(魔法)
        
///   </summary>
        Casting  =   3 ,
        
///   <summary>
        
///  无
        
///   </summary>
        None  =   9 ,
    }

    枚举类型不仅直观而且使用起来非常方便,后面的课程大家会逐渐感受到它非凡的魔力。

2)移植原先MainPage中关于精灵的所有属性到Sprite控件内部并公开:

代码
         #region  属性

        
///   <summary>
        
///  获取或设置速度系数
        
///   </summary>
         public   double  Speed {  get set ; }

        
///   <summary>
        
///  获取或设置脚底中心
        
///   </summary>
         public  Point Center {  get set ; }

        
///   <summary>
        
///  获取或设置朝向
        
///   </summary>
         public  SpriteDirection Direction {  get set ; }

        
///   <summary>
        
///  获取或设置状态
        
///   </summary>
         public  SpriteState State {  get set ; }

        
#endregion

    3)移植原先MainPage中的精灵动作动画计时器及相关实行方法到精灵内部:

代码
        Image body  =   new  Image();

        DispatcherTimer dispatcherTimer 
=   new  DispatcherTimer() {
            Interval 
=  TimeSpan.FromMilliseconds( 120 )
        };

        
public  Sprite() {
            
this .Children.Add(body);
            
Stand();
            dispatcherTimer.Tick 
+=   new  EventHandler(dispatcherTimer_Tick);
            dispatcherTimer.Start();
        }

        
int  currentFrame, startFrame, endFrame;
        
void  dispatcherTimer_Tick( object  sender, EventArgs e) {
            
if  (currentFrame  >  endFrame) { currentFrame  =  startFrame; }
            
body.Source  =   new  BitmapImage( new  Uri( string .Format( @" /{0};component/Res/Sprite/{1}-{2}-{3}.png " , Global.ProjectName, ( int )State, ( int )Direction, currentFrame), UriKind.Relative));
            currentFrame
++ ;
        }
 
       #region 方法

        
/// <summary>
        
/// 计算当前坐标与目标点之间的正切值以获取朝向
        
/// </summary>
        
/// <param name="current">当前坐标</param>
        
/// <param name="target">目标坐标</param>
        
/// <returns>朝向代号</returns>
        public void SetDirection(Point current, Point target) {
            
double tan = (target.Y - current.Y) / (target.X - current.X);
            
if (Math.Abs(tan) >= Math.Tan(Math.PI * 3 / 8&& target.Y <= current.Y) {
                Direction 
= SpriteDirection.North;
            } 
else if (Math.Abs(tan) > Math.Tan(Math.PI / 8&& Math.Abs(tan) < Math.Tan(Math.PI * 3 / 8&& target.X > current.X && target.Y < current.Y) {
                Direction 
= SpriteDirection.NorthEast;
            } 
else if (Math.Abs(tan) <= Math.Tan(Math.PI / 8&& target.X >= current.X) {
                Direction 
= SpriteDirection.East;
            } 
else if (Math.Abs(tan) > Math.Tan(Math.PI / 8&& Math.Abs(tan) < Math.Tan(Math.PI * 3 / 8&& target.X > current.X && target.Y > current.Y) {
                Direction 
= SpriteDirection.SouthEast;
            } 
else if (Math.Abs(tan) >= Math.Tan(Math.PI * 3 / 8&& target.Y >= current.Y) {
                Direction 
= SpriteDirection.South;
            } 
else if (Math.Abs(tan) > Math.Tan(Math.PI / 8&& Math.Abs(tan) < Math.Tan(Math.PI * 3 / 8&& target.X < current.X && target.Y > current.Y) {
                Direction 
= SpriteDirection.SouthWest;
            } 
else if (Math.Abs(tan) <= Math.Tan(Math.PI / 8&& target.X <= current.X) {
                Direction 
= SpriteDirection.West;
            } 
else if (Math.Abs(tan) > Math.Tan(Math.PI / 8&& Math.Abs(tan) < Math.Tan(Math.PI * 3 / 8&& target.X < current.X && target.Y < current.Y) {
                Direction 
= SpriteDirection.NorthWest;
            } 
else {
                Direction 
= ;
            }
        }

        
/// <summary>
        
/// 获取Silverlight项目名(或许还有更直接的办法)
        
/// </summary>
        
/// <returns>Silverlight项目名</returns>
        string ProjectName() {
            
return Application.Current.GetType().Assembly.FullName.Split(',')[];
        }

        
#endregion

    42.2中通过Storyboard分别对精灵进行XY方向的同时位移以实现精灵移动(每次用到两个doubleAnimation),其实2D坐标本身就是Point,那么我们完全可以通过一个PointAnimation来取代两个doubleAnimation,当然前提是创建一个可以为Storyboard动画所识别的坐标关联属性 - Coordinate

         ///   <summary>
        
///  获取或设置坐标(关联属性,又称:依赖属性)
        
///   </summary>
         public  Point Coordinate {
            
get  {  return  (Point)GetValue(CoordinateProperty); }
            
set  { SetValue(CoordinateProperty, value); }
        }
        
public   static   readonly  DependencyProperty CoordinateProperty  =  DependencyProperty.Register(
            
" Coordinate " ,
            
typeof (Point),
            
typeof (Sprite),
            
new  PropertyMetadata(ChangeCoordinateProperty)
        );
        
static   void  ChangeCoordinateProperty(DependencyObject d, DependencyPropertyChangedEventArgs e) {
            Sprite sprite 
=  (Sprite)d;
            Point p 
=  (Point)e.NewValue;
            Canvas.SetLeft(sprite, p.X 
-  sprite.Center.X);
            Canvas.SetTop(sprite, p.Y 
-  sprite.Center.Y);
            Canvas.SetZIndex(sprite, (
int )p.Y);
        }

    你问我这些语句是什么意思,我明确告诉你是固定格式,你依葫芦画瓢就OK了。当然如有兴趣想深入了解的朋友可以去MSDNSilverlight4文档查阅相关资料。

5)用面向对象的思维定义精灵动作:

代码
         #region  动作

        
///   <summary>
        
///  站立
        
///   </summary>
         public   void  Stand() {
            currentFrame 
=  startFrame  =   ;
            State 
=  SpriteState.Stand;
            endFrame 
=   3 ;
        }

        
///   <summary>
        
///  跑动
        
///   </summary>
         void  Run() {
            
if  (State  !=  SpriteState.Run) {
                currentFrame 
=  startFrame  =   ;
                State 
=  SpriteState.Run;
                endFrame 
=   5 ;
            }
        }

        Storyboard storyboard 
=   new  Storyboard();
        
///   <summary>
        
///  直线向目的地跑动
        
///   </summary>
        
///   <param name="destination"> 目标点 </param>
         public   void  RunTo(Point destination) {
            SetDirection(Coordinate, destination);
            
int  duration  =  Convert.ToInt32(Math.Sqrt(Math.Pow((destination.X  -  Coordinate.X),  2 +  Math.Pow((destination.Y  -  Coordinate.Y),  2 ))  *  Speed);
            PointAnimation animation 
=   new  PointAnimation() {
                
// 省略From默认取当前值为起点
                To  =  destination,
                Duration 
=   new  Duration(TimeSpan.FromMilliseconds(duration)),
            };
            Storyboard.SetTarget(animation, 
this );
            Storyboard.SetTargetProperty(animation, 
new  PropertyPath( " Coordinate " ));
            storyboard.Pause();
            storyboard.Completed 
-=  storyboard_Completed;
            storyboard 
=   new  Storyboard();
            storyboard.Children.Add(animation);
            storyboard.Completed 
+=   new  EventHandler(storyboard_Completed);
            storyboard.Begin();
            Run();
        }

        
void  storyboard_Completed( object  sender, EventArgs e) {
            Stand();
        }

        
#endregion

    到此,我们完成了精灵控件从MainPage中剥离出来如此庞大一个工程。

Silverlight MMORPG网页游戏开发课程[一期] 第三课:封装游戏控件

然而辛苦是值得的,好处可想而知。在MainPage中我们只需通过类似如下方式即可轻松创建出带任意参数的精灵,敲上短短一行代码hero.RunTo(destination)即可指挥精灵向目标处移动而无须考虑任何其他细节逻辑,面向对象为我们带来编码上的简洁易用优势由此得以很好体现:

         Sprite hero  =   new  Sprite() {
            
Speed  =   4 ,
            
Center  =   new  Point( 75 120 ),
            
Direction  =  SpriteDirection.South,
            
Coordinate  =   new  Point( 100 100 ),
        
};

        
public  MainPage() {
            InitializeComponent();
            LayoutRoot.Children.Add(hero);
            LayoutRoot.MouseLeftButtonDown 
+=   new  MouseButtonEventHandler(LayoutRoot_MouseLeftButtonDown);
        }

        
void  LayoutRoot_MouseLeftButtonDown( object  sender, MouseButtonEventArgs e) {
            Point destination 
=  e.GetPosition(LayoutRoot);
            
hero.RunTo(destination);
        }

 

3.2通过类(Class)封装游戏对象(交叉参考:一切起源于这个真实的世界 从零开始搭建游戏主体框架 面向对象的思想塑造游戏对象 )

作为用户控件的Sprite.xaml继承自UserControl,由于C#单继承的特性,我们很难对其进一步的抽象或衍生,这有背于面向对象的开发思想。

那么我们是否可以以纯类文件的形式来描述精灵控件呢?这是当然的。

只需在Controls项目上右键->添加->新建项->选择类即可。这时我们将3.1中的Sprite.xaml.cs中的所有代码全部复制进这个新类中,并且让该类暂时先继承自Canvas(相当于自身就是一个LayoutRoot),那么剩下需要修改的地方一会功夫就可轻松搞定。最后删除掉Sprite.xaml控件,将这个新类重新命名为Sprite.cs,这才是游戏中对象控件的最终形态。

另外,Silverlight于客户端运行这一特性提示我们大可放心的使用静态类及方法。本节中我在Logic项目中新加入一个名为Global.cs的静态类,它将用于游戏后续开发中一切全局变量及方法的存放,为游戏开发提供更大便利。当然本节我暂时只是将项目名变量获取方法由Sprite控件转移到其内部:

     ///   <summary>
    
///  全局
    
///   </summary>
     public   static   class  Global {

        
///   <summary>
        
///  游戏项目名
        
///   </summary>
         public   static   string  ProjectName  =  Application.Current.GetType().Assembly.FullName.Split( ' , ' )[ ];

    }

 

 

Silverlight MMORPG网页游戏开发课程[一期] 第三课:封装游戏控件

本课小结:本节介绍了两种封装游戏对象控件的方案,通过UserControl为载体的游戏控件具备XAML表现层及CodeBehind逻辑层,非常有利于美工(Blend)加程序(VS)协同合作的开发环境,唯一缺点就是无法很好的扩展;以继承自CanvasClass为载体的游戏控件可以充分激发程序员们的想像与创造力,基于优秀架构的设计是高性能游戏所必不可少的基石。

本课源码:点击进入目录下载

课后作业

作业说明

参考资料:中游在线[WOWO世界] Silverlight C# 游戏开发:游戏开发技术

教程Demo在线演示地址http://silverfuture.cn


原文链接: http://www.cnblogs.com/alamiye010/archive/2010/07/29/1788257.html

你可能感兴趣的:(Silverlight MMORPG网页游戏开发课程[一期] 第三课:封装游戏控件)