嵌套渲染循环是不是一种糟糕的做法

Is it bad practice to have nested render loops?

本文关键字:一种 循环 是不是 嵌套      更新时间:2023-10-16

我正在将一个游戏从Ruby移植到C++。有一个主渲染循环,用于更新和绘制内容。现在让我们假设在游戏中,你想在另一个屏幕上选择一个项目。原始代码中的方法是执行Item item = getItemFromMenu(); getItemFromMenu是一个打开菜单并有自己的更新/渲染循环的函数,这意味着在播放器打开另一个屏幕的整个过程中,您处于嵌套的渲染循环中。我觉得这是一个糟糕的方法,但我不知道为什么。另一方面,它非常方便,因为我只需调用一个函数就可以打开菜单,因此代码是本地化的。你知道这是不是一个糟糕的设计吗?我犹豫要不要把它发布到gamedev上,但由于这主要是一个设计问题,我把它发布在这里

编辑:一些伪代码给你一个想法:

代码主要部分的常见循环:

while(open) {
   UpdateGame();
   DrawGame();
}

现在在UpdateGame()中,我会做一些类似的事情:

if(keyPressed == "I") {
   Item& item = getItemFromInventory();
}

getItemFromInventory():

 while(true) {
    UpdateInventory();
    if(item_selected) return item;
    DrawInventory();
 }

处理这样的事情的一个好方法是用类似InvalidateInventory()的东西替换DrawInventory()调用,这将标记库存的当前图形状态为过时,并请求在下一帧渲染期间重新绘制它(这将在主循环到达DrawGame()后很快发生)。

通过这种方式,您可以继续运行主循环,但屏幕上只有被视为需要重新绘制的部分是无效的,在正常游戏过程中,您可以将您的(2/3)D环境作为正常处理部分无效,但在库存中,您始终可以仅将库存资产标记为需要重新画出,这将开销降至最低。

如果您使用标志来指示当前游戏状态,那么内部循环的另一部分UpdateInventory()可以是UpdateGame()的一部分,例如:

UpdateGame()
{
    switch(gameState)
    {
        case INVENTORY:
            UpdateInventory();
            break;
        case MAIN:
        default:
            UpdateMain();
            break;
    }
}

如果你真的想,你也可以把它应用于绘图:

DrawGame()
{
    switch(gameState)
    {
        case INVENTORY:
            DrawInventory();
            break;
        case MAIN:
        default:
            DrawMain();
            break;
    }
}

但我认为绘图应该被封装,你应该告诉它需要绘制屏幕的哪个部分,而不是游戏的哪个单独区域。

使用嵌套渲染循环创建的内容在功能上是一个状态机(就像大多数游戏渲染循环一样)。嵌套循环的问题是,很多时候你会想在嵌套循环中做与外循环相同的事情(处理输入、处理IO、更新调试信息等)。

我发现最好有一个渲染循环,并使用有限状态机(FSM)来表示您的实际状态。你的州可能看起来像:

  • 主菜单状态
  • 选项菜单状态
  • 库存状态
  • 世界视图状态

您可以连接状态之间的转换以在它们之间移动。玩家点击按钮可能会触发转换,该转换可以播放动画或其他内容,然后移动到新状态。有了FSM,你的循环可能看起来像:

while (!LeaveGame()) {
  input = GetInput();
  timeInfo = GetTimeInfo();
  StateMachine.UpdateCurrentState(input, timeInfo);
  StateMachine.Draw();
}

一个完整的FSM对于一个小型游戏来说可能有点重量级,所以你可以尝试使用一堆游戏状态的简化状态机。每次用户执行转换到新状态的操作时,都会将状态推送到堆栈上。同样,当他们离开一个状态时,你会将其弹出。通常只有堆栈的顶部会接收输入,堆栈上的其他项目可能会/可能不会绘制(取决于你的偏好)。这是一种常见的方法,根据你与谁交谈有一些优点和缺点。

最简单的选择是只抛出一个switch语句来选择要使用的渲染函数(类似于darvids0n的答案)。如果你正在写一款街机克隆游戏或一款小型益智游戏,那就太好了。