实体死了后,如何最好地将其从游戏循环中删除?


16

好的,所以我有一个遍历并更新的所有实体的大清单。在AS3中,我可以将其存储为数组(动态长度,无类型),向量(类型)或链表(非本地列表)。目前,我正在使用数组,但如果速度更快,我打算更改为Vector或链表。

无论如何,我的问题是,当实体被销毁时,应如何将其从列表中删除?我可以取消其位置,将其剪接或只是在其上设置标记以说“跳过我,我死了”。我正在汇总我的实体,因此已死亡的实体很可能在某个时候再次处于活动状态。对于每种类型的馆藏,我最好的策略是什么,哪种馆藏类型和删除方法的组合最有效?


一个Vector的长度不是固定的,但是可以输入,这使其优于Array。缺点是没有快速的语法来定义预填充列表,但是我认为您不需要。
巴特·范·海克洛姆

Answers:


13

我将所有添加/删除存储在单独的列表中,并在遍历更新循环后执行这些操作。


10

Flixel框架使用Dead标志(实际上是确定是否应绘制,更新等的几个标志)。我想说的是,如果您要恢复实体,并且如果性能存在问题,则可以使用Dead标志。以我的经验,实例化新实体是您所描述的用例中最昂贵的操作,并且由于Flash有时需要大量收集垃圾,因此拼接或清空元素可能导致内存膨胀。


1
+1代表flixel。回收dead真正有助于提高性能。
雪盲2012年

3

尽管某些技术本质上比其他技术更有效,但是只有在目标平台上的周期用尽时,这才有意义。使用任何技术可以使您更快地完成游戏。同时,请尽量不要依赖于容器数据结构的特定实现,如果需要,它将帮助您进行后续优化。

只是为了解决这里其他人已经讨论过的一些技术。如果实体的顺序很重要,则死标记可以使您在下一帧的更新循环中进行拼接。例如。非常简单的伪代码:

void updateGame()
{
  // updateEntities()
  Entity* pSrcEntity = &mEntities[0];
  Entity* pDstEntity = &mEntities[0];
  newNumEntities = 0;
  for (int i = 0; i < numEntities; i++)
  {
    if (!pSrcEntity->isDead)
    {
       // could be inline but whatever.
       updateEntity(pDstEntity, pSrcEntity);
       // if entity just died, don't update the pDstEntity pointer, 
       // and just let the next entity updated overwrite it.
       if (!pDstEntity->isDead)
       {
          pDstEntity++;
          newNumEntities++;
       }
    }
    pSrcEntity++;
  }
}
numEntities = newNumEntities;

这些是此方案的特征:

  • 实体的自然紧凑性(尽管可以收回实体插槽之前可能有1帧的延迟)。
  • 没有随机的重新排序问题。
  • 双链表具有O(1)插入/删除功能,但是很难预取以实现最佳的缓存延迟隐藏。将它们保持在紧凑的阵列中可以使块预取技术很好地工作。
  • 在销毁多对象的情况下,您不必执行多余的移位副本即可保持顺序和紧凑性(在更新过程中,所有操作一次完成)
  • 您可以利用在更新过程中接触需要已经在缓存中的数据。
  • 如果您的源实体和目标实体Poitner要分开排列,则效果很好。然后,您可以对实体数组进行双缓冲以利用多核/。一个线程更新/写入第N帧的实体,而另一个线程渲染第N-1帧的前一帧的实体。
  • 紧凑性意味着更容易将整个DMA直接存储到异构处理器,以实现更多的CPU工作量分流。SPU或GPU。

+1。我喜欢这个。尽管我几乎不需要池中的有序更新,但如果遇到这种情况,我会将其添加到要记住的事情中:o)
Kaj 2010年

2

就我一般的编程经验而言,拼接通常是一个缓慢的操作,涉及将所有现有元素上移一个。我认为将其设置为null将是最好的解决方案。死标志将起作用,但是您需要注意不要让它使您的代码混乱。

实际上,我们实际上只是在谈论聊天室中的资源池。这是一个很好的做法,很高兴听到您这样做。:)


1
如果更新顺序不重要,则拼接应像将最后一个实体移至当前索引并减少池计数和迭代器索引一样简单。
Kaj 2010年

哇,好极了Kaj!:)
Ricket

