• 表驱动


    在做简单2D RPG游戏时,可能会遇到NPC与玩家对话的情况。

    如何判定NPC能否和玩家对话,一般有两个条件:
    1. NPC在玩家附近(地图中相邻的格子中)
    2. NPC和玩家面对面

    通常会写如下代码:

     1 enum SpriteDirection
     2 {
     3     UP,
     4     DOWN,
     5     LEFT,
     6     RIGHT,
     7 }
     8 
     9 public bool CanTalk(NPC npc)
    10 {
    11     return Nearest(npc) && FaceToFace(npc);
    12 }
    13 
    14 public bool Nearest(npc)
    15 {
    16     //省略, 通常直接使用地图的二维数组判断
    17 }
    18 
    19 public bool FaceToFace(NPC npc)
    20 {
    21     if(this.Direction == SpriteDirection.UP && npc.Direction == SpriteDirection.DOWN)
    22     {
    23         return true;
    24     }
    25     if(this.Direction == SpriteDirection.DOWN && npc.Direction == SpriteDirection.UP)
    26     {
    27         return true;
    28     }
    29     if(this.Direction == SpriteDirection.LEFT && npc.Direction == SpriteDirection.RIGHT)
    30     {
    31         return true;
    32     }
    33     if(this.Direction == SpriteDirection.RIGHT && npc.Direction == SpriteDirection.LEFT)
    34     {
    35         return true;
    36     }
    37     return false;
    38 }

    我们看看FaceToFace函数虽然明了,但是写得实在是长了点,其实我们可以利用一点手段进行简化:

    public bool FaceToFace(NPC npc)
    {
        int []direction = new int[4] = {-1,1,-2,2};
        return direction[(int)this.Direction] + direction[(int)npc.Direction] == 0;
    }

    其实当时没知道这叫表驱动的时候就想到这种写法了(当时很自豪0m0),但是在可读性却比原先函数低,就看各位如何取舍了。

    含有就是FaceToFace和Nearest函数可以写在一齐,一步到位:

    首先我们要知道下地图提供的API

    class Map
    {
        public int GetRow();                        // 返回整个地图的行数
        public int GetColumn();                        // 返回整个地图的列数
        public int GetIndex(int row, int col);                 // 根据二维数组(坐标)返回一维数组的索引
        public void GetRowColumn(int index, ref int row, ref int col);    // 根据一维数组索引返回二维数组坐标
        public int GetSpriteIndex(Sprite sprite);             // Sprite 派生出 玩家类 和NPC类
    }
    // 玩家类
    public bool CanTalk(NPC npc)
    {
        int []direction = new int[4]{-map.GetColumn(), map.GetColumn(), -1, 1};
        int myIndex = map.GetSpriteIndex(this);
        int npcIndex = map.GetSpriteIndex(npc);
        return myIndex + direction[(int)this.Direction] == npcIndex &&
            npcIndex + direction[(int)npc.Direction] == myIndex;
        // 当然还有越界问题需要处理, 例如npc在上一行最右,你在本行最左等问题,只要他们的行列相减的绝对值再相加等于1就行了.
    }

     再举一个有关数学的例子,如图:

    AB是一线段,长度不定(大于0),从AB中截取长度为10的点AC,如果AB长度<10,则是延长AB到AC。
    其中AB与粉线交点为X(也有 没有交点的情况),点P到AB的垂点为P'(也有 没有垂点的情况),求AC、AX、AP'三条线段中最短的一条。(P点会移动,粉红色线也会移动)

    很多时候会求出AX的距离,然后判断是否小于10,是则替换点,再是AP'与最短点比较。。。(讲的比较模糊,直接上代码)

    vec3f D; // 与A距离最近点
    vec3f A;
    vec3f B;
    
    vec3f C = B-A;
    C.SetLength(10); // 先设置长度为10
    C += A; //还原成图中C点
    
    D = C; //先默认设置为C为比较主体
    
    vec3f X;
    if(粉线与AB有交点(ref X)) // 如果检测到有交点,则修改X为交点坐标
    {
        if(Distance(A,D) > Distance(A,X)) //Distance是求两点距离函数
        {
            D = X;
        }
    }
    
    vec3f PP;
    if(P点与AB有垂点(ref PP))
    {
        if(Distance(A,D) > Distance(A,PP))
        {
            D = PP;
        }
    }
    
    return D;

    上面代码中直接默认AC 10 为相比较主体。其实可以发现AC、AP'、AX 三个点是同等关系,是不分上下的,用表驱动可以写成是如下形式(没有相比较主体):

    vec3f A;
    vec3f B;
    
    bool []hit = new bool[3]; //是否有焦点
    vec3f []pos = new vec3f[3]; 
    
    hit[0] = true;
    pos[0] = B-A;
    pos[0].SetLength(10);
    pos[0] += A;
    
    hit[1] = 粉线与AB有交点(ref pos[1]);
    hit[2] = P点与AB有垂点(ref pos[2]);
    
    vec3f D; // 与A距离最近点
    bool h = false;
    for(int i=0;i<3;++i)
    {
        if(!h && hit[i])
        {
            h = true;
            D = pos[i];
        }
        
        if(hit[i])
        {
            if(Distance(A,D) > Distance(A,pos[i]))
            {
                D = pos[i];
            }
        }
    }
    Assert(h);
    return D;

    虽然下面的代码比较长,但是我还是比较喜欢下面这种方法~


    以前的空间日志:

    表驱动:

    人类阅读复杂数据结构远比复杂的控制流程容易,或者说数据驱动开发是非常有价值的。《代码大全2》声称这个是表驱动法。[参考:http://dennis-zane.javaeye.com/blog/183886]

    [参考:http://www.cnblogs.com/shinn/archive/2008/04/16/1157141.html]

    //如果if else语句嵌套到都不知道最里面的if代表什么的时候,switch长到很牛*的时候就可以用表查询了。

    //表驱动法的本质是时间与空间的转换,即利用空间来换取时间,博主完全可以再深入的分析什么时候用表驱动,什么时候不应该用,怎么用才最好等等.

    //当你发现,大量重复的代码写到你想要问候别人长辈的时候,就可以考虑使用表驱动法了。

    简单例子:

     String _1 = "星期一";
     String _2 = "星期二";
     String _3 = "星期三";
     String _4 = "星期四";
     String _5 = "星期五";
     String _6 = "星期六";
     String _7 = "星期日";
     
     String GetDay(int day){
    
      if(day == 0){
       return _1;
      }
      if(day == 1){
       return _2;
      }
      //....
      throw new RuntimeException();
     }
    
    /////////////////////////////////////////////////////////////////
    
    //改进后:
    
     char[] days = "一二三四五六日".toCharArray();
     String GetDay(int day){
      return "星期"+days[day-1];
     }

     其他参考:“表驱动”那点事儿。。。 http://www.cnblogs.com/lijian2010/archive/2010/12/16/1908374.html

  • 相关阅读:
    【BZOJ】2237: [NCPC2009]Flight Planning
    【BZOJ】2216: [Poi2011]Lightning Conductor
    实用工具/学习网站
    windows下MongoDB的安装及配置
    Error:(4, 25) java: 找不到符号符号:类 xxx位置:程序包 xxx.xxx
    java实现经典排序算法
    Java使用RSA加密解密签名及校验
    Java实现数字签名
    The bean 'xxx' could not be injected as a 'com.zp.demo.service.xxx' because it is a JDK dynamic proxy that implements: com.zp.demo.service.ReportService
    springboot+shiro 跨域解决(OPTIONS)
  • 原文地址:https://www.cnblogs.com/godzza/p/3001165.html
Copyright © 2020-2023  润新知