วนรอบเกม, วิธีตรวจสอบเงื่อนไขหนึ่งครั้ง, ทำอะไรแล้วไม่ทำอีกครั้ง


13

ตัวอย่างเช่นฉันมีคลาสเกมและมันจะคอยintติดตามชีวิตของผู้เล่น ฉันมีเงื่อนไข

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

อย่างไรก็ตามเงื่อนไขนี้ยังคงมีการยิงต่อเนื่อง ตัวอย่างเช่นฉันต้องการตั้งเวลาเพื่อออกจากเกมใน 5 วินาที ปัจจุบันมันจะตั้งค่าเป็น 5 วินาทีทุกเฟรมและเกมจะไม่มีวันสิ้นสุด

นี่เป็นเพียงตัวอย่างเดียวและฉันมีปัญหาเดียวกันในหลาย ๆ ด้านของเกม ฉันต้องการตรวจสอบเงื่อนไขบางอย่างแล้วทำบางครั้งเพียงครั้งเดียวแล้วไม่ตรวจสอบหรือทำรหัสในคำสั่ง if อีกครั้ง วิธีแก้ปัญหาที่เป็นไปได้บางอย่างที่อยู่ในใจกำลังมีอยู่boolสำหรับแต่ละเงื่อนไขและตั้งค่าboolเมื่อเงื่อนไขเริ่มทำงาน อย่างไรก็ตามในทางปฏิบัติสิ่งนี้ได้รับการจัดการที่ยุ่งมากboolsเนื่องจากพวกมันจะต้องถูกเก็บไว้เป็นฟิลด์คลาสหรือคงที่ภายในวิธีการเอง

การแก้ปัญหาที่เหมาะสมสำหรับสิ่งนี้คืออะไร (ไม่ใช่สำหรับตัวอย่างของฉัน แต่สำหรับโดเมนปัญหา) คุณจะทำสิ่งนี้ในเกมได้อย่างไร?


ขอบคุณสำหรับคำตอบตอนนี้โซลูชันของฉันคือการผสมผสานระหว่างคำตอบ ktodisco และ crancran
EddieV223

คำตอบ:


13

ฉันคิดว่าคุณสามารถแก้ปัญหานี้ได้โดยเพียงแค่พยายามควบคุมเส้นทางรหัสที่เป็นไปได้อย่างระมัดระวังมากขึ้น ตัวอย่างเช่นในกรณีที่คุณกำลังตรวจสอบว่าจำนวนชีวิตของผู้เล่นลดลงต่ำกว่าหนึ่งทำไมไม่ตรวจสอบเฉพาะเมื่อผู้เล่นเสียชีวิตแทนที่จะเป็นทุกเฟรม?

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

พิมพ์somethingและsomething elseเพียงครั้งเดียว มันไม่ได้ให้โค้ดที่ดูดีที่สุด แต่ใช้ได้ดี ฉันค่อนข้างมั่นใจว่ามีวิธีในการปรับปรุงบางทีโดยการรับรองชื่อตัวแปรที่ไม่ซ้ำกันให้ดีขึ้น สิ่งนี้#defineจะใช้งานได้เพียงครั้งเดียวในระหว่างรันไทม์ ไม่มีวิธีรีเซ็ตและไม่มีวิธีบันทึก

แม้จะมีกลอุบาย แต่ฉันขอแนะนำให้คุณควบคุมโค้ดโฟลว์ของคุณก่อนดีกว่าและใช้สิ่งนี้#defineเป็นทางเลือกสุดท้ายหรือเพื่อการดีบักเท่านั้น


+1 ดีในครั้งเดียวถ้าวิธีแก้ปัญหา ยุ่ง แต่เท่ห์
ทำลาย

1
มีปัญหาใหญ่อย่างหนึ่งกับ ONE_TIME_IF ของคุณคุณไม่สามารถทำให้มันเกิดขึ้นได้อีก ตัวอย่างเช่นผู้เล่นกลับไปที่เมนูหลักและกลับสู่ฉากเกมในภายหลัง คำสั่งนั้นจะไม่ยิงอีกครั้ง และมีเงื่อนไขที่คุณอาจต้องเก็บไว้ในระหว่างการรันแอปพลิเคชันเช่นรายการของรางวัลที่ได้รับไปแล้ว วิธีการของคุณจะให้รางวัลแก่ผู้เล่นสองครั้งหากผู้เล่นได้รับมันในแอพพลิเคชั่นที่ต่างกันสองตัว
Ali1S232

1
@Gajoo นั่นเป็นเรื่องจริงมันเป็นปัญหาหากเงื่อนไขจำเป็นต้องรีเซ็ตหรือบันทึก ฉันแก้ไขเพื่อชี้แจงว่า มันเป็นเหตุผลที่ฉันแนะนำว่ามันเป็นทางเลือกสุดท้ายหรือเพียงแค่การดีบั๊ก
kevintodisco

ฉันขอแนะนำให้ทำ {} ขณะที่ (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 เพื่อจัดการการเปลี่ยนแปลง / การผลัก / การเปลี่ยนระหว่างสถานะมากกว่าการเปลี่ยนคำสั่ง

คุณสามารถทำให้โซลูชันการจัดการสถานะของคุณมีความซับซ้อนได้ตามที่คุณต้องการรวมถึงสถานะที่ใช้งานอยู่หลายสถานะเป็นพาเรนต์เดียวที่มีสถานะแอ็คทีฟลูกโดยใช้ลำดับชั้นบางอย่าง ฯลฯ ใช้สิ่งที่สมเหตุสมผลสำหรับคุณ โซลูชันรูปแบบสถานะจะทำให้โค้ดของคุณสามารถนำมาใช้ซ้ำได้ง่ายขึ้นและง่ายต่อการดูแลรักษาและติดตาม


1

หากคุณกังวลเกี่ยวกับประสิทธิภาพการทำงานของการตรวจสอบหลายครั้งมักไม่สำคัญ เหมือนกันสำหรับการมีเงื่อนไขบูล

หากคุณสนใจในสิ่งอื่นคุณสามารถใช้สิ่งที่คล้ายกับรูปแบบการออกแบบที่ไม่มีค่าเพื่อแก้ไขปัญหานี้

ในกรณีนี้สมมติว่าคุณต้องการเรียกวิธีfooถ้าผู้เล่นไม่มีชีวิตเหลือ คุณจะใช้สิ่งนี้เป็นสองคลาสหนึ่งซึ่งเรียกfooและที่ไม่ทำอะไรเลย ให้เรียกพวกเขา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 () เรียกใช้เมธอดที่ตั้งค่าตัวจับเวลาให้ทำงานในไม่กี่วินาทีตัวจับเวลานั้นจะถูกตั้งค่าเป็นเวลาเดียวกันในแต่ละเฟรม เท่าที่ฉันสามารถบอกได้ว่าการแก้ปัญหาของคุณเหมือนกับ (numLives <1) {// do stuff} else {// empty}
EddieV223

อืมฉันเห็นสิ่งที่คุณพูด การแก้ปัญหาอาจจะมีการย้ายifการตรวจสอบลงในFooClassอินสแตนซ์ของตัวเองดังนั้นจึงดำเนินการเพียงครั้งเดียวและเช่นเดียวกันสำหรับ CallFooinstantiating
ashes999
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.