MMORPG大型游戏设计与开发(攻击区域 扇形)

距离上次发布已经有了很长一段时间,期间由于各种原因没有更新这方面的技术分享,在这里深表遗憾。在MMO或其他的游戏中,会有针对各种形状的计算,通常在攻击区域里不会很复杂,常见的为矩形、圆形、扇形。今天分享的是判断一个目标点是否在扇形内的计算,用到的是比较简单的运算,高效率的算法我很尽快更新。

数学知识

   在这里需要大家回顾一下初中以及高中的代数和平面几何的知识,想必大部分的朋友在这方面和我一样几乎忘记了或是对这些数学知识感觉有些头痛。不过大家没有必要担心,在实际运用中,我们都不会涉及太复杂的计算,因为我们不需要追求的十分精确。

  但是在以下内容中,大家需要知道一些基本的定理和公式:勾股定理、余弦定理。

  需要了解弧度和角度的一些转换:角度 = 弧度 * 180.0 / ∏

  一些数学函数:atan2、acos等。

  中心线:以中心线顺、逆时针展开半角,形成一个完整的目标扇形区域。

  极坐标概念:

  如上图坐标点A的平面坐标为(7.96, 5.43),对应的极坐标为(ρ, θ)

  相对坐标的概念:

  在扇形的计算中,我们的X轴与Y轴的方向与原点的的X轴和Y轴平行,在上图中,如果以A点作为中心点,那么B的相对坐标为(xb - xa, yb - ya)。

 扇形与点的关系

  示例图1:

  必要的数据: 原点A(攻击者坐标)、方向点B(direction)、目标点C(point)、扇形角度(β)、扇形的半径(r)。

  扇形的有效区域:

  如上图中的扇形的角度为180°,其有效面积为:以X轴为中心分布的可攻击区域1(area1紫色区域)和可攻击区域2(area2绿色区域)。

  如果目标点的极坐标为(ρ,Θ),那么上述的目标点判断为:目标点到原点A的距离小于扇形的半径(ρ <= r)、在区域1(0 <= Θ <= α + β / 2)或区域2((a - β / 2) + 360 <= Θ <= 360)。

  因为我们的极坐标的范围为(0-360),所以如果算出来的扇形最小的角度为负,需要转换为正来比较。

  从上述的判断中,并没有将所有的情况考虑到,如果需要的扇形角度超过180或者方向点落在不同的象限内,计算的方式是不同的,接下来再看一张示意图。

  示例图2:

  如上图所示,这样的面积计算就不能使用第一种方式,方向的极坐标的角度为315°,而扇形的角度的不过270°,那么α、β、γ到底变成了怎样的关系,我们求出了方向的极坐标是否能把这样两个面积的角度分别计算出来?

  区域1(0 <= Θ <= α + β / 2),如果是上图的数据则为0 <= Θ <= 315 + 270 / 2,范围就是[0,450],如果用词判断则象限2中的空白部分的本不符合的点就符合条件了,其实我们可以看出区域1的实际范围为[0,90],同理区域2的范围为[180,360]。而超过了360°的角度只要减去360,这里的450转换为正常角度则为90刚刚是我们的正确角度。而第一种方式中的区域二的区域为[a - β / 2,360],在上图中刚刚为[180,360]是符合的。

  如何判断一个扇形是否跨域了X轴正方向?

  因为我们的扇形是以中心线分成两个部分,那么只需要判断这两个部分是否超过了就可以,当前如果中心线在X轴正方向时绝对是跨域了两个方向。

  三种跨域的方式:示例图1中(a - β / 2)的值小于零、示例图2中的(α + β / 2)大于360、方向点在X轴正方向上(极坐标的角度Θ为0或绝对坐标X大于0和Y等于0)。

  同时两个扇形的角度需要转换:小于0的角度需要转换为正(示例图1中a - β / 2,加上360)、大于360的角度需要转换为360以内的角度(示例图中的α + β / 2,减去360)。

  那么两个区域就为[0, a + β / 2]、[a - β / 2, 360],需要注意这里的角度都需要进行上述的转换。

  示例图3:

  如果扇形的范围没有跨域X轴正方向,那么角度的范围是[a - β / 2, a + β / 2]。

代码实现

  我们知道了点和面的关系后,就能够对目标点进行判定了,编码实现才有了依据。(上面三种情况是否会有遗漏,暂时没有考虑过,欢迎大家指正)

  基础结构定义:

struct point_struct {
  double x;
  double y;
  point_struct() : x{.0}, y{.0} {}
};
using point_t = point_struct;

  相对坐标转换(没有方向):

/**
 * 没有方向的相对坐标转换,x、y轴的方向与原点相同
 * @param origin 坐标系原点
 * @param point 需要转换的点
 */
