游戏循环,如何检查条件一次,先做某件事,然后再不做


13

例如,我有一个Game类,它保留了一个int跟踪玩家生活的信息。我有条件

if ( mLives < 1 ) {
    // Do some work.
}

但是,这种情况会不断触发,并且工作会反复进行。例如,我想设置一个计时器以在5秒内退出游戏。当前,它将继续将其设置为每帧5秒,并且游戏永远不会结束。

这只是一个例子,我在游戏的多个方面也遇到相同的问题。我想检查某种情况,然后一次只做一次,然后再不检查或再次执行if语句中的代码。我想到的一些可能的解决方案是bool针对每个条件设置a ,并bool在条件触发时设置。但是在实践中,这很麻烦bools,因为必须将它们存储为类字段,或者静态地存储在方法本身中,因为它们很多。

对此的正确解决方案是什么(不是针对我的示例,而是针对问题域)?您将如何在游戏中做到这一点?


感谢您的回答,我的解决方案现在是ktodisco和crancran答案的组合。
EddieV223

Answers:


13

我认为您可以通过对可能的代码路径进行更仔细的控制来解决此问题。例如,如果您要检查玩家的生命数是否已降至1以下,为什么不只在玩家失去生命时才检查而不是每帧?

void subtractPlayerLife() {
    // Generic life losing stuff, such as mLives -= 1
    if (mLives < 1) {
        // Do specific stuff.
    }
}

反过来,这假定subtractPlayerLife仅在特定情况下才调用此方法,这可能是由于必须检查每个帧的条件(可能是冲突)导致的。

通过仔细控制代码的执行方式,您可以避免使用诸如静态布尔值之类的混乱解决方案,还可以逐个减少在单个帧中执行多少代码。

如果有些东西似乎无法重构,而您真的只需要检查一次,那么状态(如enum)或静态布尔值是可行的方法。为了避免自己声明静态变量,可以使用以下技巧:

#define ONE_TIME_IF(condition, statement) \
    static bool once##__LINE__##__FILE__; \
    if(!once##__LINE__##__FILE__ && condition) \
    { \
        once##__LINE__##__FILE__ = true; \
        statement \
    }

这将使以下代码:

while (true) {
    ONE_TIME_IF(1 > 0,
        printf("something\n");
        printf("something else\n");
    )
}

打印somethingsomething else只有一次。它不会产生看起来最好的代码,但是可以工作。我也很确定有改进的方法,也许可以通过更好地确保唯一的变量名来实现。不过,这#define在运行时只能运行一次。无法重置它,也无法保存它。

尽管有技巧,但我强烈建议您先更好地控制代码流,并将其#define用作最后的手段,或者仅用于调试目的。


如果解决方案,则+1不错。凌乱,但酷。
kender

1
您的ONE_TIME_IF有一个大问题,您无法使其再次发生。例如,玩家返回主菜单,然后返回游戏场景。该声明将不会再次触发。并且在应用程序运行之间可能需要保留一些条件,例如,已经获得的奖杯列表。如果玩家在两次不同的应用程序运行中获得奖杯,则您的方法将奖励奖杯两次。
Ali1S232 2013年

1
@Gajoo是的,如果需要重置或保存条件是一个问题。我已进行编辑以澄清这一点。这就是为什么我推荐将其作为最后的手段,或者仅用于调试的原因。
kevintodisco

我可以建议do {} while(0)习语吗?我知道我会亲自弄些东西,并在不该使用的地方加上分号。酷宏,好极了。
michael.bartnett

5

这听起来像使用状态模式的经典案例。在一种状态下,您要检查生命条件是否小于1。如果该条件成立,则过渡到延迟状态。此延迟状态等待指定的持续时间,然后过渡到退出状态。

在最简单的方法中:

switch(mCurrentState) {
  case LIVES_GREATER_THAN_0:
    if(lives < 1) mCurrentState = LIVES_LESS_THAN_1;
    break;
  case LIVES_LESS_THAN_1:
    timer.expires(5000); // expire in 5 seconds
    mCurrentState = TIMER_WAIT;
    break;
  case TIMER_WAIT:
    if(timer.expired()) mCurrentState = EXIT;
    break;
  case EXIT:
    mQuit = true;
    break;
}

您可以考虑使用State类以及StateManager / StateMachine来处理状态之间的更改/推送/转换,而不是使用switch语句。

您可以根据需要使状态管理解决方案更加复杂,包括多个活动状态,使用某个层次结构的单个活动父状态以及许多活动子状态等。当然,现在使用对您有意义的方法,但是我确实认为状态模式解决方案将使您的代码更加可重用,并且更易于维护和遵循。


1

如果您担心性能,则多次检查的性能通常并不重要。同上,具有布尔条件。

如果您对其他东西感兴趣,可以使用类似于null设计模式的东西来解决此问题。

在这种情况下,foo如果玩家没有生命,那么假设您要调用一个方法。您可以将其实现为两个类,一个调用foo,另一个不执行任何操作。让我们打电话给他们DoNothingCallFoo就像这样:

class SomeLevel {
  int numLives = 3;
  FooClass behaviour = new DoNothing();
  ...
  void tick() { 
    if (numLives < 1) {
      behaviour = new CallFoo();
    }

    behaviour.execute();
  }
}

这是一个“原始”示例;关键点是:

  • 跟踪其中的某些实例FooClass可以是DoNothingCallFoo。它可以是基类,抽象类或接口。
  • 始终调用execute该类的方法。
  • 默认情况下,该类不执行任何操作(DoNothing实例)。
  • 当生命用尽时,该实例将交换为实际执行某项操作的CallFoo实例(实例)。

这本质上就像是策略和空模式的混合。


但是它会继续在每个帧中创建新的CallFoo(),您必须在创建新帧之前将其删除,并且还会不断发生,这无法解决问题。例如,如果我让CallFoo()调用将计时器设置为在几秒钟内执行的方法,则该计时器将设置为每帧相同的时间。据我所知,您的解决方案与if(numLives <1){//做东西} else {// empty}一样
EddieV223 2013年

嗯,我明白你在说什么。解决方案可能是将if支票移到FooClass实例本身中,因此只执行一次,然后实例化CallFoo
ashes999
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.