ปัญหาการปะทะกันของ Platformer AABB 2D


51

http://dl.dropbox.com/u/3724424/Programming/Gifs/game6.gif

ฉันมีปัญหากับการแก้ไขการชนของ AABB


ฉันแก้ไขทางแยก AABB โดยการแก้ไขแกน X ก่อนแล้วตามด้วยแกน Y สิ่งนี้ทำเพื่อป้องกันข้อผิดพลาดนี้: http://i.stack.imgur.com/NLg4j.png


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


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

เมื่อผู้เล่นย้ายเข้ามาในแนวดิ่งแหลม (ได้รับผลกระทบจากแรงโน้มถ่วงตกเข้าไปในพวกเขา) เขาผลักแกน Y เพราะไม่มีการทับซ้อนกันบนแกน X เพื่อเริ่มต้น


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

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


ฉันต้องแก้ปัญหาการชนกันของ AABB ในแบบที่ทั้งสองกรณีที่อธิบายไว้ข้างต้นทำงานได้ตามที่ตั้งใจไว้

นี่คือรหัสที่มาการชนกันของฉันในปัจจุบัน: http://pastebin.com/MiCi3nA1

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


ฉันพยายามใช้ระบบการชนกันเช่นเดียวกับในการสาธิตแพลตฟอร์ม XNA AppHub (โดยการคัดลอกวางสิ่งของส่วนใหญ่) อย่างไรก็ตามข้อผิดพลาด "กระโดด" เกิดขึ้นในเกมของฉันในขณะที่มันไม่ได้เกิดขึ้นในการสาธิต AppHub [กระโดดบั๊ก: http://i.stack.imgur.com/NLg4j.png ]

หากต้องการข้ามฉันจะตรวจสอบว่าผู้เล่นเป็น "onGround" หรือไม่จากนั้นเพิ่ม -5 ใน Velocity.Y

เนื่องจาก Velocity.X ของผู้เล่นนั้นสูงกว่า Velocity.Y (อ้างอิงจากพาเนลที่สี่ในแผนภาพ) onGround ถูกตั้งค่าเป็นจริงเมื่อไม่ควรทำดังนั้นจึงอนุญาตให้ผู้เล่นกระโดดกลางอากาศได้

ฉันเชื่อว่าสิ่งนี้จะไม่เกิดขึ้นในการสาธิต AppHub เพราะ Velocity.X ของผู้เล่นจะไม่สูงกว่า Velocity.Y แต่ฉันอาจเข้าใจผิด

ฉันแก้ไขสิ่งนี้ก่อนโดยการแก้ไขบนแกน X ก่อนจากนั้นบนแกน Y แต่นั่นทำให้เกิดการชนกับสไปค์ดังที่ฉันได้กล่าวไว้ข้างต้น


5
ที่จริงไม่มีอะไรผิดพลาด วิธีที่ฉันใช้คือกดที่แกน X ก่อนแล้วจึงใช้กับแกน Y นั่นเป็นเหตุผลที่ผู้เล่นได้รับการผลักในแนวนอนแม้ในแนวดิ่ง ฉันต้องการค้นหาวิธีแก้ปัญหาที่หลีกเลี่ยง "ปัญหาการกระโดด" และผลักผู้เล่นบนแกนตื้น (แกนที่มีการแทรกซึมน้อยที่สุด) แต่ฉันไม่สามารถหาได้
Vittorio Romeo

1
คุณสามารถตรวจจับใบหน้าของสิ่งกีดขวางที่ผู้เล่นสัมผัสและแก้ไขปัญหานั้น
Ioachim

4
@Johnathan Hobbs อ่านคำถาม วีรู้ว่าโค้ดของเขากำลังทำอะไรอยู่ แต่ไม่รู้ว่าจะแก้ปัญหาอย่างไร การก้าวผ่านโค้ดจะไม่ช่วยเขาในสถานการณ์นี้
AttackHobo

4
@Maik @Jonathan: ไม่มีอะไร "ผิดพลาด" กับโปรแกรม - เขาเข้าใจอย่างถ่องแท้ว่าทำไมและทำไมอัลกอริธึมของเขาจึงไม่ทำตามที่เขาต้องการ เขาไม่รู้วิธีเปลี่ยนอัลกอริทึมเพื่อทำสิ่งที่เขาต้องการ ดังนั้นตัวดีบักจึงไม่มีประโยชน์ในกรณีนี้
BlueRaja - Danny Pflughoeft

1
@AttackingHobo @BlueRaja ฉันเห็นด้วยและฉันได้ลบความคิดเห็นของฉัน นั่นคือฉันเพิ่งกระโจนสู่ข้อสรุปเกี่ยวกับสิ่งที่เกิดขึ้น ฉันขอโทษจริง ๆ ที่ทำให้คุณต้องอธิบายและเข้าใจผิดอย่างน้อยหนึ่งคน สุจริตคุณสามารถไว้วางใจให้ฉันดูดซับคำถามได้อย่างถูกต้องก่อนที่ฉันจะออกคำตอบใด ๆ ในครั้งต่อไป
doppelgreener

คำตอบ:


9

ตกลงฉันคิดออกว่าทำไมสาธิต platformer XNA AppHubไม่ได้มี "กระโดด" ข้อผิดพลาด: การสาธิตการทดสอบกระเบื้องชนจากบนลงล่าง เมื่อเทียบกับ "กำแพง" ผู้เล่นอาจซ้อนทับหลายแผ่น ลำดับการแก้ไขมีความสำคัญเนื่องจากการแก้ไขการชนหนึ่งอาจแก้ไขการชนอื่น ๆ (แต่ในทิศทางที่แตกต่างกัน) onGroundคุณสมบัติเป็นเพียงการตั้งค่าเมื่อการปะทะกันได้รับการแก้ไขโดยการผลักดันผู้เล่นขึ้นไปบนแกน y ความละเอียดนี้จะไม่เกิดขึ้นหากความละเอียดก่อนหน้าผลักเครื่องเล่นลงและ / หรือแนวนอน

ฉันสามารถสร้างข้อผิดพลาด "กระโดด" ในการสาธิต XNA โดยการเปลี่ยนบรรทัดนี้:

for (int y = topTile; y <= bottomTile; ++y)

สำหรับสิ่งนี้:

for (int y = bottomTile; y >= topTile; --y)

(ฉันยัง tweaked ค่าคงที่เกี่ยวข้องกับฟิสิกส์บางอย่าง แต่นี่ไม่ควรสำคัญ)

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


ฟังดูน่าสนใจ ... คุณคิดว่าการสั่งทุกอย่างโดยพิกัด Y ก่อนที่จะตรวจจับการชนสามารถแก้ปัญหาทั้งหมดได้หรือไม่? มีผู้ใดบ้าง
Vittorio Romeo

Aaaand ฉันพบคำว่า "catch" ใช้วิธีนี้ข้อผิดพลาดการกระโดดได้รับการแก้ไข แต่ถ้าฉันตั้ง Velocity.Y เป็น 0 หลังจากกดปุ่มมันจะเกิดขึ้นอีกครั้ง
Vittorio Romeo

@Vee: ตั้งค่าPosition = nextPositionทันทีภายในforลูปมิฉะนั้นจะonGroundยังคงมีวิธีแก้ไขปัญหาการชน (การตั้งค่า) ที่ไม่ต้องการ ผู้เล่นควรถูกผลักลงในแนวตั้ง (และไม่ขึ้น) เมื่อกระทบเพดานดังนั้นonGroundไม่ควรตั้ง นี่คือวิธีที่ XNA ทำการสาธิตและฉันไม่สามารถทำซ้ำข้อบกพร่อง "ฝ้า" ที่นั่นได้
Leftium

pastebin.com/TLrQPeBU - นี่คือรหัสของฉัน: ฉันทำงานกับตำแหน่งตัวเองโดยไม่ต้องใช้ตัวแปร "nextPosition" มันควรจะทำงานได้ดีในการสาธิต XNA แต่ถ้าฉันรักษาเส้นที่ตั้งค่า Velocity.Y ไว้ที่ 0 (บรรทัดที่ 57) จะไม่ใส่เครื่องหมายข้อคิดเห็นลงไปดังนั้นข้อผิดพลาดการกระโดดจะเกิดขึ้น หากฉันลบมันผู้เล่นจะลอยตัวเมื่อกระโดดขึ้นไปบนเพดาน สามารถแก้ไขได้หรือไม่
Vittorio Romeo

@Vee: รหัสของคุณไม่แสดงวิธีการonGroundตั้งค่าดังนั้นฉันไม่สามารถตรวจสอบได้ว่าทำไมการกระโดดจึงไม่ถูกต้อง การสาธิต XNA ปรับปรุงคล้ายคลึงของสถานที่ให้บริการภายในisOnGround HandleCollisions()นอกจากนี้หลังจากตั้งค่าVelocity.Yเป็น 0 ทำไมแรงโน้มถ่วงไม่เริ่มขยับเครื่องเล่นลงอีกครั้ง ฉันเดาว่าonGroundคุณสมบัตินี้ตั้งไว้ไม่ถูกต้อง ดูการสาธิตของ XNA previousBottomและisOnGround( IsOnGround)
Leftium

9

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

รหัสของคุณจะเป็นดังนี้:

// This loop repeats, until our object has been fully pushed outside of all
// collision objects
while ( StillCollidingWithSomething(object) )
{
  float xDistanceToResolve = XDistanceToMoveToResolveCollisions( object );
  float yDistanceToResolve = YDistanceToMoveToResolveCollisions( object );
  bool xIsColliding = (xDistanceToResolve != 0.f);

  // if we aren't colliding on x (not possible for normal solid collision 
  // shapes, but can happen for unidirectional collision objects, such as 
  // platforms which can be jumped up through, but support the player from 
  // above), or if a correction along y would simply require a smaller move 
  // than one along x, then resolve our collision by moving along y.

  if ( !xIsColliding || fabs( yDistanceToResolve ) < fabs( xDistanceToResolve ) )
  {
    object->Move( 0.f, yDistanceToResolve );
  }
  else // otherwise, resolve the collision by moving along x
  {
    object->Move( xDistanceToResolve, 0.f );
  }
}

การแก้ไขครั้งใหญ่ : จากการอ่านความเห็นเกี่ยวกับคำตอบอื่น ๆ ฉันคิดว่าฉันได้สังเกตเห็นข้อสันนิษฐานที่ไม่ได้กำหนดซึ่งจะทำให้วิธีการนี้ไม่ทำงาน (และอธิบายได้ว่าทำไมฉันไม่เข้าใจปัญหาที่บางคน - แต่ไม่ใช่ทั้งหมด - คนเห็นด้วยวิธีนี้) เพื่อทำอย่างละเอียดนี่คือ pseudocode บางส่วนที่เพิ่มเติมแสดงให้เห็นอย่างชัดเจนว่าฟังก์ชันที่ฉันอ้างถึงก่อนหน้านี้ควรทำอะไรจริง:

bool StillCollidingWithSomething( MovingObject object )
{
  // loop over every collision object in the world.  (Implementation detail:
  // don't test 'object' against itself!)
  for( int i = 0; i < collisionObjectCount; i++ )
  {
    // if the moving object overlaps any collision object in the world, then
    // it's colliding
    if ( Overlaps( collisionObject[i], object ) )
      return true;
  }
  return false;
}

float XDistanceToMoveToResolveCollisions( MovingObject object )
{
  // check how far we'd have to move left or right to stop colliding with anything
  // return whichever move is smaller
  float moveOutLeft = FindDistanceToEmptySpaceAlongNegativeX(object->GetPosition());
  float moveOutRight = FindDistanceToEmptySpaceAlongX(object->GetPosition());
  float bestMoveOut = min( fabs(moveOutLeft), fabs(moveOutRight) );

  return minimumMove;
}

float FindDistanceToEmptySpaceAlongX( Vector2D position )
{
  Vector2D cursor = position;
  bool colliding = true;
  // until we stop colliding...
  while ( colliding )
  {
    colliding = false;
    // loop over all collision objects...
    for( int i = 0; i < collisionObjectCount; i++ )
    {
      // and if we hit an object...
      if ( Overlaps( collisionObject[i], cursor ) )
      {
        // move outside of the object, and repeat.
        cursor.x = collisionObject[i].rightSide;
        colliding = true;

        // break back to the 'while' loop, to re-test collisions with
        // our new cursor position
        break;
      }
    }
  }
  // return how far we had to move, to reach empty space
  return cursor.x - position.x;  
}

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

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


1
i.stack.imgur.com/NLg4j.png - สิ่งนี้เกิดขึ้นหากฉันใช้วิธีการด้านบน
Vittorio Romeo

1
บางทีฉันอาจเข้าใจโค้ดของคุณไม่ถูกต้อง แต่ให้ฉันอธิบายปัญหาในแผนภาพ: เนื่องจากผู้เล่นเร็วกว่าบนแกน X การเจาะ X คือ> มากกว่าการเจาะ Y การชนเลือกแกน Y (เนื่องจากการแทรกซึมมีขนาดเล็ก) และผลักผู้เล่นในแนวตั้ง สิ่งนี้จะไม่เกิดขึ้นหากผู้เล่นช้าพอที่จะให้การเจาะ Y อยู่เสมอ> มากกว่าการบุก X
Vittorio Romeo

1
@Vee บานหน้าต่างของแผนภาพใดที่คุณอ้างถึงในที่นี้ว่าเป็นพฤติกรรมที่ไม่ถูกต้องโดยใช้วิธีการนี้ ฉันสมมติว่าบานหน้าต่างที่ 5 ซึ่งผู้เล่นแทรกซึมอย่างชัดเจนใน X น้อยกว่าใน Y ซึ่งจะส่งผลให้ถูกผลักออกไปตามแกน X ซึ่งเป็นพฤติกรรมที่ถูกต้อง คุณจะได้รับพฤติกรรมที่ไม่ดีถ้าคุณเลือกแกนสำหรับการแก้ปัญหาโดยพิจารณาจากความเร็ว (ในภาพของคุณ) และไม่ได้อิงตามการเจาะตามจริง (ตามที่ฉันแนะนำ)
Trevor Powell

1
@ เทรเวอร์: อ่าฉันเห็นสิ่งที่คุณพูด ในกรณีนี้คุณสามารถทำมันได้if(fabs( yDistanceToResolve ) < fabs( xDistanceToResolve ) || xDistanceToResolve == 0) { object->Move( 0.f, yDistanceToResolve ); } else { object->Move( xDistanceToResolve, 0.f ); }
BlueRaja - Danny Pflughoeft

1
@ Bluelaja จุดที่ดี ฉันได้แก้ไขคำตอบเพื่อใช้เงื่อนไขน้อยลงตามที่คุณแนะนำ ขอบคุณ!
เทรเวอร์พาวเวลล์

6

แก้ไข

ฉันได้ปรับปรุงมัน

ดูเหมือนว่าสิ่งสำคัญที่ฉันเปลี่ยนคือการจำแนกทางแยก

ทางแยกมีทั้ง:

  • กระแทกเพดาน
  • ยอดฮิต (ผลักออกจากพื้น)
  • การชนผนังกลางอากาศ
  • กำแพงชนกัน

และฉันแก้ไขพวกเขาตามลำดับที่

กำหนดการชนกันของพื้นดินในฐานะผู้เล่นที่มีอย่างน้อย 1/4 วิธีบนแผ่นกระเบื้อง

นี่คือการชนกันของพื้นดินและผู้เล่น ( สีน้ำเงิน ) จะนั่งอยู่ด้านบนของไทล์ ( สีดำ ) การชนกันของพื้นดิน

แต่นี่ไม่ใช่การชนกันของพื้นดินและผู้เล่นจะ "ลื่น" ที่ด้านขวาของไทล์เมื่อเขาตกลงบนพื้น ไม่ใช่ gonig ที่จะเป็นผู้เล่นปะทะกันบนพื้น

ด้วยวิธีนี้ผู้เล่นจะไม่ถูกจับที่ผนังด้านข้างอีกต่อไป


ฉันลองวิธีการของคุณ (ดูการติดตั้งของฉัน: pastebin.com/HarnNnnp ) แต่ฉันไม่รู้ว่าจะตั้งค่า Velocity ของผู้เล่นเป็น 0 ฉันได้ลองตั้งค่าเป็น 0 ถ้า encrY <= 0 แต่นั่นทำให้ผู้เล่นหยุดกลางอากาศ ในขณะที่เลื่อนไปตามกำแพงและยังช่วยให้เขากระโดดข้ามกำแพงซ้ำ ๆ
Vittorio Romeo

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

ขออภัยที่โพสต์ความคิดเห็นอื่น แต่หลังจากคัดลอกรหัสของคุณในโครงการใหม่เปลี่ยน sp เป็น 5 และทำให้กระเบื้องวางไข่ในรูปแบบผนังข้อผิดพลาดการกระโดดเกิดขึ้น (ดูแผนภาพนี้: i.stack.imgur.com /NLg4j.png)
Vittorio Romeo

ลองตอนนี้เลย
bobobobo

+1 สำหรับการทำงานตัวอย่างโค้ด (ที่ขาดหายไปในแนวนอนย้าย "เข็ม" บล็อกแม้ว่า :)
Leftium

3

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

โซลูชันของฉัน:

เมื่อฉันเจอกับปัญหาเช่นนี้ใน platformer 2d ของฉันฉันทำสิ่งต่อไปนี้:

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


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

ฉันยังไม่พบสถานการณ์ (นอกเหนือจากความเร็วสูง) ว่าวิธีนี้ใช้ไม่ได้
ultifinitus

ฟังดูดี แต่คุณสามารถอธิบายได้จริง ๆ ว่าคุณจะแก้ปัญหาการแทรกซึมได้อย่างไร คุณแก้ปัญหาบนแกนที่เล็กที่สุดเสมอหรือไม่? คุณตรวจสอบด้านข้างของการชนหรือไม่
Vittorio Romeo

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

2

ในวิดีโอเกมฉันได้ตั้งโปรแกรมวิธีการคือให้ฟังก์ชั่นบอกว่าตำแหน่งของคุณถูกต้องหรือไม่ บูล ValidPos (int x, int y, int wi, int เขา);

ฟังก์ชั่นตรวจสอบกล่องขอบเขต (x, y, wi, เขา) กับรูปทรงเรขาคณิตทั้งหมดในเกมและส่งกลับเท็จถ้ามีทางแยกใด ๆ

ถ้าคุณต้องการที่จะย้ายพูดไปทางขวารับตำแหน่งผู้เล่นเพิ่ม 4 ถึง x และตรวจสอบว่าตำแหน่งที่ถูกต้องถ้าไม่ตรวจสอบด้วย + 3, + 2 ฯลฯ จนกว่ามันจะเป็น

หากคุณต้องการแรงโน้มถ่วงคุณต้องมีตัวแปรที่เพิ่มขึ้นตราบใดที่คุณไม่กระแทกกับพื้นดิน (ชนพื้นดิน: ValidPos (x, y + 1, wi, เขา) == จริง, y เป็นบวกลงที่นี่ ถ้าคุณสามารถย้ายระยะทางนั้นได้ (เช่น. ValidPos (x, y + แรงโน้มถ่วง, wi, เขา) จะคืนค่าจริง) คุณกำลังตกหลุม, มีประโยชน์บางครั้งเมื่อคุณไม่สามารถควบคุมตัวละครของคุณได้เมื่อล้ม

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

หากไม่เป็นเช่นนั้นคุณต้องหาตำแหน่งที่เหมาะสม หากวัตถุในเกมไม่สามารถเคลื่อนที่ได้เร็วกว่า 2 พิกเซลต่อการปฏิวัติเกมคุณจะต้องตรวจสอบว่าตำแหน่ง (x, y-1) ถูกต้องหรือไม่แล้ว (x, y-2) จากนั้น (x + 1, y) เป็นต้น ฯลฯ ควรตรวจสอบช่องว่างทั้งหมดระหว่าง (x-2, y-2) ถึง (x + 2, y + 2) หากไม่มีตำแหน่งที่ถูกต้องก็หมายความว่าคุณถูก 'บี้'

HTH

Valmond


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

"คุณกำลังทำอะไรกับการเปลี่ยนแปลงในตำแหน่งของคุณคือการตรวจสอบ" ขออภัย แต่สิ่งที่เกินความหมาย? BTW แน่นอนว่าคุณจะใช้เทคนิคการแบ่งพื้นที่ (BSP, Octree, quadtree, Tilemap และอื่น ๆ ) เพื่อเพิ่มความเร็วในเกม แต่คุณจะต้องทำเช่นนั้นเสมอ (ถ้าแผนที่มีขนาดใหญ่) คำถามไม่เกี่ยวกับเรื่องนั้น แต่ เกี่ยวกับอัลกอริทึมที่ใช้ในการเคลื่อนย้าย (อย่างถูกต้อง) ผู้เล่น
Valmond

@Valmond ฉันคิดว่า @Jeff หมายความว่าหากคุณผู้เล่นเคลื่อนที่ไปทางซ้าย 10 พิกเซลคุณสามารถตรวจจับการชนกันได้สูงสุด 10 ครั้ง
Jonathan Connell

ถ้านั่นคือสิ่งที่เขามีความหมายแล้วเขาก็พูดถูกและควรเป็นเช่นนั้น (ใครจะบอกได้ว่าเราหยุดที่ +6 ไม่ใช่ +7?) คอมพิวเตอร์เร็วฉันใช้มันกับ S40 (~ 200mhz และไม่เร็ว kvm) และมันใช้งานได้อย่างมีเสน่ห์ คุณสามารถลองเพิ่มประสิทธิภาพของอัลโกเริ่มต้นได้เสมอ แต่นั่นจะให้มุมตัวพิมพ์ที่คุณต้องการเช่นเดียวกับ OP
Valmond

นั่นคือสิ่งที่ฉันหมายถึง
Jeff

2

ฉันมีคำถามสองสามข้อก่อนที่จะเริ่มตอบคำถามนี้ อย่างแรก, ในข้อผิดพลาดดั้งเดิมที่คุณติดอยู่บนผนัง, กระเบื้องเหล่านั้นอยู่บนแผ่นกระเบื้องด้านซ้ายแต่ละอันซึ่งตรงข้ามกับแผ่นใหญ่แผ่นเดียว? และถ้าพวกเขาเป็นผู้เล่นที่ติดอยู่ระหว่างพวกเขา? ถ้าใช่ทั้งสองคำถามเหล่านั้นเพียงให้แน่ใจว่าคุณตำแหน่งใหม่ที่ถูกต้อง นั่นหมายความว่าคุณจะต้องตรวจสอบว่ามีการชนกันในจุดที่บอกให้ผู้เล่นย้ายหรือไม่ ดังนั้นแก้ปัญหาการกระจัดต่ำสุดดังที่อธิบายไว้ด้านล่างแล้วย้ายผู้เล่นของคุณตามนั้นหากเขาทำได้ย้ายไปที่นั่น. เกือบใต้จมูก: P นี่จะแนะนำข้อผิดพลาดอีกอันหนึ่งซึ่งฉันเรียกว่า "มุมคดี" เป็นหลักในแง่ของมุม (เช่นด้านล่างซ้ายที่แหลมแนวนอนออกมาใน. gif ของคุณ แต่ถ้าไม่มีหนามแหลม) จะไม่แก้ไขการชนเพราะมันจะคิดว่าไม่มีความละเอียดที่คุณสร้างขึ้นทำให้ตำแหน่งที่ถูกต้อง . ในการแก้ปัญหานี้เพียงแค่เก็บบูลว่าการชนนั้นได้รับการแก้ไขหรือไม่และรายการความละเอียดการเจาะต่ำสุดทั้งหมด หลังจากนั้นหากการชนกันไม่ได้รับการแก้ไขให้วนซ้ำความละเอียดที่คุณสร้างขึ้นและติดตามความละเอียดสูงสุด X และความละเอียดสูงสุด Y (ค่าสูงสุดไม่จำเป็นต้องมาจากความละเอียดเดียวกัน) จากนั้นแก้ไขการชนกันของค่าสูงสุดเหล่านั้น ดูเหมือนว่าจะแก้ปัญหาทั้งหมดของคุณเช่นเดียวกับปัญหาที่ฉันพบ

    List<Vector2> collisions = new List<Vector2>();
        bool resolved = false;
        foreach (Platform p in testPlat)
        {
            Vector2 dif = p.resolveCollision(player.getCollisionMask());

            RectangleF newPos = player.getCollisionMask();

            newPos.X -= dif.X;
            newPos.Y -= dif.Y;


            if (!PlatformCollision(newPos)) //This checks if there's a collision (ie if we're entering an invalid space)
            {
                if (dif.X != 0)
                    player.velocity.X = 0; //Do whatever you want here, I like to stop my player on collisions
                if (dif.Y != 0)
                    player.velocity.Y = 0;

                player.MoveY(-dif.Y);
                player.MoveX(-dif.X);
                resolved = true;
            }
            collisions.Add(dif);
        }

        if (!resolved)
        {
            Vector2 max = Vector2.Zero;

            foreach (Vector2 v in collisions)
            {
                if (Math.Abs(v.X) > Math.Abs(max.X))
                {
                    max.X = v.X;
                }
                if (Math.Abs(v.Y) > Math.Abs(max.Y))
                {
                    max.Y = v.Y;
                }
            }

            player.MoveY(-max.Y);

            if (max.Y != 0)
                player.velocity.Y = 0;
            player.MoveX(-max.X);

            if (max.X != 0)
                player.velocity.X = 0;
        }

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


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

ซอฟแวร์ Metanet มีการกวดวิชาคลาสสิกบนวิธีการที่นี่ มันยังไปเป็นรูปทรงอื่นเช่นกัน

นี่คือฟังก์ชั่น XNA ที่ฉันสร้างขึ้นเพื่อหาเวกเตอร์ที่ทับซ้อนกันของสองรูปสี่เหลี่ยมผืนผ้า:

    public Point resolveCollision(Rectangle otherRect)
    {
        if (!isCollision(otherRect))
        {
            return Point.Zero;
        }

        int minOtherX = otherRect.X;
        int maxOtherX = otherRect.X + otherRect.Width;

        int minMyX = collisionMask.X;
        int maxMyX = collisionMask.X + collisionMask.Width;

        int minOtherY = otherRect.Y;
        int maxOtherY = otherRect.Y + otherRect.Height;

        int minMyY = collisionMask.Y;
        int maxMyY = collisionMask.Y + collisionMask.Height;

        int dx, dy;

        if (maxOtherX - minMyX < maxMyX - minOtherX)
        {
            dx = (maxOtherX - minMyX);
        }
        else
        {
            dx = -(maxMyX - minOtherX);
        }

        if (maxOtherY - minMyY < maxMyY - minOtherY)
        {
            dy = (maxOtherY - minMyY);
        }
        else
        {
            dy = -(maxMyY - minOtherY);
        }

        if (Math.Abs(dx) < Math.Abs(dy))
            return new Point(dx, 0);
        else
            return new Point(0, dy);
    }

(ฉันหวังว่ามันจะง่ายต่อการติดตามเพราะฉันมั่นใจว่าจะมีวิธีที่ดีกว่าในการนำไปใช้ ... )

isCollision (Rectangle) เป็นเพียงการโทรไปยัง Rectangle'sIntersects XNA (Rectangle) ของ XNA

ฉันได้ทดสอบสิ่งนี้กับแพลตฟอร์มที่เคลื่อนที่ได้และดูเหมือนว่าจะทำงานได้ดี ฉันจะทำแบบทดสอบเพิ่มเติมคล้ายกับ. gif ของคุณมากขึ้นเพื่อให้แน่ใจและรายงานกลับมาหากไม่ได้ผล



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

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

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

ผนังทำจากกระเบื้อง 16x16 เดือยเป็นกระเบื้องขนาด 16x16 หากฉันแก้ปัญหาบนแกนต่ำสุดจะเกิดปัญหาการกระโดด (ดูแผนภาพ) วิธีแก้ปัญหา "แสนแฮ็ค" ของคุณจะทำงานกับสถานการณ์ที่แสดงใน. gif หรือไม่
Vittorio Romeo

ใช่ทำงานในเหมือง ขนาดตัวละครของคุณคืออะไร?
Jeff

1

มันง่ายมาก

เขียนใหม่ spikes มันเป็นความผิดของหนาม

การชนของคุณควรเกิดขึ้นในหน่วยที่ไม่ต่อเนื่องและจับต้องได้ ปัญหาเท่าที่ฉันเห็นมันคือ:

1. Player is outside spikes
2. Spikes move into intersection position with the player
3. Player resolves incorrectly

ปัญหาเกิดขึ้นกับขั้นตอนที่ 2 ไม่ใช่ 3 !!

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

เดือยแหลมควรตรวจสอบการมีอยู่ของผู้เล่นและเมื่อพวกเขาเคลื่อนไหวพวกเขาควรผลักเขาตามความจำเป็น

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

โดยปกติแล้วพวกเขาจะถูกเรียกโดยวนเหตุการณ์ อย่างไรก็ตามพวกเขายังสามารถถูกเรียกโดยวัตถุเพื่อผลักดันผู้เล่น

function spikes going up:

    move spikes up

    if intersecting player:
        d = player bottom edge + spikes top edge

        player.moveY(-d)
    end if

end function

เห็นได้ชัดว่าผู้เล่นที่ยังคงต้องการที่จะตอบสนองต่อการ spikes ถ้าเขาจะเข้าสู่พวกเขา

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


ในกรณีที่รุนแรงซึ่งmoveYล้มเหลวในการลบทางแยก (เพราะกำแพงอีกด้านกำลังขวางทาง) คุณสามารถตรวจสอบอีกครั้งเพื่อหาทางแยกและลอง moveX หรือเพียงแค่ฆ่าเขา
Chris Burt-Brown
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.