动手前我们不妨先看一下《曹操传》中所有的战斗范围图例:

unity2D战棋游戏开发设计与实现_unity2D战棋游戏开发设计与实现

见到此类素材大家会否觉得实在亲切。没错,就是我们小时候读书最常玩的找规律数学题。

既然要找出规律,那么就得先分析出其中的共性与变化。最直接的共性便是所有的格子均是“对称”的;而发生变化的则是某些图例似乎都被“挖”去了一些;再往下想,这些被“挖”掉的格子同样也是“对称”的。由此,基于集合的差集运算第一时间浮现在脑海。

规律把握的是否正确离不开证明过程,接下来我们随便以上图中任意几个图例为例加以验证:

unity2D战棋游戏开发设计与实现_图例_02

unity2D战棋游戏开发设计与实现_List_03

unity2D战棋游戏开发设计与实现_图例_04

……

是不是开始有些激动了?

最后的总结:所有“对称”的战斗范围都可以是基于某种规律N格长度的范围与基于另一种规律M格长度范围的差集,其中这两种规律可以相同,N与M也可以相同。由此便可衍生出如文中开头所有的战斗范围,以及更多未列出来的。

有了以上强大的理论依据作为基础,接下来的编码便是手到擒来,我的思路大致如下。

第一步,定义最常用的基本规律(即连续的战斗范围类型):




/// 
     
   <summary> 
   
     
   /// 
    战斗范围类型
     
   /// 
     
   </summary> 
   
 
        
   public 
     
   enum 
    AttackRangeTypes {
         
   /// 
     
   <summary> 
   
         
   /// 
    无
         
   /// 
     
   </summary> 
   
 
           None  
   = 
     
   0 
   ,
         
   /// 
     
   <summary> 
   
         
   /// 
    全八面(方形)
         
   /// 
     
   </summary> 
   
 
           Square  
   = 
     
   1 
   ,
         
   /// 
     
   <summary> 
   
         
   /// 
    斜四面(菱形)
         
   /// 
     
   </summary> 
   
 
           Diamond  
   = 
     
   2 
   ,
         
   /// 
     
   <summary> 
   
         
   /// 
    正四向(十字)
         
   /// 
     
   </summary> 
   
 
           Cross  
   = 
     
   3 
   ,
         
   /// 
     
   <summary> 
   
         
   /// 
    斜四向(交叉)
         
   /// 
     
   </summary> 
   
 
           Oblique  
   = 
     
   4 
   ,
    }


除None外,它们2长度范围分别对应以下图例:

unity2D战棋游戏开发设计与实现_unity2D战棋游戏开发设计与实现_05

第二步,以这些战斗范围(List<Point>)为返回值构造方法:



     

List 
   < 
   Point 
   > 
    GetRange(Point center, AttackRangeTypes attackRangeType,  
   int 
    range) {
            List 
   < 
   Point 
   > 
    points  
   = 
     
   new 
    List 
   < 
   Point 
   > 
   ();
             
   for 
    ( 
   int 
    x  
   = 
     
   - 
   range; x  
   <= 
    range; x 
   ++ 
   ) {
                 
   for 
    ( 
   int 
    y  
   = 
     
   - 
   range; y  
   <= 
    range; y 
   ++ 
   ) {
                     
   switch 
    (attackRangeType) {
                         
   case 
    AttackRangeTypes.None:
                             
   continue 
   ;
                         
   case 
    AttackRangeTypes.Square:
                             
   break 
   ;
                         
   case 
    AttackRangeTypes.Diamond:
                             
   if 
    (Math.Abs(x)  
   + 
    Math.Abs(y)  
   > 
    range) {  
   continue 
   ; }
                             
   break 
   ;
                         
   case 
    AttackRangeTypes.Cross:
                             
   if 
    (Math.Abs(x)  
   != 
     
   0 
     
   && 
    Math.Abs(y)  
   != 
     
   0 
   ) {  
   continue 
   ; }
                             
   break 
   ;
                         
   case 
    AttackRangeTypes.Oblique:
                             
   if 
    (Math.Abs(x)  
   != 
    Math.Abs(y)) {  
   continue 
   ; }
                             
   break 
   ;
                    }
                    points.Add( 
   new 
    Point(center.X  
   + 
    x, center.Y  
   + 
    y));
                }
            }
             
   return 
    points;
        }

         
   /// 
     
   <summary> 
   
         
   /// 
    获取攻击范围坐标列表
         
   /// 
     
   </summary> 
   
         
   /// 
     
   <param name="rangeType"> 
   范围类型 
   </param> 
   
         
   /// 
     
   <param name="range"> 
   范围 
   </param> 
   
         
   /// 
     
   <param name="exclusionRangeType"> 
   排除范围类型 
   </param> 
   
         
   /// 
     
   <param name="exclusionRange"> 
   排除范围 
   </param> 
   
         
   /// 
     
   <returns></returns> 
   
 
            
   public 
    List 
   < 
   Point 
   > 
    AttackRange(AttackRangeTypes rangeType,  
   int 
    range, AttackRangeTypes exclusionRangeType,  
   int 
    exclusionRange) {
            List 
   < 
   Point 
   > 
    points  
   = 
    GetRange(Coordinate, rangeType, range);
            List 
   < 
   Point 
   > 
    excludePoints  
   = 
    GetRange(Coordinate, exclusionRangeType, exclusionRange);
             
   return 
    points.Except(excludePoints).ToList();
        }



第三步,游戏中,将玩家控制的角色坐标Coordinate作为以上方法的Center参数,通过前文提到的组合方式最终完成SLG战棋角色战斗范围动态设定:

unity2D战棋游戏开发设计与实现_Math_06

……

 

unity2D战棋游戏开发设计与实现_List_07



通过动态组合差集的方式设定SLG战棋角色的攻击范围,无论是灵活性、适用性还是拓展性都显得极其强大。当然,每个人的思维方式不一样,写出的算法也会大相径庭,游戏开发的乐趣就在于此:优化永无止尽。

手记思考:当问题出现时若能正确的把握其本质规律,一切都将显得那么的简单。