Нецілі значення швидкості - чи є чистіший спосіб зробити це?


21

Часто мені хочеться використовувати значення швидкості, наприклад, 2,5 для переміщення свого персонажа в грі на основі пікселів. Виявлення зіткнення, як правило, буде складніше, якщо я це зробити. Тому я закінчую щось подібне:

moveX(2);
if (ticks % 2 == 0) { // or if (moveTime % 2 == 0)
    moveX(1);
}

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


11
Чи вирішили ви зменшити цілочисельну одиницю меншою (наприклад, 1/10-а частина одиниці відображення), тоді 2,5 перекладається на 25, і ви все ще можете розцінювати її як ціле число для всіх перевірок і ставитись до кожного кадру послідовно.
DMGregory

6
Ви можете розглянути можливість прийняття алгоритму рядків Брезена, який можна реалізувати, використовуючи лише цілі числа.
n0rd

1
Це зазвичай робиться на старих 8-бітових консолях. Дивіться такі статті, як приклад того, як реалізується рух підпікселів методами з фіксованою точкою: tasvideos.org/GameResources/NES/BattleOfOlympus.html
Лукас

Відповіді:


13

Bresenham

У старі часи, коли люди ще писали власні основні відеопрограми для малювання ліній та кіл, було нечувано використовувати алгоритм рядків Брезенхема.

Брезенхем вирішує цю проблему: ви хочете намалювати лінію на екрані, яка переміщує dxпікселі в горизонтальному напрямку, в той же час обертаючи dyпікселі у вертикальному напрямку. Існує притаманний "плаваючий" персонаж рядкам; навіть якщо у вас є цілі пікселі, ви закінчуєте раціональні нахили.

Але алгоритм повинен бути швидким, це означає, що він може використовувати лише цілу арифметику; і воно також відходить без будь-якого множення чи ділення, лише додавання і віднімання.

Ви можете адаптувати це до свого випадку:

  • Ваш "х напрямок" (з точки зору алгоритму Брезена) - ваш годинник.
  • Ваш "y напрямок" - це значення, яке ви хочете збільшити (тобто, позиція вашого персонажа - обережно, це насправді не "y" вашого спрайту чи іншого на екрані, більше абстрактне значення)

"x / y" тут - не розташування на екрані, а значення одного з ваших вимірів у часі. Очевидно, якщо ваш спрайт працює в довільному напрямку по екрану, у вас буде кілька бресенхамів, які працюють окремо, 2 для 2D, 3 для 3D.

Приклад

Скажімо, ви хочете перемістити свого персонажа простим рухом від 0 до 25 вздовж однієї з осей. Коли він рухається зі швидкістю 2,5, він прибуде туди в кадрі 10.

Це те саме, що "малювання лінії" від (0,0) до (10,25). Візьміть алгоритм лінійки Брезенама і нехай він працює. Якщо ви зробите це правильно (і коли ви вивчите це, дуже швидко стане зрозуміло, як ви це зробите правильно), тоді він створить для вас 11 "балів" (0,0), (1,2), (2, 5), (3,7), (4,10) ... (10,25).

Підказки щодо адаптації

Якщо ви перейдете на цей алгоритм Google і знайдете якийсь код (у Вікіпедії є досить великий договір про нього), вам потрібно стежити за деякими речами:

  • Це, очевидно, працює для всіх видів dxта dy. Вас зацікавив один конкретний випадок (тобто його ніколи не будете dx=0).
  • Звичайна реалізація матиме кілька різних випадків для квадранта на екрані, в залежності від того , dxі dyє позитивними, негативними, а також того abs(dx)>abs(dy)чи ні. Ви, звичайно, також вибираєте те, що вам тут потрібно. Ви повинні переконатися, що напрямок, який збільшується 1кожним галочкою, завжди є вашим "годинниковим" напрямком.

Якщо застосувати ці спрощення, результат справді буде дуже простим і позбудеться будь-яких результатів.


1
Це має бути прийнятою відповіддю. Запрограмувавши ігри на C64 у 80-х та фрактали на ПК у 90-х, я все ще скучаю із використанням плаваючої точки, де б я не міг цього уникнути. Або, звичайно, при повсюдно поширених FPU в сучасних процесорах аргумент продуктивності в основному є суперечливим, але для арифметики з плаваючою точкою все-таки потрібно набагато більше транзисторів, черпаючи більше енергії, і багато процесорів повністю закриють свої FPU, поки вони не використовуються. Тож уникнення плаваючої точки змусить користувачів мобільних телефонів подякувати вам за те, що ви не висмоктуєте батареї так швидко.
Гунтрам Блом підтримує Моніку

@GuntramBlohm Прийнята відповідь прекрасно працює і при використанні Fixed Point, хоча, я думаю, це хороший спосіб це зробити. Як ви ставитесь до цифр з фіксованою точкою?
leetNightshade

Змінив це на прийняту відповідь, дізнавшись, що саме так вони робили за 8-бітні та 16-бітні дні.
Акумулятор

26

Є чудовий спосіб зробити саме те, що ви хочете.

Крім floatшвидкості, вам потрібно мати другу floatзмінну, яка буде містити і накопичувати різницю між реальною швидкістю і округлою швидкістю. Потім ця різниця поєднується із самою швидкістю.

#include <iostream>
#include <cmath>

int main()
{
    int pos = 10; 
    float vel = 0.3, vel_lag = 0;

    for (int i = 0; i < 20; i++)   
    {
        float real_vel = vel + vel_lag;
        int int_vel = std::lround(real_vel);
        vel_lag = real_vel - int_vel;

        std::cout << pos << ' ';
        pos += int_vel;
    }
}

Вихід:

10 10 11 11 11 12 12 12 12 13 13 13 14 14 14 15 15 15 15 16


5
Чому ви віддаєте перевагу цьому рішенню над використанням float (або фіксованої точки) як для швидкості, так і для положення і лише округлення позиції до цілих чисел в самому кінці?
CodesInChaos

@CodesInChaos Я не віддаю перевагу своєму рішенню над цим. Коли я писав цю відповідь, я про це не знав.
HolyBlackCat

16

Використовуйте плаваючі значення для переміщення та цілі значення для зіткнення та візуалізації.

Ось приклад:

class Character {
    float position;
public:
    void move(float delta) {
        this->position += delta;
    }
    int getPosition() const {
        return lround(this->position);
    }
};

При переміщенні ви використовуєте, move()що накопичує дробові позиції. Але зіткнення та візуалізація можуть мати справу з інтегральними позиціями за допомогою getPosition()функції.


Зауважте, що у випадку з мережевою грою використання типів з плаваючою комою для світового моделювання може бути складним. Див., Наприклад, gafferongames.com/networking-for-game-programmers/… .
Ліорі

@liori Якщо у вас є клас з фіксованою точкою, який діє як замінна плаваюча заміна для float, чи не в основному це вирішує ці проблеми?
leetNightshade

@leetNightshade: залежить від реалізації.
liori

1
Я б сказав, що на практиці питання не існує, яке обладнання, здатне запускати сучасну мережеву гру, не має IEEE 754?
Сопель
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.