การกระทำของเกมที่ใช้เวลาหลายเฟรมให้เสร็จสมบูรณ์


20

ฉันไม่เคยเขียนโปรแกรมเกมมาก่อนจริงๆคำถามตรงไปตรงมา

ลองนึกภาพฉันกำลังสร้างเกม Tetris ด้วยลูปหลักกำลังมองหาสิ่งนี้

for every frame
    handle input
    if it's time to make the current block move down a row
        if we can move the block
            move the block
        else
            remove all complete rows
            move rows down so there are no gaps
            if we can spawn a new block
                spawn a new current block
            else
                game over

ทุกสิ่งทุกอย่างในเกมเกิดขึ้นทันที - สิ่งต่าง ๆ เกิดขึ้นทันทีแถวถูกลบออกทันที ฯลฯ แต่ถ้าฉันไม่ต้องการให้สิ่งต่าง ๆ เกิดขึ้นทันที (เช่นสิ่งมีชีวิต)?

for every frame
    handle input
    if it's time to make the current block move down a row
        if we can move the block
            move the block
        else
            ?? animate complete rows disappearing (somehow, wait over multiple frames until the animation is done)
            ?? animate rows moving downwards (and again, wait over multiple frames)
            if we can spawn a new block
                spawn a new current block
            else
                game over

ในพงษ์โคลนนิ่งของฉันนี่ไม่ใช่ปัญหาเพราะทุกเฟรมฉันแค่ขยับลูกบอลและตรวจสอบการชน

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

คำตอบ:


11

วิธีการแก้ปัญหาแบบดั้งเดิมนี้เป็นเครื่องสถานะ จำกัด ซึ่งจะถูกแนะนำในความคิดเห็นหลาย

ฉันเกลียดเครื่องจักรรัฐ จำกัด

แน่นอนว่าพวกเขาเรียบง่ายพวกเขาได้รับการสนับสนุนในทุกภาษา แต่พวกเขาเจ็บปวดอย่างมากกับการทำงาน การจัดการทุกครั้งต้องใช้รหัสคัดลอกและวางบั๊กจำนวนมากและการปรับแต่งเอฟเฟกต์ในรูปแบบขนาดเล็กอาจเป็นการเปลี่ยนแปลงรหัสอย่างมาก

หากคุณสามารถใช้ภาษาที่รองรับพวกเขาฉันขอแนะนำ coroutines พวกเขาให้คุณเขียนโค้ดที่มีลักษณะดังนี้:

function TetrisPieceExplosion()
  for brightness = 0, 1, 0.2 do
    SetExplosionBrightness(brightness)
    coroutine.yield()
  end

  AllowNewBlockToFall()

  SpawnABunchOfParticles()

  RemoveBlockPhysics()

  for transparency = 0, 1, 0.5 do
    SetBlockTransparency(transparency)
    coroutine.yield()
  end

  RemoveBlockGraphics()
end

เห็นได้ชัดว่าค่อนข้าง pseudocodey แต่ก็ควรมีความชัดเจนว่าไม่เพียง แต่เป็นแบบนี้ง่ายเชิงเส้นคำอธิบายของผลกระทบที่พิเศษ แต่มันได้อย่างง่ายดายช่วยให้เราวางบล็อกใหม่ในขณะที่การเคลื่อนไหวยังคงจบ การทำสิ่งนี้ให้สำเร็จด้วยเครื่องรัฐจะน่ากลัวมาก

เพื่อความรู้ที่ดีที่สุดของฉันฟังก์ชั่นนี้ไม่สามารถใช้งานได้ง่ายใน C, C ++, C #, Objective C หรือ Java นี่เป็นหนึ่งในเหตุผลหลักที่ฉันใช้ Lua กับตรรกะของเกมทั้งหมด :)


คุณสามารถนำบางสิ่งไปใช้กับบรรทัดเหล่านี้ในภาษา OOP อื่น ๆ ลองนึกภาพบางActionคลาสและคิวการกระทำ เมื่อการดำเนินการเสร็จสมบูรณ์ให้ลบออกจากคิวและดำเนินการต่อไป ฯลฯ วิธียืดหยุ่นกว่าเครื่องรัฐ
bummzack

3
ใช้งานได้แล้ว แต่คุณกำลังมองหาที่มาจาก Action สำหรับทุก ๆ การกระทำ นอกจากนี้ยังถือว่ากระบวนการของคุณเหมาะสมในคิวอย่าง - ถ้าคุณต้องการแยกหรือลูปกับเงื่อนไขสิ้นสุดไม่ได้กำหนดโซลูชั่นคิวแบ่งลงอย่างรวดเร็ว ก็แน่นอนสะอาดกว่าวิธีการที่เครื่องของรัฐ แต่ผมคิดว่าคนที่กล้าหาญ coroutines ยังคงไว้ในการอ่าน :)
ZorbaTHut

ทรู แต่สำหรับตัวอย่างเช่น Tetris มันควรจะเพียงพอ :)
bummzack

