【发布时间】:2021-03-08 00:30:39
【问题描述】:
我目前正在使用 C++ 和 SDL2 开发一个非常简单的“落沙”模拟游戏,但在让水以更逼真的方式流动时遇到了问题。我基本上有一个单元格网格,我从下到上、从左到右进行迭代,如果我找到一个水单元格,我只需检查下面、从下到左、从下到右、从左到右的空单元格并且它移动到它找到的第一个(如果两个对角单元格或两个水平单元格都是空闲的,它会随机选择)。然后我将它移入的单元格标记为已处理,以便在该循环的其余部分不再检查它。
我的问题是粒子如何移动的一种“左偏”;如果我在屏障上方生成一个正方形的水细胞,一旦粒子开始到达屏障,它们基本上都会向左移动而不会移动,而右侧的细胞将以正确的方式向下流动。所以不是形成一个漂亮的三角形,均匀地流向两边,整个形状只会向左移动。每当我从左到右迭代时,这种效果就会逆转,所以我知道这与此有关,但到目前为止,我一直在努力修复它。我最初认为这是我如何将单元格标记为已处理的问题,但在数小时的测试中我没有发现该系统存在明显的错误。有没有人在开发这样的模拟时遇到过类似的挑战,或者知道我错过了什么?任何帮助将不胜感激。
编辑: 好的,所以我取得了一些进展,但是我遇到了另一个似乎与迭代无关的错误,因为现在我保存了旧单元格的副本并从中读取以决定更新,然后更新原始单元格并显示出来。这已经使沙子更好地工作,但是水平检查游离细胞的水现在在水平移动时“消失”了。我整个上午都在测试它,但还没有找到解决方案,我认为这可能与我复制数组的方式有关,但据我所知,它似乎有效。
新的 sn-ps:
Simulation.cpp
void Simulation::update()
{
copyStates(m_cells, m_oldCells); // so now oldcells is the last new state
for(int y = m_height - 1; y>= 0; y--)
for(int x = 0; x < m_width; x++)
{
Cell* c = getOldCell(x, y); // check in the old state for possible updates
switch(c->m_type)
{
case EMPTY:
break;
case SAND:
if(c->m_visited == false) update_sand(x, y);
break;
case WATER:
if(c->m_visited == false) update_water(x, y);
break;
default:
break;
}
}
}
void Simulation::update_water(int x, int y)
{
bool down = (getOldCell(x, y+1)->m_type == EMPTY) && checkBounds(x, y+1) && !getOldCell(x, y+1)->m_visited;
bool d_left = (getOldCell(x-1, y+1)->m_type == EMPTY) && checkBounds(x-1, y+1) && !getOldCell(x-1, y+1)->m_visited;
bool d_right = (getOldCell(x+1, y+1)->m_type == EMPTY) && checkBounds(x+1, y+1) && !getOldCell(x+1, y+1)->m_visited ;
bool left = (getOldCell(x-1, y)->m_type == EMPTY) && checkBounds(x-1, y) && !getOldCell(x-1, y)->m_visited ;
bool right = (getOldCell(x+1, y)->m_type == EMPTY) && checkBounds(x+1, y) && !getOldCell(x+1, y)->m_visited ;
// choose random dir if both are possible
if(d_left && d_right)
{
int r = rand() % 2;
if(r) d_right = false;
else d_left = false;
}
if(left && right)
{
int r = rand() % 2;
if(r) right = false;
else left = false;
}
if(down)
{
getCell(x, y+1)->m_type = WATER; // we now update the new state
getOldCell(x, y+1)->m_visited = true; // mark as visited so it will not be checked again in update()
} else if(d_left)
{
getCell(x-1, y+1)->m_type = WATER;
getOldCell(x-1, y+1)->m_visited = true;
} else if(d_right)
{
getCell(x+1, y+1)->m_type = WATER;
getOldCell(x+1, y+1)->m_visited = true;
} else if(left)
{
getCell(x-1, y)->m_type = WATER;
getOldCell(x-1, y)->m_visited = true;
} else if(right)
{
getCell(x+1, y)->m_type = WATER;
getOldCell(x+1, y)->m_visited = true;
}
if(down || d_right || d_left || left || right) // the original cell is now empty; update the new state
{
getCell(x, y)->m_type = EMPTY;
}
}
void Simulation::copyStates(Cell* from, Cell* to)
{
for(int x = 0; x < m_width; x++)
for(int y = 0; y < m_height; y++)
{
to[x + y * m_width].m_type = from[x + y * m_width].m_type;
to[x + y * m_width].m_visited = from[x + y * m_width].m_visited;
}
}
Main.cpp
sim.update();
Uint32 c_sand = 0xedec9a00;
for(int y = 0; y < sim.m_height; y++)
for(int x = 0; x < sim.m_width; x++)
{
sim.getCell(x, y)->m_visited = false;
if(sim.getCell(x, y)->m_type == 0) screen.setPixel(x, y, 0);
if(sim.getCell(x, y)->m_type == 1) screen.setPixel(x, y, c_sand);
if(sim.getCell(x, y)->m_type == 2) screen.setPixel(x, y, 0x0000cc00);
}
screen.render();
我附上了一个显示问题的 gif,希望这可能有助于使它更清楚一点。你可以看到沙子被正常放置,然后是水和它放置后形成的奇怪图案(注意它在生成时是如何向左移动的,不像沙子)
【问题讨论】:
-
不是从左到右处理,而是从外到内工作。我们只能在没有看到您的代码的情况下进行推测。 Edit 包含minimal reproducible example 的问题。
-
@1201ProgramAlarm 好的,我实际上添加了一个新系统,它制作了一个单独的单元格副本,检查旧的单元格是否有更改,然后相应地更新新的单元格,这确实完全消除了奇怪的效果为沙粒。然而,它为水粒子引入了一个新错误,现在当它们与屏障/沙子接触时会消失。不过感谢您的建议。
-
想想当你将原始单元格设置为 EMPTY 时,
update_water的最后一个 if 会发生什么。是否覆盖了之前的更改? -
我现在真的不确定。我添加了一个额外的方向检查,以查看是否访问了 old_cell,我意识到我没有检查过,但这再次引入了明显的方向偏差,但它确实阻止了水的消失。关于您的问题,我不完全确定,但我认为我不可能覆盖更改,因为我只通过每个单元格一次,或者我错过了什么?
-
用非常简单的设置创建一些单元测试,只设置几个像素。也许您可以使随机数生成器对这些测试具有确定性,或者让它运行多次并获得不同结果的统计数据。
标签: c++ simulation game-physics particle-system cellular-automata