ゲームループ、条件を1回確認し、何かをしてから、もう一度やる方法


13

たとえば、ゲームクラスがありint、プレーヤーの生活を追跡するを保持しています。条件付き

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

ただし、この状態は引き続き発火し、作業は繰り返し行われます。たとえば、5秒でゲームを終了するタイマーを設定したいです。現在、フレームごとに5秒に設定し続け、ゲームは終了しません。

これはほんの一例であり、ゲームのいくつかの領域で同じ問題を抱えています。何らかの条件を確認してから何かを1回だけ実行し、ifステートメントのコードを再度確認したり実行したりしないようにします。頭に浮かぶ可能性のある解決策のいくつかboolは、条件ごとにを持ちbool、条件が発生するタイミングを設定します。しかし、実際には、これはboolsクラスフィールドとして、またはメソッド自体内に静的に格納する必要があるため、大量のを非常に扱いにくくなります。

これに対する適切な解決策は何ですか(私の例ではなく、問題の領域のために)?ゲームでこれをどのように行いますか?


回答をありがとう、私のソリューションはktodiscoとcrancranの回答の組み合わせになりました。
EddieV223

回答:


13

考えられるコードパスをより慎重に制御するだけで、この問題を解決できると思います。たとえば、プレーヤーのライフ数が1を下回ったかどうかを確認する場合、すべてのフレームではなく、プレーヤーがライフを失ったときにのみ確認してください。

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

これは、subtractPlayerLife特定の場合にのみ呼び出されることを想定しています。これは、おそらくすべてのフレームをチェックする必要がある条件(衝突など)に起因する可能性があります。

コードの実行方法を慎重に制御することで、静的なブール値などの厄介な解決策を回避し、1つのフレームで実行されるコードの量をビット単位で削減できます。

リファクタリングが不可能と思われるものがあり、実際に一度だけチェックする必要がある場合は、状態(など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を追加します。乱雑だが、クール。
ケンダー

1
ONE_TIME_IFに大きな問題が1つあります。再び発生させることはできません。たとえば、プレーヤーはメインメニューに戻り、後でゲームシーンに戻ります。そのステートメントは再び起動しません。また、すでに取得したトロフィーのリストなど、アプリケーションの実行間で保持する必要がある条件があります。プレイヤーが2つの異なるアプリケーションの実行でトロフィーを獲得した場合、メソッドはトロフィーに2回報います。
Ali1S232

1
@Gajoo確かに、条件をリセットまたは保存する必要がある場合は問題です。それを明確にするために編集しました。それが、最後の手段として、または単にデバッグのためにそれを推奨した理由です。
ケビントディスコ

do {} while(0)イディオムを提案できますか?私は個人的に何かを台無しにして、セミコロンを配置すべきではない場所に置くことを知っています。しかし、クールなマクロ、ブラボー。
michael.bartnett

5

これは、状態パターンを使用する古典的なケースのように聞こえます。1つの状態で、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;
}

switchステートメントではなく、StateクラスをStateManager / StateMachineと共に使用して、状態間の変更/プッシュ/遷移を処理することを検討できます。

複数のアクティブな状態、いくつかの階層を使用した多数のアクティブな子状態を含む単一のアクティブな親状態など、状態管理ソリューションを必要に応じて洗練させることができます。状態パターンソリューションは、コードをより再利用可能にし、保守とフォローを容易にします。


1

パフォーマンスが心配な場合、通常、複数回のチェックのパフォーマンスは重要ではありません。ブール条件を持っているために同じ。

他の何かに興味がある場合は、nullデザインパターンに似たものを使用してこの問題を解決できます。

この場合、fooプレーヤーにライフが残っていない場合にメソッドを呼び出すと仮定しましょう。これを2つのクラスとして実装します。1つはを呼び出しfoo、もう1つは何も実行しません。それらDoNothingを呼び出して、次のCallFooようにしましょう:

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

    behaviour.execute();
  }
}

これは少し「生の」例です。キーポイントは次のとおりです。

  • いくつかのインスタンスのトラックに保つFooClassことができるDoNothingかをCallFoo。基本クラス、抽象クラス、またはインターフェースになります。
  • 常にexecuteそのクラスのメソッドを呼び出します。
  • デフォルトでは、クラスは何もしません(DoNothingインスタンス)。
  • 寿命が尽きると、インスタンスは実際に何か(CallFooインスタンス)を実行するインスタンスと交換されます。

これは基本的に、戦略とヌルパターンの組み合わせに似ています。


しかし、フレームごとに新しいCallFoo()を作成し続けます。これは、新しいフレームを削除する前に削除する必要があります。また、発生し続けても問題は修正されません。たとえば、CallFoo()がタイマーを数秒で実行するように設定するメソッドを呼び出した場合、そのタイマーは各フレームで同じ時間に設定されます。私の知る限り、あなたのソリューションはif(numLives <1){// do stuff} else {// empty}と
同じである-EddieV223

うーん、あなたの言っていることがわかります。解決策は、ifチェックをFooClassインスタンス自体に移動することです。したがって、チェックは一度だけ実行され、インスタンス化にも同じように実行されますCallFoo
ashes999
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.