Co-routines rock- แต่ Lua เป็นภาษาที่ดูดในวิธีอื่น ๆ มากมายฉันแค่ไม่สามารถแนะนำได้
DeadMG

ตราบใดที่คุณต้องการให้ได้ระดับสูงสุด (และไม่ใช่จากการเรียกฟังก์ชันที่ซ้อนกัน) คุณสามารถทำสิ่งเดียวกันใน C # ได้ แต่ใช่ Lua coroutines rock
munificent

8

ฉันใช้สิ่งนี้จากการเข้ารหัสเกมโดย Mike McShaffry

เขาพูดเกี่ยวกับ 'ผู้จัดการกระบวนการ' ซึ่งทำให้รายการงานที่ต้องทำสำเร็จลุล่วงไปได้ ตัวอย่างเช่นกระบวนการจะควบคุมภาพเคลื่อนไหวสำหรับการวาดดาบ (AnimProcess) หรือเปิดประตูหรือในกรณีของคุณทำให้แถวหายไป

กระบวนการจะถูกเพิ่มลงในรายการของตัวจัดการกระบวนการซึ่งจะวนซ้ำทุกเฟรมและ Update () เรียกในแต่ละเฟรม หน่วยงานที่เหมือนจริงมาก แต่สำหรับการกระทำ จะมีค่าสถานะทำลายเพื่อลบออกจากรายการเมื่อเสร็จสิ้น

สิ่งที่เรียบร้อยอีกอย่างเกี่ยวกับพวกเขาคือวิธีที่พวกเขาสามารถเชื่อมโยงโดยมีตัวชี้ไปยังกระบวนการถัดไป ด้วยวิธีนี้กระบวนการแถวภาพเคลื่อนไหวของคุณอาจประกอบด้วย:

  • AnimationProcess สำหรับแถวหายไป
  • MovementProcess เพื่อเอาชิ้นส่วนออก
  • ScoreProcess เพื่อเพิ่มคะแนนให้กับคะแนน

(เนื่องจากกระบวนการสามารถใช้งานได้ครั้งเดียวแบบมีเงื่อนไขหรือมีระยะเวลา X)

หากคุณต้องการรายละเอียดเพิ่มเติมถามไป


3

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


1

คุณต้องทราบความแตกต่างของเวลาระหว่างกรอบก่อนหน้าและกรอบปัจจุบันเสมอจากนั้นคุณต้องทำสองสิ่ง

- ตัดสินใจว่าจะอัปเดตโมเดลของคุณเมื่อใด: ใน tetris เมื่อการลบแถวเริ่มต้นขึ้นคุณไม่ต้องการให้สิ่งต่าง ๆ ชนกันกับแถวอีกต่อไปดังนั้นคุณจึงลบแถวออกจาก 'model' ของแอปพลิเคชันของคุณ

- จากนั้นคุณต้องจัดการกับวัตถุที่อยู่ในสถานะการเปลี่ยนเป็นคลาสที่แยกต่างหากซึ่งจะแก้ไขภาพเคลื่อนไหว / เหตุการณ์ในช่วงเวลาหนึ่ง ในตัวอย่าง tetris คุณจะทำให้แถวหายไปอย่างช้าๆ (เปลี่ยนความทึบของแต่ละเฟรมเล็กน้อย) หลังจาก opaqueness เป็น 0 คุณถ่ายโอนบล็อคทั้งหมดที่ด้านบนของแถวหนึ่งลงมา

สิ่งนี้อาจดูค่อนข้างซับซ้อนในตอนแรก แต่คุณจะได้รับสิ่งนี้เพียงแค่ทำให้แน่ใจว่ามีบทคัดย่อในชั้นเรียนต่าง ๆ มากมายสิ่งนี้จะทำให้ง่ายขึ้น ตรวจสอบให้แน่ใจว่าเหตุการณ์ที่ต้องใช้เวลาเช่นการลบแถวใน tetris นั้นเป็นประเภท "Fire and Forget" เพียงแค่สร้างวัตถุใหม่ที่จัดการทุกสิ่งที่ต้องทำโดยอัตโนมัติและเมื่อทุกอย่างเสร็จสิ้น ลบตัวเองออกจาก Scenegraph ของคุณ


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

0

คุณต้องคิดว่าเกมนี้เป็น "เครื่องจักรสถานะ จำกัด " เกมดังกล่าวสามารถอยู่ในสถานะใดสถานะหนึ่งในหลาย ๆ กรณี: ในกรณีของคุณ "การคาดหวังอินพุต", "ชิ้นส่วนที่เลื่อนลง", "การกระจายแถว"

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

if state == ACCEPTING_INPUT:
    if player presses any key:
        handle input
    row_timer = row_timer - time_since_last_frame
    if row_timer < 0:
        state = MOVING_PIECE_DOWN
elif state == MOVING_PIECE_DOWN:
    piece.y = piece.y + piece.speed*time_since_last_frame
    if piece.y >= target_piece_y:
        piece.y = target_piece_y
        state = ACCEPTING_INPUT
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.