Як перемістити об’єкт по колу іншого об’єкта?


11

Я настільки поза математикою, що це боляче, але для когось із вас це має бути шматок пирога. Я хочу перемістити предмет навколо іншого за його століттями або окружністю по простому круговому шляху. Наразі мій алгоритм гри знає, як рухатись та розміщувати спрайт просто на краю перешкоди, і тепер він чекає, коли наступна точка переміститься залежно від різних умов.

Отже, математична проблема полягає в тому, як отримати (aX, aY) і (bX, bY) позиції, коли я знаю центр (cX, cY), положення об'єкта (oX, oY) та відстань, необхідну для переміщення (d)

введіть тут опис зображення


1
Це dлінійна відстань чи це дуга?
MichaelHouse

Це лінійна відстань у пікселях
Lumis

Чи знаєте ви взагалі, що таке вектори і основні операції над ними?
Патрік Хьюз

@Patrick Ні, я думаю, мені доведеться пройти курс векторів. Оскільки це кадр за кадром анімації, код повинен бути швидким і оптимізованим.
Луміс

Відповіді:


8

( CAVEAT: тут я використовую два наближення: перше приймає d як довжину дуги, а друге сприймає це як ортогональну довжину. Обидва ці наближення повинні бути хорошими для відносно невеликих значень 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 - одна сторона трикутника ізоцели, дві інші сторони якої є радіусом кола (від cX / cY до oX / oY та aX / aY відповідно), а ділення цього трикутника ізоцелей дає нам два правильних трикутника, кожен з яких має d / 2 як одну сторону і радіус як гіпотенузу; це означає, що синус половини нашого кута дорівнює (d / 2) / радіус, і тому повний кут удвічі більший за цей:

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

Зверніть увагу, як якби ви вийняли асин з цієї формули і скасували 2, це буде те саме, що і остання формула; це те саме, що говорити, що sin (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, ортогональний вектору до вашого центру (зауважте, що a вектор, ортогональний до (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
Стівен, я думаю, що спробую наблизити спершу, оскільки це лише гра, де важливіше відчувати себе природним та цікавим, ніж точним. Також швидкість має значення. Дякую за цей довгий і хороший підручник!
Луміс

Вау, Стівен, ваше наближення працює як мрія! Чи можете ви сказати мені, як змінити код, щоб отримати bX, bY. Мені ще не зрозуміло твоя ортогональна концепція ...
Луміс

2
Звичайно! Ви дійсно захочете зрозуміти векторну математику в якийсь момент, і як тільки ви це підозрюю, це стане другою природою; щоб отримати bX / bY, ви просто повинні піти «навпаки» - іншими словами, замість того, щоб додавати (конкретний) ортогональний вектор, просто віднімайте його. Згідно з наведеним вище кодом, це буде "newDeltaX = deltaX-orthoX; newDeltaY = deltaY-orthoY; ', після чого проводиться те саме обчислення newLength, а потім' bX = cX + newDeltaX радіус / newLength; bY = cY + newDeltaY радіус / newLength; '.
Стівен Стадницький

В основному, цей код буде вказувати newDeltaX / newDeltaY у напрямку bX / bY (замість в напрямку aX / aY), а потім обрізати, щоб підходити та додавати до центру так само, як ви отримаєте aX / aY.
Стівен Стадницький

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

Щось подібне повинно зробити, я вважаю. Я не знаю Java, тому я здогадався про точний синтаксис.

Ось зображення, яке надає користувач, Byte56щоб проілюструвати, як може виглядати цей трикутник: као трикутник


1
Я робив відповідь, але це все. Ви можете використовувати зображення, яке я зробив :) i.imgur.com/UUBgM.png
MichaelHouse

@ Byte56: Дякую, у мене не було жодної ручки редактора зображень для ілюстрації.
Нік Фостер

Зверніть увагу, що радіус також слід обчислити; повинні бути більш прості способи отримання j, ніж повний обчислення SSS, оскільки у нас є трикутник ізоцели.)
Стівен Стадницький,

Так, це здається простим, навіть мені! В Android немає Vector2, тому, мабуть, я можу просто використовувати ці значення окремо. Цікаво, що тут я знайшов клас Vector2, створений вручну для Android: code.google.com/p/beginning-android-games-2/source/browse/trunk/…
Луміс,

(Я підробив власну відповідь, щоб знайти правильну лінійну відстань - другий обчислення deltaTheta там, як 2 * asin (d / (2 * радіус)), - як ви знайдете j тут.)
Стівен Стадницький,

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
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.