จะย้ายวัตถุไปตามเส้นรอบวงของวัตถุอื่นได้อย่างไร?


11

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

ดังนั้นปัญหาทางคณิตศาสตร์นี่คือวิธีการรับตำแหน่ง(aX, aY)และ( bX, bY)เมื่อฉันรู้ว่า Center (cX, cY),ตำแหน่งของวัตถุ(oX, oY)และระยะทางที่ต้องย้าย(d)

ป้อนคำอธิบายรูปภาพที่นี่


1
คือdระยะทางที่เส้นหรือมันเป็นโค้ง?
MichaelHouse

มันเป็นระยะทางเชิงเส้นพิกเซล
Lumis

คุณคุ้นเคยกับพาหะและการดำเนินงานพื้นฐานของพวกมันอย่างไร
Patrick Hughes

@ แพทริคไม่ฉันคิดว่าฉันจะต้องทำหลักสูตรเกี่ยวกับเวกเตอร์ เนื่องจากนี่เป็นภาพเคลื่อนไหวแบบเฟรมต่อเฟรมจึงควรมีความรวดเร็วและปรับให้เหมาะสม
Lumis

คำตอบ:


8

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

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

deltaX = oX-cX;
deltaY = oY-cY;

และเมื่อเรามีเวกเตอร์สัมพัทธ์นี้แล้วเราสามารถรู้รัศมีของวงกลมที่เรากำลังทำอยู่โดยหาความยาวของมัน:

radius = sqrt(deltaX*deltaX+deltaY*deltaY);

ยิ่งไปกว่านั้นจากเวกเตอร์สัมพัทธ์เราสามารถหามุมที่แม่นยำที่เส้นจาก cX ถึง oX อยู่ที่:

curTheta = atan2(deltaX, deltaY);

ทีนี้สิ่งต่าง ๆ ก็มีเล่ห์เหลี่ยมนิดหน่อย ก่อนอื่นทำความเข้าใจว่าเส้นรอบวงของวงกลม - นั่นคือ 'ความยาวส่วนโค้ง' ของส่วนโค้งที่มีหน่วยวัดมุมเชิงมุม2π - คือ2πr โดยทั่วไปความยาวส่วนโค้งของส่วนโค้งที่มีการวัดเชิงมุมของθตามแนววงกลมรัศมี r เป็นเพียงθr หากเราต้องใช้ d ในแผนภาพของคุณเป็นความยาวส่วนโค้งและเนื่องจากเรารู้ว่ารัศมีเราสามารถหาการเปลี่ยนแปลงในทีต้าเพื่อให้เราเข้าสู่ตำแหน่งใหม่โดยเพียงแค่หารออก:

deltaTheta = d/radius; // treats d as a distance along the arc

สำหรับกรณีที่ d ต้องเป็นระยะเชิงเส้นสิ่งต่าง ๆ มีความซับซ้อนเล็กน้อย แต่โชคดีที่ไม่มาก ที่นั่น d คือด้านหนึ่งของรูปสามเหลี่ยม isoceles ซึ่งอีกสองด้านคือรัศมีของวงกลม (จาก cX / cY ถึง oX / oY และ aX / aY ตามลำดับ) และแบ่งครึ่งสามเหลี่ยม isoceles นี้ให้เราสองสามเหลี่ยมมุมฉากซึ่งแต่ละอัน มี d / 2 เป็นด้านเดียวและมีรัศมีเท่ากับด้านตรงข้ามมุมฉาก นี่หมายความว่าไซน์ของครึ่งมุมของเราคือ (d / 2) / รัศมีและมุมเต็มเป็นสองเท่านี้:

deltaTheta = 2*asin(d/(2*radius)); // treats d as a linear distance

โปรดสังเกตว่าถ้าคุณนำสารออกจากสูตรนี้และยกเลิก 2s สิ่งนี้จะเหมือนกับสูตรสุดท้าย นี่ก็เหมือนกับที่บอกว่าบาป (x) นั้นประมาณ x สำหรับค่าเล็ก ๆ ของ x ซึ่งเป็นการประมาณที่มีประโยชน์ที่จะรู้

ตอนนี้เราสามารถหามุมใหม่ได้โดยเพียงแค่การเพิ่มหรือลบ:

newTheta = curTheta+deltaTheta; // This will take you to aX, aY. For bX/bY, use curTheta-deltaTheta

เมื่อเรามีมุมใหม่แล้วเราสามารถใช้ตรีโกณมิติพื้นฐานเพื่อค้นหาเวกเตอร์ที่เกี่ยวข้องของเรา:

newDeltaX = radius*cos(newTheta);
newDeltaY = radius*sin(newTheta);

และจากตำแหน่งศูนย์กลางของเราและเวกเตอร์สัมพัทธ์เราสามารถหาจุดเป้าหมายได้

