จะแก้ไขระหว่างสองสถานะเกมได้อย่างไร


24

รูปแบบที่ดีที่สุดในการสร้างระบบคือตำแหน่งของวัตถุทั้งหมดที่จะถูกแก้ไขระหว่างสถานะการอัพเดทสองสถานะ

การอัปเดตจะทำงานที่ความถี่เดิมเสมอ แต่ฉันต้องการให้สามารถแสดงผลที่ FPS ใด ๆ ดังนั้นการเรนเดอร์จะราบรื่นเท่าที่จะเป็นไปได้ไม่ว่าเฟรมต่อวินาทีไม่ว่าจะต่ำกว่าหรือสูงกว่าความถี่การอัปเดตก็ตาม

ฉันต้องการอัปเดต 1 เฟรมเป็นการประมาณค่าในอนาคตจากเฟรมปัจจุบันเป็นเฟรมในอนาคต คำตอบนี้มีลิงก์ที่พูดถึงการทำสิ่งนี้:

การกำหนดเวลาแบบกึ่งถาวรหรือแบบคงที่ครบหรือไม่

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


2
เห็บเป็นเห็บตรรกะ? ดังนั้นการอัปเดต fps ของคุณ <การแสดงผล fps หรือไม่
The Duck คอมมิวนิสต์

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

นี่อาจเป็นเรื่องยากจริง ๆ ในขณะที่เรนเดอร์ตำแหน่งวัตถุทั้งหมดของคุณควรจะยังคงอยู่ (การเปลี่ยนแปลงในระหว่างกระบวนการเรนเดอร์อาจทำให้เกิดพฤติกรรมที่ไม่แยก)
Ali1S232

การแก้ไขจะคำนวณสถานะ ณ เวลาระหว่างเฟรมการอัพเดทที่คำนวณแล้ว 2 ตัว คำถามนี้เกี่ยวกับการคาดการณ์ไม่ได้คำนวณสถานะหลังจากผ่านช่วงเวลาหนึ่งหรือไม่? เนื่องจากการปรับปรุงครั้งต่อไปยังไม่ได้คำนวณแม้แต่
Maik Semder

ฉันคิดว่าถ้าเขามีการอัพเดท / การแสดงผลเธรดเพียงรายการเดียวจะไม่สามารถเกิดขึ้นอีกครั้งในการอัปเดตตำแหน่งการแสดงผลอีกครั้ง คุณเพิ่งส่งตำแหน่งไปยัง GPU แล้วอัปเดตอีกครั้ง
zacharmarz

คำตอบ:


22

คุณต้องการแยกการอัปเดต (ติ๊กตรรกะ) และอัตราการวาด (เห็บแสดงผล)

การอัปเดตของคุณจะสร้างตำแหน่งของวัตถุทั้งหมดในโลกที่จะวาด

ฉันจะครอบคลุมความเป็นไปได้ที่แตกต่างกันสองแบบที่นี่สิ่งที่คุณร้องขอการคาดการณ์และวิธีอื่นการแก้ไข

1

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

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

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

2

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


ในความคิดของฉันคุณอาจต้องการการแก้ไขตามที่ฉันได้อธิบายเนื่องจากเป็นเรื่องง่ายกว่าที่ทั้งสองจะนำไปใช้และการวาดเศษเสี้ยวของวินาที (เช่น 1/60 วินาที) ที่อยู่หลังสถานะอัปเดตปัจจุบันของคุณนั้นดี


แก้ไข:

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

เมื่อคุณสร้างวัตถุที่วาดได้มันจะเก็บคุณสมบัติที่จำเป็นในการวาด (เช่นข้อมูลสถานะที่จำเป็นในการวาด)

สำหรับตัวอย่างนี้เราจะเก็บตำแหน่งและการหมุน คุณอาจต้องการเก็บคุณสมบัติอื่น ๆ เช่นสีหรือตำแหน่งพิกัดพื้นผิว (เช่นถ้าเลื่อนพื้นผิว)

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

previous_stateวัตถุที่เก็บสำเนาที่สองของมัน ผมจะใส่ไว้ในอาร์เรย์และการอ้างอิงถึงพวกเขาเป็นและprevious_state[0] previous_state[1]ต้องการสำเนาสองชุดในทำนองเดียวกันcurrent_stateต้องการสองสำเนาของมัน

เพื่อติดตามว่ามีการใช้สำเนาของ double buffer ใดเราจัดเก็บตัวแปรstate_indexซึ่งมีให้สำหรับทั้งการอัพเดตและการดึงเธรด

