Vòng lặp trò chơi, cách kiểm tra điều kiện một lần, làm một cái gì đó, sau đó không làm lại


13

Ví dụ: tôi có một lớp Trò chơi và nó giữ một lớp inttheo dõi cuộc sống của người chơi. Tôi có một điều kiện

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

Tuy nhiên, điều kiện này tiếp tục bắn và công việc được thực hiện nhiều lần. Ví dụ: tôi muốn đặt hẹn giờ để thoát trò chơi trong 5 giây. Hiện tại, nó sẽ tiếp tục cài đặt nó thành 5 giây mỗi khung hình và trò chơi không bao giờ kết thúc.

Đây chỉ là một ví dụ và tôi có cùng một vấn đề trong một số lĩnh vực của trò chơi của tôi. Tôi muốn kiểm tra một số điều kiện và sau đó làm một cái gì đó một lần và chỉ một lần, và sau đó không kiểm tra hoặc làm lại mã trong câu lệnh if. Một số giải pháp có thể xuất hiện trong tâm trí là có một boolđiều kiện cho từng điều kiện và đặt thời boolđiểm điều kiện kích hoạt. Tuy nhiên, trong thực tế, điều này trở nên rất phức tạp khi xử lý rất nhiều bools, vì chúng phải được lưu trữ dưới dạng các trường lớp hoặc tĩnh trong chính phương thức.

Giải pháp thích hợp cho vấn đề này (không phải cho ví dụ của tôi, nhưng cho miền vấn đề) là gì? Làm thế nào bạn sẽ làm điều này trong một trò chơi?


Cảm ơn câu trả lời, giải pháp của tôi bây giờ là sự kết hợp của câu trả lời ktodisco và crancran.
EddieV223

Câu trả lời:


13

Tôi nghĩ rằng bạn có thể giải quyết vấn đề này đơn giản bằng cách kiểm soát cẩn thận hơn các đường dẫn mã có thể của bạn. Ví dụ: trong trường hợp bạn đang kiểm tra xem số lượng cuộc sống của người chơi đã giảm xuống dưới một, tại sao không kiểm tra chỉ khi người chơi mất mạng, thay vì mỗi khung hình?

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

Đến lượt mình, điều này giả định rằng subtractPlayerLifesẽ chỉ được gọi trong những dịp cụ thể, có lẽ sẽ xuất phát từ các điều kiện mà bạn phải kiểm tra mọi khung hình (va chạm, có lẽ).

Bằng cách kiểm soát cẩn thận cách mã của bạn thực thi, bạn có thể tránh các giải pháp lộn xộn như booleans tĩnh và cũng cắt giảm từng bit một về số lượng mã được thực thi trong một khung.

Nếu có một cái gì đó dường như không thể tái cấu trúc và bạn thực sự chỉ cần kiểm tra một lần, thì trạng thái (như một enum) hoặc booleans tĩnh là cách để đi. Để tránh tự khai báo số liệu thống kê, bạn có thể sử dụng mẹo sau:

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

mà sẽ làm cho mã sau đây:

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

in somethingsomething elsechỉ một lần. Nó không mang lại mã tìm kiếm tốt nhất, nhưng nó hoạt động. Tôi cũng khá chắc chắn có nhiều cách để cải thiện nó, có lẽ bằng cách đảm bảo tốt hơn các tên biến duy nhất. Điều này #definesẽ chỉ hoạt động một lần trong thời gian chạy mặc dù. Không có cách nào để thiết lập lại nó, và không có cách nào để lưu nó.

Mặc dù có mánh khóe, tôi thực sự khuyên bạn nên kiểm soát tốt hơn luồng mã của mình trước tiên và sử dụng điều này #definenhư là phương sách cuối cùng hoặc chỉ cho mục đích gỡ lỗi.


+1 đẹp một lần nếu giải pháp. Lộn xộn, nhưng mát mẻ.
kender

1
có một vấn đề lớn với ONE_TIME_IF của bạn, bạn không thể làm điều đó xảy ra lần nữa. Ví dụ, người chơi quay lại menu chính và sau đó quay lại cảnh trò chơi. Câu nói đó sẽ không nổ nữa. và có những điều kiện bạn có thể cần phải giữ giữa các lần chạy ứng dụng, ví dụ như danh sách các danh hiệu đã có được. phương pháp của bạn sẽ thưởng cho chiếc cúp hai lần nếu người chơi đạt được nó trong hai lần chạy ứng dụng khác nhau.
Ali1S 232

