Получение компьютера для осуществления 360 градусов = 0 градусов, поворот пушки

Я делаю игру, и в ней находится компьютерная пушка. Пулеметная башня может вращаться на 360 градусов.

Он использует триггер для определения угла, в котором он должен нацелиться на пистолет (objdeg), и текущий угол пистолета хранится в (gundeg)

следующий код вращает пистолет с установленной скоростью

if (objdeg > gundeg)
{
    gundeg++;
}
if (objdeg < gundeg)
{
    gundeg--;
}

Проблема в том, что если объект находится под углом 10 градусов, пистолет вращается, стреляет и разрушает его, если другая цель появляется на 320 градусов, пистолет будет вращаться на 310 градусов против часовой стрелки, а не просто поворачиваться на 60 градусов по часовой стрелке, чтобы поразить его.

Как я могу исправить свой код, чтобы он не действовал глупо?

Ответ 1

Если вам нужно повернуть более чем на 180 градусов в одном направлении, чтобы навести револьверную головку, то быстрее будет повернуть другое направление.

Я бы просто проверил это, а затем повернул в соответствующем направлении

if (objdeg != gundeg)
{
    if ((gundeg - objdeg) > 180)
       gundeg++;
    else
       gundeg--;
}

ОБНОВЛЕНИЕ: новое решение

Я уточнил свое решение на основе отзывов в комментариях. Это определяет, находится ли цель "налево или направо" от башни и решает, в какую сторону повернуть. Затем он инвертирует это направление, если цель находится на расстоянии более 180 градусов.

if (objdeg != gundeg)
{
  int change = 0;
  int diff = (gundeg - objdeg)%360;
  if (diff < 0)
     change = 1;
  else
     change = -1;

  if (Math.Abs(diff) > 180)
     change = 0 - change;

  gundeg += change;
 }

Ответ 2

Вы можете полностью исключить разделение (и mod), если вы представляете свои углы в чем-то, называемом "BAMS", который обозначает систему бинарных угловых измерений. Идея состоит в том, что если вы храните свои углы в N битном целочисленном, вы используете весь диапазон этого целого для представления угла. Таким образом, нет необходимости беспокоиться о переполнении за 360, потому что естественные свойства modulo-2 ^ N вашего представления заботятся об этом для вас.

Например, скажем, вы используете 8 бит. Это сокращает круг до 256 возможных ориентаций. (Вы можете выбрать больше бит, но 8 удобно для примера). Пусть 0x00 стоит 0 градусов, 0x40 означает 90 градусов, 0x80 - 180 градусов, а 0xC0 - 270 градусов. Не беспокойтесь о "знаке", опять же, BAMS является естественным для углов. Если вы интерпретируете 0xC0 как "без знака" и масштабируются до 360/256 градусов за каждый счет, ваш угол равен (+192) (360/256) = +270; но если вы интерпретируете 0xC0 как "подписанный", ваш угол равен (-64) (360/256) = -90. Обратите внимание, что -90 и +270 означают то же самое в терминах angular.

Если вы хотите применить триггерные функции к вашим BAMS-углам, вы можете предварительно вычислить таблицы. Есть трюки, чтобы уменьшить таблицы, но вы можете видеть, что таблицы не так велики. Для хранения всей таблицы синуса и косинуса значений двойной точности для 8-битного BAMS не требуется более 4 Кбайт памяти, питание цыплят в сегодняшней среде.

Поскольку вы упоминаете об использовании этого в игре, вы, вероятно, можете уйти с 8-битными или 10-битными представлениями. Каждый раз, когда вы добавляете или вычитаете углы, вы можете принудительно привести результат в N бит, используя логическую операцию И, например, angle & = 0x00FF для 8 бит.

ЗАБУДЬТЕ ЛУЧШУЮ ЧАСТЬ (отредактируйте)

Задача "Поворот-направо" и "Поворот-Левая" легко решается в системе BAMS. Просто воспользуйтесь разницей и не забудьте сохранить только N значимых бит. Интерпретация MSB как знакового бита указывает, каким образом вы должны повернуть. Если разница отрицательная, поверните противоположный путь абс() разницы.