เธรดการอัพเดทจะคำนวณคุณสมบัติทั้งหมดของวัตถุโดยใช้ข้อมูลของตัวเอง (โครงสร้างข้อมูลใด ๆ ที่คุณต้องการ) จากนั้นมันจะคัดลอกcurrent_state[state_index]ไปprevious_state[state_index]และสำเนาข้อมูลใหม่ที่เกี่ยวข้องกับการวาดภาพpositionและการเข้าสู่rotation current_state[state_index]จากนั้นก็state_index = 1 - state_indexเพื่อพลิกสำเนาที่ใช้ในปัจจุบันของบัฟเฟอร์คู่

ทุกอย่างในย่อหน้าข้างต้นจะต้องทำโดยใช้ล็อคเอาออก current_stateทุกอย่างในย่อหน้าข้างต้นจะต้องมีการกระทำที่มีการล็อคที่ถ่ายออกมาในการอัพเดตและดึงเธรดจะนำการล็อกนี้ออก การล็อคจะดำเนินการในช่วงระยะเวลาของการคัดลอกข้อมูลสถานะซึ่งรวดเร็ว

ในเธรดการเรนเดอร์จากนั้นคุณทำการแก้ไขเชิงเส้นกับตำแหน่งและการหมุนดังนี้:

current_position = Lerp(previous_state[state_index].position, current_state[state_index].position, elapsed/update_tick_length)

ที่ไหน elapsedเป็นระยะเวลาที่ได้ผ่านในเธรด render เนื่องจากเห็บปรับปรุงล่าสุดและupdate_tick_lengthเป็นระยะเวลาที่อัตราการปรับปรุงการแก้ไขของคุณใช้เวลาต่อเห็บ (เช่นที่ 20fps ปรับปรุงupdate_tick_length = 0.05)

หากคุณไม่ทราบว่าLerpฟังก์ชั่นข้างต้นเป็นแล้วบทความวิกิพีเดียเช็คเอาท์ในเรื่อง: เชิงเส้นการแก้ไข อย่างไรก็ตามหากคุณไม่ทราบว่า lerping คืออะไรคุณอาจยังไม่พร้อมที่จะใช้การปรับปรุง / วาดแบบแยกส่วนด้วยการวาดการแก้ไข


1
+1 จะต้องทำเช่นเดียวกันสำหรับทิศทาง / การหมุนและสถานะอื่น ๆ ที่เปลี่ยนแปลงตลอดเวลาเช่นภาพเคลื่อนไหวของวัสดุในระบบอนุภาคเป็นต้น
Maik Semder

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

นี่เป็นการทบทวนปัญหาและความแตกต่างระหว่างการประมาณค่าและการประมาณค่า มันไม่ใช่คำตอบ

1
ในตัวอย่างของฉันฉันเก็บตำแหน่งและการหมุนในสถานะ คุณสามารถเก็บความเร็ว (หรือความเร็ว) ในสถานะได้เช่นกัน จากนั้นคุณก็ค่อยๆแทรกซึมระหว่างความเร็วด้วยวิธีเดียวกัน ( Lerp(previous_speed, current_speed, elapsed/update_tick_length)) คุณสามารถทำได้ด้วยหมายเลขใด ๆ ที่คุณต้องการจัดเก็บในสถานะ การ Lerping ให้ค่าระหว่างสองค่ากับค่า lerp
Olhovsky

1
สำหรับการแก้ไขการเคลื่อนที่เชิงมุมขอแนะนำให้ใช้slerpแทน lerp ที่ง่ายที่สุดคือการจัดเก็บ quaternions ของทั้งสองรัฐและ slerp ระหว่างพวกเขา ไม่เช่นนั้นจะใช้กฎเดียวกันกับความเร็วเชิงมุมและการเร่งความเร็วเชิงมุม คุณมีกรณีทดสอบสำหรับภาพเคลื่อนไหวโครงกระดูกหรือไม่?
Maik Semder

-2

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

สมมติว่าคุณมีลิงที่ตำแหน่ง x ตอนนี้คุณยังมี "addX" ที่คุณเพิ่มไปยังตำแหน่งของลิงต่อเฟรมโดยใช้คีย์บอร์ดหรือส่วนควบคุมอื่น ๆ สิ่งนี้จะทำงานตราบใดที่คุณมีอัตราเฟรมที่รับประกัน สมมติว่า x ของคุณคือ 100 และ addX ของคุณคือ 10 หลังจาก 10 เฟรม x + = addX ของคุณควรสะสมเป็น 200