aX = cX+newDeltaX;
aY = cY+newDeltaY;

ขณะนี้มีทั้งหมดนี้มีบางส่วนที่ใหญ่ประการที่จะตระหนักถึง สำหรับหนึ่งคุณจะสังเกตเห็นว่าคณิตศาสตร์นี้ส่วนใหญ่เป็นทศนิยมและในความเป็นจริงมันจะต้องมี; การพยายามใช้วิธีนี้เพื่ออัปเดตเป็นวงวนและวนกลับเป็นค่าจำนวนเต็มในทุกขั้นตอนสามารถทำทุกอย่างจากการทำให้แวดวงของคุณไม่ปิด (ไม่ว่าจะวนเข้าหรือออกไปด้านนอกทุกครั้งที่คุณวนรอบ) เพื่อไม่เริ่มต้น สถานที่! (ถ้า d ของคุณเล็กเกินไปคุณอาจค้นพบว่า aX / aY หรือ bX / bY รุ่นโค้งมนเป็นตำแหน่งเริ่มต้นของคุณ oX / oY) สำหรับอีกอันนี่แพงมากโดยเฉพาะอย่างยิ่งสิ่งที่พยายาม ทำ; โดยทั่วไปถ้าคุณรู้ว่าตัวละครของคุณกำลังจะเคลื่อนไหวเป็นวงกลมคุณควรวางแผนล่วงหน้าทั้งหมดก่อนไม่ใช่ทำเครื่องหมายจากเฟรมหนึ่งไปอีกเฟรมเช่นนี้เนื่องจากการคำนวณที่แพงที่สุดจำนวนมากที่นี่สามารถโหลดแบบด้านหน้าเพื่อลดต้นทุน อีกวิธีที่ดีในการลดต้นทุนหากคุณต้องการอัปเดตแบบเพิ่มขึ้นแบบนี้คือไม่ต้องใช้ตรีโกณฯ ในตอนแรก ถ้า d มีขนาดเล็กและคุณไม่ต้องการให้ถูกต้องแต่ใกล้มากคุณสามารถทำ 'เคล็ดลับ' ได้โดยเพิ่มเวกเตอร์ที่มีความยาว d ไปยัง oX / oY, orthogonal ให้กับเวกเตอร์ไปยังศูนย์กลางของคุณ (โปรดสังเกตว่า เวกเตอร์ orthogonal ถึง (dX, dY) มอบให้โดย (-dY, dX)) แล้วย่อให้เหลือความยาวที่เหมาะสม ฉันจะไม่อธิบายรหัสนี้อย่างเป็นขั้นเป็นตอน แต่หวังว่ามันจะสมเหตุสมผลสำหรับสิ่งที่คุณเคยเห็น โปรดทราบว่าเรา 'ย่อขนาด' เดลตาเวกเตอร์ใหม่โดยปริยายในขั้นตอนสุดท้าย

deltaX = oX-cX; deltaY = oY-cY;
radius = sqrt(deltaX*deltaX+deltaY*deltaY);
orthoX = -deltaY*d/radius;
orthoY = deltaX*d/radius;
newDeltaX = deltaX+orthoX; newDeltaY = deltaY+orthoY;
newLength = sqrt(newDeltaX*newDeltaX+newDeltaY*newDeltaY);
aX = cX+newDeltaX*radius/newLength; aY = cY+newDeltaY*radius/newLength;

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

ว้าวสตีเว่นการประมาณของคุณทำงานเหมือนฝัน! คุณช่วยบอกวิธีการเปลี่ยนรหัสเพื่อรับ bX, bY ได้ไหม ผมไม่ได้ล้างเลยมุมฉากกับแนวคิดของคุณ ...
Lumis

2
แน่นอน! คุณจะต้องเข้าใจคณิตศาสตร์เวกเตอร์ในบางครั้งและเมื่อคุณสงสัยว่านี่จะเป็นลักษณะที่สอง เพื่อให้ได้ bX / bY คุณแค่ต้อง 'ไปทางอื่น' - กล่าวอีกนัยหนึ่งแทนที่จะเพิ่มเวกเตอร์ orthogonal (โดยเฉพาะ) เพียงแค่ลบมัน ในแง่ของรหัสข้างต้นนี้จะเป็น 'newDeltaX = deltaX-orthoX; newDeltaY = deltaY-orthoY; 'ตามด้วยการคำนวณ newLength เดียวกันจากนั้น' bX = cX + newDeltaX radius / newLength; bY = cY + newDeltaYรัศมี / newLength; '
Steven Stadnicki

โดยทั่วไปรหัสนั้นจะชี้ newDeltaX / newDeltaY ไปในทิศทางของ bX / bY (แทนที่จะไปในทิศทางของ aX / aY) จากนั้นตัดแต่งให้พอดีและเพิ่มไปยังศูนย์กลางเช่นเดียวกับที่คุณได้รับ aX / aY
Steven Stadnicki

