zoukankan      html  css  js  c++  java
  • 表驱动

    在做简单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

  • 相关阅读:
    yii2 gii 命令行自动生成控制器和模型
    控制器中的方法命名规范
    Vue Property or method "" is not defined on the instance but referenced during render. Make sure that this property is reactive, either in the data option, or for class-based
    IDEA插件:GsonFormat
    Spring Boot : Access denied for user ''@'localhost' (using password: NO)
    Typora添加主题
    Git基础命令图解
    Java Joda-Time 处理时间工具类(JDK1.7以上)
    Java日期工具类(基于JDK1.7版本)
    Oracle SQL Developer 连接Oracle出现【 状态: 失败 -测试失败: ORA-01017: invalid username/password; logon denied】
  • 原文地址:https://www.cnblogs.com/godzza/p/3001165.html
Copyright © 2011-2022 走看看