Эта уродливая маленькая программа на C демонстрирует. Попробуйте ввести его как 20 10 и 20 30. Затем попробуйте обмануть его, обернув вокруг нулевой точки. Дайте ему 20 -10, он повернуть налево. Дайте ему 20 350, он все равно поворачивается налево. Обратите внимание, что поскольку это сделано в 8 бит, это 181 неотличимо от 180, так что не удивляйтесь, если вы его подаете 20 201, и он поворачивается направо, а не влево - в разрешении, обеспечиваемом восемью битами, поворачивая налево и поворачиваясь вправо этот случай тот же. Поместите 20 205, и это будет более коротким способом.

#include <stdio.h>
#include <math.h>

#define TOBAMS(x)    (((x)/360.0) * 256)
#define TODEGS(b)    (((b)/256.0) * 360)

int main(void)
{
    double a1, a2;     // "real" angles
    int b1, b2, b3;    // BAMS angles


    // get some input
    printf("Start Angle ? ");
    scanf("%lf", &a1);

    printf("Goal Angle ? ");
    scanf("%lf", &a2);

    b1 = TOBAMS(a1);
    b2 = TOBAMS(a2);

    // difference increases with increasing goal angle
    // difference decreases with increasing start angle
    b3 = b2 - b1;
    b3 &= 0xff;

    printf("Start at %7.2lf deg and go to %7.2lf deg\n", a1, a2);
    printf("BAMS   are 0x%02X and 0x%02X\n", b1, b2);
    printf("BAMS diff is 0x%02X\n", b3);

    // check what would be the 'sign bit' of the difference
    // negative (msb set) means turn one way, positive the other
    if( b3 & 0x80 )
    {
        // difference is negative; negate to recover the
        // DISTANCE to move, since the negative-ness just
        // indicates direction.

        // cheap 2 complement on an N-bit value:
        // invert, increment, trim
        b3 ^= -1;       // XOR -1 inverts all the bits
        b3 += 1;        // "add 1 to x" :P
        b3 &= 0xFF;     // retain only N bits

        // difference is already positive, can just use it
        printf("Turn left %lf degrees\n", TODEGS(b3));
        printf("Turn left %d counts\n", b3);
    }
    else
    {
        printf("Turn right %lf degrees\n", TODEGS(b3));
        printf("Turn right %d counts\n", b3);
    }

    return 0;
}

Ответ 3

До нормализованного значения до [0,360):

(I.e - полуоткрытый диапазон)

Используйте оператор модуля для выполнения "получения деления":

361 % 360

будет 1.

В языках стиля C/С++/... это будет

gundeg %= 360

Примечание (спасибо комментарию): если gundeg - тип с плавающей точкой, вам нужно либо использовать библиотечную функцию, либо в C/С++: fmod, либо сделать это самостоятельно (.NET):

double FMod(double a, double b) {
  return a - Math.floor(a / b) * b;
}

Какой способ поворота?

Какой бы способ ни был короче (и если поворот на 180 °, тогда ответ произвольный), на С#, и если направление измеряется против часовой стрелки

TurnDirection WhichWayToTurn(double currentDirection, double targetDirection) {
  Debug.Assert(currentDirection >= 0.0 && currentDirection < 360.0
               && targetDirection >= 0.0 && targetDirection < 360.0);

  var diff = targetDirection - currentDirection ;
  if (Math.Abs(diff) <= FloatEpsilon) {
    return TurnDirection.None;
  } else if (diff > 0.0) {
    return TurnDirection.AntiClockwise;
  } else {
    return TurnDirection.Clockwise;
  }
}

NB. Для этого требуется тестирование.

Обратите внимание на использование assert для подтверждения предварительного условия нормализованных углов, и я использую assert, потому что это внутренняя функция, которая не должна получать непроверенные данные. Если это была обычно многократно используемая функция, проверка аргумента должна вызывать исключение или возвращать ошибку (в зависимости от языка).