/**
 *    Y
 *    |
 *    |      CY
 *    |      |
 *    |      |    .point
 *    |      |
 *    |      |
 *    |  origin----------------- CX
 *    |
 *    |
 *    O--------------------------------------------------- X
 *
 */
point_t absolute_to_relative(const point_t &origin, const point_t &point) {
  point_t result;
  result.x = point.x - origin.x;
  result.y = point.y - origin.y;
  return result;
}

  极坐标转换:

/**
 * 简单极坐标转换(转换后的x为斜边,y为角度),
 * 减少数学函数调用(可以忽略,因为通常情况下这几种情况命中概率极低)
 * @param point 需要转换的点
 */
point_t to_spolar_coordinate(const point_t &point) {
  point_t result;
  result.x = 0;
  result.y = -1;
  if (0 == point.x == point.y) {
    result.y = 0;
    return result;
  }
  if (0 == point.y) {
    result.x = abs(point.x);
    result.y = point.x > 0 ? 0 : 180;
    return result; 
  }
  if (0 == point.x) {
    result.x = abs(point.y);
    result.y = point.y > 0 ? 90 : 270;
    return result;
  }
  if (abs(point.x) == abs(point.y)) {
    result.x = 1.41421 * abs(point.x);
    if (point.x > 0 && point.y > 0) {
      result.y = 45;
    } else if (point.x < 0 && point.y > 0) {
      result.y = 135;
    } else if (point.x < 0 && point.y < 0) {
      result.y = 225;
    } else if (point.x > 0 && point.y < 0) {
      result.y = 315;
    }
  }
  return result;
}
/**
 * 转换为极坐标(转换后的x为斜边,y为角度)
 * @param point 需要转换的点
 */
inline point_t to_polar_coordinate(const point_t &point) {
  point_t result;
  result.x = sqrt(point.x * point.x + point.y * point.y);
  result.y = (180.0 / PI) * atan2(point.y , point.x); //弧度转角度
  result.y = result.y < .0 ? result.y + 360.0 : result.y;
  return result;
}

  判断是否在扇形内:

/**
 * 判断一个点是否在扇形内(相对中心点)
 * @param center 扇形的中心点
 * @param direction 中心线的方向坐标
 * @param r 半径
 * @param angle 角度(0 < angle < 360)
 * @param point 需要检查的点
 */
bool in_circular_sector(const point_t &center, 
                        const point_t &direction, 
                        double r, 
                        int angle,
                        const point_t &point) {
  //实际使用中,我们会把方向点的极坐标放到外部进行计算
  point_t d_rpoint = absolute_to_relative(center, direction); //方向相对坐标
  point_t d_pc_point = to_spolar_coordinate(d_rpoint); //方向极坐标
  if (-1 == d_pc_point.y) { //简单的如果转换不出,则需要调用角度函数计算
    d_pc_point = to_polar_coordinate(d_rpoint);
  }
  point_t rpoint = absolute_to_relative(center, point); //目标相对坐标
  point_t pc_point = to_polar_coordinate(rpoint); //目标极坐标
  if (pc_point.x > r) return false;
  bool result = false;
  auto half_angle = angle / 2;
  auto angle_counter = d_pc_point.y - half_angle; //中心线顺时针方向的范围
  auto angle_clockwise = d_pc_point.y + half_angle; //中心线逆时针方向的范围
  /*
  std::cout << "angle_counter: " << angle_counter << " angle_clockwise: " 
            << angle_clockwise << " d_pc_point.y" << d_pc_point.y << std::endl;
  */
  if (0 == d_pc_point.y || angle_counter < 0 || angle_clockwise > 360) {
    angle_counter = angle_counter < 0 ? angle_counter + 360 : angle_counter;
    angle_clockwise = angle_clockwise > 360 ? angle_counter - 360 : angle_counter;
    if (pc_point.y >= 0 && pc_point.y <= angle_counter) {
      result = true;
    } else if (pc_point.y >= angle_clockwise && pc_point.y <= 360) {
      result = true;
    }
  } else {
    result = angle_counter <= pc_point.y && angle_clockwise >= pc_point.y;
  }
  return result;
}

  实际使用的优化:

  可以先求出圆范围内的所有玩家对象,这样可以减少atan2调用,其次方向点的极坐标放到循环之外,减少循环次数。

PF人员招募

开篇语
  我们没有大神,只有解决问题的人。
  我们没有强悍的技术,只有一颗向往简单的心。
  我们没有惊人的理论,只有一堆不可思议的妄想。
  我们不需要复杂,只需要够简洁。
  我们不需要固定的思维,只需要你能想得到。

PF托管地址

  https://github.com/viticm/plainframework1

PF安装教程

  http://www.cnblogs.com/lianyue/p/3974342.html

PF交流QQ群

  348477824(同时欢迎技术人员加入进行交流)

posted @ 2016-12-27 10:09 恋月 阅读(...) 评论(...) 编辑 收藏