1
@Gajoo Đó là sự thật, đó là một vấn đề nếu điều kiện cần phải được đặt lại hoặc lưu. Tôi đã chỉnh sửa để làm rõ điều đó. Đó là lý do tại sao tôi đề xuất nó như là phương sách cuối cùng, hoặc chỉ để gỡ lỗi.
kevintodisco

Tôi có thể đề xuất thành ngữ do {} while (0) không? Tôi biết cá nhân tôi đã làm hỏng cái gì đó và đặt dấu chấm phẩy ở nơi tôi không nên. Mặc dù macro mát mẻ, bravo.
michael.bartnett

5

Điều này nghe có vẻ như trường hợp cổ điển của việc sử dụng mẫu trạng thái. Ở một trạng thái, bạn kiểm tra tình trạng của mình cho cuộc sống <1. Nếu điều kiện đó trở thành sự thật, bạn chuyển sang trạng thái trì hoãn. Trạng thái trì hoãn này chờ thời lượng được chỉ định và sau đó chuyển sang trạng thái thoát của bạn.

Theo cách tiếp cận tầm thường nhất:

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;
}

Bạn có thể cân nhắc sử dụng lớp Trạng thái cùng với StateManager / StateMachine để xử lý việc thay đổi / đẩy / chuyển đổi giữa các trạng thái thay vì câu lệnh chuyển đổi.

Bạn có thể làm cho giải pháp quản lý trạng thái của mình trở nên tinh vi như bạn muốn, bao gồm nhiều trạng thái hoạt động, trạng thái cha mẹ hoạt động đơn lẻ với nhiều trạng thái con đang hoạt động bằng cách sử dụng một số phân cấp, v.v ... Tất nhiên, bây giờ tôi thực sự nghĩ giải pháp mẫu trạng thái sẽ làm cho mã của bạn có thể tái sử dụng nhiều hơn và dễ dàng hơn để duy trì và làm theo.


1

Nếu bạn lo lắng về hiệu suất, hiệu suất kiểm tra nhiều lần thường không đáng kể; ditto vì có điều kiện bool.

Nếu bạn quan tâm đến một cái gì đó khác, bạn có thể sử dụng một cái gì đó tương tự như mẫu thiết kế null để giải quyết vấn đề này.

Trong trường hợp này, giả sử bạn muốn gọi một phương thức foonếu người chơi không còn sự sống. Bạn sẽ thực hiện điều này như hai lớp, một lớp gọi foovà một lớp không làm gì cả. Hãy gọi cho họ DoNothingCallFoonhư vậy:

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

    behaviour.execute();
  }
}

Đây là một chút của một ví dụ "thô"; những điểm chính là:

  • Theo dõi một số trường hợp FooClasscó thể DoNothinghoặc CallFoo. Nó có thể là một lớp cơ sở, lớp trừu tượng hoặc giao diện.
  • Luôn gọi executephương thức của lớp đó.
  • Theo mặc định, lớp không làm gì ( DoNothingví dụ).
  • Khi cuộc sống hết, cá thể được hoán đổi cho một cá thể thực sự làm một cái gì đó ( CallFooví dụ).

Điều này về cơ bản giống như một sự pha trộn của các mô hình chiến lược và null.


Nhưng nó sẽ tiếp tục tạo CallFoo () mới cho mỗi khung hình mà bạn phải xóa trước khi bạn mới và cũng tiếp tục xảy ra mà không khắc phục được sự cố. Ví dụ: nếu tôi có CallFoo () gọi một phương thức đặt bộ hẹn giờ để thực thi trong vài giây, thì bộ hẹn giờ đó sẽ được đặt thành cùng thời gian cho mỗi khung. Theo như tôi có thể nói giải pháp của bạn giống như nếu (numLives <1) {// làm công cụ} khác {// trống}
EddieV223

Hmm, tôi thấy những gì bạn đang nói. Giải pháp có thể là di chuyển ifséc vào FooClasschính cá thể, vì vậy nó chỉ được thực hiện một lần và ditto để khởi tạo CallFoo.
tro999
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.