MMO中,游戏场景即游戏地图,服务器要做的就是将地图上所有的玩家信息同步。为了减轻服务器压力,根据游戏本身的设定,如能见度,服务器只需根据不同的玩家返回相应的数据即可。
假设将地图切成了N个矩形块,在 (0, 0) 这个坐标上的玩家,我们只需要给玩家返回 (-1, 1) (0, 1) (1, 1) (-1, 0) (0, 0) (1, 0) (-1, -1) (0, -1) (1, -1) 矩形块上的数据即可。
但是,使用矩形切片,每次需要计算的场景为 9。如果使用正六边形切片,每次需要计算的场景则为 7。使用正六边形,并不会减少总的数据量,但能减少周围场景服务器的访问次数。而且,相比矩形,正六边形的计算更为复杂。
六边形cube坐标系:
六边形区域计算:
将cube坐标系转换成offset坐标系(此处假定step=0.5):
初始化将地图的某个点的相对 cube 坐标传入,并设置步长,计算出 radius 的值。
xy::utils::hexagon::Hexagon::Hexagon(double x, double y, double z, long double step) : cube{x, y, z}, step(step) {
this->offset = {static_cast<double>((y - x) * step), static_cast<double>(-std::sqrt(3) * z * step)};
this->radius = (2.0 * std::sqrt(3) / 3.0) * step;
}
根据玩家的绝对坐标,判断玩家是否跨越了此区域。
double xy::utils::hexagon::Hexagon::compute_distance(double x, double y) {
return std::sqrt(std::pow(x - this->offset.x, 2) + std::pow(y - this->offset.y, 2));
}
bool xy::utils::hexagon::Hexagon::has_in_radius(double x, double y) {
return this->compute_distance(x, y) <= this->radius ? true : false;
}
如果玩家跨越了此区域,获取玩家当前所在的区域,然后将玩家的数据复制到玩家所在区域去。
这里需要用到 offset 转 cube 的公式。
xy::utils::hexagon::Hexagon::Cube xy::utils::hexagon::Hexagon::offset2cube(double x, double y) {
x -= this->offset.x;
y -= this->offset.y;
return this->offset2cube_absolutely(x, y);
}
xy::utils::hexagon::Hexagon::Cube xy::utils::hexagon::Hexagon::offset2cube_absolutely(double x, double y) {
/*
ax + by = m
cx + dy = n
x = (md - bn) / (ad - bc)
y = (mc - an) / (bc - ad)
*/
double a = 1.0, b = -1.0, c = 1.0, d = 1.0;
double z = -std::sqrt(3) / 3.0 * y / this->step;
double m = -x / this->step, n = -z;
x = (d * m - b * n) / (a * d - b * c);
y = (c * m - a * n) / (b * c - a * d);
Cube cube {x, y, z};
return cube;
}
如果用 python 的 numpy 结合线性代数解方程组就简单了,可惜 python 运算效率实在太慢,用来做服务器将大大降低承载量。
import numpy
import math
def offset2cube_absolutely(offset_x, offset_y, step=0.5):
a = numpy.asarray([
[1, -1],
[1, 1]
])
z = -(math.sqrt(3) / 3) * offset_y / step
b = numpy.asarray([-offset_x / step, -z])
x, y = numpy.linalg.solve(a, b)
return x, y, z
完整代码:
hexagon.h
#ifndef XY_HEXAGON
#define XY_HEXAGON
namespace xy {
namespace utils {
namespace hexagon {
class Hexagon {
public:
struct Cube {
double x, y, z;
};
public:
struct Offset {
double x, y;
};
public:
Hexagon(double, double, double, long double=0.5);
~Hexagon();
private:
Cube cube;
Offset offset;
long double step;
long double radius;
public:
Cube& get_cube();
Offset& get_offset();
long double get_step() const;
long double get_radius() const;
public:
double compute_distance(Offset&);
double compute_distance(double, double);
bool has_in_radius(Offset&);
bool has_in_radius(double, double);
Cube offset2cube(Offset&);
Cube offset2cube(double, double);
Cube offset2cube_absolutely(Offset&);
Cube offset2cube_absolutely(double, double);
};
}
}
}
#endif
hexagon.cpp
#include <cmath>
#include "hexagon.h"
xy::utils::hexagon::Hexagon::Hexagon(double x, double y, double z, long double step) : cube{x, y, z}, step(step) {
this->offset = {static_cast<double>((y - x) * step), static_cast<double>(-std::sqrt(3) * z * step)};
this->radius = (2.0 * std::sqrt(3) / 3.0) * step;
}
xy::utils::hexagon::Hexagon::~Hexagon() {}
xy::utils::hexagon::Hexagon::Cube& xy::utils::hexagon::Hexagon::get_cube() {
return this->cube;
}
xy::utils::hexagon::Hexagon::Offset& xy::utils::hexagon::Hexagon::get_offset() {
return this->offset;
}
long double xy::utils::hexagon::Hexagon::get_step() const {
return this->step;
}
long double xy::utils::hexagon::Hexagon::get_radius() const {
return this->radius;
}
double xy::utils::hexagon::Hexagon::compute_distance(Offset& offset) {
return this->compute_distance(offset.x, offset.y);
}
double xy::utils::hexagon::Hexagon::compute_distance(double x, double y) {
return std::sqrt(std::pow(x - this->offset.x, 2) + std::pow(y - this->offset.y, 2));
}
bool xy::utils::hexagon::Hexagon::has_in_radius(Offset& offset) {
return this->has_in_radius(offset.x, offset.y);
}
bool xy::utils::hexagon::Hexagon::has_in_radius(double x, double y) {
return this->compute_distance(x, y) <= this->radius ? true : false;
}
xy::utils::hexagon::Hexagon::Cube xy::utils::hexagon::Hexagon::offset2cube(Offset& offset) {
return this->offset2cube(offset.x, offset.y);
}
xy::utils::hexagon::Hexagon::Cube xy::utils::hexagon::Hexagon::offset2cube(double x, double y) {
x -= this->offset.x;
y -= this->offset.y;
return this->offset2cube_absolutely(x, y);
}
xy::utils::hexagon::Hexagon::Cube xy::utils::hexagon::Hexagon::offset2cube_absolutely(Offset& offset) {
return this->offset2cube_absolutely(offset.x, offset.y);
}
xy::utils::hexagon::Hexagon::Cube xy::utils::hexagon::Hexagon::offset2cube_absolutely(double x, double y) {
/*
ax + by = m
cx + dy = n
x = (md - bn) / (ad - bc)
y = (mc - an) / (bc - ad)
*/
double a = 1.0, b = -1.0, c = 1.0, d = 1.0;
double z = -std::sqrt(3) / 3.0 * y / this->step;
double m = -x / this->step, n = -z;
x = (d * m - b * n) / (a * d - b * c);
y = (c * m - a * n) / (b * c - a * d);
Cube cube {x, y, z};
return cube;
}
main.cpp
#include <iostream>
#include <cmath>
#include "hexagon.h"
using namespace std;
using namespace xy::utils::hexagon;
void relatively_demo() {
Hexagon* hexagon_12_1 = new Hexagon(-1, 2, -1);
Hexagon* hexagon_20_2 = new Hexagon(2, 0, -2);
const Hexagon::Cube cube_tmp_0 = hexagon_12_1->offset2cube(hexagon_20_2->get_offset());
cout << "(2, 0, -2)相对(-1, 2, -1),其偏移量为: x=" << cube_tmp_0.x << " y=" << cube_tmp_0.y << " z=" << cube_tmp_0.z << endl;
const Hexagon::Cube cube_tmp_1 = hexagon_20_2->offset2cube(hexagon_12_1->get_offset());
cout << "(-1, 2, -1)相对(2, 0, -2),其偏移量为: x=" << cube_tmp_1.x << " y=" << cube_tmp_1.y << " z=" << cube_tmp_1.z << endl;
if (hexagon_12_1) {
delete hexagon_12_1;
}
if (hexagon_20_2) {
delete hexagon_20_2;
}
}
void absolutely_demo() {
Hexagon* hexagon = new Hexagon(2, -1, -1);
const Hexagon::Cube& cube_tmp_0 = hexagon->offset2cube_absolutely(-1.0 / 2.0, 3.0 * sqrt(3) / 2.0);
cout << "(-1/2, 3*sqrt(3)/2): " << "x=" << cube_tmp_0.x << " y=" << round(cube_tmp_0.y) << " z=" << cube_tmp_0.z << endl;
const Hexagon::Cube& cube_tmp_1 = hexagon->offset2cube_absolutely(3.0 / 2.0, sqrt(3) / 2.0);
cout << "(3/2, sqrt(3)/2): " << "x=" << cube_tmp_1.x << " y=" << round(cube_tmp_1.y) << " z=" << cube_tmp_1.z << endl;
Hexagon::Offset offset_tmp{ -2, -sqrt(3) };
const Hexagon::Cube& cube_tmp_2 = hexagon->offset2cube_absolutely(offset_tmp);
cout << "(-2, -sqrt(3): " << "x=" << cube_tmp_2.x << " y=" << round(cube_tmp_2.y) << " z=" << cube_tmp_2.z << endl;
if (hexagon) {
delete hexagon;
}
}
int main() {
relatively_demo();
cout << endl;
absolutely_demo();
return 0;
}
运行结果:
正六边形坐标系参考:正六边形网格化(Hexagonal Grids)原理与实现
二元一次方程组解法参考:怎么用C编程语言求解二元一次方程组的解