9

จัดรูปสามเหลี่ยมโดยใช้ทั้งสองด้านที่คุณมีอยู่แล้ว (ด้านหนึ่งมาจาก 'c' ถึง 'o' อีกด้านจาก 'o' ถึง 'a') และด้านที่สามเปลี่ยนจาก 'a' ถึง 'c' คุณยังไม่รู้ว่า 'a' อยู่ที่ไหนเพียงแค่จินตนาการว่ามีประเด็นอยู่ในตอนนี้ คุณจะต้องตรีโกณมิติเพื่อคำนวณมุมของมุมที่อยู่ตรงข้ามกับด้าน 'd' คุณมีความยาวด้านข้าง c <-> o และ c <-> a เพราะพวกมันทั้งรัศมีของวงกลม

ตอนนี้คุณมีความยาวของสามด้านของสามเหลี่ยมนี้ที่คุณยังไม่เห็นคุณสามารถกำหนดมุมที่ตรงกันข้ามกับด้าน 'd' ของสามเหลี่ยม นี่คือสูตร SSS (ด้านข้าง) หากคุณต้องการ: http://www.teacherschoice.com.au/maths_library/trigonometry/solve_trig_sss.htm

เมื่อใช้สูตร SSS คุณจะได้มุม (ซึ่งเราจะเรียกว่า 'j') ที่อยู่ตรงข้ามกับ 'd' ดังนั้นตอนนี้เราสามารถคำนวณ (aX, aY)

// This is the angle from 'c' to 'o'
float angle = Math.atan2(oY - cY, oX - cX)

// Add the angle we calculated earlier.
angle += j;

Vector2 a = new Vector2( radius * Math.cos(angle), radius * Math.sin(angle) );

ตรวจสอบให้แน่ใจว่ามุมที่คุณคำนวณนั้นเป็นเรเดียนเสมอ

หากคุณต้องการคำนวณรัศมีของวงกลมคุณสามารถใช้การลบเวกเตอร์ลบจุด 'c' จากจุด 'o' จากนั้นรับความยาวของเวกเตอร์ที่ได้

float lengthSquared = ( inVect.x * inVect.x
                      + inVect.y * inVect.y
                      + inVect.z * inVect.z );

float radius = Math.sqrt(lengthSquared);

ฉันเชื่อว่าสิ่งนี้ควรทำ ฉันไม่รู้จักจาวาดังนั้นฉันเดาว่ามันถูกต้อง

นี่คือภาพที่กำหนดโดยผู้ใช้Byte56เพื่อแสดงให้เห็นว่าสามเหลี่ยมนี้อาจมีลักษณะอย่างไร: เฉาสามเหลี่ยม


1
ฉันกำลังทำคำตอบ แต่นี่คือมัน คุณสามารถใช้รูปภาพที่ฉันสร้างขึ้นได้ :) i.imgur.com/UUBgM.png
MichaelHouse

@ Byte56: ขอบคุณฉันไม่ได้จัดการแก้ไขภาพเพื่อแสดง
Nic Foster

โปรดทราบว่าต้องคำนวณรัศมีด้วย ควรมีวิธีการที่ง่ายกว่าในการรับ j มากกว่าการคำนวณ SSS แบบเต็มเนื่องจากเรามีรูปสามเหลี่ยมของ isoceles)
Steven Stadnicki

ใช่ว่าดูเหมือนง่ายสำหรับฉัน! Android ไม่มี Vector2 ดังนั้นฉันเดาว่าฉันสามารถใช้ค่าแยกกันได้ ฉันค้นพบคลาส Vector2 ที่สร้างขึ้นด้วยตนเองสำหรับ Android ที่นี่: code.google.com/p/beginning-android-games-2/source/browse/trunk/…
Lumis

(ฉัน tweaked คำตอบของฉันเองเพื่อค้นหาระยะทางเชิงเส้นที่ถูกต้อง - การคำนวณที่สองของ deltaTheta ที่นั่นเช่น 2 * asin (d / (2 * รัศมี)) เป็นวิธีที่คุณจะหา j ที่นี่)
สตีเวน Stadnicki

3

ในการทำให้ obj2 หมุนไปรอบ ๆ obj1 อาจลอง:

float angle = 0; //init angle

//call in an update
obj2.x = (obj1.x -= r*cos(angle));
obj2.y = (obj1.y += r*sin(angle));
angle-=0.5;

สิ่งนี้ไม่แสดงวิธีรับมุมในตอนแรกและดูเหมือนว่าคุณจะแสดงวิธีการโคจรแทนการค้นหาพิกัดเหมือนคำถามที่ถาม
MichaelHouse

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