2

就个人而言,我将使用链接列表。快速遍历喜欢的列表以及添加和删除项目。如果您需要直接访问结构中的项目(例如,访问索引),则使用数组或向量将是一个不错的选择,但这听起来并不是您需要的。

每当您从链接列表中删除项目时,都可以将其添加到对象池中,然后可以对其进行循环以节省内存分配。

我在多个项目中使用了多边形数据结构,并对它们感到非常满意。

编辑:很抱歉,就删除策略而言,我的答案不是很明确:我建议从列表中删除该条目,直到它死了,然后将其直接添加到池结构中(回收)。由于从链接列表中删除项目非常有效,因此我认为没有问题。


1
我认为您在这里建议一个双向链接列表?(前进/后退)?另外:您是否建议在链接元素上使用某种类型的池,还是在链接列表中动态分配每个指针持有者?
西蒙

是的,它必须是最适合该任务的双向链接列表。感谢您指出了这一点!关于项目的重用:我正在考虑一种专门的池类/数据结构,该类可按需创建新对象或在池中有某些对象时使用现有实例。因此,最好从列表中删除“死”项并将它们添加到池中以备后用。
bummzack 2010年

一个单链表就可以了。双链表仅提供双向迭代的优势。要遍历单链列表并选择删除当前项目,您需要跟踪上一个条目。
deft_code

@caspin是的。如果您使用的是单链表,则需要跟踪先前的节点,并next在删除节点后将其指针链接到该节点。如果您不想自己麻烦做,那么双链表将是您选择的DataStructure。
bummzack 2010年

1

“只是在上面设置一个标志,说“跳过我,我已经死了。”我正在汇集我的实体,因此某个已死的实体很可能在某个时候再次存在”

我认为您针对此特定应用回答了您自己的问题。如果您打算对数组进行除推入和弹出操作以外的其他操作,我将远离数组。如果计划进行繁重的操作,链接列表将是更明智的选择。综上所述,如果您打算将同一实体重新集成到游戏中,则仅设置一个布尔变量并在游戏操作循环中进行检查是有意义的。


0

我在使用的lib上找到的一种干净通用的解决方案是使用可锁定的映射。

你有2个操作lock()unlock(),当你迭代在地图上你lock(),现在从这个角度每次变更地图不生效运行,它只是被压入一个CommandQueue一旦你调用运行unlock()

因此,删除实体将具有以下伪代码:

void lockableMap::remove(std::string id) {
   if(isLocked) {
       commandQueue.add(new RemoveCommand(id));
   } else {
       //remove element from map
   }

当你 unlock()

isLocked = false
commandQueue.execute(this);

唯一需要考虑的是,您将仅在循环后删除实体。

编辑:这是西蒙提出的解决方案。



0

我有两种方法。

调用要删除的对象时,它实际上设置了两个标志:

1.告诉容器对象已被删除

2.告诉容器要求删除哪些对象

void object::deleteObject()
{
    container->objectHasBeenDeleted = true;
    isToDelete = true;
}

一个 使用对象向量

std::vector<object*> objects;

然后在更新功能中,检查是否已删除对象,如果是,则遍历所有对象并删除具有删除标志的对象

void container::update()
{
    if (objectHasBeenDeleted)
    {
        std::vector<object*>::iterator ListIterator;
        for(ListIterator=objects.begin(); ListIterator!=objects.end();)
        {
            if( (*ListIterator)->isToDelete )
            {
                ListIterator = objects.erase(ListIterator);
                delete *ListIterator;
            }
            else {
                ++ListIterator;
            }
        }
    objectHasBeenDeleted = false;
    }
}

两个 使用(指向a的)对象向量。

std::vector<object*> *objects;

在更新功能中,如果要删除对象,请遍历这些对象,然后将不希望删除的对象添加到新向量中。删除对象向量并将指针设置为新向量

void container::update()
{
    if (objectHasBeenDeleted)
    {
        std::vector<object*> *newVector;
        unsigned long i;
        for (i = 0; i < objects->size(); i++)
        {
            if (!objects->at(i)->isToDelete)
            {
                newVector->push_back(objects->at(i));
            }
        }
        delete objects;
        objects = newVector;
        objectHasBeenDeleted = false;
    }
}
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.