游戏中战斗伤害范围-弹道飞行
回顾前瞻
在上一篇文章《游戏中战斗伤害范围攻击计算完整全版》我们计算了扇形,多边形,圆形,等伤害范围获取。
但是前天的多边形规整计算中,我发现一个问题,就是在获取多边形判断的时候,总有验证不足的情况,也就是未包含出现!
最后百度几何原理,得到一个算法
1 /*我们可以把多边形可以看做是一条从某点出发的闭合路,可以观察到在内部的点永远都在路的同一边。 2 给定线段的两个点P0(x0,y0)和P1(x1,y1),目标点P(x,y),它们有如下的关系: 3 计算(y - y0) (x1 - x0) - (x - x0) (y1 - y0) 4 如果答案小于0则说明P在线段的右边,大于0则在左边,等于0说明在线段上。 5 */
更简单便捷的方式,支持浮点数计算:
1 /** 2 * 验证点在多边形内 3 * 4 * @param x 5 * @param z 6 * @return 7 */ 8 public boolean contains(double x, double z) { 9 /*我们可以把多边形可以看做是一条从某点出发的闭合路,可以观察到在内部的点永远都在路的同一边。 10 给定线段的两个点P0(x0,y0)和P1(x1,y1),目标点P(x,y),它们有如下的关系: 11 计算(y - y0) (x1 - x0) - (x - x0) (y1 - y0) 12 如果答案小于0则说明P在线段的右边,大于0则在左边,等于0说明在线段上。 13 */ 14 double p1x = 0, p1z = 0, p2x = 0, p2z = 0, ret = 0; 15 for (int i = 0; i < pointCount; i++) { 16 p1x = pointXs[i]; 17 p1z = pointZs[i]; 18 if (i == pointCount - 1) { 19 p2x = pointXs[0]; 20 p2z = pointZs[0]; 21 } else { 22 p2x = pointXs[i + 1]; 23 p2z = pointZs[i + 1]; 24 } 25 double ss = sq(p1x, p1z, p2x, p2z, x, z); 26 if (ss != 0) { 27 /*答案小于0则说明P在线段的右边,大于0则在左边,等于0说明在线段上。*/ 28 if (ret != 0) { 29 /*如果不是0,表示方向反向了*/ 30 if ((ss > 0 && ret < 0) || (ss < 0 && ret > 0)) { 31 return false; 32 } 33 } 34 ret = ss; 35 } 36 } 37 return true; 38 } 39 40 double sq(double p1x, double p1z, double p2x, double p2z, double x, double z) { 41 return (z - p1z) * (p2x - p1x) - (x - p1x) * (p2z - p1z); 42 }
再次验证测试
1 public static void main(String[] args) { 2 double px = 2.901; 3 double py = 3.001; 4 PolygonCheck polygonCheck = new PolygonCheck(4); 5 polygonCheck.add(2, 2); 6 polygonCheck.add(3, 2); 7 polygonCheck.add(3, 5); 8 polygonCheck.add(2, 5); 9 System.out.println(polygonCheck.contains(3, 5.001)); 10 System.out.println(polygonCheck.contains(3, 5)); 11 System.out.println(polygonCheck.contains(2.5, 4)); 12 }
结果
1 --- exec-maven-plugin:1.2.1:exec (default-cli) @ net.sz.game.engine --- 2 false 3 true 4 true 5 ------------------------------------------------------------------------
这时修正过后的验证算法,具体实现请把函数复制到上一篇《游戏中战斗伤害范围攻击计算完整全版》文章 PolygonCheck 类中就ok了
====================================华丽丽分割线==================================
弹道飞行前奏
划分弹道飞行:
1,普通弹道飞行,计算飞行距离锁定目标伤害;
2,真实的弹道飞行,计算飞行碰撞目标伤害;
第二类:
2.1:飞行弹道是多边形,直线飞行;
2.2:飞行弹道是扇形,直线飞行;
2.3:飞行弹道是圆形,直线飞行;
当然上面提到的都可以是曲线,赛尔线,等飞行情况;
但是都是非常复杂的飞行方式。可以一步一步参考修改;
普通弹道飞行计算
最简单的计算法则规则是:策划配置弹道飞行速度(S)飞行的距离(R);计算飞行时间;时间(T)
普通弹道飞行,只计算飞行时间,得到延迟伤害计算:
T=R*1000/S;得到基本飞行时间,加入伤害定时器,延迟处理就行了,法师的普通伤害从技能放出,特效飞行开始延迟T计算技能伤害;
当然这里的T应该还要加上从释放技能开始动作释放时间,T+SKill_T(技能动作持续释放时间,以后才是弹道飞行);才是真正的技能弹道飞行时间;
范围弹道飞行计算
弹道飞行必须处理好,技能攻击对象数量总和,以及已经攻击过的对象id集合存储;
范围伤害弹道飞行;
圆形的飞行弹道范围计算,其实也是最简单的,就是模拟移动位置就是了;
只需要改变原点A的位置就可以了;每一次得到攻击对象,都判断点位到A的距离是否小于等于R;
弹道飞行复杂计算
弹道飞行处理中;
扇形计算,扇形弹道飞行,原点是不变化位置,只计算飞行R,R1(V1扇形),R2(V2),每一次飞攻击范围为R2-R1(V1到V2之间);知道飞行R2大于R,
多边形计算弹道飞行的时候必须要移动多边形,那就是要移动多边形的中心点才能得到新的多边形;
多边形和扇形的计算公式和注意事项,下面会有点展示;
PS,第一次移动的多边形外顶点(C,D)是下一次多边形的内定的(B,A)的起始点;
必须要记录弹道飞行时间,处理时间,飞行速度,飞行距离和上一次飞行距离;
/*飞行的总距离 除以 飞行时间,得到每一单位时间飞行的距离,单位时间ms*/
伤害定时器测试类
1 package net.sz.tmp; 2 3 import java.util.HashSet; 4 import net.sz.game.engine.navmesh.Vector3; 5 import net.sz.game.engine.struct.PolygonCheck; 6 import net.sz.game.engine.struct.Vector; 7 import net.sz.game.engine.thread.TimerTaskEvent; 8 import net.sz.game.engine.utils.MoveUtil; 9 import org.apache.log4j.Logger; 10 11 /** 12 * 伤害计算定时器 13 * <br> 14 * author 失足程序员<br> 15 * mail 492794628@qq.com<br> 16 * phone 13882122019<br> 17 */ 18 public class HitTimer extends TimerTaskEvent { 19 20 private static final Logger log = Logger.getLogger(HitTimer.class); 21 /*飞行速度 每一毫秒*/ 22 private float flySpeedMS = 0; 23 /*上一次飞行距离 计算*/ 24 private float upFlyDis = 0f; 25 /*最后一次飞行距离 计算*/ 26 private float lastFlyDis = 0f; 27 /*最后一次飞行时间计算时间差 计算*/ 28 private long lastFlyTime = 50; 29 private int attackMAX = 5; 30 private HashSet<Long> attackIds = null; 31 /*hittimer 执行间隔时间*/ 32 private int[] flyArray; 33 /*定时器执行间隔时间循环次数*/ 34 private int flyCount = 0; 35 /*测试默认飞行距离都是5米*/ 36 private float limit2 = 5; 37 /*目标中心点*/ 38 private Vector3 center; 39 /*当前朝向*/ 40 private Vector centerdir; 41 42 public HitTimer(Vector3 center, Vector centerdir, int actionCount, int forCount, boolean removeZore, float flyspeed, int[] intervalTime) { 43 super(actionCount, intervalTime[0]); 44 45 this.flyCount = forCount; 46 /*飞行的总距离 除以 飞行时间,得到每一单位时间飞行的距离,单位时间ms*/ 47 this.flySpeedMS = limit2 / (limit2 * 1000f / flyspeed); 48 this.center = center; 49 this.centerdir = centerdir; 50 51 if (removeZore) { 52 /* 53 为什么要除去第0个参数,是因为在初始化伤害计算公式的时候技能动作需要释放时间 54 然后才是弹道飞行时间,当然也不是所有技能都是如此 55 */ 56 flyArray = new int[intervalTime.length - 1]; 57 for (int i = 1; i < intervalTime.length; i++) { 58 flyArray[i - 1] = intervalTime[i]; 59 } 60 } else { 61 flyArray = intervalTime; 62 } 63 } 64 65 /*我这里只写弹道飞行代码*/ 66 @Override 67 public void run() { 68 69 /*把最后一次移动距离赋值给上一次移动距离*/ 70 this.upFlyDis = this.lastFlyDis; 71 /*计算时间差*/ 72 long tmptime = lastFlyTime; 73 if (lastFlyTime > 20000) { 74 tmptime = System.currentTimeMillis() - lastFlyTime; 75 } 76 /* 这里拿到间隔时间应该位移偏移量 */ 77 float mvr = tmptime * flySpeedMS; 78 /*弹道累计飞行距离*/ 79 this.lastFlyDis += mvr; 80 if (this.lastFlyDis >= limit2) { 81 this.lastFlyDis = limit2; 82 /*累计飞行距离已经超过弹道距离*/ 83 this.setCancel(true); 84 } 85 86 /*计算本次剩余能攻击对象数量*/ 87 int qtarget_max = attackMAX; 88 if (this.attackIds != null) { 89 qtarget_max -= this.attackIds.size(); 90 if (qtarget_max <= 0) { 91 /*需要取消该计算公式*/ 92 this.setCancel(true); 93 return; 94 } 95 } 96 /*获取战斗对象*/ 97 { 98 /*例如待验证的对象坐标点*/ 99 double px = 5.2, pz = 6.3; 100 101 { 102 /*扇形*/ 103 double atan360 = 0; 104 /*扇形的边*/ 105 double aTan360_A1 = 0; 106 /*扇形的边*/ 107 double aTan360_A2 = 0; 108 /*默认是60度夹角的扇形 左右偏移30°*/ 109 double skillAngle = 30; 110 /*有角度 为扇形*/ 111 atan360 = centerdir.getAtan360(); 112 aTan360_A1 = MoveUtil.getATan360(atan360, -1 * skillAngle); 113 aTan360_A2 = MoveUtil.getATan360(atan360, skillAngle); 114 /*如果整个扇形有偏移设置 115 if (skillDir != 0) { 116 aTan360_A1 = MoveUtil.getATan360(aTan360_A1, skillDir); 117 aTan360_A2 = MoveUtil.getATan360(aTan360_A2, skillDir); 118 } 119 */ 120 { 121 /* 122 在获取场景对象的时候判断距离由于多边形和圆形存在切面夹角问题 123 所有在获取场景对象的时候放宽2码 124 125 范围攻击 126 getFighters(this.center, this.centerVr, type, skillModel.getQrange_limit2(), this.upFlyDis, this.lastFlyDis, this.attackIds, 127 this.attacker, this.target, fighters, qtarget_max, skillModel.getQangle(), skillModel.getQdir()); 128 得到所有可攻击对象, 129 */ 130 /*获取当前点和目标点 朝向*/ 131 double tmpTan360 = MoveUtil.getATan360(center.getX(), center.getZ(), px, pz); 132 if ((aTan360_A1 > aTan360_A2 && ((aTan360_A1 <= tmpTan360 && tmpTan360 <= 360) || (0 <= tmpTan360 && tmpTan360 <= aTan360_A2))) 133 || (aTan360_A1 > aTan360_A2 && aTan360_A1 <= tmpTan360 && tmpTan360 <= aTan360_A2)) { 134 /*"修正后的夹角:" + aTan360_1 + " ~ 360 和 0 ~" + aTan360_2*/ 135 double distance = MoveUtil.distance(center.getX(), center.getZ(), px, pz); 136 if (!(upFlyDis < distance && distance <= lastFlyDis)) { 137 /* 138 改对象不在攻击扇形范围内 139 return; 140 */ 141 } else { 142 /* 143 记得添加本次可攻击对象到集合 144 this.attackIds.add(0L); 145 */ 146 } 147 } else { 148 /* 149 改对象不在攻击扇形范围内 150 return; 151 */ 152 } 153 } 154 } 155 { 156 /*多边形*/ 157 Float qskillwidth = 3f; //矩形宽度 158 Float qskillhight = 4f; //矩形长度 159 int qoffset = 0; //矩形原点修正值 160 161 if (lastFlyDis > 0) { 162 qoffset += upFlyDis; 163 if (qoffset < 0) { 164 qoffset = 0; 165 } 166 qskillhight = lastFlyDis - qoffset; 167 } 168 169 /*获取矩形范围*/ 170 PolygonCheck rectangle = MoveUtil.getRectangle(centerdir, center.getX(), center.getZ(), qoffset, qskillwidth, qskillhight); 171 /* 172 在获取场景对象的时候判断距离由于多边形和圆形存在切面夹角问题 173 所有在获取场景对象的时候放宽2码 174 double radius = lastFlyDis + 2; 175 176 范围攻击 177 getFighters(this.center, this.centerVr, type, skillModel.getQrange_limit2(), this.upFlyDis, this.lastFlyDis, this.attackIds, 178 this.attacker, this.target, fighters, qtarget_max, skillModel.getQangle(), skillModel.getQdir()); 179 得到所有可攻击对象, 180 */ 181 boolean contains = rectangle.contains(px, pz); 182 if (!contains) { 183 /* 184 改对象不在攻击扇形范围内 185 return; 186 */ 187 } else { 188 /* 189 记得添加本次可攻击对象到集合 190 this.attackIds.add(0L); 191 */ 192 } 193 } 194 } 195 196 if (this.attackIds != null) { 197 /*攻击对象以满足,不在下一次循环执行*/ 198 if (this.attackIds.size() >= attackMAX) { 199 /*需要取消该计算公式*/ 200 this.setCancel(true); 201 } 202 } 203 204 /*设置本次移动时间*/ 205 lastFlyTime = System.currentTimeMillis(); 206 /*设置下一次执行时间*/ 207 if ((flyCount == -1)/*如果是永久执行*/ 208 || (flyCount > 1 && ((getExecCount() / flyArray.length) < flyCount))/*循环次数*/) { 209 this.setIntervalTime(flyArray[getExecCount() % flyArray.length]); 210 } else { 211 this.setCancel(true); 212 } 213 } 214 215 }
测试代码
public static void main(String[] args) { /*假设技能释放中心点*/ Vector3 vector3 = new Vector3(3.56, 5.36); /*假设技能释放朝向*/ Vector v12Vector = MoveUtil.getV12Vector(vector3.getX(), vector3.getZ(), 6.3, 7.2); /*技能释放动作持续时间是500ms,技能弹道飞行间隔执行时间是100ms*/ int[] intervalTime = new int[]{500, 100}; /*默认技能弹道飞行是死循环执行,forcount 用 -1*/ HitTimer hitTimer = new HitTimer(vector3, v12Vector, -1, -1, true, 20, intervalTime); /*内部检查定时器停止工作*/ }
本次测试代码就没有提供测试结果了;
主要是提供思路,提供游戏服务器,在处理伤害计算中,模拟弹道飞行,伤害处理同步机制;
不知道,有没有哪位大神指点指点其他更好的方式:
PS:我们得到所有可攻击对象的时候,由于机制问题,你得到的对象顺序处理的时候,排序是你查找到的第一个对象加入,但是第一个对象也行并非是距离中心点最近的对象
1 if (fighters.size() > maxNum) { //超过技能目标人数上限 2 int size = fighters.size(); 3 Person[] mds = fighters.toArray(new Person[0]); 4 if (attack != null) { 5 //先冒泡排序 ->按照从远到近 6 for (int i = 1; i < mds.length; i++) { 7 for (int j = 0; j < mds.length - i; j++) { 8 Person d1 = mds[j]; 9 Person d2 = mds[j + 1]; 10 if (attack.distance(d1) < attack.distance(d2)) { 11 mds[j] = d2; 12 mds[j + 1] = d1; 13 } 14 } 15 } 16 } 17 int index = 0; 18 while (size > maxNum) { 19 //删除距离最远的目标 20 fighters.remove(mds[index]); 21 size--; 22 index++; 23 } 24 }
所有排序所有对象,删除,最远的对象开始;得到本次可攻击对象目标;
谢谢各位大爷们;
看到这里;
大爷们是不是留个言,或者点个推荐呢???
看在小弟辛辛苦苦,分享的精神上好嘛;
PS:
跪求保留标示符 /** * @author: Troy.Chen(失足程序员, 15388152619) * @version: 2021-07-20 10:55 **/ C#版本代码 vs2010及以上工具可以 java 开发工具是netbeans 和 idea 版本,只有项目导入如果出现异常,请根据自己的工具调整 提供免费仓储。 最新的代码地址:↓↓↓ https://gitee.com/wuxindao 觉得我还可以,打赏一下吧,你的肯定是我努力的最大动力