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