ตอนนี้แทนที่จะเป็น addX เมื่อคุณมีอัตราเฟรมที่เปลี่ยนแปลงคุณควรคิดในแง่ของความเร็วและความเร่ง ฉันจะนำคุณไปสู่การคำนวณทางคณิตศาสตร์ทั้งหมด แต่ง่ายมาก สิ่งที่เราอยากรู้คือระยะทางที่คุณต้องการเดินทางต่อมิลลิวินาที (1 / 1,000 ของวินาที)

หากคุณกำลังถ่ายภาพ 30 FPS ดังนั้น velX ของคุณควรเป็น 1 ใน 3 ของวินาที (10 เฟรมจากตัวอย่างล่าสุดที่ 30 FPS) และคุณรู้ว่าคุณต้องการเดินทาง 100 'x' ในเวลานั้นดังนั้นตั้งค่า velX ของคุณเป็น 100 distance / 10 FPS หรือ 10 distance ต่อเฟรม ในหน่วยมิลลิวินาทีที่ทำงานได้ที่ 1 ระยะทาง x ต่อ 3.3 มิลลิวินาทีหรือ 0.3 'x' ต่อมิลลิวินาที

ตอนนี้ทุกครั้งที่คุณอัปเดตสิ่งที่คุณต้องทำคือหาเวลาที่ผ่านไป ไม่ว่าจะเป็น 33 มิลลิวินาที (1 / 30th ของวินาที) หรืออะไรก็ตามคุณแค่คูณระยะทาง 0.3 ด้วยจำนวนมิลลิวินาทีที่ผ่านไป ซึ่งหมายความว่าคุณต้องการตัวจับเวลาที่ให้ความแม่นยำกับ ms (มิลลิวินาที) แต่ตัวจับเวลาส่วนใหญ่ให้สิ่งนี้แก่คุณ ทำอะไรแบบนี้:

var startTime = getTimeInMillisecond ()

... ในภายหลัง ...

var time = getTimeInMillisecond ()

var elapsedTime = time-startTime

startTime = เวลา

... ตอนนี้ใช้เวลาผ่านไปนี้เพื่อคำนวณระยะทางทั้งหมดของคุณ


1
เขาไม่มีอัตราการอัปเดตที่เปลี่ยนแปลง เขามีอัตราการอัพเดทคงที่ พูดตามตรงฉันไม่รู้จริงๆว่าคุณกำลังพยายามทำอะไรอยู่ที่นี่: /
Olhovsky

1
??? -1 นั่นคือประเด็นทั้งหมดฉันมีอัตราการอัปเดตที่รับประกัน แต่มีอัตราการเรนเดอร์ที่เปลี่ยนแปลงและฉันต้องการให้มันราบรื่นโดยไม่ต้องพูดติดอ่าง
AttackHobo

อัตราการอัปเดตที่เปลี่ยนแปลงไม่สามารถใช้งานได้ดีกับเกมในเครือข่ายเกมแข่งขันระบบเล่นซ้ำหรืออะไรก็ตามที่ต้องอาศัยการเล่นเกมที่กำหนดไว้
AttackHobo

1
การอัพเดทคงที่ยังช่วยให้สามารถรวมการหลอกหลอกได้ง่าย เช่นถ้าคุณต้องการทวีคูณความเร็วของคุณทีละ 0.9 เฟรมคุณจะคิดได้อย่างไรว่าจะคูณด้วยถ้าคุณมีเฟรมที่เร็วหรือช้า การแก้ไขแบบคงที่บางครั้งก็เป็นที่ต้องการอย่างมาก - การจำลองทางฟิสิกส์แทบทั้งหมดใช้อัตราการอัพเดทคงที่
Olhovsky

2
ถ้าฉันใช้อัตราเฟรมที่ผันแปรและตั้งค่าสถานะเริ่มต้นที่ซับซ้อนโดยมีวัตถุจำนวนมากที่กระเด็นออกจากกันไม่มีการรับประกันว่ามันจะจำลองเหมือนกันทุกประการ ในความเป็นจริงมันน่าจะจำลองแตกต่างกันเล็กน้อยในแต่ละครั้งโดยมีความแตกต่างเล็กน้อยในช่วงเริ่มต้นประกอบกับช่วงเวลาสั้น ๆ ในสภาวะที่แตกต่างกันอย่างสิ้นเชิง
AttackHobo
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.