笔者现在参与开发的 2d游戏 最近扩展了小队的角色数量 由之前的5人 扩展到了8人,经常出现多个小人叠在一起的情况。现在着手解决这个问题。
最早的逻辑是 小队中有一名队长 玩家可以操作控制队长移动,所有成员在距队长超过指定距离时会触发跟随逻辑,移动到队长所在位置
leader: moveTo(x,y);
other1: moveTo(leader.x,leader.y);
...
othern: moveTo(leader.x,leader.y);
在到达指定点后小人叠在一起在一起,想要避免这种情况,加入碰撞检测无疑是最好的,但是对于初始设计就没有碰撞逻辑的游戏来说改造成本过高。待机时的小队成员堆叠最难看,所以先从这个这块开始优化。
早期的优化方案是每个人随机落后主角一点距离:
leader: moveTo(x,y);
//三目运算符处的逻辑是让队员不会超过队长
other1: dx1=rand(50,100);dx2=rand(50,100);moveTo(leader.x + (leader.x>other1.x?dx1:-dx1),leader.y+(leader.y>other1.y?dy1:-dy1));
...
othern:dxn=rand(50,100);dxn=rand(50,100);moveTo(leader.x + (leader.x>othern.x?dxn:-dxn),leader.y+(leader.y>othern.y?dyn:-dyn));
这个方案在(小队人数<5)效果还是可以的,但是队伍扩充到8人时,堆叠的现象还是比较容易出现
笔者的想法是 在队长周围预设一些位置,在移动结束后 所有跟随队长的队员向队长申请一个权重最小位置,然后移动到指定位置,并增加这个位置的权重
class leader {
int around_sites[8]; //记录周围8个位置的权重
int getASite() {
//遍历around_sites 找到权重最低的位置
vector<int> vec_idx;
for(){
...
}
//vec_idx 中随机一个位置
idx = vec_idx[wrand(vec_idx.size())];
//增加权重
around_sites[idx] += 1;
if (idx>= 4) //自己在4号位 这里+1 后边计算位置好算一些
return idx+1
return idx
}
void clearSites() {
//清空权重
memset(around_sites,0,sizeof(around_sites));
}
void onMoveEnd() {
//在队长移动结束后清空权重
clearSites();
}
...
}
class other {
int site = -1; //位于队长周围的位置编号
onIdle() {
if (离队长太远了)
moveToLeader();
return;
if (队长在移动) {
return;
}
//在队长周围,且队长没有移动,在队长附近找一个较优位置并前往
if (site<0) {
site = leader->getASite();
moveToLeader(site);
}
}
//这个要结合游戏在适当的位置做重置
void resetSite() {
site = -1;
}
void moveToLeader(int site=-1) {
int x,y;
if (site<0) {
//这里可以结合上边的优化处理
x = leader.x + dx;
y = leader.y + dy;
} else {
// 6 | 3 | 0
// 7 | 4 | 1
// 8 | 5 | 2
x = leader.x + (site / 3 - 1) * GRIDSIZE; //GRIDSIZE:地图网格的尺寸
y = leader.y + (site % 3 - 1) * GRIDSIZE;
}
moveTo(x, y)
}
}
目前看效果还可以,先这样,后边有优化再做补充。
策划提出两个优化 :
1.每次移动后尽量保持原有阵型
2.当队长进行微小移动到队员位置时队员要避开
解决方案:队员在调整位置之前先通过坐标差值计算出自己相对于队长的位置, 配合权重按照一定的优先级来选择目标位置。
//site优先级
static const short SitePriority[9][8] = {
{0, 1, 3, 2, 6, 5, 7, 8}, // 0
{1, 0, 2, 3, 5, 7, 6, 8}, // 1
{2, 1, 5, 0, 8, 3, 7, 6}, // 2
{3, 0, 6, 1, 7, 5, 2, 8}, // 3
{1, 3, 5, 7, 0, 2, 6, 8}, // 4
{5, 2, 8, 1, 7, 3, 0, 6}, // 5
{6, 7, 3, 0, 8, 5, 1, 2}, // 6
{7, 6, 8, 3, 5, 1, 2, 0}, // 7
{8, 7, 5, 6, 2, 1, 3, 0} // 8
};
class leader {
map<int,short> around_site_weight; // 周围点位的占用权重
leader():around_site_weight({{0, 0}, {1, 0}, {2, 0}, {3, 0}, {5, 0}, {6, 0}, {7, 0}, {8, 0}}) {
//构造函数
}
int getASite(int prefer_site) {
int site = 0;
short weight = 999;
auto sites = SitePriority[prefer_site];
for (int i = 0; i < 8; i++) {
// 权重相同的按照优先级来排,权重不同的按权重来取
auto s = sites[i];
auto w = around_site_weight[s];
if (w >= 0 && weight > w) {
site = s;
weight = w;
}
}
//增加权重
around_sites[site] += 1;
return site;
}
void clearSites() {
//清空权重 这里可以再判断一下周围的位置 是阻挡则将权重设为-1
memset(around_sites,0,sizeof(around_sites));
...
}
void onMoveEnd() {
//在队长移动结束后清空权重
clearSites();
}
...
}
class other {
int site = -1; //位于队长周围的位置编号
onIdle() {
if (离队长太远了)
moveToLeader();
return;
if (队长在移动) {
resetSite(); //解决问题2
return;
}
//在队长周围,且队长没有移动,在队长附近找一个较优位置并前往
if (site<0) {
int prefer_site = calcNowSite(); //解决问题1
site = leader->getASite(prefer_site);
moveToLeader(site);
}
}
//这个要结合游戏在适当的位置做重置
void resetSite() {
site = -1;
}
//这个函数用来计算自身相对于队长的位置,用来解决尽量保持队形的问题
int calcNowSite()
{
const Point &mpos = leader->GetPosition();
const Point &spos = this->GetPosition();
int site = 4; //队长所在的位置
int dx = spos.x - mpos.x;
int dy = spos.y - mpos.y;
if (abs(dx) < GRIDSIZE / 2 && abs(dy) < GRIDSIZE / 2) {
return site; //队长太近了
}
//这里按斜率坐划分
if (dx == 0) {
site = dy > 0 ? 5 : 3;
} else {
float y_x = (float)dy / (float)dx;
if (y_x > 2.0f || y_x < -2.0f) {
site = dy > 0 ? 5 : 3;
} else if (y_x > 0.5f) {
site = dx > 0 ? 2 : 6;
} else if (y_x > -0.5f) {
site = dx > 0 ? 1 : 7;
} else {
site = dx > 0 ? 0 : 8;
}
}
return site;
}
void moveToLeader(int site=-1) {
int x,y;
if (site<0) {
//这里可以结合上边的优化处理
x = leader.x + dx;
y = leader.y + dy;
} else {
// 6 | 3 | 0
// 7 | 4 | 1
// 8 | 5 | 2
x = leader.x + (site / 3 - 1) * GRIDSIZE; //GRIDSIZE:地图网格的尺寸
y = leader.y + (site % 3 - 1) * GRIDSIZE;
}
moveTo(x, y)
}
}