Также обратите внимание. для разработки таких вещей нет ничего лучше, чем карандаш и бумага (моя первоначальная версия была неправильной, потому что я смешивал с использованием (-180,180) и [0,360].

Ответ 4

Я склоняюсь к решению, которое

  • не имеет много вложенных операторов if
  • не предполагает, что любой из двух углов находится в определенном диапазоне, например, [0, 360] или [-180, 180]
  • имеет постоянное время выполнения

Решение для перекрестного произведения, предложенное Krypes, удовлетворяет этому критерию, однако сначала необходимо сгенерировать векторы из углов. Я считаю, что метод JustJeff BAMS также удовлетворяет этому критерию. Я предложу другой...

Как обсуждалось в Почему модуль отличается в разных языках программирования?, который ссылается на превосходную статью Википедии, есть много способов выполнить операцию по модулю. Общие реализации округляют частное к нулю или отрицательной бесконечности.

Однако если вы округлите до ближайшего целого числа:

double ModNearestInt(double a, double b) {
    return a - b * round(a / b);
}

Имеет свойство nice, которое возвращается остаток

  • всегда в интервале [-b/2, +b/2]
  • всегда кратчайшее расстояние до нуля

Так,

double angleToTarget = ModNearestInt(objdeg - gundeg, 360.0);

будет наименьшим углом между objdeg и gundeg, а знак будет указывать направление.

Обратите внимание, что (С#) Math.IEEERemainder(objdeg - gundeg, 360.0) или (C++) fmod(objdeg - gundeg, 360.0) уже делает это для вас, то есть уже ModNearestInt существует в связанных математических библиотеках.

Ответ 5

Просто сравните следующее:

gundeg - objdeg
objdeg - gundeg 
gundeg - objdeg + 360
objdeg - gundeg + 360

и выберите ту, которая имеет минимальное абсолютное значение.

Ответ 6

Здесь образец С# для обработки, это будет правильным.

public class Rotater
{
    int _position;
    public Rotater()
    {

    }
    public int Position
    {
        get
        {
            return _position;
        }
        set            
        {
            if (value < 0)
            {
                _position = 360 + value;
            }
            else
            {
                _position = value;
            }
            _position %= 360;
        }
    }
    public bool RotateTowardsEx(int item)
    {
        if (item > Position)
        {
            if (item - Position < 180)
            {
                Position++;
            }
            else
            {
                Position--;
            }
            return false;
        }
        else if (Position > item)
        {
            if (Position - item < 180)
            {
                Position--;
            }
            else
            {
                Position++;
            }
            return false;
        }
        else
        {
            return true;
        }
    }
}

    static void Main(string[] args)
    {


        do
        {
            Rotater rot = new Rotater();
            Console.Write("Enter Starting Point: ");
            var startingPoint = int.Parse(Console.ReadLine());
            rot.Position = startingPoint;
            int turns = 0;

            Console.Write("Enter Item Point: ");
            var item = int.Parse(Console.ReadLine());
            while (!rot.RotateTowardsEx(item))
            {
                turns++;
            }
            Console.WriteLine(string.Format("{0} turns to go from {1} to {2}", turns, startingPoint, item));
        } while (Console.ReadLine() != "q");


    }

Кредит Джону Пири за вдохновение

Изменить: Я не был доволен настройкой Position Position, поэтому я очистил его

Ответ 7

Вам нужно решить, следует ли вращать влево или вправо, на основе которого находится более короткое расстояние. Затем вам понадобится модуль:

if (objdeg > gundeg)
{
    if (objdeg - gundeg < 180)
    {
        gundeg++;
    }
    else
    {
        gundeg--;
    }
}
if (objdeg < gundeg)
{
    if (gundeg - objdeg < 180)
    {
        gundeg--;
    }
    else
    {
        gundeg++;
    }
}
if (gundeg < 0)
{
    gundeg += 360;
}
gundeg = gundeg % 360;

Ответ 8

На самом деле, это более простой способ подойти к этой проблеме. Перекрестное произведение двух векторов дает вам вектор, представляющий нормальный (например, перпендикуляр). Как артефакт этого, учитывая два вектора a, b, лежащие на плоскости xy, a x b = c влечет c = (0,0, + -1).

Знак z-компонента c (например, вне зависимости от того, выходит ли он или переходит в плоскость xy) зависит от того, будет ли его левый или правый поворот вокруг оси z для того, чтобы a был равен b.

Vector3d turret
Vector3d enemy

if turret.equals(enemy) return;
Vector3d normal = turret.Cross(enemy);
gundeg += normal.z > 0 ? 1 : -1; // counter clockwise = +ve

Ответ 9

Попробуйте делить на 180 с помощью целочисленного деления и поворота на основе четного/нечетного результата?

749/180 = 4 Таким образом, вы поворачиваете по часовой стрелке на 29 градусов (749% 180)

719/180 = 3 Итак, вы поворачиваете против часовой стрелки на 1 градус (180 - 719% 180)

Ответ 10

Проблема заключается в поиске направления, которое даст кратчайшее расстояние.

Однако вычитание может привести к отрицательным числам, и это необходимо учитывать.
Если вы двигаете пистолет на один шаг при каждой проверке, я не знаю, когда вы будете выполнять модуль.
И, если вы хотите переместить пистолет за один шаг, вы просто добавите/вычтите дельта правильно.

С этой целью Киршштейн, кажется, думает ближе всего ко мне. Я работаю с целым числом в этом простом psudo-коде.

if (objdeg != gundeg)
{
    // we still need to move the gun
    delta = gundeg - objdeg
    if (delta > 0)
        if (unsigned(delta) > 180)
           gundeg++;
        else
           gundeg--;
    else // delta < 0
        if (unsigned(delta) > 180)
           gundeg--;        
        else
           gundeg++;

    if (gundeg == 360)
        gundeg = 0;
    else if (gundeg == -1)
        gundeg = 359;
}

Попробуйте выполнить эту операцию с помощью gundeg = 10 и objdeg = 350, чтобы увидеть, как gundeg будет перемещен от 10 до 0, а затем 359 до 350.

Ответ 11

Вот как я недавно реализовал что-то подобное в игре:

double gundeg;

// ...

double normalizeAngle(double angle)
{
    while (angle >= 180.0)
    {
        angle -= 360.0;
    }
    while (angle < -180.0)
    {
       angle += 360.0;
    }
    return angle;
}

double aimAt(double objAngle)
{
    double difference = normalizeAngle(objdeg - gundeg);
    gundeg = normalizeAngle(gundeg + signum(difference));
}

Все угловые переменные ограничены -180.. + 180, что облегчает этот вид вычислений.

Ответ 12

При риске bikeshedding сохранение степеней как целого, а не как своего собственного класса может быть примером "примитивной одержимости". Если я правильно помню, книга "Прагматический программист" предложила создать класс для хранения степеней и выполнения операций над ними.

Ответ 13

Здесь пример короткого теста псевдокода, о котором я могу думать, отвечает на проблему. Он работает в вашем домене с положительными углами 0..359, и он обрабатывает граничные условия перед обработкой "нормальных".

if (objdeg >= 180 and gundeg < 180)
    gundeg = (gundeg + 359) % 360;
else if (objdeg < 180 and gundeg >= 180)
    gundeg = (gundeg + 1) % 360;
else if (objdeg > gundeg)
    gundeg = (gundeg + 1) % 360;
else if (objdeg < gundeg)
    gundeg = (gundeg + 359) % 360;
else
    shootitnow();

Ответ 14

Это может быть немного поздно... Наверное, очень поздно... Но у меня недавно была аналогичная проблема, и я обнаружил, что это отлично работает в GML.

var diff = angle_difference(gundeg, objdeg)

if (sign(diff)>0){
   gundeg --;
}else{
   gundeg ++;
}